Finding nested elements Typescript Array object - javascript

I have a bunch of content in an array with type:
export interface StaticContent {
level : number,
id : string,
title: string,
content : string[] | StaticContent[],
}
I'm trying to search for an object where id to query is a sub-element to a content object. I think I've probably set the interface up wrong but I'm having a mental block.
Any ideas?
Example:
data = [
{
level: 0,
id: 'first-element',
title: 'Title of the first element'
content: [
`I am some random string of content`
]
},
{
level: 1,
id: 'second-element',
title: 'Title of the second element'
content: [
{
level: 0,
id: 'first-sub-element',
title: 'Title of first sub element'
content: [
` I am some content attached to the first sub element `
]
}
}]
let idToFind = 'first-sub-element'
let element = data.find(t => t.id === idToFind)
let title = element.title
In this example the result is an undefined element.
I expect
element = {
level: 0,
id: 'first-sub-element',
title: 'Title of first sub element'
content: [
` I am some content attached to the first sub element `
]
}
}

You need to use recursion or do a recursive search in place.
Here is recursion example:
function searchInContent(idToFind: string, content: StaticContent[] | string[]): StaticContent | null {
for (const c of content) {
if (typeof c === 'string') continue
// now we can assume that c is StaticContent
if (c.id === idToFind) {
// FOUND RESULT
return c
}
// Perform deep search in content
const result = searchInContent(idToFind, c.content)
if (result !== null) {
// stop searching and pass found result
return result
}
}
return null
}
let idToFind = 'first-sub-element'
let element = searchInContent(data)
let title = element.title

You are trying to find the content object for a specific id. What you could do is flatMap() the contents and then use find() to find the correct matching object.
const data = [{
level: 0,
id: 'first-element',
title: 'Title of the first element',
content: [
`I am some random string of content`
]
},
{
level: 1,
id: 'second-element',
title: 'Title of the second element',
content: [{
level: 0,
id: 'first-sub-element',
title: 'Title of first sub element',
content: [
` I am some content attached to the first sub element `
]
}, {
level: 0,
id: 'first-sub-elemeeent',
title: 'Title of first sub element',
content: [
` I am some content attached to the first sub element `
]
}]
}
];
const idToFind = 'first-sub-element';
const result = data.flatMap(d => d.content).find(c => c.id === idToFind);
console.log(result);

Related

Node Js how to fetch data from database in an hierarchical way

I'm writing a back code using NodeJs to fetch some data from backend, I want dataBase data to be like this
like this:
data = [{
name: "Admin",
id: '1',
children: [
{ name: "Admin", id: "1" },
{ name: "groupe1", id: "2" },
{
name: "groupe2", id: "1455", children: [
{ name: "groupe2", id: "1455" },
{ name: "gro", id: "5444" },
{ name: "hhrr", id: "45" }
]
}
]
}]
the idea is simple we have a list of group each group has a parent I want to display all the groups list in an hierarchical way the top one of the tree is done
Some groups are parents and groups in the same time and some others are only groups if the group is not parent we add an object with its name and ID in the array of children of his parent
if this groups is a parent that's mean it has children we add an object with its ID and name in the array of children of his parents, and we add property children for the object which is array named children with for the first time an object with the name and the id of the group etc...
i tryed to do this but it did not work
const getParentsByType = async ({ name, _id }) => {
let parentResult = [
{
id: _id,
name: name,
children: [
{
id: _id,
name: name,
},
],
},
];
parentResult= await findParent(_id, parentResult[0].children, 0);
return parentResult;
};
const findParent = async (parentId, parentResult, itemPos) => {
let children = await Models.GroupModel.find({ parent: parentId, status: true }).select('name _id');
for (let i = 0; i < children.length; i++) {
let childrenList = await Models.GroupModel.find({ parent: children[i]._id, status: true }).select('name _id');
if (childrenList.length != 0) {
parentResult.push(buildParentWithChild(children[i]._id, children[i].name));
findParent(children[i]._id,parentResult.children[i],itemPos++)
} else {
parentResult.push(buildParent(children[i]._id, children[i].name));
}
}
return parentResult
};
and this the model of the data base
const Group = mongoose.Schema({
name: {
type: String,
required: true,
},
status: {
type: Boolean,
required: true,
},
parent: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Group',
},
});
i had two days trying to resolve tis but with no result
i need some helps and Thank you
Try parsing your returned data. It validates your data as objects i dont see any problem with your function regardless i still have no idea what format your a trying to build.
let children = JSON.parse(JSON.stringify(await Models.GroupModel.find({ parent: parentId, status: true }).select('name _id')));
let childrenList = JSON.parse(JSON.stringify(await Models.GroupModel.find({ parent: children[i]._id, status: true }).select('name _id')));
If I understand you right, you want to convert the array returned by Models.GroupModel.find, and which looks like
var dbresult = [
{_id: "1", parent: null, name: "one"},
{_id: "2", parent: "1", name: "two"}
];
into a hierarchical structure. This can be done with a function that adds all children of a given parent p, including, recursively, their children. Like the following:
function children(p) {
var result = [];
for (r of dbresult) if (r.parent === p) {
var row = {_id: r._id, name: r.name};
var chld = children(r._id);
if (chld.length > 0) row.children = chld;
result.push(row);
}
return result;
}
console.log(JSON.stringify(children(null)));
Note that this approach requires only one database access (to fill the dbresult) and is therefore probably faster than your findParent function.

Get text of previous header in HTML

I have a HTML which looks like this:
<h1>Title</h1>
<p>Some additional content, can be multiple, various tags</p>
<h2><a id="123"></a>Foo</h2>
<p>Some additional content, can be multiple, various tags</p>
<h3><a id="456"></a>Bar</h3>
Now, for each anchor with id, I want to find out the header hierarchy, e.g. for the anchor with id="123" I would like to get something like [{level: 1, title: "Title"}, {level: 2, title: "Foo"}], similarly for anchor with id="456", I would like to get [{level: 1, title: "Title"}, {level: 2, title: "Foo"}, {level: 3, title: "Bar"}].
My code looks like this so far:
const linkModel: IDictionary<ILinkModelEntry> = {};
const $ = cheerio.load(html);
$("a").each((_i, elt) => {
const anchor = $(elt);
const id = anchor.attr().id;
if (id) {
const parent = anchor.parent();
const parentTag = parent.prop("tagName");
let headerHierarchy: any[] = [];
if (["H1", "H2", "H3", "H4", "H5", "H6"].includes(parentTag)) {
let level = parseInt(parentTag[1]);
headerHierarchy = [{level, text: parent.text()}];
level--;
while (level > 0) {
const prevHeader = parent.prev("h" + level);
const text = prevHeader.text();
headerHierarchy.unshift({level, text});
level--;
}
}
linkModel["#" + id] = {originalId: id, count: count++, headerHierarchy};
}
});
What am I doing wrong, since
const prevHeader = parent.prev("h" + level);
const text = prevHeader.text();
always returns an empty string (i.e. "")?
If I understand correctly, you're looking to capture hierarchy. If your example had another <h1> followed by more <h2> and <h3>s below it, you'd want to pop the stack of parents back down to that new <h1> level for linking future <h2> and <h3> children rather than have an array of all elements back up to that first <h1>Title</h1>.
Here's one approach:
const cheerio = require("cheerio"); // ^1.0.0-rc.12
const html = `
<h1>Title</h1>
<p>Some additional content, can be multiple, various tags</p>
<h2><a id="123"></a>Foo</h2>
<p>Some additional content, can be multiple, various tags</p>
<h3><a id="456"></a>Bar</h3>
<h1>Another Title</h1>
<h2><a id="xxx"></a>Foo 2</h2>
<h3><a id="yyy"></a>Bar 2</h3>`;
const $ = cheerio.load(html);
const result = {};
const stack = [];
[...$("h1,h2,h3,h4,h5,h6")].forEach(e => {
const level = +$(e).prop("tagName")[1];
while (stack.length && level <= stack.at(-1).level) {
stack.pop();
}
if (!stack.length || level >= stack.at(-1).level) {
stack.push({level, title: $(e).text()});
}
if ($(e).has("a[id]").length) {
const id = $(e).find("a[id]").attr("id");
result[`#${id}`] = [...stack];
}
});
console.log(result);
Output:
{
'#123': [ { level: 1, title: 'Title' }, { level: 2, title: 'Foo' } ],
'#456': [
{ level: 1, title: 'Title' },
{ level: 2, title: 'Foo' },
{ level: 3, title: 'Bar' }
],
'#xxx': [
{ level: 1, title: 'Another Title' },
{ level: 2, title: 'Foo 2' }
],
'#yyy': [
{ level: 1, title: 'Another Title' },
{ level: 2, title: 'Foo 2' },
{ level: 3, title: 'Bar 2' }
]
}
If you actually want the whole chain of ancestors linearly back to the first, then remove the while loop (unlikely your intent).

Javascript Filter or Reduce each JSON object by child array of tags which contains search word

I need help regarding filtering below given Javascript Array of Objects by it's sub-child array property.
I have one react app in which I want to filter articles when user types string in search box, but my question is related to just filtering objectby it's child array value.
// My JSON Object
this.state = {
articles: [
{title: 'title 1', tags :["JavaScript", "ES6"], category: "JavaScript"},
{title: 'title 2', tags :["React", "TypeScript"], category: "React"},
{title: 'title 3', tags :["JavaScript", "Inheritance", "Prototype"], category: "JavaScript"}
]
};
// Output needed
// If user start typing in searchbox like "jav" then it should filter only those items which tags name matching with "jav". Tags is an Array
[
{title: 'title 1', tags :["JavaScript", "ES6"], category: "JavaScript"},
{title: 'title 3', tags :["JavaScript", "Inheritance", "Prototype"], category: "JavaScript"}
]
I had tried below given code but not giving proper result:
this.state.articles.reduce((newArticles: any, article: any) => {
// let test1 = [];
if (article.tags) {
article.tags.map(tag => {
if (tag && tag.toLowerCase().indexOf(event.target.value) === -1) {
newArticles.push(article);
// return article;
}
});
} else {
// newArticles.push(article);
}
console.log('test1 =', newArticles);
return newArticles;
}, []);
Please let me know if my question is not clear or need more information on it.
Thanks,
Jignesh Raval
You can do this with filter method in inside check if some tag includes part of string that you want to search for.
let articles = [{title: 'title 1', tags :["JavaScript", "ES6"], category: "JavaScript"},{title: 'title 2', tags :["React", "TypeScript"], category: "React"},{title: 'title 3', tags :["JavaScript", "Inheritance", "Prototype"], category: "JavaScript"}]
let search = 'java';
let result = articles.filter(({tags}) => {
return tags.some(e => e.toLowerCase().includes(search.toLowerCase()))
})
console.log(result)
You can do this with filter method in inside check if some tag includes part of the string that you want to search for.
let articles = [{title: 'title 1', tags :["JavaScript", "ES6"], category: "JavaScript"},{title: 'title 2', tags :["React", "TypeScript"], category: "React"},{title: 'title 3', tags :["JavaScript", "Inheritance", "Prototype"], category: "JavaScript"}]
let search = "ES";
let result = articles.filter(((data)=>data.tags.some(v => v.includes(search))))
console.log(result)
I think this is the solution. The fact you had unnescessary elements, is that you only checked if the property children had a value and was not undefined. with the check node.children.length > 0 you check if the array has at least 1 item.
// the filter
const nodes = $.grep(data, function f(node) {
const nodeIncludesValue = node.name.toLowerCase().includes(value);
if (node.children && node.children.length > 0) {
return $.grep(node.children, f) || nodeIncludesValue;
}
return nodeIncludesValue;
});
// No unnecessary elements
createTree(nodes, container)
{
const list = document.createElement('ul');
nodes.forEach((node) => {
const listItem = document.createElement('li');
listItem.textContent = node.name;
list.appendChild(listItem);
if (node.children && node.children.length > 0) {
const childList = document.createElement('li');
this.createTree(node.children, childList);
list.appendChild(childList);
}
});
container.appendChild(list);
}
Hopefully this helps you :)

Dynamic array filtering by object property

I have a react live search dropdown component that filters through an array of objects by a search term. It filters my objects by title and then returns a list of all the related objects. This works fine.
Current:
Data Structure
data: [
{ id: 1, title: 'Some title here' },
{ id: 2, title: 'Another title' },
{ id: 3, title: 'last title' },
]
Component
<LiveSearch
term={term}
data={data} />
Inside Live search component
Filter data by term and render list
return data
.filter(item => item.title.toLowerCase().includes(term.toLowerCase())
.map((item, idx) => <li key={idx}>{item.title}</li>
My objects to search by are getting more advanced and what I would like to be able to do is pass into my component an array of property names I would like to compare to the search term.
My thinking process behind it is to loop through the object properties and if on of the properties matches the term the loop breaks and returns true adding that object to the list of items to be displayed.
Goal
Data Structure
data: [
{ id: 1, country: 'Canada', title: 'Some title here' },
{ id: 2, country: 'Australia', title: 'Another title' },
{ id: 3, country: 'Netherlands', title: 'last title' },
]
Component
<LiveSearch
searchFields={['country', 'title']}
term={term}
data={data} />
Inside Component filtering
return data
.filter(item => {
// Dynamic filtering of terms here
})
.map((item, idx) => <li key={idx}>{item.title}</li>
Inside the filter I'm trying to get a loop through the array and dynamically produce logic similar to this
item.searchFields[0].toLowerCase().includes(term.toLowerCase()) ||
item.searchFields[1].toLowerCase().includes(term.toLowerCase())
But obviously could loop over an infinite number of searchfields/properties
Use Array#some()
Something like
term = term.toLowerCase()
return data
.filter(item => {
return searchFields.some(field => item[field].toLowerCase().includes(term))
}).map(...
Check if some of the searchFields match:
// Checks wether a value matches a term
const matches = (value, term) => value.toLowerCase().includes(term.toLowerCase());
// Checks wether one of the fields in the item matcues the term
const itemMatches = (fields, term) => item => fields.some(field => matches(item[field], term);
// Filter the data to only contain items where on of the searchFields matches the term
const result = props.data.filter( itemMatches(props.searchFields, props.term) );
return result.map(item => <li key={idx}>{item.title}</li>);
You can use Array .some combined with .filter
let result = data.filter(obj =>
searchFields.some(s =>
obj[s] != undefined && obj[s].toLowerCase() === term
));
let data = [
{ id: 1, country: 'Canada', title: 'Some title here' },
{ id: 2, country: 'Australia', title: 'Another title' },
{ id: 3, country: 'Netherlands', title: 'last title' },
], searchFields = ["country", "title"], term = "canada";
let result = data.filter(obj =>
searchFields.some(s =>
obj[s] != undefined && obj[s].toLowerCase() === term
));
console.log(result);

How do i search 'titles' from array of objects & return only objects that match searchTerm?

bit of a newbie! I am trying to re-populate a carousel of images... based on an array of search results. But really hitting surprising amount of issues.
I'm using JS/Jquery and have, say, an array of objects that exist from my api:
let arrayOfObjects = [
{id: 0, title: 'Beauty & The Beast', img: 'https://imgthing1.com' },
{id: 1, title: 'The Brainiac', img: 'https://imgthing2.com' },
{id: 2, title: 'Mac and Me', img: 'https://imgthing3.com' }
];
Then i have my searchTerm which i want to filter the array down, and return a new array of results from:-
function checkWords(searchTerm, arr) {
let results = [];
let st = searchTerm.toLowerCase();
// **** i map through the array - if the search term (say its 'a' is the same
// as the first character of an object's 'title'... then it stores
// that object in results, ready to be rendered. ****
arr.map((each) => {
if (st === each.title.charAt(0)) {
results.push(each)
}
})
console.log(finalResults);
}
But i can't work out how to keep it matching... based on:
'Bea' vs 'Beauty & The Beast' - pass.
'Beat' vs 'Beauty & The Beast' - fail.
You could use Array#filter and check if the string contains the wanted string at position zero.
let arrayOfObjects = [{ id: 0, title: 'Beauty & The Beast', img: 'https://imgthing1.com' }, { id: 1, title: 'The Brainiac', img: 'https://imgthing2.com' }, { id: 2, title: 'Mac and Me', img: 'https://imgthing3.com' }];
function checkWords(searchTerm, arr) {
let st = searchTerm.toLowerCase();
return arr.filter(each => each.title.toLowerCase().indexOf(st) === 0);
}
console.log(checkWords('bea', arrayOfObjects));

Categories

Resources