Javascript Array doesn't map correctly - javascript

I'm "consolling" the entire code... but I can't find any issue, only a weird behaviour.
Let me explain:
I've an angular component (let's call it parent) which send some tags to his child through a inputTags array.
Then I need to set another list with ALL the tags of the user, called allTags.
The array (both inputTags and allTags) is formatted like this:
{ id: 'tagId', name: 'tagName' }
I need to make an unified array of those two. The expected output should contain an array of items that it's formatted like this: { id: 'tagId', name: 'tagName', selected: boolean }
In order to do this I'm mapping the allTags array in this way:
Let's suppose that:
inputTags = [
{ id: 'work', name: 'Work' },
{ id: 'motivation', name: 'Motivation' }
];
allTags = [
{ id: 'network', name: 'Network' },
{ id: 'work', name: 'Work' },
{ id: 'smart', name: 'Smart' },
{ id: 'motivation', name: 'Motivation' }
];
Now... allTags are actually retrived from a server, so my code looks something like this:
this.tagsService.getAll().subscribe(tags => {
this.allTags = tags.map(tag => {
let select = false;
this.inputTags.forEach(inputTag => { select = (inputTag.id === tag.id) })
return {
id: tag.id,
name: tag.name,
selected: select,
};
});
})
This for me seems quite standard, but in fact NO, because instead of getting:
allTags = [
{ id: 'network', name: 'Network', selected: false },
{ id: 'work', name: 'Work', selected: true }, // is selected
{ id: 'smart', name: 'Smart', selected: false },
{ id: 'motivation', name: 'Motivation', selected: true } // is selected
];
I get this:
allTags = [
{ id: 'network', name: 'Network', selected: false },
{ id: 'work', name: 'Work', selected: false }, // is NOT selected
{ id: 'smart', name: 'Smart', selected: false },
{ id: 'motivation', name: 'Motivation', selected: true } // is selected
];
Basically the issue is that it's selecting only one tag, not multiple tags.

You can try some:
this.allTags = tags.map(tag => {
return {
id: tag.id,
name: tag.name,
selected: this.inputTags.some(inputTag => inputTag.id === tag.id)
};
});

JavaScript Array map() Method
*)creates a new array with the results of calling a function for every array element and it calls the provided function once for each element in an array, in order.
Note: map() Method does not execute the function for array elements without values and it does not change the original array.

Try the following:
this.allTags = allTags.map(tag => ({
id: tag.id,
name: tag.name,
selected: inputTags.some(i => i.id === tag.id),
}))

Related

Filtering object with nested arrays by several values

I'm trying to filter object with nested arrays by several criteria. Filtering options are generated dynamically and stored in array. This options values are theme id's in nested objects. If filtering options contain for example 2 id's values I need to show all objects that have that theme id's.
let data = {
'17 may': [
{
id: 31,
name: 'Test Name',
theme: {
id: 2,
name: 'Theme Test Name',
},
},
],
'18 may': [
{
id: 41,
name: 'Test Name',
theme: {
id: 2,
name: 'Theme Test Name',
},
},
{
id: 43,
name: 'Test Name',
theme: {
id: 3,
name: 'Theme Test Name',
},
},
],
'19 may': [
[
{
id: 51,
name: 'Test Name',
theme: {
id: 1,
name: 'Theme Test Name',
},
},
{
id: 52,
name: 'Test Name',
theme: {
id: 2,
name: 'Theme Test Name',
},
},
],
],
};
filteringOptions = [1,2]; // theme id's
I use filtering function for nested objects. It's working fine, but I dont' know how to pass more than one filtering option.
filterArray(array, filters) {
const filterKeys = Object.keys(filters);
return array.filter((item) => {
return filterKeys.every((key) => {
if (typeof filters[key] !== 'function') return true;
return filters[key](item[key]);
});
});
}
Filtering algorithm
const filteredByThemeId = {};
for (const day in data) {
filteredByTheme[day] = [];
this.data[day].map((item, index) => {
filteredByThemeId[day][index] = [
...filterArray(item, {
theme:
(theme) => {
if (!theme) return;
return theme.id === 2; // works fine, but I need to pass all values from filtering options array (options can contain 2, 5, 10 etc. values)
},
}),
];
});
}
Suppose you want to filter for all values in list called filterList = [2,5,10]. Instead of return theme.id === 2, you can try return filterList.includes(theme.id)

Filter nested array of objects to array of objects

I have an array of objects which is nested. How do I make an array of objects with this one getting values from nested properties as well? If onClick property is empty, it means there is a property called children and the parent element should not be a part of the new list. I need to loop through the children array and get the values. Please see the expected output below.
const headers = [{
title: 'Arun',
id: 'arunId',
onClick: 'onClickArun'
},
{
title: "George",
id: 'georgeId',
onClick: '',
children: [{
title: 'David',
id: 'davidId',
onClick: 'onClickDavid'
},
{
title: 'Patrick',
id: 'patrickId',
onClick: 'onClickPatrick'
}
]
},
{
title: 'Mark',
id: 'markId',
onClick: 'onClickMark'
}
];
console.log(headers.map(item => {
return {
title: item.title,
onClick: item.onClick
}
}))
Expected Output:
[{
title: 'Arun',
onClick: 'onClickArun'
},
{
title: 'David',
onClick: 'onClickDavid'
},
{
title: 'Patrick',
onClick: 'onClickPatrick'
},
{
title: 'Mark',
onClick: 'onClickMark'
}
]
Any help is greatly appreciated.
You could take Array#flatMap with a recursive callback.
const
map = ({ title, onClick, children }) => onClick
? { title, onClick }
: children.map(map);
var headers = [{ title: 'Arun', id: 'arunId', onClick: 'onClickArun' }, { title: "George", id: 'georgeId', onClick: '', children: [{ title: 'David', id: 'davidId', onClick: 'onClickDavid' }, { title: 'Patrick', id: 'patrickId', onClick: 'onClickPatrick' }] }, { title: 'Mark', id: 'markId', onClick: 'onClickMark' }],
result = headers.flatMap(map);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can do this using Array.prototype.reduce, where you just need to check if the parent onClick prop is empty and the children prop is present or not:
var headers = [{ title: 'Arun', id: 'arunId', onClick: 'onClickArun' }, { title: "George", id: 'georgeId', onClick: '', children: [{ title: 'David', id: 'davidId', onClick: 'onClickDavid' }, { title: 'Patrick', id: 'patrickId', onClick: 'onClickPatrick' }] }, { title: 'Mark', id: 'markId', onClick: 'onClickMark' }];
function getObject(headers, acc){
return headers.reduce((acc, ele) => {
if(!ele.onClick.length && ele.children){
acc = getObject(ele.children, acc);
}else{
acc.push({"title": ele.title, "onClick": ele.onClick});
}
return acc;
}, acc);
}
console.log(getObject(headers, []));
Looks like you need a depth first search.
You run on the array, for each item with children recurse on it and pass along the existing array. otherwise, add the item to the list.
function getChildren(array, total = []){
for(let item of array) {
if(item.children) {
getChildren(item.children, total)
} else {
total.push({
title: item.title,
onClick: item.onClick
})
}
}
return total
}
let headers = [{
title: 'Arun',
id: 'arunId',
onClick: 'onClickArun'
},
{
title: "George",
id: 'georgeId',
onClick: '',
children: [{
title: 'David',
id: 'davidId',
onClick: 'onClickDavid'
},
{
title: 'Patrick',
id: 'patrickId',
onClick: 'onClickPatrick'
}
]
},
{
title: 'Mark',
id: 'markId',
onClick: 'onClickMark'
}
]
// take children, only if onClick is empty
.map(item => item.onClick ? item : item.children)
// make flat array
headers = [].concat.apply([], headers)
.map(item => {
const temp = {};
temp.title = item.title;
temp.onClick = item.onClick;
return temp; // take only onClick and title property from each item
})

How to replace values in my array of objects using key from other object in another array

I have 2 arrays of objects
NOTE: status and original-language can't be set manually as they change all the time, these are custom fields. The slugs from fields make up all custom fields.
const items = [
{
name: 'Surviving the Game',
status: 'ffdb29ba075fcbc0b71295c31a13d64f',
original-language: 'b4ebbe06702794d1cf375274197267b2',
},
{
name: 'Some Movie',
status: 'cd53c082c6ca9e7d3ec66890e66c01f3',
original-language: '7a1cac74217747933bb3915888dea090',
},
];
const fields = [
{
slug: 'status',
options: [
{
name: 'Released',
id: 'ffdb29ba075fcbc0b71295c31a13d64f',
},
{
name: 'Upcoming',
id: 'cd53c082c6ca9e7d3ec66890e66c01f3',
},
],
},
{
slug: 'original-language',
options: [
{
name: 'de',
id: 'b4ebbe06702794d1cf375274197267b2',
},
{
name: 'en',
id: '7a1cac74217747933bb3915888dea090',
},
],
},
];
status and original-language in [items] have an id value which matches an option in the corresponding fields array.
I am trying to return a new array for [items] with the name from options with the matching id.
eg:
[
{
name: 'Surviving the Game',
status: 'Released',
original-language: 'de',
},
{
name: 'Some Movie',
status: 'Upcoming',
original-language: 'en',
},
];
How would I go about this with ES6/7?
I am not sure where to start
I would accomplish this by creating a lookup object that houses lookups for both your statuses and languages. You can then use this lookup object when mapping through your items.
var items = [
{
name: 'Surviving the Game',
status: 'ffdb29ba075fcbc0b71295c31a13d64f',
"original-language": 'b4ebbe06702794d1cf375274197267b2'
},
{
name: 'Some Movie',
status: 'cd53c082c6ca9e7d3ec66890e66c01f3',
"original-language": '7a1cac74217747933bb3915888dea090'
}
];
var fields = [
{
slug: 'status',
options: [
{
name: 'Released',
id: 'ffdb29ba075fcbc0b71295c31a13d64f'
},
{
name: 'Upcoming',
id: 'cd53c082c6ca9e7d3ec66890e66c01f3'
}
]
},
{
slug: 'original-language',
options: [
{
name: 'de',
id: 'b4ebbe06702794d1cf375274197267b2'
},
{
name: 'en',
id: '7a1cac74217747933bb3915888dea090'
}
]
}
];
const lookup = {};
fields.forEach(field => {
lookup[field.slug] = field.options.reduce((all, option) => ({
...all,
[option.id]: option.name
}), {})
});
const translatedItems = items.map(item => {
return Object.entries(item)
.reduce((all, [key, val]) => ({
...all,
[key]: lookup[key] ? lookup[key][val] : val
}),{});
});
console.log(translatedItems);
I'd define a function that obtains the value for a field, like so:
function valueForField(field, id) {
const field = fields.find((itemfields) => itemfields.slug === field);
if(!field) return null;
const option = field.options.find(option => option.id === id);
if(!option) return null;
return option.name;
}
This can then be used like so:
const newItems = items.map(item => {
const { name } = item;
const newItem = {name};
newItem["original-language"] = valueForField('original-language', item["original-language"]);
newItem.status = valueForField('status', item.status);
return newItem;
});
Use map to create a new array of objects having name, status and originalLanguage fields along with the find method to get the name from fields for every status identifier.
const items = [{
name: 'Surviving the Game',
status: 'ffdb29ba075fcbc0b71295c31a13d64f',
originalLanguage: 'b4ebbe06702794d1cf375274197267b2',
},
{
name: 'Some Movie',
status: 'cd53c082c6ca9e7d3ec66890e66c01f3',
originalLanguage: '7a1cac74217747933bb3915888dea090',
},
];
const fields = [{
slug: 'status',
options: [{
name: 'Released',
id: 'ffdb29ba075fcbc0b71295c31a13d64f',
},
{
name: 'Upcoming',
id: 'cd53c082c6ca9e7d3ec66890e66c01f3',
},
],
},
{
slug: 'original-language',
options: [{
name: 'de',
id: 'b4ebbe06702794d1cf375274197267b2',
},
{
name: 'en',
id: '7a1cac74217747933bb3915888dea090',
},
],
},
],
newArr = items.map(i => ({
name: i.name,
status: fields.find(f => f.slug == 'status').options.find(o => o.id == i.status).name,
originalLanguage: fields.find(f => f.slug == 'original-language').options.find(l => l.id == i.originalLanguage).name
}));
console.log(newArr);

Filtering array of objects by searching nested object properties

I have an array of objects that I want to filter by comparing a nested property to a search term.
For example:
var array = [
{category: 'Business'
users: [
{name: 'Sally'
tags: [{tag: 'accounting'}, {tag: 'marketing'},...]
},
{name: 'Bob'
tags: [{tag: 'sales'}, {tag: 'accounting'},...]
}...
]
},
{category: 'Heritage'
users: [
{name: 'Linda'
tags: [{tag: 'Italy'}, {tag: 'Macedonia'},...]
},
{name: 'George'
tags: [{tag: 'South Africa'}, {tag: 'Chile'},...]
},...
]
},...
[
Essentially I want to filter the base array of objects by a search terms that include characters from the tag property string in the nested objects 2 arrays down.
So a search for 'market' would result in
[
{category: 'Business'
users: [
{name: 'Sally'
tags: [{tag: 'accounting'}, {tag: 'marketing'},...]
},
{name: 'Bob'
tags: [{tag: 'sales'}, {tag: 'accounting'},...]
}...
]
}
]
Thank you.
You could use Array#filter with looking into the nested arrays by using Array#some.
If the tag is found in a nested array, then iteration stops and the result is given back to the filter callback.
var array = [{ category: 'Business', users: [{ name: 'Sally', tags: [{ tag: 'accounting' }, { tag: 'marketing' }] }, { name: 'Bob', tags: [{ tag: 'sales' }, { tag: 'accounting' }] }] }, { category: 'Heritage', users: [{ name: 'Linda', tags: [{ tag: 'Italy' }, { tag: 'Macedonia' }] }, { name: 'George', tags: [{ tag: 'South Africa' }, { tag: 'Chile' }] }] }],
tag = 'marketing',
result = array.filter(a => a.users.some(u => u.tags.some(t => t.tag.includes(tag))));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The solution using Array.prototype.some() function:
var arr = [{ category: 'Business', users: [{ name: 'Sally', tags: [{ tag: 'accounting' }, { tag: 'marketing' }] }, { name: 'Bob', tags: [{ tag: 'sales' }, { tag: 'accounting' }] }] }, { category: 'Heritage', users: [{ name: 'Linda', tags: [{ tag: 'Italy' }, { tag: 'Macedonia' }] }, { name: 'George', tags: [{ tag: 'South Africa' }, { tag: 'Chile' }] }] }],
search_key = 'market',
result = [];
arr.forEach(function(o){
if (o.users.some(function(v){
return v.tags.some(function(i){ return i.tag.indexOf(search_key) !== -1; });
})) {
result.push(o);
}
});
console.log(result);
Try this:
function search(term){
return
Array.filter(array,function(item){
return JSON.stringify(obj).indexOf(term)!=-1;
});
}
So :
console.log(search('market'));
I hope to be helpful for you:)
The concatAll and concatMap definitions are taken from http://reactivex.io/learnrx/
Array.prototype.concatAll = function() {
var results = [];
this.forEach(function(subArray) {
results.push.apply(results, subArray);
});
return results;
};
Array.prototype.concatMap = function(projectionFunctionThatReturnsArray) {
return this.
map(function(item) {
return projectionFunctionThatReturnsArray(item);
}).
// apply the concatAll function to flatten the two-dimensional array
concatAll();
};
function filterByTags(keyword) {
return array.filter(function (item) {
var allTags = item.users.concatMap(function (user) {
return user.tags.map(function (tag) {
return tag.tag;
});
});
return allTags.some(function (tag) {
return tag.indexOf(keyword) > -1;
});
});
}
console.log(filterByTags('market'));
Of course you could inline the allTags variable for more conciseness.
The filter applied to the initial array will return all items that have users whose tags contain the keyword supplied. The strategy is to build a flattened version of the users' tags and apply some on that.
You can use array.filter like this:
function getFiltered(val) {
return array.filter(category == val);
}
This function will return a new array instance, only with the category keys you passed as the val params.
Note: I am taking a shortcut-like approach to this, primarily to provide a different perspective to the problem.
Instead of deep-searching the properties and arrays under the main array, you can create a json string of the users property and search within that. So I have created a new property usersString that temporarily stores the JSON string of the value against users property.
item.usersString = JSON.stringify(item.users);
Now, this would not be a perfect implementation, but it would almost always work. Also, if you stored this property within the browser (without storing it back to the DB), and used it to quick-search for every time user searches, I think it would be more performant that deep-searching entire array.
var array = [{
category: 'Business',
users: [{
name: 'Sally',
tags: [{
tag: 'accounting'
}, {
tag: 'marketing'
}]
},
{
name: 'Bob',
tags: [{
tag: 'sales'
}, {
tag: 'accounting'
}]
}
]
},
{
category: 'Heritage',
users: [{
name: 'Linda',
tags: [{
tag: 'Italy'
}, {
tag: 'Macedonia'
}]
},
{
name: 'George',
tags: [{
tag: 'South Africa'
}, {
tag: 'Chile'
}]
}
]
}
];
var key = "market";
// Convert the users property into a string - so that it works as a quick search target.
array.forEach(function(item) {
item.usersString = JSON.stringify(item.users);
});
var filteredItems = array.filter(function(item) {
return item.usersString.toLowerCase().indexOf(key.toLowerCase()) >= 0;
});
// Delete the usersString property - if required.
filteredItems.forEach(function(item) {
item.usersString = undefined;
// Or,
// delete item.usersString;
})
console.log(filteredItems);

Efficient way to do the filter using loadash or any other library?

I am filtering array whenever checkboxes are checked. There are totally 7 checkboxe each is associated with an object.
here is my code,
if (this.deliveryConcession[0].checked) {
this.allItems = this.allItems.filter(fil => fil.deliveryconcession.readytoship === this.deliveryConcession[0].checked);
}
if (this.deliveryConcession[1].checked) {
this.allItems = this.allItems.filter(fil => fil.deliveryconcession.instantdownload === this.deliveryConcession[1].checked);
}
if (this.deliveryConcession[2].checked) {
this.allItems = this.allItems.filter(fil => fil.deliveryconcession.unespecifiedshipment === this.deliveryConcession[2].checked);
}
if (this.seatConcession[0].checked) {
this.allItems = this.allItems.filter(fil => fil.seatingConcession.parking === this.seatConcession[0].checked);
}
if (this.seatConcession[1].checked) {
this.allItems = this.allItems.filter(fil => fil.seatingConcession.restrictedview === this.seatConcession[1].checked);
}
if (this.seatConcession[2].checked) {
this.allItems = this.allItems.filter(fil => fil.seatingConcession.wheelchair === this.seatConcession[2].checked);
}
if (this.seatConcession[3].checked) {
this.allItems = this.allItems.filter(fil => fil.seatingConcession.alcoholFree === this.seatConcession[3].checked);
}
here is my objects for filter,
seatConcession = [
{ id: 1, name: 'Parking pass included', checked: false },
{ id: 2, name: 'Unrestricted view', checked: false },
{ id: 3, name: 'Wheel chair accessible', checked: false },
{ id: 4, name: 'Without age restrictions', checked: false }
];
deliveryConcession = [
{ id: 1, name: 'Ready to ship(paper)', checked: false },
{ id: 2, name: 'Instant download(e-ticket)', checked: false },
{ id: 3, name: 'Unspecified shipment(paper)', checked: false }
];
how can i improve the above with simple loadash filter or another way?
let keys = [
["readytoship", "deliveryConcession"],
["instantdownload", "deliveryConcession"],
/* and so on, make sure to order */
];
this.allItems.filter(item => {
return keys.every((arr, i) => {
let [k, attr] = arr;
return item[attr][k] === this[attr][i].checked;
});
});
You will need to order the keys array appropriately. But now it's a two-liner. Other than let and the arrow functions this is all valid ES 5, no lodash required.
EDIT
Since you still haven't actually posted the relevant code this is still something of a stab in the dark, but taking your sample input of
seatConcession = [
{ id: 1, name: 'Parking pass included', checked: false },
{ id: 2, name: 'Unrestricted view', checked: false },
{ id: 3, name: 'Wheel chair accessible', checked: false },
{ id: 4, name: 'Without age restrictions', checked: false }
];
deliveryConcession = [
{ id: 1, name: 'Ready to ship(paper)', checked: false },
{ id: 2, name: 'Instant download(e-ticket)', checked: false },
{ id: 3, name: 'Unspecified shipment(paper)', checked: false }
];
And assuming you have a list of which checkboxes are checked that is ordered in the same order as the objects like so
let checked = [true, false, true, true, false /* etc */];
You want to do something like this:
let filtered = seatConcessions
.concat(deliveryConcession)
.filter((obj, i) => checked[i]);
You will have to adapt this to your specific case (again, since the sample input you put up is different than the code you wrote), but is a pattern for doing this in general.

Categories