KnockoutJS - Comparing observableArray with another observableArray - javascript

I have a json data formatted like this
{
"ITEMS": [
{
"option": "one",
"values": ["A","B","C","D"]
},
{
"option": "two",
"values": ["E","F","G"]]
}
]
}
and script code like this
function Item(data) {
this.option = ko.observable(data.option);
this.values = ko.observableArray(data.values);
}
var ProductIndexVM = (function() {
function ProductIndexVM() {
items = ko.observable();
filtered_items = ko.observableArray([]);
selectedItems = ko.computed(function() {
return ko.utils.arrayFilter(items(), function(item) {
// return something using compare
});
});
// after ajax success
var mappedItems = $.map(result.ITEMS, function(item) {
return new Item(item)
});
items(mappedItems);
}
return ProductIndexVM;
})();
After successful ajax request data has been mapped into items observable. The problem is I need to compare filtered_items observableArray with item functions' values observableArray so that if items inside filtered_items match with any one of the item functions' values it will return that or not. Simplifying whole thing I want to sort out items with only those contains in filtered_items.
As an example if filtered_items have content same as below
["A", "C", "E"]
the output will be
{
"ITEMS": [
{
"option": "one",
"values": ["A","C"]
},
{
"option": "two",
"values": ["E"]]
}
]
}
How can I compare that inside selectedItems?

This really has nothing much to do with knockout, its a simple algorithmic question. Given an array of object and an array of "selected values" how can I filter the former using the latter:
var input = {
"ITEMS": [
{
"option": "one",
"values": ["A","B","C","D"]
},
{
"option": "two",
"values": ["E","F","G"]
}
]
}
var filters = ["A","C","E"];
var result = input.ITEMS.map(item => {
return {
"option": item.option,
"values": item.values.filter(value => filters.indexOf(value)>-1)
}
});
console.log(result);
Now applied to your knockout, just read your observable arrays as you normally would but use the same algorithm as demonstrated above.

Related

inserting item into a nested javascript object

how does one go about inserting an item into a nested javascript array of objects (with and without using a library)? running to a problem where once you insert the item after traversing, how would you reassign it back to the original object without manually accessing the object like data.content[0].content[0].content[0] etc..? already tried Iterate through Nested JavaScript Objects but could not get the reassignment to work
const data = {
"content": [
{
"name": "a",
"content": [
{
"name": "b",
"content": [
{
"name": "c",
"content": []
}
]
}
]
}
]
}
inserting {"name": "d", "content": []} into the contents of c
const data = {
"content": [
{
"name": "a",
"content": [
{
"name": "b",
"content": [
{
"name": "c",
"content": [{"name": "d", "content": []}]
}
]
}
]
}
]
}
const data = {
"content": [{
"name": "a",
"content": [{
"name": "b",
"content": [{
"name": "c",
"content": []
}]
}]
}]
}
const insert = createInsert(data)
insert({
"name": "d",
"content": []
}, 'c')
console.log(data)
// create a new function that will be able to insert items to the object
function createInsert(object) {
return function insert(obj, to) {
// create a queue with root data object
const queue = [object]
// while there are elements in the queue
while (queue.length) {
// remove first element from the queue
const current = queue.shift()
// if name of the element is the searched one
if (current.name === to) {
// push the object into the current element and break the loop
current.content.push(obj)
break
}
// add child elements to the queue
for (const item of current.content) {
queue.push(item)
}
}
}
}
It looks like we should assume that the name property uniquely identifies an object in the data structure. With that assumption you could create a mapping object for it, so to map a given name to the corresponding object in the nested structure. Also keep track which is the parent of a given object.
All this meta data can be wrapped in a decorator function, so that the data object gets some capabilities to get, add and remove certain names from it, no matter where it is in the hierarchy:
function mappable(data) {
const map = { "__root__": { content: [] } };
const parent = {};
const dfs = (parentName, obj) => {
parent[obj.name] = parentName;
map[obj.name] = obj;
obj.content?.forEach?.(child => dfs(obj.name, child));
}
Object.defineProperties(data, {
get: { value(name) {
return map[name];
}},
add: { value(parentName, obj) {
this.get(parentName).content.push(obj);
dfs(parentName, obj);
}},
remove: { value(name) {
map[parent[name]].content = map[parent[name]].content.filter(obj =>
obj.name != name
);
delete map[name];
delete parent[name];
}}
});
data.add("__root__", data);
}
// Demo
const data = {"content": [{"name": "a","content": [{"name": "b","content": [{"name": "c","content": []}]}]}]};
mappable(data);
data.add("c", { name: "d", content: [] });
console.log(data);
console.log(data.get("d")); // { name: "d", content: [] }
data.remove("d");
console.log(data.get("d")); // undefined
console.log(data); // original object structure

return subarray of object based on object key in array

Given this data structure:
let assets = [{
"photos": [{
"id": 1,
"label": "bad-syn.jpg",
"size": 38284
}]
}, {
"documents": [{
"id": 109
}]
}]
]
How can I retrieve the subarray based on the photos key? There can be other keys.
My function just returns the entire structure:
findAssets: function (key) {
return this.assets.find((asset) => {
return asset[key]
})
}
If you want to return the photos sub array or in other words only the value of the given key, you just want to access the key value from the .find() result using [key]:
findAssets = function(key){
return assets.find((asset) => {
return asset[key]
})[key]
}
Demo:
let assets = [{
"photos": [{
"id": 1,
"label": "bad-syn.jpg",
"size": 38284
}]
}]
findAssets = function(key){
return assets.find((asset) => {
return asset[key]
})[key]
}
console.log(findAssets("photos"));
Note:
This assumes the given keyexists in your assets objects, otherwise it can throw an error.
You want to use .map() and because photos is an array as well you need to map twice.
let assets = [{
"photos": [{
"id": 1,
"label": "bad-syn.jpg",
"size": 38284
}]
}]
function byKey(k) {
return assets.map(a => a.photos.map(p => p[k]));
}
console.log(byKey('label'));

Map object values to keys - Javascript

I have an array from an API call.
var response = {
"data": {
"data": [{
"1": "Arun",
"index": "name"
}, {
"1": 70.78,
"index": "score"
}]
}
}
I connect to a lot of other API's and they return me a similar response but the keys change. Sometimes it might be
var response = {
"data": {
"data": [{
"values": "Harry",
"index": "name"
}, {
"values": 45,
"index": "score"
}]
}
}
var response = {
"data": {
"data": [{
"4": "Richard",
"index": "name"
}, {
"4": 98,
"index": "score"
}]
}
}
I would like to get an array like this.
[
{
name: 'Arun',
score: 70.78
}
]
This is what I did.
var response = {
"data": {
"data": [{
"1": "Arun",
"index": "name"
}, {
"1": 70.78,
"index": "score"
}]
}
}
const result = [];
const mappedData = _.map(response.data.data, (item) => {
return {
[item.index]: item[1]
};
});
const resultObject = _.reduce(mappedData, (result, currentObject) => {
for (const key in currentObject) {
if (currentObject.hasOwnProperty(key)) {
result[key] = currentObject[key];
}
}
return result;
}, {});
result.push(resultObject)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
So instead of hardcoding "1" or "values" in the map function, is there a more universal way to get the key and achieve the same result?
Thanks.
Use reduce rather than map, so you're updating the same object, not creating an array.
And since the property containing the value can vary, I use a loop to look for the first property that isn't named index, and use its value.
var response = {
"data": {
"data": [{
"1": "Arun",
"index": "name"
}, {
"1": 70.78,
"index": "score"
}]
}
}
const mappedData = response.data.data.reduce((acc, item) => {
var value;
// find the property that isn't named "item"
for (var i in item) {
if (i != "index") {
value = item[i];
break;
}
}
acc[item.index] = value;
return acc;
}, {});
console.log(mappedData)
There's no need for lodash for this, the built-in reduce function is fine (but _.reduce will work similarly).
Since you only care about the values of that object and it only has two keys you can do this quite easily in lodash with reduce & fromPairs:
var response = { "data": { "data": [{ "1": "Arun", "index": "name" }, { "1": 70.78, "index": "score" }] } }
const rv = (o) => _.reverse(_.values(o))
const r = _.reduce(response.data.data, (a,c) => _.fromPairs([rv(a), rv(c)]))
console.log(r)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
The same thing converted to ES6 would be:
var response = { "data": { "data": [{ "1": "Arun", "index": "name" }, { "1": 70.78, "index": "score" }] } }
const rv = (o) => Object.values(o).reverse() // reverse values
const fp = (arr) => arr.reduce((r, [k,v]) => (r[k] = v, r), {}) // from pairs
const result = response.data.data.reduce((a,c) => fp([rv(a), rv(c)]))
console.log(result)
The main idea here is to first get the object values in an array form, reverse them so the key & value are in the correct order and then reduce that array via from pairs to create the final object.
The main advantage of this approach is that we never deal with the object keys and only focus on the values which is what you really care about. This way the keys can be any value and it would still not matter.
You could try deleting the key-pair index and using the first value of the resulting object:
const mappedData = _.map(response.data.data, (item) => {
var tempObj = Object.assign({}, item)
var index = tempObj.index;
delete tempObj.index;
var otherData = Object.values(tempObj)[0];
return {
[index]: otherData
};
});
Just modified the #barmar approach. I have used Object.keys to get keys from object. This will remove the any hard-coded dependency.
var response = {
"data": {
"data": [{
"1": "Arun",
"index": "name"
}, {
"1": 70.78,
"index": "score"
}]
}
}
const mappedData = response.data.data.reduce((acc, item,i) => {
var key = Object.keys(item);
acc[item[key[1]]] = item[key[0]]
return acc ;
}, {});
console.log(mappedData)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

Create JSON Array dynamically from an object

I have an object A as shown below.
var A = {
"1": [ "1_1", "1_2", "1_3" ],
"2": [ "2_1", "2_2" ]
};
Need to build a new array dynamically using js. Suppose
object A key should map to attribute text of Array AA and value should be to children as given below.
var AA = [
{
"text": "1",
"state": "open",
"children": [
{ "text": "1_1" },
{ "text": "1_2" },
{ "text": "1_3" }
]
},
{
"text": "2",
"state": "open",
"children": [
{ "text": "2_1" },
{ "text": "2_2" }
]
}
];
This is my function but its not working as expected. Could someone pls help?
function constructJSONArr() {
var A = {
"1": [ "1_1", "1_2", "1_3" ],
"2": [ "2_1", "2_2" ]
};
for (var key in A) {
var tempArr = [];
tempArr.push(key);
for (var i = 0; i < key.length; i++) {
return {
'text': key,
'state': 'closed',
'children': A[key].map(function(child) {
return {
'text': child
};
})
}
}
}
}
When you return inside a function, the function ends and returns immediately. In your case, the return inside the for loop causes the function to return the 1st key object. To solve this, you need to create the objects and push them into an arr. You can return freely inside Array.map() because each iteration invokes a function.
Fixed solution:
Iterate with for...in. Get the key. Push a new object into arr. Use the key as the text property, the state, and children. To create the children get the array from the original object by the key, and use Array.map() to generate the child objects. Return arr.
var A = {
"1": ["1_1", "1_2", "1_3"],
"2": ["2_1", "2_2"]
};
function constructJSONArr(A) {
var arr = [];
for (var key in A) {
arr.push({
text: key,
state: 'closed',
children: A[key].map(function(t) {
return {
text: t
};
})
});
}
return arr;
}
var result = constructJSONArr(A);
console.log(result);
ESNext solution
Use Object.entries() to get keys and respective values from the object A. Iterate the entries with two nested Array.map() calls. The 1st to create the outer object, and the 2nd to create the children.
const A = {
"1": ["1_1", "1_2", "1_3"],
"2": ["2_1", "2_2"]
};
const constructJSONArr = (obj) =>
Object.entries(obj).map(([text, children]) => ({
text,
state: 'closed',
children: children.map((text) => ({
text
}))
}));
var result = constructJSONArr(A);
console.log(result);
You can use Object.keys() to iterate through the object and Array.map to create the new array.
var A = {
"1": ["1_1", "1_2", "1_3"],
"2": ["2_1", "2_2"]
};
var transformed = Object.keys(A).map(key => {
return {
text: key,
state: "open",
children: A[key].map(value => {
return {
text: value
};
})
};
});
console.log(transformed);

Is there possible to loop inside JavaScript object literal

So this is my question (maybe stupid), is there any possible to do this:
var data {
"label" : value,
"sets" : [
for (var i=0; i < item.length; i++)
{
somedata: "data"
}
]
}
to reach result:
var data {
"label" : value,
"sets" : [
{
somedata: "data1"
},
{
somedata: "data2"
}
]
}
Much thx for help.
As jimm101 has pointed out, you are not working with JSON, that's just JavaScript (the var in there proves it) . If you want to calculate a value inside a literal JavaScript object, you can use an immediately invoked function
var data = {
"label" : value,
"sets" : (function(){
var arr = [];
for (var i=0; i < item.length; i++) {
arr.push( {somedata: "data" + i} ) ;
}
return arr;
})()
};
As dystroy has pointed out You can also use Array.map to return a transformed array, without needing an immediately invoked function, which looks a little nicer
You may use functional programming :
var data = {
"label" : "value",
"sets" : item.map(function(_,i){ return {somedata: "data"+(i+1)} })
}
Use the following:
var data = {
label: value,
get sets(){
var array = [];
/* write your logic to fill the array here. */
return array;
}
}
Reference here
As others have commented, JSON is data, not code. It looks like you're making javascript code though, since JSON also wouldn't include the var data part.
JSON => JavaScript Object Notation, a wide-spread way of representing data.
javascsript object => A structure within the javascript programming language that uses JavaScript Object Notation.
You can do something like this.
var data = {
"label" : 'my_label',
};
item = ['one','two','another'];
data.sets = [];
for (var i=0; i < item.length; i++)
{
data.sets.push({'somedata': item[i]});
}
You can use array comprehension (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Array_comprehensions), but it's not supported yet by all browsers (ECMAScript 6).
var value = "test";
var item = ["data1", "data2", "data3"];
var data = {
"label" : value,
"sets" : [for (x of item) {somedata: x}]
};
/*
Result :
data = {
"label":"test",
"sets":[
{"somedata":"data1"},
{"somedata":"data2"},
{"somedata":"data3"}
]
}
*/
You can have nested data in JSON like for example
var myObject = {
"first": "John",
"last": "Doe",
"age": 39,
"sex": "M",
"salary": 70000,
"registered": true,
"interests": [ "Reading", "Mountain Biking", "Hacking" ],
"favorites": {
"color": "Blue",
"sport": "Soccer",
"food": "Spaghetti"
},
"skills": [
{
"category": "JavaScript",
"tests": [
{ "name": "One", "score": 90 },
{ "name": "Two", "score": 96 }
]
},
{
"category": "CouchDB",
"tests": [
{ "name": "One", "score": 79 },
{ "name": "Two", "score": 84 }
]
},
{
"category": "Node.js",
"tests": [
{ "name": "One", "score": 97 },
{ "name": "Two", "score": 93 }
]
}
]
};
You can access such an array and its contents using a loop in your program
Source: http://www.json.com/

Categories