I am using angular and I have an array of Objects let's say Item(SmallItem, Bollean, Boolean) and in my code I add elements to this array by pushing, like for example:
this.Items.push(new Item(smallItem, false, true));
However, I would like to push an Item if and only if item with the same smallItem does not exist in Items. How do I go about it?
You can simply go back to the basic and do something along thoses lines :
const itemToAdd = new Item(smallItem, false, true);
if(this.Items.findIndex((item) => item.smallItem === itemToAdd.smallItem) < 0) {
this.Items.push(itemToAdd);
}
or if you don't want to create the Item if it is not added :
if(this.Items.findIndex((item) => item.smallItem === smallItemToAdd) < 0) {
this.Items.push(new Item(smallItemToAdd, false, true););
}
You can remove the items afterward with smallItem in it like
Items.forEach((index,item)=>{
if(item.smallItem){
Items.splice(index,1)
)
an other approach could be:
var find = false;
for (let item of Items) {
// same smallItem value
if(item.smallItem) {
find = true;
}
}
if(!find) {
this.Items.push(new Item(smallItem, false, true));
}
Give this a try:
let items = [
{ name: 'Item 1', booleanA: true, booleanB: true },
{ name: 'Item 2', booleanA: true, booleanB: true },
{ name: 'Item 3', booleanA: true, booleanB: true },
{ name: 'Item 4', booleanA: true, booleanB: true },
{ name: 'Item 5', booleanA: true, booleanB: true }
];
function addItemIfNotAlreadyPresent(itemToAdd) {
let itemAlreadyExist = items.find(
item => item.name === itemToAdd.name && item.booleanA === itemToAdd.booleanA && item.booleanB === itemToAdd.booleanB
);
if(!itemAlreadyExist) {
items.push(itemToAdd);
}
}
let itemAlreadyPresent = { name: 'Item 1', booleanA: true, booleanB: true };
addItemIfNotAlreadyPresent(itemAlreadyPresent);
console.log('Items after trying to add itemAlreadyPresent: ', items);
let itemNotPresent = { name: 'Item 6', booleanA: true, booleanB: true };
addItemIfNotAlreadyPresent(itemNotPresent);
console.log('Items after trying to add itemNotPresent: ', items);
Related
Thats my object tabs:
tabs: [
{
visible: true,
title: 'tab 0',
products: [{id:1, send: true, delivered: true}, {id:1, send: true, delivered: true}],
},
{
visible: true,
title: 'tab 1',
products: [{id:11, send: true, delivered: false}, {id:21, send: true, delivered: true}],
}
],
const allDerivered = (product) => product.delivered;
options.tabs.forEach(function (projectProducts){
if (projectProducts.products.every(allDerivered)){
statusTabs = 'DELIVERED';
}
});
Like the first products tabs are allDerivered it doesn't iterate through all the tabs object. How I can apply the allDerivered function to all the tabs?
I'm going to guess that you want to set statusTabs to "DELIVERED" if all products in all tabs are delivered, and not if any product in any tab is not delivered. If so, you can do that in a few ways.
You could use nested every calls:
if (options.tabs.every(({products}) => products.every(({delivered}) => delivered)) {
statusTabs = "DELIVERED";
}
Or nested loops with a label and a directed break:
let allDelivered = true;
outer: for (const {products} of options.tabs) {
for (const {delivered} of products) {
if (!delivered) {
allDelivered = false;
break outer;
}
}
}
if (allDelivered) {
statusTabs = "DELIVERED";
}
That's less cumbersome if you're also assigning to statusTabs in the false case:
statusTabs = "DELIVERED";
outer: for (const {products} of options.tabs) {
for (const {delivered} of products) {
if (!delivered) {
statusTabs = "UNDELIVERED";
break outer;
}
}
}
I'm going to make that assumption for the rest of the answer.
I didn't use allDelivered in the above because the name doesn't match what the function does (it only checks if one product is delivered, not if the all are) and because what the function does is just a single check of a property value which is just as clear done inline. But if you wanted a reusable function that does that check, you could have one:
const isDelivered = ({delivered}) => delivered;
Then the two code blocks above would be:
if (options.tabs.every(({products}) => products.every(isDelivered)) {
statusTabs = "DELIVERED";
}
Or nested loops with a label and a directed break:
statusTabs = "DELIVERED";
outer: for (const {products} of options.tabs) {
for (const product of products) {
if (isDelivered(product)) {
statusTabs = "UNDELIVERED";
break outer;
}
}
}
You could go further and have an allProductsDelivered function for tabs:
const allProductsDelivered = ({products}) => products.every(isDelivered);
Then the nested every would be:
if (options.tabs.every(allProductsDelivered)) {
statusTabs = "DELIVERED";
}
And the loops version would be:
statusTabs = "DELIVERED";
for (const {products} of options.tabs) {
if (!allProductsDelivered(tab)) {
statusTabs = "UNDELIVERED";
break;
}
}
I'm guessing you want something like this:
const tabs = [{
visible: true,
title: 'tab 0',
products: [{
id: 1,
send: true,
delivered: true
}, {
id: 1,
send: true,
delivered: true
}],
},
{
visible: true,
title: 'tab 1',
products: [{
id: 11,
send: true,
delivered: false
}, {
id: 21,
send: true,
delivered: true
}],
}
];
const isNotDelivered = (product) => product.delivered == false;
tabs.forEach(tab => {
const areAllProductsDelivered = !tab.products.find(isNotDelivered);
if (areAllProductsDelivered) {
console.log(`${tab.title} is completely delivered`);
} else {
console.log(`${tab.title} is NOT completely delivered`);
}
});
I'm working on an vue-application where I have a component for driving licenses.
I have the following:
data() {
return {
custom_licenses: [],
basic_licenses: []
}
}
within my methods, I have this:
regular_licenses() {
this.$store.dispatch("license/read").then(response => {
response.licenses.map((license, key) => {
// PUSH LICENSES WITH TYPE 'BASIC' TO this.basic_licenses
// PUSH LICENSES WITH TYPE 'CUSTOM' TO this.custom_licenses
});
});
},
and in my created() i have this:
created() {
this.regular_licenses()
}
The response from my dispatch, returns this:
licenses:
[
{
id: 1,
type: 'basic',
name: 'AMa'
},
{
id: 2,
type: 'basic',
name: 'A2'
},
{
id: 3,
type: 'basic',
name: 'C'
},
{
id: 4,
type: 'custom',
name: 'C1'
},
{
id: 5,
type: 'custom',
name: 'D'
},
and so on...
]
Now I want to loop through the array and separate or push them into custom_licenses and basic_licenses based on the type-attribute - how can I achieve that?
Try this
regular_licenses() {
this.$store.dispatch("license/read").then(response => {
response.licenses.map((license, key) => {
switch (license.type)
case 'basic':
this.basic_licenses.push({ ...license });
break;
case 'custom':
this.custom_licenses.push({ ...license });
break;
});
});
},
Update your Code Block:
response.licenses.map((license, key) => {
// PUSH LICENSES WITH TYPE 'BASIC' TO this.basic_licenses
if(license['type'] == 'basic') {
//deep clone
let tmpLicense = JSON.parse(JSON.stringify(license));
basic_licenses.push(tmpLicense);
} else if(license['type'] == 'custom') {
// PUSH LICENSES WITH TYPE 'CUSTOM' TO this.custom_licenses
//deep clone
let tmpLicense = JSON.parse(JSON.stringify(license));
custom_licenses.push(tmpLicense);
}
});
I have an array of objects that have deeply nested children and sometimes children within children. I am attempting to handle this recursively, but I am getting stuck.
The goal of the function is to return a single data object that matches the id.
My Data looks like this:
data: [
{
id: 'RAKUFNUBNY00UBZ40950',
name: 'Grade 1 Cover',
activityId: 'RAKUFNUBNY00UBZ40950',
nodeType: 'activity',
suppressed: false,
hidden: false
},
{
children: [
{
id: 'SLWDYEQHTZAFA3ALH195',
name: 'Build Background Video',
activityId: 'SLWDYEQHTZAFA3ALH195',
nodeType: 'activity',
suppressed: false,
hidden: false,
assetReference: {
referenceId: 'UWFHA5A1E0EGKCM0W899',
assetType: 'image'
}
},
{
children: [
{
id: 'HQUCD2SSRKMYC2PJM636',
name: 'Eat or Be Eaten Splash Card',
activityId: 'HQUCD2SSRKMYC2PJM636',
nodeType: 'activity',
suppressed: false,
hidden: true
},
{
children: [
{
id: 'ZDTWEZFL13L8516VY480',
name: 'Interactive Work Text: Eat or Be Eaten',
activityId: 'ZDTWEZFL13L8516VY480',
nodeType: 'activity',
suppressed: false,
hidden: true,
defaultLaunchMode: 'modal'
}
],
My attempt at solving this is like this:
findNode(id, currentNode) {
console.log('id', id);
console.log('findNode', currentNode);
var i, currentChild, result, counter;
counter = 0;
console.log('first conditional statement', currentNode);
if (id && currentNode.id === id) {
return currentNode[0];
} else {
counter++;
// Use a for loop instead of forEach to avoid nested functions
// Otherwise "return" will not work properly
console.log('counter', counter);
console.log('currentNode', currentNode[counter]);
console.log('currentNode Children', currentNode.children);
for (i = counter; i < currentNode.children.length; i += 1) {
console.log(currentNode[i].children[i]);
currentChild = currentNode[i].children[i];
// Search in the current child
result = this.findNode(id, currentChild);
// Return the result if the node has been found
if (result !== false) {
return result;
}
}
// The node has not been found and we have no more options
return false;
}
}
The code above fails because I having an extremely difficult time keeping track of a counter to loop through everything.
I also added a sample picture of my data output to give you a better example of how my data is structured. Any help will be greatly appreciated.
You shouldn't need a counter to locate a single node with a matching id. Try this simpler approach:
function findNode (id, array) {
for (const node of array) {
if (node.id === id) return node;
if (node.children) {
const child = findNode(id, node.children);
if (child) return child;
}
}
}
It will return undefined if there is no match.
To avoid the need for manual iteration, you might consider using an array method like reduce instead - return the accumulator if it's truthy (that is, an object was found already), or return the object being iterated over if the ID matches, or recursively iterate over the object's children to find a match.
const data=[{id:'RAKUFNUBNY00UBZ40950',name:'Grade 1 Cover',activityId:'RAKUFNUBNY00UBZ40950',nodeType:'activity',suppressed:!1,hidden:!1},{children:[{id:'SLWDYEQHTZAFA3ALH195',name:'Build Background Video',activityId:'SLWDYEQHTZAFA3ALH195',nodeType:'activity',suppressed:!1,hidden:!1,assetReference:{referenceId:'UWFHA5A1E0EGKCM0W899',assetType:'image'}},{children:[{id:'HQUCD2SSRKMYC2PJM636',name:'Eat or Be Eaten Splash Card',activityId:'HQUCD2SSRKMYC2PJM636',nodeType:'activity',suppressed:!1,hidden:!0},{children:[{id:'ZDTWEZFL13L8516VY480',name:'Interactive Work Text: Eat or Be Eaten',activityId:'ZDTWEZFL13L8516VY480',nodeType:'activity',suppressed:!1,hidden:!0,defaultLaunchMode:'modal'}],}],}],}]
function findId(id, arr) {
return arr.reduce((a, item) => {
if (a) return a;
if (item.id === id) return item;
if (item.children) return findId(id, item.children);
}, null);
}
console.log(findId('HQUCD2SSRKMYC2PJM636', data));
If your ids are unique and finding an object by id is a common task, you might want to consider creating a lookup object to improve performance. Creating the lookup object is an O(n) task; afterwards, looking up an object by id is O(1).
const data = [ { id: 'RAKUFNUBNY00UBZ40950', name: 'Grade 1 Cover', activityId: 'RAKUFNUBNY00UBZ40950', nodeType: 'activity', suppressed: false, hidden: false }, { children: [ { id: 'SLWDYEQHTZAFA3ALH195', name: 'Build Background Video', activityId: 'SLWDYEQHTZAFA3ALH195', nodeType: 'activity', suppressed: false, hidden: false, assetReference: { referenceId: 'UWFHA5A1E0EGKCM0W899', assetType: 'image' } }, { children: [ { id: 'HQUCD2SSRKMYC2PJM636', name: 'Eat or Be Eaten Splash Card', activityId: 'HQUCD2SSRKMYC2PJM636', nodeType: 'activity', suppressed: false, hidden: true }, { children: [ { id: 'ZDTWEZFL13L8516VY480', name: 'Interactive Work Text: Eat or Be Eaten', activityId: 'ZDTWEZFL13L8516VY480', nodeType: 'activity', suppressed: false, hidden: true, defaultLaunchMode: 'modal' } ] } ] } ] } ];
const lookup = {};
const registerIds = a => {
a.forEach(o => {
if ('id' in o) {
lookup[o.id] = o;
} else if ('children' in o) {
registerIds(o.children)
}
});
}
registerIds(data);
console.log(lookup)
Sorry for my two cents, just want to add a universal method that includes nested arrays
const cars = [{
id: 1,
name: 'toyota',
subs: [{
id: 43,
name: 'supra'
}, {
id: 44,
name: 'prius'
}]
}, {
id: 2,
name: 'Jeep',
subs: [{
id: 30,
name: 'wranger'
}, {
id: 31,
name: 'sahara'
}]
}]
function searchObjectArray(arr, key, value) {
let result = [];
arr.forEach((obj) => {
if (obj[key] === value) {
result.push(obj);
} else if (obj.subs) {
result = result.concat(searchObjectArray(obj.subs, key, value));
}
});
console.log(result)
return result;
}
searchObjectArray(cars, 'id', '31')
searchObjectArray(cars, 'name', 'Jeep')
I hope this helps someone
I've copied the Grid Component Example into a single-file component (Grid.vue). Within that component, I'm not able to access the columns prop. console.log(this.columns) always prints: [__ob__: Observer] to the log. Can someone tell me why? This works fine in their example on the page and in JSFiddle.
Here's my Grid.vue file:
<script>
export default {
name: 'grid',
props: {
data: Array,
columns: Array,
filterKey: String
},
data: function() {
var sortOrders = {}
console.log(this.columns)
this.columns.forEach((column) => {
sortOrders[column] = 1
});
return {
sortCol: '',
sortOrders: sortOrders
}
},
computed: {
filteredData: function () {
var sortCol = this.sortCol
var filterKey = this.filterKey && this.filterKey.toLowerCase()
var order = this.sortOrders[sortCol] || 1
var data = this.data
if (filterKey) {
data = data.filter((row) => {
return Object.keys(row).some((key) => {
return String(row[key]).toLowerCase().indexOf(filterKey) > -1
})
})
}
if (sortCol) {
data = data.slice().sort((a, b) => {
a = a[sortCol]
b = b[sortCol]
return (a === b ? 0 : a > b ? 1 : -1) * order
})
}
return data
}
},
filters: {
capitalize: function (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
},
methods: {
sortBy: function (key) {
this.sortCol = key
console.log(this.sortOrders[key])
this.sortOrders[key] = this.sortOrders[key] * -1
console.log(this.sortOrders[key])
}
},
created() {
},
mounted() {
// var app = this
},
}
</script>
I'm using this component within another component like so:
<template>
<div>
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<grid :data="things" :columns="thingColumns" :filterKey="searchQuery"></grid>
</div>
</template>
<script>
import Grid from './Grid.vue';
export default {
name: 'things-grid',
data: function() {
return {
things: [],
thingColumns: [],
searchQuery: ''
}
},
mounted() {
var app = this
app.things = [
{id: 1, this: 'this 1', that: 'that 1', thing: 'thing 1'},
{id: 2, this: 'this 2', that: 'that 2', thing: 'thing 2'},
{id: 3, this: 'this 3', that: 'that 3', thing: 'thing 3'},
{id: 4, this: 'this 4', that: 'that 4', thing: 'thing 4'},
{id: 5, this: 'this 5', that: 'that 5', thing: 'thing 5'},
]
app.thingColumns = [
'this', 'that', 'thing'
]
app.searchQuery = ''
},
components: { Grid }
}
</script>
In:
<grid :data="things" :columns="thingColumns" :filterKey="searchQuery"></grid>
The value of this.thingColumns is passed as :columns when mounting.
Thus, the console.log(this.columns) inside Grid.vue/data() prints when it is mounting.
And when it is mounting, thingColumns is empty in the parent:
data: function() {
return {
things: [],
thingColumns: [], // initially empty
searchQuery: ''
}
},
mounted() {
var app = this
// ...
app.thingColumns = [ // this code only runs after it is mounted
'this', 'that', 'thing'
]
// ...
},
Since the console.log(this.columns) inside Grid.vue/data() prints when it is mounting, that is, before it is mounted, it prints an empty array:
[__ob__: Observer] // this is an empty array, the __ob__ thing is related to Vue internals
Because, well, parent's thingColumns will only have data after the mounted() hook executes.
And since it is a reactive array, when you update it, it will update the child grid component as well.
Solution:
Move the property initalization code from mounted() to created():
created() { // was mounted()
var app = this
// ...
app.thingColumns = [
'this', 'that', 'thing'
]
// ...
},
This will initialize the data sooner and make it available in time for the console.log() in the child to pick it up.
Consider the following object:
var content = [
{
id: 'tab1',
langCode: 'en',
title: 'rocket'
},
{
id: 'tab1',
langCode: 'da',
title: 'raket'
},
{
id: 'tab2',
langCode: 'en',
title: 'factory'
},
{
id: 'tab3',
langCode: 'es',
title: 'boligrafo'
},
];
I'd like to reduce this array, to a new array with the following restriction:
No duplicate IDs
Values from the local language should take preference
In case there are no local translation, fall back to English
All other translations should be discarded (even if they have a unique ID)
That means, the output from the data above would be as follows, if the local language were Danish:
[
{
id: 'tab1',
langCode: 'da',
title: 'raket'
},
{
id: 'tab2',
langCode: 'en',
title: 'factory'
},
];
My goal is to keep the code as short and readable as possible, and I have ES6 and Lodash at my full disposal. This is what I have so far:
const LOCAL_LANG = 'da'; // Hard coded for the sake of the example
let localArr = content.filter(item => item.langCode === LOCAL_LANG);
if(LOCAL_LANG !== 'en') {
let enArr = content.filter(item => item.langCode === 'en');
for (let i = 0; i < enArr.length; i++) {
if (!_.find(localArr, { 'id': enArr[i].id})) {
localArr.push(enArr[i]);
}
}
}
This does the trick, but it creates two duplicate arrays, and then merges them back together in what I feel is a clumpsy way. I would like to see a more elegant solution – preferably one, where I don't have to pass over the arrays so many times.
An alternative (and perhaps slightly cleaner) solution would be to reduce the array on langCode === 'da' || langCode === 'en' on first pass, and then remove duplicates ... but still I'm just feeling I'm missing the most obvious solution.
Thanks!
I would reduce everything into an object keyed by ID to enable easy lookups without those extra _.find calls:
const targetLang = 'da';
const fallbackLang = 'en';
const itemsByKey = content.reduce((allItems, item) => {
if (item.langCode === targetLang
|| (!(item.id in allItems) && item.langCode === fallbackLang))
{
return Object.assign(allItems, { [item.id]: item });
} else {
return allItems;
}
}, {});
This solution will require only one pass over the original array. If you then need this lookup object converted back into an array, you'd need a second pass:
var normalizedArray = Object.keys(itemsByKey).map(key => itemsByKey[key]);
You could use a tree and filter the wanted items.
var content = [{ id: 'tab1', langCode: 'en', title: 'rocket' }, { id: 'tab1', langCode: 'da', title: 'raket' }, { id: 'tab2', langCode: 'es', title: 'boligrafo' }, { id: 'tab2', langCode: 'en', title: 'pen' }],
object = Object.create(null);
content.forEach(function (a) {
object[a.id] = object[a.id] || {}
object[a.id][a.langCode] = a;
});
var result = Object.keys(object).map(k => object[k][Object.keys(object[k]).filter(l => l !== 'en')[0] || 'en']);
console.log(result);
I would do as follows.
var content = [
{
id: 'tab1',
langCode: 'en',
title: 'rocket'
},
{
id: 'tab1',
langCode: 'da',
title: 'raket'
},
{
id: 'tab2',
langCode: 'es',
title: 'boligrafo'
},
{
id: 'tab2',
langCode: 'en',
title: 'pen'
}
],
result = content.sort((a,b) => a.langCode === "en" ? 1 : -1)
.reduce((p,c) => p.findIndex(o => o.id === c.id) === -1 ? p.concat(c) : p,[]);
console.log(result);