Avoid databinding on nested arrays - vue.js - javascript

I'm getting headache avoiding databinding on nested arrays.
Let's say i have 2 objects:
Items, array of all items
Item, a single item object
I use
Object.assign({}, object)
to avoid databinding, bit this only works for non-nested array fields.
Example:
data: {
items: [
{
name: 'Pencil case',
contents: [
{title: "Red Pencil"}, {title: "Blue Pencil"}
]
},
{
name: 'Rubber container',
contents: [
{title: "Yellow Rubber"}, {title: "Green Rubber"}
]
},
],
selected_item: {
name: 'Pencil case',
contents: [
{title: "Red Pencil"}, {title: "Blue Pencil"}
]
}
},
mounted() {
this.selected_item = Object.assign({}, this.items[0]);
}
There's no databinding on name, but there is still binding on contents.title for example. I absolutely need to assign the object absolutely without databinding.
Here's a JSFIDDLE.
In the first input binding on "title" is real, while in the second input there are no binding on "name" as expected. I can't get over it, help me please.

Related

How to filter multiple nested arrays with objects

I have an array nested with an array of objects, the problem is that I don't understand how to filter the entire property by title at once.
At the moment, filtering for the title property works fine for me, but it only works for the top level, that is, it does not work inside the elems array.
At the moment, my code looks like this
state: {
searchValue: "",
categories: [
{
title: "about",
open: false,
elems: [{ title: "portfolio" }],
},
{
title: "services",
open: false,
elems: [{ title: "jobs" }],
},
],
},
getters: {
categories(state) {
return state.categories.filter(item => {
return item.title.toLowerCase().includes(state.searchValue.toLowerCase())
})
}
}
And in the end, for a better understanding, I would like to tell what I want to achieve when the user enters the word about or portfolio into the input, for example, I want to show the first object if the word jobs or services is entered, I want to show the second object, but again, the title property of the first level works for me, but the title property inside the elems array does not work.
You can try with some for nested array:
let searchValue = "about"
const categories = [
{title: "about", open: false, elems: [{ title: "portfolio" }],},
{title: "services", open: false, elems: [{ title: "jobs" }, { title: "about" }],},
]
function cats(state) {
return state.filter(item => {
return item.title.toLowerCase().includes(searchValue.toLowerCase()) ||
item.elems.some(e => e.title.toLowerCase().includes(searchValue.toLowerCase()))
})
}
console.log(cats(categories))

Find elements with specific value in a nested array with unknown depth

I have an array with objects for which the nesting depth is unknown and can be different. An example of such an array looks like that:
let exampleArray = [
{
id: 'some-id',
label: "Item 1",
children: [
{
id: 'some-id',
label: "Child 1",
children: [
{
id: 'some-id', // How to find this for example?
label: "Child 2",
children: []
}
]
}
]
},
{
id: 'some-id',
label: "Item 2",
children: [
{
id: 'some-id',
label: "Child 1",
children: [
{
id: 'some-id',
label: "Child 2",
children: []
}
]
}
]
}
]
Each array item can have a nested array children. The basic structure is the same as the parent one.
The challenge
When I for example want to delete a nested element with a specific id, how could I do this? I think it would not be the best practice to start iterations with a static number of loops, because I don't know how big the array is nested.
If I know the ID from the children element, can I say something like: "Iterate the entire array including all nesting and find the element with the ID xy?".
Or what would be the best practice to handle such nested arrays?
A recursive function is likely what you're looking for:
function deleteById(data, id){
for(var x = 0; x < data.length; x++){
if(data[x].id === id){
data.splice(x, 1); // if it matches, remove it from the array
}
else{
deleteById(data[x].children, id);
}
}
}

Returning the ids from the object within the arrays

I have a multidimensional javascript array of objects that I am trying to use to simply collate the Unit id into a new array as shown below.
What is the best solution for returning the id within the inner value so I just get an array of the ids whatever I try seems to not work
[
{
units: [
{
id: 10000282,
name: "Group 1",
},
{
id: 10000340,
name: "Group 2",
},
{
id: 10000341,
name: "Group 3",
},
],
},
{
units: [
{
id: 10000334,
name: "Group 4",
},
],
},
]
Expected output - just return an array with simply the ids
e.g
ids = [ 10000282, 10000340, 10000341, 10000334 ]
Assuming that data is in variable data:
> data.map(o => o.units.map(u => u.id)).flat()
[ 10000282, 10000340, 10000341, 10000334 ]
This assumes you're in an environment where .flat() is a thing.
If that's not the case, the longer way around is
const ids = [];
data.forEach(o => {
o.units.forEach(u => {
ids.push(u.id);
});
});

Fastest possible object remapping for nested structure

Assume we have two different structures from two different APIs. Each has a different schema.
We have this as a return from API #1
[
{
Id: "test1",
Title: "label 1",
Children: [
{
Id: "test2",
Title: "label 2",
Children: [
{
Id: "test3",
Title: "label 3"
}
]
}
]
}
]
I need to convert it to the following scheme:
[
{
value: "test1",
label: "label 1",
children: [
{
value: "test2",
label: "label 2",
children: [
{
value: "test3",
label: "label 3"
}
]
}
]
}
]
So far I have come up with this method:
const transformItem = ({ Id, Title, Children }) => ({
value: Id,
label: Title,
children: Children ? transformData(Children) : null
});
const transformData = arr => arr.map(item => transformItem(item));
// Process data
const DataForApi2 = transformData(DataFromApi1);
From the limited benchmarking I performed and from what I can tell, in V8 (which is 95+% of our userbase) this looks fast enough as I'm not mutating any data structure (ergo hot objects are intact and retain performance) and using everything under a scope so I don't waste memory. Seems to be of linear complexity and not too bad if only performed once per client loading the app (only the first time after login).
In terms of runtime you're right this is probably the fastest we can get with O(n).
You could improve your space complexity by converting your solution from recursive to iterative. It saves space on the callstack which helps in extreme cases where trees go extremely deep.

Select first element from inner array

I'd like to select the first item from a nested Array, without fetching the whole document.
Schema/Model
Suppose I have a Schema like so:
const parentSchema = mongoose.Schema({
name: String,
children: []
});
const grandparentSchema = mongoose.Schema({
name: String,
children: [parentSchema]
})
Which would translate to this example instance:
{
name: 'Grandparent Foo',
children: [
{
name: 'Parent Foo',
children: ['Child Foo', 'Child Bar', 'Child Baz']
}
]
}
Question
I would like to get the first child of 'Parent Foo', so to boil it down I should be getting back 'Child Foo'
Notes
As you can see, the grandchildren are plain Strings, not Documents themselves (in contrast with the Parent) so I can't select them using dot notation.
I don't want to return the whole document and filter through it in code. I'd like to get over the wire only the first grandchild since the grandchildren Array (the children array of 'Parent Foo') can potentially contain millions of entries.
I need this because I want to $pop the first grandchild and return it. To do that, I plan on fetching the item first and then $pop it off, hence why I ask this question
You cannot really, without throwing extra work at the database.
As a general explanation:
Grandparent.find(
{ "children.name": "Parent Foo" },
{ "children.$": 1 }
)
Will return just the matched entry from "children" and no others should they exist.
If you explicitly need the "first" array element, then you use .aggregate():
Granparent.aggregate([
{ "$match": { "children.name": "Parent Foo" } },
{ "$addFields": {
"children": {
"$map": {
"input": {
"$filter": {
"input": "$children",
"as": "child",
"cond": { "$eq": [ "$$child.name", "Parent Foo" ] }
}
},
"as": "child",
"in": {
"name": "$$child.name",
"children": { "$arrayElemAt": [ "$$child.children", 0 ] }
}
}
}
}}
])
So there you basically use $filter to replicate the standard positional match an then use $map to reshape with $arrayElemAt or $slice to actually get the first element of the inner array.
By contrast, if you live with returning "a small amount of extra data", then you just slice off of the positional match:
Grandparent.find(
{ "children.name": "Parent Foo" },
{ "children.$": 1 }
).lean().exec((err,docs) => {
docs = docs.map( doc => {
doc.children = doc.children.map( c => c.children = c.children.slice(0,1) );
return doc;
});
// do something with docs
So we returned a little more in the cursor and just got rid of that very little bit of data with minimal effort.
Mileage may vary on this due to the actual size of real data, but if the difference is "small", then it's usually best to "trim" in the client rather than the server.

Categories

Resources