Make site map generated from flat array of pages recursive - javascript

I have a flat array of pages and I'm trying to create a hierarchical site map from them. What I have so far is very messy so I would like a better way of doing it.
This is the array of pages I have
const pages = [
{
"name": "Page 1",
"url": "/page-1/",
},
{
"name": "Page 2",
"url": "/page-2/",
},
{
"name": "Child 1",
"url": "/page-1/child-1/",
},
{
"name": "Child 2",
"url": "/page-1/child-1/child-2/",
},
{
"name": "Child 3",
"url": "/page-1/child-1/child-2/child-3/",
}
]
and this is the result I want outputting
<ul>
<li>
Page 1
<ul>
<li>
Child 1
<ul>
<li>
Child 2
<ul>
<li>
Child 3
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Page 2</li>
</ul>
This is what I have currently, which works but would like to find a better way to do it
const generateSitemap = function(pages) => {
let sitemap = "";
let organisedPages = [];
const sortPages = function (runs) {
if (pages.length === 0) return;
pages.forEach((page) => {
const title = page.name;
const url = page.url;
// Get homepage and content pages only
let pageObj = {
title: title,
url: url,
children: [],
};
// Handle top level pages first then build up children as you go deeper
if (pageLevel(url) === 1) {
organisedPages.push(pageObj);
pages = pages.filter((page1) => page !== page1);
} else if (runs === 2) {
organisedPages.forEach((oPage, i) => {
// Check to see if url is in segments and matches
let parseUrl = url.substring(1).slice(0, -1);
const urlParts = parseUrl.split("/");
const parentUrl = `/${urlParts.slice(0, -1).join("/")}/`;
if (oPage.url === parentUrl) {
organisedPages[i].children.push(pageObj);
pages = pages.filter(
(page1) => pageObj.url !== page1.systemProperties.url
);
return;
}
});
} else if (runs === 3) {
organisedPages.forEach((oPage, i) => {
// Check to see if url is in segments and matches
let parseUrl = url.substring(1).slice(0, -1);
const urlParts = parseUrl.split("/");
const parentUrl = urlParts.slice(0, -1);
const parentUrlComp = `/${parentUrl.join("/")}/`;
const parentUrl2 = parentUrl.slice(0, -1);
const parentUrl2Comp = `/${parentUrl2.join("/")}/`;
if (oPage.url === parentUrl2Comp) {
organisedPages[i].children.forEach((child, j) => {
if (child.url === parentUrlComp) {
organisedPages[i].children[j].children.push(pageObj);
pages = pages.filter(
(page1) => pageObj.url !== page1.systemProperties.url
);
return;
}
});
}
});
} else if (runs === 4) {
organisedPages.forEach((oPage, i) => {
// Check to see if url is in segments and matches
let parseUrl = url.substring(1).slice(0, -1);
const urlParts = parseUrl.split("/");
const parentUrl = urlParts.slice(0, -1);
const parentUrlComp = `/${parentUrl.join("/")}/`;
const parentUrl2 = parentUrl.slice(0, -1);
const parentUrl2Comp = `/${parentUrl2.join("/")}/`;
const parentUrl3 = parentUrl2.slice(0, -1);
const parentUrl3Comp = `/${parentUrl3.join("/")}/`;
if (oPage.url === parentUrl3Comp) {
organisedPages[i].children.forEach((child, j) => {
if (child.url === parentUrl2Comp) {
organisedPages[i].children[j].children.forEach((child1, k) => {
if (child1.url === parentUrlComp) {
organisedPages[i].children[j].children[k].children.push(
pageObj
);
pages = pages.filter(
(page1) => pageObj.url !== page1.systemProperties.url
);
return;
}
});
}
});
}
});
}
});
runs++;
if (runs < 5) {
sortPages(runs);
}
};
sortPages(1);
/**
* Check if page is a parent
*
* #param {string} url page url
* #returns {number} length of segments
*/
function pageLevel(url) {
// Remove first and last forward slash that is provided
let parseUrl = url.substring(1).slice(0, -1);
// Split parsed url by forward slash
const urlParts = parseUrl.split("/");
// Check segment length
return urlParts.length;
}
/**
* Loop through organised pages and set listing.
*/
organisedPages.forEach((page) => {
sitemap += `<li>`;
sitemap += `${page.title}`;
// Check if we need children loop for each parent page
if (page.children.length) {
sitemap += `<ul>`;
page.children.forEach((page) => {
sitemap += `<li>`;
sitemap += `${page.title}`;
// Check if we need children loop for each sub-child page
if (page.children.length) {
sitemap += `<ul>`;
page.children.forEach((page) => {
sitemap += `<li>`;
sitemap += `${page.title}`;
if (page.children.length) {
sitemap += `<ul>`;
page.children.forEach((page) => {
sitemap += `<li>`;
sitemap += `${page.title}`;
sitemap += `</li>`;
});
sitemap += `</ul>`;
}
sitemap += `</li>`;
});
sitemap += `</ul>`;
}
sitemap += `</li>`;
});
sitemap += `</ul>`;
}
sitemap += `</li>`;
});
return sitemap;
};
generateSitemap(pages)

I would choose to break the logical nesting of objects from the HTML formatting, as I think it makes for simpler functions all around. To comfortably do the nesting, I will also add a helper function to get the parent id from a url, so that, for instance,
getParentId ("/page-1/") //=> ""
getParentId ("/page-1/child-1/") //=> "/page-1"
getParentId ("/page-1/child-1/child-2/") //=> "/page1/child-1"
// ... etc
We could easily inline this function in nest, but I think it's cleaner as a helper. There is a little bit of odd complexity with this, because there is an extra slash somewhere, beginning or end. We choose to slice off the final / when searching through our list to find children.
The code looks like this:
const getParentId = (url) => url .slice (0, url .slice (0, -1) .lastIndexOf ('/'))
const nest = (pages, parentId = "", id = parentId .slice (0, -1)) => pages
.filter (({url}) => getParentId (url) == id)
.map (({url, ...rest}) => ({...rest, url, children: nest (pages, url)}))
const format = (nodes) => `<ul>${nodes .map (({name, url, children}) =>
`<li>${name}${children .length ? format (children) : ''}</li>`
).join('')}</ul>`
const pages2html = (pages) => format (nest (pages))
const pages = [{name: "Page 1", url: "/page-1/"}, {name: "Page 2", url: "/page-2/"}, {name: "Child 1", url: "/page-1/child-1/"}, {name: "Child 2", url: "/page-1/child-1/child-2/"}, {name: "Child 3", url: "/page-1/child-1/child-2/child-3/"}]
console .log (pages2html (pages))
.as-console-wrapper {max-height: 100% !important; top: 0}
Here nest turns your nodes into a format like this:
[
{
name: "Page 1",
url: "/page-1/",
children: [
{
name: "Child 1",
url: "/page-1/child-1/",
children: [
{
name: "Child 2",
url: "/page-1/child-1/child-2/",
children: [
{
name: "Child 3",
url: "/page-1/child-1/child-2/child-3/",
children: []
}
]
}
]
}
]
},
{
name: "Page 2",
url: "/page-2/",
children: []
}
]
and then format turns that into your HTML. We wrap them together in pages2html to have a single function to call, but the work is done in those two at-least-possibly reusable functions.
Note that we don't try to preserve the white-space in your requested format. We could do so, at the expense of making format quite a bit uglier. Here's a quick attempt, which I think is correct. But I wouldn't swear to it:
const format = (nodes, depth = 0) => `${depth > 0 ? '\n' : ''
}${' '.repeat (2 * depth) }<ul>${nodes .map (({name, url, children}) => `
${' ' .repeat (2 * depth + 1) }<li>
${' ' .repeat (2 * depth + 2) }${name}${
children .length ? `` + format (children, depth + 1) : ''
}
${' ' .repeat (2 * depth + 1) }</li>`
).join('')}
${' '.repeat (2 * depth)}</ul>`
We simply use a depth parameter to note the level of nesting, and use that to figure out how many spaces to use at the beginning of lines.
In general, I find this style, working as a sequence of transformations, much simpler to work with. Yes, we could do all this in a single function. It might even have fewer lines of code than my four separate functions. But each of these is simpler to understand, and simpler to change when the need arises.
And recursion will be more flexible and much simpler than your multi-level branching, as you can see from the fairly simple recursive code in next.

Related

Moving a child object into root before it's original parent inside a json array

I have the following JSON array
{"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"type":"image","attrs":{"src":"cid:avatar_de332fbebe0906c451c8859294d1553d"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~malhajj?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4"}}]},{"type":"text","text":" Mohamad AL HAJJ "},{"type":"text","text":"mentioned","marks":[{"type":"strong"}]},{"type":"text","text":" you on a page"}]},{"type":"paragraph","content":[{"type":"text","text":" ","marks":[{"type":"textColor","attrs":{"color":"#ffffff"}}]},{"type":"text","text":" "},{"type":"image","attrs":{"src":"cid:mention-icon","alt":"mention icon","title":"mention icon"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=view"}}]},{"type":"text","text":" "},{"type":"text","text":"Bassem 1-1 2022-07-28","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=view"}}]}]},{"type":"paragraph","content":[{"type":"text","text":"···"}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"#"},{"type":"text","text":"Bassem Alameddine","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~balameddine"}}]},{"type":"text","text":" "}]}]}]},{"type":"paragraph","content":[{"type":"text","text":"Release 3.0.7 is ready"}]},{"type":"paragraph","content":[{"type":"text","text":"#"},{"type":"text","text":"Bassem Alameddine","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~balameddine"}}]},{"type":"text","text":" "}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[]}]}]},{"type":"paragraph","content":[{"type":"hardBreak"},{"type":"text","text":" Research about supporting React in windows app"}]},{"type":"paragraph","content":[{"type":"text","text":"#"},{"type":"text","text":"Bassem Alameddine","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~balameddine"}}]},{"type":"text","text":" "}]},{"type":"paragraph","content":[{"type":"hardBreak"},{"type":"text","text":" "},{"type":"hardBreak"},{"type":"text","text":" Test server changes done by Ahmad"}]},{"type":"paragraph","content":[{"type":"text","text":"#"},{"type":"text","text":"Bassem Alameddine","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~balameddine"}}]},{"type":"text","text":" "}]},{"type":"paragraph","content":[{"type":"hardBreak"}]},{"type":"paragraph","content":[{"type":"image","attrs":{"src":"cid:com.atlassian.confluence.plugins.confluence-email-resources_view-page-email-adg-footer-item_icon","alt":"View page Icon","title":"View page Icon"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=view"}}]},{"type":"text","text":" "},{"type":"text","text":"View page","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=view"}}]},{"type":"text","text":" • "},{"type":"image","attrs":{"src":"cid:com.atlassian.confluence.plugins.confluence-email-resources_add-comment-to-content-email-adg-footer-item_icon","alt":"Add comment Icon","title":"Add comment Icon"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?showComments=true&showCommentArea=true&src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=comment#addcomment"}}]},{"type":"text","text":" "},{"type":"text","text":"Add comment","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?showComments=true&showCommentArea=true&src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=comment#addcomment"}}]},{"type":"text","text":" • "},{"type":"image","attrs":{"src":"cid:com.atlassian.confluence.plugins.confluence-like_view-email-adg-content-item_icon","alt":"Like Icon","title":"Like Icon"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/plugins/likes/like.action?contentId=26543947&src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=like&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4c3JmOjMwYmFmMWJhN2NjZGRkZDMwMTdmY2ExMzAwN2UwMGM0IiwicXNoIjoiODEwNWNmMmVlZjcwMGQ1MjVmYjUyODkzMjFmYTMzZGQyNDNmNDNmM2M2MWY1YmU5NzU3YjhjMWVmZmIxZTk1MSIsImlzcyI6ImNvbmZsdWVuY2Vfbm90aWZpY2F0aW9uc0JFOFUtTDNMWi1USzFPLVBGM00iLCJleHAiOjE2NTk3NzM1NTYsImlhdCI6MTY1OTE2ODc1Nn0.R4yPR6SKdUg5mc8fwgdGUa7BeWOdN_2LXDZbAKC5A-o"}}]},{"type":"text","text":" "},{"type":"text","text":"Like","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/plugins/likes/like.action?contentId=26543947&src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=like&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4c3JmOjMwYmFmMWJhN2NjZGRkZDMwMTdmY2ExMzAwN2UwMGM0IiwicXNoIjoiODEwNWNmMmVlZjcwMGQ1MjVmYjUyODkzMjFmYTMzZGQyNDNmNDNmM2M2MWY1YmU5NzU3YjhjMWVmZmIxZTk1MSIsImlzcyI6ImNvbmZsdWVuY2Vfbm90aWZpY2F0aW9uc0JFOFUtTDNMWi1USzFPLVBGM00iLCJleHAiOjE2NTk3NzM1NTYsImlhdCI6MTY1OTE2ODc1Nn0.R4yPR6SKdUg5mc8fwgdGUa7BeWOdN_2LXDZbAKC5A-o"}}]},{"type":"text","text":" "},{"type":"text","text":" ","marks":[{"type":"textColor","attrs":{"color":"#ffffff"}}]}]},{"type":"paragraph","content":[{"type":"text","text":"Manage notifications","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/users/editmyemailsettings.action?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=manage"}}]},{"type":"text","text":" "},{"type":"image","attrs":{"src":"cid:footer-desktop-logo","alt":"Confluence logo big","title":"Confluence logo big"}},{"type":"text","text":" This message was sent by Atlassian Confluence 7.18.1"},{"type":"hardBreak"},{"type":"image","attrs":{"src":"cid:footer-mobile-logo","alt":"","title":""}}]}]}
what I am trying to do is creating a recursive function that locate all the elements with type image and try to move it from inside it's parent and push it as a root element
but I am not able to push it right after or before it's original parent.
This is what I did
static recur = (arr, AttachmentsArr, madfdoc, htmlbody) => {
if (arr && arr.length > 0) {
arr.forEach(item => {
try {
switch (item.type) {
case "image":
this.handleImages(arr, madfdoc, item, htmlbody, AttachmentsArr)
break;
case "paragraph":
this.handleParaphs(item, htmlbody, AttachmentsArr)
break;
default:
break;
}
if (item.content) {
this.recur(item.content, AttachmentsArr, madfdoc, htmlbody);
}
}
catch (ex) {
console.log(ex)
}
});
}
};
static handleImages(arr, madfdoc, item, htmlbody, AttachmentsArr) {
var imghtmlobj = htmlbody.querySelector(`img[src*='${item.attrs.src}']`);
let src = item.attrs.src.replace("cid:", "");
// find the image bloburl to change it
let fnd = AttachmentsArr && AttachmentsArr.length ? AttachmentsArr.find(obj => {
return obj.ContentId ? obj.ContentId === src : undefined;
}) : undefined
var imgw = undefined;
var imgh = undefined;
if (imghtmlobj) {
let img = new Image();
img.onload = function () {
imgw = this.width;
imgh = this.height;
delete item["attrs"]
delete item["marks"]
item.type = "mediaSingle";
item.content = [];
item.content.push({
type: "media",
attrs: {
type: "external",
url: fnd ? fnd.preview : src,
width: imgw,
height: imgh
}
})
const indexOfObject = arr.findIndex(object => {
return object.type === "mediaSingle";
});
**arr.splice(indexOfObject, 1); // here is where i am deleting it from inside it's parent and push it to the root inside `madfdoc`
madfdoc.content.push(item);**
}
img.src = fnd ? fnd.preview : src;
}
}
The expected Json would be something like this
{
"type": "doc",
"version": 1,
"content": [
{
"type": "image",
"attrs": {
"src": "cid:avatar_de332fbebe0906c451c8859294d1553d"
},
"marks": [
{
"type": "link",
"attrs": {
"href": "https://wiki.infosysta.com/display/~malhajj?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4"
}
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": " test"
},
{
"type": "text",
"text": "mentioned",
"marks": [
{
"type": "strong"
}
]
},
{
"type": "text",
"text": " you on a page"
}
]
}
]
}
If I understood your requirement correctly, this should do it:
class ImagePopper {
#data;
#collector = [];
constructor(data) {
if (typeof data === "object") {
this.#data = structuredClone(data);
this.parseObject(this.#data, []);
this.#collector.reverse().forEach(({ entry, path }) => {
this.#data[path[0]].splice(path[1], 0, entry);
});
}
return this.#data || data;
}
parseArray = (arr, path) => {
_.set(
this.#data,
path,
arr.reduce((acc, entry, index) => {
if (typeof entry === "object") {
this.parseObject(entry, [...path, index]);
}
if (entry?.type === "image") {
this.#collector.push({ entry, path });
} else {
acc.push(entry);
}
return acc;
}, [])
);
};
parseObject = (obj, path) =>
Object.entries(obj).forEach(
([key, value]) =>
Array.isArray(value) && this.parseArray(value, [...path, key])
);
}
const json = {"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"type":"image","attrs":{"src":"cid:avatar_de332fbebe0906c451c8859294d1553d"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~malhajj?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4"}}]},{"type":"text","text":" Mohamad AL HAJJ "},{"type":"text","text":"mentioned","marks":[{"type":"strong"}]},{"type":"text","text":" you on a page"}]},{"type":"paragraph","content":[{"type":"text","text":" ","marks":[{"type":"textColor","attrs":{"color":"#ffffff"}}]},{"type":"text","text":" "},{"type":"image","attrs":{"src":"cid:mention-icon","alt":"mention icon","title":"mention icon"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=view"}}]},{"type":"text","text":" "},{"type":"text","text":"Bassem 1-1 2022-07-28","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=view"}}]}]},{"type":"paragraph","content":[{"type":"text","text":"···"}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"#"},{"type":"text","text":"Bassem Alameddine","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~balameddine"}}]},{"type":"text","text":" "}]}]}]},{"type":"paragraph","content":[{"type":"text","text":"Release 3.0.7 is ready"}]},{"type":"paragraph","content":[{"type":"text","text":"#"},{"type":"text","text":"Bassem Alameddine","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~balameddine"}}]},{"type":"text","text":" "}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[]}]}]},{"type":"paragraph","content":[{"type":"hardBreak"},{"type":"text","text":" Research about supporting React in windows app"}]},{"type":"paragraph","content":[{"type":"text","text":"#"},{"type":"text","text":"Bassem Alameddine","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~balameddine"}}]},{"type":"text","text":" "}]},{"type":"paragraph","content":[{"type":"hardBreak"},{"type":"text","text":" "},{"type":"hardBreak"},{"type":"text","text":" Test server changes done by Ahmad"}]},{"type":"paragraph","content":[{"type":"text","text":"#"},{"type":"text","text":"Bassem Alameddine","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~balameddine"}}]},{"type":"text","text":" "}]},{"type":"paragraph","content":[{"type":"hardBreak"}]},{"type":"paragraph","content":[{"type":"image","attrs":{"src":"cid:com.atlassian.confluence.plugins.confluence-email-resources_view-page-email-adg-footer-item_icon","alt":"View page Icon","title":"View page Icon"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=view"}}]},{"type":"text","text":" "},{"type":"text","text":"View page","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=view"}}]},{"type":"text","text":" • "},{"type":"image","attrs":{"src":"cid:com.atlassian.confluence.plugins.confluence-email-resources_add-comment-to-content-email-adg-footer-item_icon","alt":"Add comment Icon","title":"Add comment Icon"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?showComments=true&showCommentArea=true&src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=comment#addcomment"}}]},{"type":"text","text":" "},{"type":"text","text":"Add comment","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?showComments=true&showCommentArea=true&src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=comment#addcomment"}}]},{"type":"text","text":" • "},{"type":"image","attrs":{"src":"cid:com.atlassian.confluence.plugins.confluence-like_view-email-adg-content-item_icon","alt":"Like Icon","title":"Like Icon"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/plugins/likes/like.action?contentId=26543947&src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=like&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4c3JmOjMwYmFmMWJhN2NjZGRkZDMwMTdmY2ExMzAwN2UwMGM0IiwicXNoIjoiODEwNWNmMmVlZjcwMGQ1MjVmYjUyODkzMjFmYTMzZGQyNDNmNDNmM2M2MWY1YmU5NzU3YjhjMWVmZmIxZTk1MSIsImlzcyI6ImNvbmZsdWVuY2Vfbm90aWZpY2F0aW9uc0JFOFUtTDNMWi1USzFPLVBGM00iLCJleHAiOjE2NTk3NzM1NTYsImlhdCI6MTY1OTE2ODc1Nn0.R4yPR6SKdUg5mc8fwgdGUa7BeWOdN_2LXDZbAKC5A-o"}}]},{"type":"text","text":" "},{"type":"text","text":"Like","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/plugins/likes/like.action?contentId=26543947&src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=like&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4c3JmOjMwYmFmMWJhN2NjZGRkZDMwMTdmY2ExMzAwN2UwMGM0IiwicXNoIjoiODEwNWNmMmVlZjcwMGQ1MjVmYjUyODkzMjFmYTMzZGQyNDNmNDNmM2M2MWY1YmU5NzU3YjhjMWVmZmIxZTk1MSIsImlzcyI6ImNvbmZsdWVuY2Vfbm90aWZpY2F0aW9uc0JFOFUtTDNMWi1USzFPLVBGM00iLCJleHAiOjE2NTk3NzM1NTYsImlhdCI6MTY1OTE2ODc1Nn0.R4yPR6SKdUg5mc8fwgdGUa7BeWOdN_2LXDZbAKC5A-o"}}]},{"type":"text","text":" "},{"type":"text","text":" ","marks":[{"type":"textColor","attrs":{"color":"#ffffff"}}]}]},{"type":"paragraph","content":[{"type":"text","text":"Manage notifications","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/users/editmyemailsettings.action?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=manage"}}]},{"type":"text","text":" "},{"type":"image","attrs":{"src":"cid:footer-desktop-logo","alt":"Confluence logo big","title":"Confluence logo big"}},{"type":"text","text":" This message was sent by Atlassian Confluence 7.18.1"},{"type":"hardBreak"},{"type":"image","attrs":{"src":"cid:footer-mobile-logo","alt":"","title":""}}]}]}
const result = new ImagePopper(json);
// console.log({ before: json, after: result });
before.innerHTML = JSON.stringify(json, null, 2)
after.innerHTML = JSON.stringify(result, null, 2)
#media (min-width: 800px) {
.side-by-side {
display: flex;
}
.side-by-side > div {
width: 50%;
}
.scroller {
overflow: auto;
height: calc(100vh - 84px);
}
}
pre {
padding: .5rem;
background-color: #f5f5f5;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<div class="side-by-side">
<div>
<h2>Before</h2>
<div class="scroller">
<pre id="before"></pre>
</div>
</div>
<div>
<h2>After</h2>
<div class="scroller">
<pre id="after"></pre>
</div>
</div>
</div>
I'm traversing the data recursively and collect all images, storing their current path. At each level, I'm deleting the images from their parent (by omission). I used lodash's set for this (it allows passing the path as array, which was quite handy here). 1, 2
Then I'm injecting each image at top level before its root parent, in reverse order. This way I don't need to always inflate the splice index by the number of previously processed images.
Decided to wrap it as a class to keep it from interacting with the outer scope, for cleanness, readability and reusability.
That's about it.
Notes:
1 - If you don't want to use lodash's set, consider using this implementation.
2 - in your project, you shouldn't import all of lodash, as I'm doing here. Only import set:
import { set } from 'lodash-es'
// replace `_.set(` in the class with just `set(`
Focussing on your description, I would build a new json object and add the properties in the order I want them to appear.
Assuming your json data is in the data variable, the following would recursively parse the root content, remove any images it finds and later insert them after the root content element it found them in.
const data = `{"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"type":"image","attrs":{"src":"cid:avatar_de332fbebe0906c451c8859294d1553d"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~malhajj?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4"}}]},{"type":"text","text":" Mohamad AL HAJJ "},{"type":"text","text":"mentioned","marks":[{"type":"strong"}]},{"type":"text","text":" you on a page"}]},{"type":"paragraph","content":[{"type":"text","text":" ","marks":[{"type":"textColor","attrs":{"color":"#ffffff"}}]},{"type":"text","text":" "},{"type":"image","attrs":{"src":"cid:mention-icon","alt":"mention icon","title":"mention icon"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=view"}}]},{"type":"text","text":" "},{"type":"text","text":"Bassem 1-1 2022-07-28","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=view"}}]}]},{"type":"paragraph","content":[{"type":"text","text":"···"}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"#"},{"type":"text","text":"Bassem Alameddine","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~balameddine"}}]},{"type":"text","text":" "}]}]}]},{"type":"paragraph","content":[{"type":"text","text":"Release 3.0.7 is ready"}]},{"type":"paragraph","content":[{"type":"text","text":"#"},{"type":"text","text":"Bassem Alameddine","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~balameddine"}}]},{"type":"text","text":" "}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[]}]}]},{"type":"paragraph","content":[{"type":"hardBreak"},{"type":"text","text":" Research about supporting React in windows app"}]},{"type":"paragraph","content":[{"type":"text","text":"#"},{"type":"text","text":"Bassem Alameddine","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~balameddine"}}]},{"type":"text","text":" "}]},{"type":"paragraph","content":[{"type":"hardBreak"},{"type":"text","text":" "},{"type":"hardBreak"},{"type":"text","text":" Test server changes done by Ahmad"}]},{"type":"paragraph","content":[{"type":"text","text":"#"},{"type":"text","text":"Bassem Alameddine","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/~balameddine"}}]},{"type":"text","text":" "}]},{"type":"paragraph","content":[{"type":"hardBreak"}]},{"type":"paragraph","content":[{"type":"image","attrs":{"src":"cid:com.atlassian.confluence.plugins.confluence-email-resources_view-page-email-adg-footer-item_icon","alt":"View page Icon","title":"View page Icon"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=view"}}]},{"type":"text","text":" "},{"type":"text","text":"View page","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=view"}}]},{"type":"text","text":" • "},{"type":"image","attrs":{"src":"cid:com.atlassian.confluence.plugins.confluence-email-resources_add-comment-to-content-email-adg-footer-item_icon","alt":"Add comment Icon","title":"Add comment Icon"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?showComments=true&showCommentArea=true&src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=comment#addcomment"}}]},{"type":"text","text":" "},{"type":"text","text":"Add comment","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/display/MDT/Bassem+1-1+2022-07-28?showComments=true&showCommentArea=true&src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=comment#addcomment"}}]},{"type":"text","text":" • "},{"type":"image","attrs":{"src":"cid:com.atlassian.confluence.plugins.confluence-like_view-email-adg-content-item_icon","alt":"Like Icon","title":"Like Icon"},"marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/plugins/likes/like.action?contentId=26543947&src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=like&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4c3JmOjMwYmFmMWJhN2NjZGRkZDMwMTdmY2ExMzAwN2UwMGM0IiwicXNoIjoiODEwNWNmMmVlZjcwMGQ1MjVmYjUyODkzMjFmYTMzZGQyNDNmNDNmM2M2MWY1YmU5NzU3YjhjMWVmZmIxZTk1MSIsImlzcyI6ImNvbmZsdWVuY2Vfbm90aWZpY2F0aW9uc0JFOFUtTDNMWi1USzFPLVBGM00iLCJleHAiOjE2NTk3NzM1NTYsImlhdCI6MTY1OTE2ODc1Nn0.R4yPR6SKdUg5mc8fwgdGUa7BeWOdN_2LXDZbAKC5A-o"}}]},{"type":"text","text":" "},{"type":"text","text":"Like","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/plugins/likes/like.action?contentId=26543947&src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=like&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4c3JmOjMwYmFmMWJhN2NjZGRkZDMwMTdmY2ExMzAwN2UwMGM0IiwicXNoIjoiODEwNWNmMmVlZjcwMGQ1MjVmYjUyODkzMjFmYTMzZGQyNDNmNDNmM2M2MWY1YmU5NzU3YjhjMWVmZmIxZTk1MSIsImlzcyI6ImNvbmZsdWVuY2Vfbm90aWZpY2F0aW9uc0JFOFUtTDNMWi1USzFPLVBGM00iLCJleHAiOjE2NTk3NzM1NTYsImlhdCI6MTY1OTE2ODc1Nn0.R4yPR6SKdUg5mc8fwgdGUa7BeWOdN_2LXDZbAKC5A-o"}}]},{"type":"text","text":" "},{"type":"text","text":" ","marks":[{"type":"textColor","attrs":{"color":"#ffffff"}}]}]},{"type":"paragraph","content":[{"type":"text","text":"Manage notifications","marks":[{"type":"link","attrs":{"href":"https://wiki.infosysta.com/users/editmyemailsettings.action?src=mail&src.mail.product=confluence-server&src.mail.timestamp=1659168756680&src.mail.notification=com.atlassian.confluence.plugins.confluence-mentions-plugin%3Amention-created-notification&src.mail.recipient=30baf1ba7ccdddd3017fca13007e00c4&src.mail.action=manage"}}]},{"type":"text","text":" "},{"type":"image","attrs":{"src":"cid:footer-desktop-logo","alt":"Confluence logo big","title":"Confluence logo big"}},{"type":"text","text":" This message was sent by Atlassian Confluence 7.18.1"},{"type":"hardBreak"},{"type":"image","attrs":{"src":"cid:footer-mobile-logo","alt":"","title":""}}]}]}`;
const findAndRemoveImagesRecursive = (element) => {
let images = [];
if (Array.isArray(element.content)) {
element.content.forEach((c, i) => {
if (c.type === 'image') {
images.push({
...c
})
delete element.content[i];
}
else {
images = images.concat(findAndRemoveImagesRecursive(c));
}
})
element.content = element.content.filter(x=>x);
}
return images;
}
const newJson = {...data, content: []};
data.content.forEach(e => {
const images = findAndRemoveImagesRecursive(e);
newJson.content = newJson.content.concat(images);
newJson.content.push(e);
});

Convert a list object into another structure

I have a list, converted into js array. Several rows has a Tab prefixes:
var data = [
"2",
" 2.1",
" 2.1.1",
" 2.2",
"3",
"4"
]
What I'm trying to do, is to get following structure:
var data = [
"2",
"2->2.1",
"2->2.1->2.1.1",
"2->2.2",
"3",
"4"
]
Tried (Produce wrong result):
for (var i = 0; i < data.length; i++) {
var current = data;
var length = data[i].length - data[i].replaceAll(" ", "").length;
if (!length) {
console.log(current);
} else {
console.log(data[i-1] + '->' + data[i].trim());
}
}
Update (#MustSeeMelons) - your solution produce wrong results on test data attached below:
flat to tree
I solved this problem in this Q&A. We can reuse the same functions on your data -
const data = `
2
2.1
2.1.1
2.2
3
4
`
// using makeChildren and sanitize from the linked Q&A
console.log(makeChildren(sanitize(data)))
[
{
"value": "2",
"children": [
{
"value": "2.1",
"children": [
{
"value": "2.1.1",
"children": []
}
]
},
{
"value": "2.2",
"children": []
}
]
},
{
"value": "3",
"children": []
},
{
"value": "4",
"children": []
}
]
tree to flat
All that remains now is to convert the tree to flat list of paths -
function* paths(t) {
switch (t?.constructor) {
case Array:
for (const child of t)
yield* paths(child)
break
case Object:
yield [t.value]
for (const path of paths(t.children))
yield [t.value, ...path]
break
}
}
const result =
Array.from(paths(makeChildren(sanitize(data))), path => path.join("->"))
[
"2",
"2->2.1",
"2->2.1->2.1.1",
"2->2.2",
"3",
"4"
]
advantages
Decomposing the problem into smaller parts makes it easier to solve and yields reusable functions but those are not the only advantages. The intermediate tree representation gives you the ability to make other modifications in the context of the tree that the flat representation does not permit. Additionally, the paths function yields arrays of paths segments, allowing the caller to decide which final effect is desired, ie path.join("->"), or otherwise.
demo
Run the demo below to verify the result in your own browser -
const sanitize = (str = "") =>
str.trim().replace(/\n\s*\n/g, "\n")
const makeChildren = (str = "") =>
str === ""
? []
: str.split(/\n(?!\s)/).map(make1)
const make1 = (str = "") => {
const [ value, children ] = cut(str, "\n")
return { value, children: makeChildren(outdent(children)) }
}
const cut = (str = "", char = "") => {
const pos = str.search(char)
return pos === -1
? [ str, "" ]
: [ str.substr(0, pos), str.substr(pos + 1) ]
}
const outdent = (str = "") => {
const spaces = Math.max(0, str.search(/\S/))
const re = new RegExp(`(^|\n)\\s{${spaces}}`, "g")
return str.replace(re, "$1")
}
function* paths(t) {
switch (t?.constructor) {
case Array: for (const child of t) yield* paths(child); break
case Object: yield [t.value]; for (const path of paths(t.children)) yield [t.value, ...path]; break
}
}
const data = `\n2\n\t2.1\n\t\n\t2.1.1\n\t2.2\n3\n4`
console.log(
Array.from(paths(makeChildren(sanitize(data))), path => path.join("->"))
)
.as-console-wrapper { min-height: 100%; top: 0; }
remarks
outdent is generic and works whether you use literal tabs, \t \t\t \t\t\t..., or some number of spaces. What matters is the whitespace is consistent. View the original Q&A for more insight on how each part works.
There are many approaches to this.
Approach #1:
In case you're allowed to use auxiliary space, create an array that will keep track of the latest level of the rows. It is not based on the separator.
let data = ["2","\t2.1","\t\t2.1.1","\t2.2","3","4"], tab="\t", latest = [];
let output = data.map(x => {
let noTabs = x.split(tab).length-1;
latest = [...latest.slice(0, noTabs), x.replaceAll(tab, '')];
return latest.join('->');
})
console.log(output)
This will almost do what you wish, not sure how you want to group them:
const mapped = data.reduce((acc, curr, idx) => {
if (idx !== 0) {
// Get leading digit of current & previous element
const current = curr.trim()[0];
const previous = acc[idx - 1].trim()[0];
// If leading match, aggregate them
if (current === previous) {
acc.push(`${acc[idx - 1].trim()}->${curr.trim()}`);
} else {
acc.push(curr.trim());
}
} else {
acc.push(curr.trim());
}
return acc;
}, []);
Don't use for loops unless you need to break out of the loop at some point. Transforming arrays usually should be done with the map function.
I used reduce because this problem required me to access the new, already mapped element.

bug from element showing

Hi all I have following code: my code
In code I am recieving data from backend, In my case I hardcoded that part (see below data)
const skills = [
{
id: 1,
name: "Html "
},
{ id: 2, name: "CSS" },
{ id: 3, name: "Scss" },
{ id: 4, name: "Bootstrap 4" },
{ id: 5, name: "JavaScript" },
{ id: 6, name: "Jquery" },
{ id: 7, name: "React JS" },
{ id: 8, name: "Angular " },
{ id: 9, name: "Vue.js " },
{ id: 10, name: "SQL" },
{ id: 11, name: "php" },
{ id: 12, name: "Laravel" }
];
I am counting all my skill names char length and if that length is greater then allowCharCount I am hiding all rest skills and showing in number how many skills is hided. That part is working.
let lengthCount = 0;
let maxIndex = 0;
const allowCharCount = 20;
const skill = [];
export const Skill = ({ data }) => {
if (data === undefined) {
return null;
} else {
data.map((item) => {
if (lengthCount <= allowCharCount) {
maxIndex = item.id;
skill.push(item);
}
lengthCount += item.name.length;
});
const mySkills = skill.map((perSkill) => (
<span key={perSkill.id} className="skillItem">
{perSkill.name}
</span>
));
return (
<div className="skillWrapper">
<div>{mySkills}</div>
{lengthCount > allowCharCount ? (
<div className="skillNumber">+{data.length - maxIndex}</div>
) : null}
</div>
);
}
};
but when my chars count is less then allowCharCount it's not working.
If I only have first 3 items (Html, CSS, Scss) I see following view
Html CSS Scss Html CSS Scss +0.
Please help me to fix this code for that case (if chars count is less than allowCharCount) I need to show only correct items with no any +0 count
You should rethink the way you write the component and code in general
You have to store dynamic variables inside the component
To save the result of array operations, you should either mutate state or create a new variable inside the component (which is more preferable in your case).
.map method return an array of values returned by callback inside of it. If you're not going to return anything, use .forEach instead
Use .length property instead of incrementing the size of array by yourself to avoid bugs.
The reason why you get duplicated elements is that you don't clear the array before the component updates, so the algorithm just pushes these values again.
Working code example:
export const Skill = ({ data }) => {
// here I invert condition to get the rest code out of brackets
if (!data) return null;
// all of the dynamic data should be inside the component
const failIndex = React.useMemo(() => {
let charCount = 0;
for (let i = 0; i < data.length; i++) {
charCount += data[i].name.length;
if (charCount > allowCharCount) return i;
}
return -1;
}, [data]);
// check if data has element that doesn't pass the condition
const dataBeforeFail = failIndex === -1 ? data : data.slice(0, failIndex + 1);
const hiddenSkillsCount = data.length - dataBeforeFail.length;
return (
<div className="skillWrapper">
<div>
{dataBeforeFail.map((perSkill) => (
<span key={perSkill.id} className="skillItem">
{perSkill.name}
</span>
))}
</div>
{hiddenSkillsCount > 0 && (
<div className="skillNumber">+{hiddenSkillsCount}</div>
)}
</div>
);
};

JS: Generate objects, which are connected dynamically via parent field

I need to generate some objects, which represents DB documents and I need to 'connect' them via parent reference. The objects are pretty simple.
The main problem for me is to define the input
The objects should be connected in a variable way: I would like to call a function with some parameters to get the result data.To make it a bit easier to understand here an example.
The structure of the needed result data could look like this:
- main
- group
- group
- item
- item
- item
- main
So for this the result output should be:
[
{ _id: 'main1ID' },
{ _id: 'group1ID', parent: 'main1ID', type: 'group' },
{ _id: 'group2ID', parent: 'main1ID', type: 'group' },
{ _id: 'item1ID', parent: 'group1ID', type: 'item' },
{ _id: 'item2ID', parent: 'group1ID', type: 'item' },
{ _id: 'item3ID', parent: 'main1ID', type: 'item' },
{ _id: 'main2ID' },
]
As you can see a main element can have group or items as children or even have no child.
Groups can also have no child at all or items as children.
So I tried to start with a basic function, but I even don't know how to define the parameters to get this function working dynamically :-(
generateContent(2, 2, 3) could create two main object, two groups and three items, but there is no information, how they should be connected to each other. And this is the main problem for me...
function generateContent (main = 0, group = 0, item = 0) {
const mainDocs = []
for (var i = 0; i <= main; i++) {
mainDocs.push({
_id: Random.id()
})
}
// do similiar thing for group and item
// return everything
}
You would need to encode in your input the relations. It is not enough to indicate the number of each type of node. There are several encodings you could think of. A very concise one would be a string pattern like the following:
m(gg(ii)i)m
Here each letter represents the type of node to produce, and the parentheses make clear how they should relate to each other.
Here is a function that would parse such a string into the final array:
function generateContent (pattern) {
const mainDocs = [],
parents = [],
count = { m:0, g:0, i:0 };
let obj, parent;
for (let ch of pattern) {
if (ch === '(') {
parents.push(parent);
parent = obj._id;
} else if (ch === ')') {
parent = parents.pop();
} else {
let type = { m: 'main', g: 'group', i: 'item' }[ch];
obj = {
_id: type + (++count[ch]) + 'ID'
};
if (parent) obj.parent = parent;
if (ch !== 'm') obj.type = type;
mainDocs.push(obj);
}
}
return mainDocs;
}
// Example:
const result = generateContent('m(gg(ii)i)m');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

javascript find child object in nested arrays

I have a javascript structure like below (nested arrays of objects)
var categoryGroups = [
{
Id: 1, Categories: [
{ Id: 1 },
{ Id: 2 },
]
},
{
Id: 2, Categories: [
{ Id: 100 },
{ Id: 200 },
]
}
]
I want to find a child Category object matching an Id, assuming the Category Id's are all unique.
I've got this below, but was wondering if there is a more concise way of doing it:
var category, categoryGroup, found = false;
for (i = 0; i < categoryGroups.length ; i++) {
categoryGroup = categoryGroups[i];
for (j = 0; j < categoryGroup.Categories.length; j++) {
category = categoryGroup.Categories[j];
if (category.Id === id) {
found = true;
break;
}
}
if (found) break;
}
Using flatMap in ES2019
const category = categoryGroups.flatMap(cg => cg.Categories).find(c => c.Id === categoryId);
Caveat: This uses a couple of Array.prototype functions that were only added in ECMAScript 5 and thus will not work with older browsers unless you polyfill them.
You can loop over all first-level objects in your array, and then filter the categories based on your condition and collect all matches in an array. Your final result will be the first element in the array of matches (no match found if array is empty).
var matches = [];
var needle = 100; // what to look for
arr.forEach(function(e) {
matches = matches.concat(e.Categories.filter(function(c) {
return (c.Id === needle);
}));
});
console.log(matches[0] || "Not found");
JSFiddle: http://jsfiddle.net/b7ktf/1/
References:
Array.prototype.forEach
Array.prototype.concat
Array.prototype.filter
Using only Array.prototype.filter():
If you are sure that the id you are looking for exists, you can do:
var id = 200; // surely it exists
var category = arr.filter(g => g.Categories.filter(c => c.Id === id)[0])[0].Categories.filter(c => c.Id === id)[0];
If you are not sure that it exists:
var id = 201; // maybe it doesn't exist
var categoryGroup = arr.filter(e => e.Categories.filter(c => c.Id === id)[0])[0];
var category = categoryGroup ? categoryGroup.Categories.filter(c => c.Id === id)[0] : null;
jsfiddle
Using reduce and recursion :
function nestedSearch(value) {
return categoryGroups.reduce(function f(acc, val) {
return (val.Id === value) ? val :
(val.Categories && val.Categories.length) ? val.Categories.reduce(f, acc) : acc;
});
}
> try on JSFiddle
check the code in the fiddle
var categoryGroups = [
{
Id: 1, Categories: [
{ Id: 1 },
{ Id: 2 },
]
},
{
Id: 2, Categories: [
{ Id: 100 },
{ Id: 200 },
]
}
]
var id = 100;
var x = 'not found';
var category, categoryGroup, found = false;
for (i = 0; i < categoryGroups.length ; i++) {
categoryGroup = categoryGroups[i];
for (j = 0; j < categoryGroup.Categories.length; j++) {
category = categoryGroup.Categories[j];
if (category.Id == id) {
var x = category.Id;
found = true;
break;
}
}
if (found) break;
}
alert(x);
The above code checks if id = 100 is found in the array. If found will alert the value else alerts that its not found. value '100' has been hardcoded for the sake of demo
You could wrap it inside a function to get rid of the awkward break; syntax and you can load each element into a variable inside the for(;;) construct to shave off a few lines.
function subCategoryExists(groups, id)
{
for (var i = 0, group; group = groups[i]; ++i) {
for (var k = 0, category; category = group.Categories[k]; ++k) {
if (category.Id == id) {
return true;
}
}
}
return false;
}
var found = subCategoryExists(categoryGroups, 100);
Easy way using lodash library of NodeJS (assuming you are using NodeJS):
const _ = require('lodash');
let category ;
let categoryGroup = _.find(categoryGroups, (element)=>{
category = _.find(element.Categories, {Id : 100});
return category;
});
console.log(categoryGroup); // The category group which has the sub category you are looking for
console.log(category); // The exact category you are looking for
If you want to actually return the inner category (instead of just checking for it's presence) you can use reduce:
return categoryGroups.reduce((prev, curr) => {
//for each group: if we already found the category, we return that. otherwise we try to find it within this group
return prev || curr.Categories.find(category => category.Id === id);
}, undefined);
This short-circuits on the inner categories, and touches each categoryGroup once. It could be modified to short-cicuit on the categoryGroups as well.
Here's a JS Fiddle demonstration.
You could use underscore:
var cat = _(categoryGroups).
chain().
pluck('Categories').
flatten().
findWhere({Id: 2}).
value();
What I'm doing here is that I'm extracting all Categories values in a single array and then grepping for the correct categories.
EDIT: sorry, didn't get your question right the first time. As the comments suggest, you might not want to use underscore just for that, but that's how I would do it :)
We are using object-scan for our data processing now. It's very powerful once you wrap your head around it. For your questions this would look like this:
// const objectScan = require('object-scan');
const lookup = (id, data) => objectScan(['Categories.Id'], {
useArraySelector: false,
abort: true,
rtn: 'parent',
filterFn: ({ value }) => value === id
})(data);
const categoryGroups = [{ Id: 1, Categories: [{ Id: 1 }, { Id: 2 }] }, { Id: 2, Categories: [{ Id: 100 }, { Id: 200 }] }];
console.log(lookup(1, categoryGroups));
// => { Id: 1 }
console.log(lookup(100, categoryGroups));
// => { Id: 100 }
console.log(lookup(999, categoryGroups));
// => undefined
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

Categories

Resources