Traverse through an Array to obtain nested object - traversal

I am trying to traverse through my array to get a specific object nested inside of it.
Some objects contain a children property, which should be traversed until a matching object is found.
Here's some example data, I am trying to obtain the object with id as 4
const items = [{
id: 1,
title: 'Title for Item 1'
},
{
id: 2,
title: 'Title for Item 2',
children: [
{
id: 3,
title: "Title for Item 3",
children: [
{
id: 4,
title: "Title for Item 4",
}
]
}
]
},
]
I've written some traversal code but it returns undefined.
const items = [{
id: 1,
title: 'Title for Item 1'
},
{
id: 2,
title: 'Title for Item 2',
children: [
{
id: 3,
title: "Title for Item 3",
children: [
{
id: 4,
title: "Title for Item 4",
}
]
}
]
},
]
const getItem = (items) => {
if (!items) return;
const item = items && items.find(i => i.id === 4);
if (!item) {
items.forEach(i => {
return getItem(i.children)
})
// This is where undefined is returned
} else {
console.log({
item
}) // Prints the correct object.
return item;
}
};
const s = getItem(items); // undefined
document.querySelector('#foo').textContent = s ? s : 'undefined';
<div id="foo"></div>

At least two issues explain why it does not work:
A return statement in a forEach callback will return the returned value to nowhere. Nothing happens with it.
The result of the recursive call is not checked. It needs to be checked to see if it is defined. Depending on that you can decide whether to continue the loop or exit from it.
Replace that forEach with a for...of loop so you can return "out of it", but only do that when you have a match, otherwise you need to continue the loop:
for (const item of items) {
const match = getItem(item.children);
if (match) return match;
}
Note that in your snippet you should not set the textContent to the return value, as that is an object and will get converted to the string "[Object object]". You could for instance just grab the title string and put that in textContent:
const items = [{
id: 1,
title: 'Title for Item 1'
},
{
id: 2,
title: 'Title for Item 2',
children: [
{
id: 3,
title: "Title for Item 3",
children: [
{
id: 4,
title: "Title for Item 4",
}
]
}
]
},
]
const getItem = (items) => {
if (!items) return;
const item = items && items.find(i => i.id === 4);
if (!item) {
for (const item of items) {
const match = getItem(item.children);
if (match) return match;
}
} else {
console.log({
item
}) // Prints the correct object.
return item;
}
};
const s = getItem(items); // undefined
document.querySelector('#foo').textContent = s ? s.title : 'undefined';
<div id="foo"></div>

Related

add label in dropdown item but MUST depends other variable (FLUTTER)

List<Map<String, dynamic>> category = [
{
"name": "One",
"detail": ['11', '12', '13', '14'],
"department": "aaa",
},
{
"name": "two",
"detail": ['21', '22', '23', '24'],
"department": "bbb",
},
{
"name": "three",
"detail": ['31', '32', '33', '34'],
"department": "ccc",
},
{
"name": "four",
"detail": ['41', '42', '43', '44'],
"department": "aaa",
},
{
"name": "five",
"detail": ['41', '42', '43', '44'],
"department": "aaa",
},
];
for (final item in category) {
if (item["department"] == "aaa") {
for (final value in item.values) {
if (value is List) {
for (final listValue in value) {
data.add({'value': listValue, 'bold': false});
}
} else {
data.add({'value': item['department'], 'bold': true});
}
}
}
}
I have used the above (loop) method to doing the dropdown, but the category "name" will repeat many times, as shown in first picture
May I know how to make the list category be like the second picture dropdown, for example, the name will be the label, detail will be item of the label. Lastly, the 'department' is for classify showing which data, let say I want to show the data that department is 'aaa' means that 3 list data will be shown in the dropdown item.
Looking at your data named as "category" which is a list of Maps, I think you can add labels and achieve the required functionality that includes using custom variable in the following way:
const dept = 'aaa';
final data = category.where((element) => element['department'] == dept);
List<DropdownMenuItem<String>>? get_items() {
final List<DropdownMenuItem<String>> _dropdownItems1 = [];
for (final val in data) {
for (final value in val.values) {
if (value is List) {
for (final listValue in value) {
_dropdownItems1.add(
DropdownMenuItem(
child: Text(listValue),
value: listValue,
),
);
}
} else if (value != dept) {
_dropdownItems1.add(DropdownMenuItem(
child:
Text(value, style: const TextStyle(fontWeight: FontWeight.bold)),
value: value,
));
}
}
}
return _dropdownItems1;
}
Now, in the dropdownbutton you can simply call "get_items()" to get the dropdown menu items for creating the dropdown menu.
It can be done as mentioned in the code below.
DropdownButton<String>(
value: selectedItem,
items: get_items(),
onChanged: (value) {
setState(() {
selectedItem = value;
});
}),
The output will be as follows:
Output Dropdown menu

How to create reactive search input field in Vue.js?

I want to build a seach input field that sorts an object array while typing, using vue 3 with script setup.
input field:
<input type="search" placeholder="Search" v-model="state.search">
script setup:
const state = reactive({
search: ''
})
const array = [
{id: 1, title: 'Valhalla', content: '123'},
{id: 2, title: 'Wurstopia', content: '456'},
{id: 3, title: 'Brandon', content: '789'}
]
const search = computed(() => {
// sort array reactively according to search (use title as sorting criteria)
const result = sort(array['title'], state.search)
})
Is using computed the right approach for this? How do I reactively sort the array for search input ~ title?
If making this reactive is a problem, I am also happy with an approach of just submitting the input and sorting the array afterwards.
Edit:
I've tried the approach of #AdriHM but it produces exactly the same unsorted array:
const state = reactive({
search: '',
array: [
{id: 1, title: 'Valhalla', content: '123'},
{id: 2, title: 'Wurstopia', content: '456'},
{id: 3, title: 'Brandon', content: '789'}
]
})
function mySort(searchKey){
let matchedKeys = [], notMatchedKeys = [];
for(let i = 0; i < state.array.length; i++) {
if (state.array[i]['title'].match(searchKey) ) {
matchedKeys.push(state.array[i])
} else{
notMatchedKeys.push(state.array[i])
}
}
}
console.log(mySort(state.search))
Output:
(3) [Proxy, Proxy, Proxy]
0: Proxy {id: 1, title: 'Valhalla', content: '123'}
1: Proxy {id: 2, title: 'Wurstopia', content: '456'}
2: Proxy {id: 3, title: 'Brandon', content: '789'}
length: 3
[[Prototype]]: Array(0)
If what you want to do is a sort you can do it like this:
<template>
<input type="search" placeholder="Search" v-model="state.search">
{{ state.array }}
</template>
<script lang="ts" setup>
import {reactive, watch} from "vue";
const state = reactive({
search: '',
array: [
{id: 1, title: 'Valhalla', content: '123'},
{id: 2, title: 'Wurstopia', content: '456'},
{id: 3, title: 'Brandon', content: '789'}
]
})
function mySort(searchKey: string){
let matchedKeys = [], notMatchedKeys = [];
for(let i = 0; i < state.array.length; i++) {
if (state.array[i]['title'].match(searchKey) ) {
matchedKeys.push(state.array[i])
} else{
notMatchedKeys.push(state.array[i])
}
}
return matchedKeys.concat(notMatchedKeys);
}
watch(() => state.search, () => {
// sort of filter
state.array = mySort(state.search)
})
</script>
It will only put at first position the element that match the query but you have the logic to make the array changing with watch.
If I understand correctly, the objective is to filter an array by a search term, and display the results sorted by title.
The sorting itself does not need to be reactive because the array is always sorted by title. The array can be sorted once, and then the sorted array can be filtered reactively in the computed prop.
Use Array.prototype.sort() to sort array[] by the title field.
In the computed prop, use Array.prototype.filter() to include only items whose title or content field contains state.search. filter() does not change the order of the results, so no additional sorting is needed.
<script setup>
import { reactive, computed } from 'vue'
const state = reactive({
search: '',
})
const array = [
{ id: 1, title: 'Valhalla', content: '123' },
{ id: 2, title: 'Wurstopia', content: '456' },
{ id: 3, title: 'Brandon', content: '789' },
]
1️⃣
array.sort((a, b) => a.title.localeCompare(b.title))
const results = computed(() => {
2️⃣
return array.filter(item => item.title.includes(state.search) || item.content.includes(state.search))
})
</script>
demo

What is the best way to combine two immutable lists?

I have two lists and i'm trying to combine them to a new list so that the existing ids are updated and the new ones are added to list and after that sorted by the id. Is there a better or more efficient way to do this?
// Original list
const list = Immutable.List([
{ id: 1, name: 'List Item 1' },
{ id: 2, name: 'List Item 2' },
{ id: 3, name: 'List Item 3' },
]);
// One updated item and two new items
const newList = Immutable.List([
{ id: 2, name: 'Updated List Item 2' },
{ id: 4, name: 'New List Item 4' },
{ id: 5, name: 'New List Item 5' },
]);
// Get updated ids
const ids = newList.map((item) => item.id);
// Filter out updated ids from orignial list
const filteredList = list.filterNot(item => ids.includes(item.id));
// Concat and sort by id
const concatList = newList
.concat(filteredList)
.sortBy(item => item.id);
console.log(concatList.toJS());
/* Outputs as desired
[
{ id: 1, name: "List Item 1" },
{ id: 2, name: "Updated List Item 2" },
{ id: 3, name: "List Item 3" },
{ id: 4, name: "New List Item 4" },
{ id: 5, name: "New List Item 5" }
]
*/
This is how I would do it, using reduce and merge:
function reduceToMap(result, item) { return result.set(item.id, item) }
const list = Immutable.List([
{ id: 1, name: 'List Item 1' },
{ id: 2, name: 'List Item 2' },
{ id: 3, name: 'List Item 3' },
]).reduce(reduceToMap, Immutable.Map());
// One updated item and two new items
const newList = Immutable.List([
{ id: 2, name: 'Updated List Item 2' },
{ id: 4, name: 'New List Item 4' },
{ id: 5, name: 'New List Item 5' },
]).reduce(reduceToMap, Immutable.Map());
console.log(...list.merge(newList).values())
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js"></script>

How can I filter a TreeStore in ExtJS 6.2?

I need to filter a Tree (search for a node by comparing a text to a node attribute).
This fiddle is exactly what I need, but seems it is not working with ExtJS 6.2.
After a day trying to find out what is wrong in my code, I've decided to simply change the framework version in the fiddle from 4.2.1 (default) to 6.2.981 classic gray and can see the code is obsolete (removing all nodes or an arbitrary amount I can't see why).
Can someone verify that code and tell me how to port it to v6.2?
EDIT:
Almost there. This code is doing the job, but stops in deep level 2, not all the tree: JSFiddle
Ext.define('TreeFilter', {
extend: 'Ext.AbstractPlugin',
alias: 'plugin.treefilter',
collapseOnClear: true, // collapse all nodes when clearing/resetting the filter
allowParentFolders: false, // allow nodes not designated as 'leaf' (and their child items) to be matched by the filter
init: function(tree) {
var me = this;
me.tree = tree;
tree.filter = Ext.Function.bind(me.filter, me);
tree.clearFilter = Ext.Function.bind(me.clearFilter, me);
},
filter: function(value, property, re) {
var me = this,
tree = me.tree,
matches = [], // array of nodes matching the search criteria
root = tree.getRootNode(), // root node of the tree
property = property || 'text', // property is optional - will be set to the 'text' propert of the treeStore record by default
re = re || new RegExp(value, "ig"), // the regExp could be modified to allow for case-sensitive, starts with, etc.
visibleNodes = [], // array of nodes matching the search criteria + each parent non-leaf node up to root
viewNode;
if (Ext.isEmpty(value)) { // if the search field is empty
me.clearFilter();
return;
}
tree.expandAll(); // expand all nodes for the the following iterative routines
// iterate over all nodes in the tree in order to evalute them against the search criteria
root.cascadeBy(function(node) {
if (node.get(property).match(re)) { // if the node matches the search criteria and is a leaf (could be modified to searh non-leaf nodes)
matches.push(node) // add the node to the matches array
}
});
if (me.allowParentFolders === false) { // if me.allowParentFolders is false (default) then remove any non-leaf nodes from the regex match
Ext.each(matches, function(match) {
if (!match.isLeaf()) {
Ext.Array.remove(matches, match);
}
});
}
Ext.each(matches, function(item, i, arr) { // loop through all matching leaf nodes
root.cascadeBy(function(node) { // find each parent node containing the node from the matches array
if (node.contains(item) == true) {
visibleNodes.push(node) // if it's an ancestor of the evaluated node add it to the visibleNodes array
}
});
if (me.allowParentFolders === true && !item.isLeaf()) { // if me.allowParentFolders is true and the item is a non-leaf item
item.cascadeBy(function(node) { // iterate over its children and set them as visible
visibleNodes.push(node)
});
}
visibleNodes.push(item) // also add the evaluated node itself to the visibleNodes array
});
root.cascadeBy(function(node) { // finally loop to hide/show each node
viewNode = Ext.fly(tree.getView().getNode(node)); // get the dom element assocaited with each node
if (viewNode) { // the first one is undefined ? escape it with a conditional
viewNode.setVisibilityMode(Ext.Element.DISPLAY); // set the visibility mode of the dom node to display (vs offsets)
viewNode.setVisible(Ext.Array.contains(visibleNodes, node));
}
});
}
,
clearFilter: function() {
var me = this,
tree = this.tree,
root = tree.getRootNode();
if (me.collapseOnClear) {
tree.collapseAll();
} // collapse the tree nodes
root.cascadeBy(function(node) { // final loop to hide/show each node
viewNode = Ext.fly(tree.getView().getNode(node)); // get the dom element assocaited with each node
if (viewNode) { // the first one is undefined ? escape it with a conditional and show all nodes
viewNode.show();
}
});
}
});
// EXAMPLE
var store = Ext.create('Ext.data.TreeStore', {
root: {
expanded: true,
children: [{
text: "detention",
leaf: true
}, {
text: "homework",
expanded: false,
children: [{
text: "book report",
leaf: true
}, {
text: "algebra",
leaf: true
}]
}, {
text: "chores",
expanded: false,
children: [{
text: "do homework",
leaf: true
}, {
text: "walk dog",
leaf: true
}, {
text: "clean room",
leaf: true
}, {
text: "wash dishes",
leaf: true
}, {
text: "laundry",
leaf: true
}]
}, {
text: "buy lottery tickets",
leaf: true
}, {
text: "take over world",
leaf: true
}, {
text: "Sencha",
expanded: false,
children: [{
text: "Touch",
expanded: false,
children: [{
text: 'Viewport',
leaf: true
}, {
text: 'Panel',
leaf: true
}, {
text: 'Carousel',
leaf: true
}]
}, {
text: "ExtJS",
expanded: false,
children: [{
text: 'viewport.Viewport',
leaf: true
}, {
text: 'panel.Panel',
leaf: true
}, {
text: 'tree.Panel',
leaf: true
}]
}]
}]
}
});
Ext.create('Ext.tree.Panel', {
title: 'Simple Tree',
width: 200,
height: 150,
store: store,
rootVisible: false,
renderTo: Ext.getBody(),
plugins: [{
ptype: 'treefilter',
allowParentFolders: true
}],
dockedItems: [{
xtype: 'toolbar',
dock: 'top',
items: [{
xtype: 'trigger',
triggerCls: 'x-form-clear-trigger',
onTriggerClick: function() {
this.reset();
this.focus();
},
listeners: {
change: function(field, newVal) {
var tree = field.up('treepanel');
tree.filter(newVal);
},
buffer: 250
}
}]
}]
});
Without using filter plugin i have added some useful code in change event and it works for all depth.
"dockedItems": [{
xtype: 'toolbar',
dock: 'top',
items: [{
xtype: 'textfield', //As of Ext JS 5.0 trigger class has been deprecated. It is recommended to use {#link Ext.form.field.Text Text Field}.
triggerCls: 'x-form-clear-trigger',
width: 300,
onTriggerClick: function () {
this.reset();
this.focus();
},
listeners: {
change: function (field, newVal) {
var reportBuilderStore = field.up('panel').getStore();
if (!Ext.isEmpty(field.value)) {
reportBuilderStore.filterBy(function (rec) {
var childs = !Ext.isEmpty(rec.get('children')) ? rec.get('children').map(function (x) {
return x.text;
}) : [];
var matched = false;
for (var val of childs) {
if (val.toUpperCase().match((field.value).toUpperCase())) {
matched = true;
break;
}
}
if (!Ext.isEmpty(rec.get('text').toUpperCase().match((field.value).toUpperCase())) || rec.get('text').toUpperCase() == "ROOT" || matched)
return true;
});
} else {
reportBuilderStore.clearFilter();
}
},
buffer: 250
}
}
]
}
],

How to iterate over multiple arrays without nested observables

I must iterate over array, find correspondent objects in other array an merge the result in a object.
Assume I have three arrays
var users = [
{ name: "A", type: 2, level: 1 },
{ name: "B", type: 1, level: 2 }
]
var types = [
{ description: "Type 1", id: 1 },
{ description: "Type 2", id: 2 }
]
var levels = [
{ description: "Level 1", id: 1 },
{ description: "Level 2", id: 1 }
]
I want to have following result:
var users = [
{ name: "A", type: 2, level: 1, levelDescription: "Level 1", typeDescription: "Type 2" },
{ name: "B", type: 1, level: 2, levelDescription: "Level 2", typeDescription: "Type 1" }
]
I know I can achieve it like that
var usersObservable = RX.Observable.fromArray(users);
var typesObservable = Rx.Observable.fromArray(types);
var levelsOBservable = Rx.Observable.fromArray(levels);
var uiUsers= [];// not really needed because I will use the same users array again.
usersObservable.map(function(user) {
typesObservable.filter(function(type) {
return type.id == user.type;
}).subscribeOnNext(function(userType) {
user.typeDescription = userType.description;
});
return user;
}).map(function(user) {
levelsOBservable.filter(function(level) {
return level.id == user.levelId;
}).subscribeOnNext(function(level) {
user.levelDescription = level.description;
});
return user;
})
.subscribeOnNext(function(user) {
uiUsers.push(user);
})
I would like to have a solution without nested Observables.
Thanks.
I am not sure why you are using Rx at all for this problem. You have data in space (i.e. arrays), not data over time (i.e. an observable sequence). But you force these arrays into Rx to then create a very complicated solution.
I think you are looking for something like the answer here https://stackoverflow.com/a/17500836/393615 where you would join the source array types. In your case you just "inner-join" twice to combine all three data sets.
You can archive this by using the switchMap operator that combines the result of a filtered stream with the latest value of the original stream and uses a projection function to merge the results into a single object. This can be generalised in your example such that you can use a generic higher order function in both cases. See fiddle.
Full code (ES2015, RxJS5):
const users = [
{ name: "A", type: 2, level: 1 },
{ name: "B", type: 1, level: 2 }
];
const types = [
{ description: "Type 1", id: 1 },
{ description: "Type 2", id: 2 }
];
const levels = [
{ description: "Level 1", id: 1 },
{ description: "Level 2", id: 2 }
];
const users$ = Rx.Observable.from(users);
const types$ = Rx.Observable.from(types);
const levels$ = Rx.Observable.from(levels);
function join(s$, sourceProperty, targetProperty, streamProperty) {
return function(initObj) {
const stream$ = s$.filter(x => x.id === initObj[sourceProperty]);
return Rx.Observable.combineLatest(
Rx.Observable.of(initObj),
stream$,
(obj, streamObj) => {
const prop = streamObj[streamProperty];
return Object.assign({}, obj, { [targetProperty]: prop });
}
);
};
}
users$
.switchMap(join(types$, 'type', 'typeDescription', 'description'))
.switchMap(join(levels$, 'level', 'levelDescription', 'description'))
.subscribe(x => console.log(x));