Context - my project
I'm working on a project of chrome bookmark extension for which I'd notably like to generate my bookmarks as an interactive jsTree. I'm quite new to jQuery/jsTree.
My references
I used these 2 repos as reference:
- Chrome Extension Samples - API Bookmark Samples
- Chrome Extensions Samples - Functional Bookmark Samples
I've used the API Bookmark Sample's recursive function displayBookmarks.
Note: I tweaked the case if (node.children) {...} to display the titles of the subFolders
// Recursively display the bookmarks
function displayBookmarks(nodes, parentNode) {
for (const node of nodes) {
// If the node is a bookmark, create a list item and append it to the parent node
if (node.url) {
const listItem = document.createElement('li');
listItem.textContent = node.title;
parentNode.appendChild(listItem);
}
// If the node has children (=> is a folder), recursively display them
// Tweak to show subfolders' titles: append both a li element,
// then an ul elmeent right underneath
if (node.children) {
const subFolderTitle = node.title //+ node.id // to reveal bookmark id;
const listItem = document.createElement('li');
listItem.textContent = subFolderTitle;
parentNode.appendChild(listItem);
const sublist = document.createElement('ul');
parentNode.appendChild(sublist);
displayBookmarks(node.children, sublist);
}
}
}
Then, I called displayBookmarks on one of my folders, to test it, in a chain of promises, followed by creating the instance on the container bookmarkList that wraps my rendered subFolder, like explained in jsTree official doc - Populating a tree using HTML:
//GET A SUBTREE - for Folder Test only:
const targetId = 'FolderTest.id'; // an id as a string got by looking at node.list
const bookmarkList = document.getElementById('bookmarkList');
chrome.bookmarks.getSubTree(targetId)
.then( (res) => displayBookmarks(res, bookmarkList) )
.catch( (err) => console.log(err) );
//create an instance when the DOM is ready
$(function () {
$("#bookmarkList").jstree();
});
Note: alternatively, I made this chaining after chrome.bookmarks.getSubTree work as well:
chrome.bookmarks.getSubTree(targetId)
.then( (res) => displayBookmarks(res, bookmarkList) )
.then( () => $("#bookmarkList").jstree() )
.catch( (err) => console.log(err) );
and here is my HTML in my popup.html:
<h1>Bookmark Viewer + Fetch test</h1>
<!-- bookmark Tree forming -->
<div id="bookmarkList"></div>
the Folder Structure I use is the following:
- FolderTest
- jQuery html method()
- test1
- test2
- How to insert a jsTree into a dynamically generated container?
- test2
My output
when I temporarily comment my jstree instance, this is what I have in my popup: folder structure rendered in popup - without jsTree --> I find there the hierarchy I expect.
and when I uncomment to make the jstree happen, here is what I've got: folder structure rendered in popup - with jsTree --> I can't see my master node "FolderTest", and I can't see any clickable arrow on test1 - that would then allow me to expand test2, and so on - either.
What I also tried
- I also tried another dummy tree coded statically in my HTML, just to make sure I could render the tree, and it worked.
- I checked the link that I've used in my test2 folder: How to insert a jsTree into a dynamically generated container? but without any success.
- I'm aware of the details of the jsTree detailed API, but I don't quite understand how to build my jQuery to make the nested expansion happen.
- Wander across stack & google looking for a similar issue
If anyone has already done it or seen a similar doc/stack issue bringing some solution or workaround, then feel free to refer to it. I've spent a fair amount of my weekend on it now so any help welcome!
Edit 24/07 - Found a fix
I've just found a fix, and here is how I got there:
- The original code in
popup.jsfrom Chrome Extension Samples - API Bookmark Samples contains this original code fordisplayBookmarks:
// Recursively display the bookmarks
function displayBookmarks(nodes, parentNode) {
for (const node of nodes) {
// If the node is a bookmark, create a list item and append it to the parent node
if (node.url) {
const listItem = document.createElement('li');
listItem.textContent = node.title;
parentNode.appendChild(listItem);
}
// If the node has children, recursively display them
if (node.children) {
const sublist = document.createElement('ul');
parentNode.appendChild(sublist);
displayBookmarks(node.children, sublist);
}
}
}
- The fix I offered earlier - when raising my question, offered a "visually OK" hierachy, showing the parent folders' title. However, it was not functionally OK. What I needed is an unordered lists wrapping one
<li></li>(parent), itself containing wrapped unordered sublists like so:<ul><li>...</li><li>...</li></ul>, where<li>...</li><li>...</li>is for a succession of children. So I modified the code as such, following this velog.io article about Recursive DOM manipulation & Tree UIs as a reference:
// Recursively display the bookmarks
function displayBookmarks(nodes, parentNode) {
for (const node of nodes) {
const li = document.createElement('li');
li.textContent = node.title;
// If the node has children (=> is a folder), recursively display them
// Fixed DOM manip to have nesting of <ul><li>... as needed for a tree
if (node.children) {
const ul = document.createElement("ul");
li.append(ul);
parentNode.append(li);
displayBookmarks(node.children, ul);
} else {
parentNode.append(li)
}
}
}
- This tree will render the top node as
<li></li>wrapping element, and jsTree needs a top<ul></ul>wrapping it all. So I created this in my HTML:
<!-- bookmark Tree forming -->
<div id="bookmarkContainer">
<ul id="startNode"></ul>
</div>
- and instead of calling my fixed
displayBookmarkson thedivelement, I do so on theulelement. jsTree still needs to create the instance on a wrappingdivcontainer, which I pre-prepared in my HTML and referred to in my selector below:
//GET A SUBTREE - getSubTree('myNodeId') - for Folder Test only:
const targetId = FolderTest.id;
const startNode = document.getElementById('startNode');
chrome.bookmarks.getSubTree(targetId)
.then( (res) => displayBookmarks(res, startNode) )
.then( () => $("#bookmarkContainer").jstree() )
.catch( (err) => console.log(err) );
If anyone thinks of an alternative fix, or if the moderation thinks it can be closed, then feel free to do either. Also, it's my first stack question (and now my first edit) ever so, if I missed on some of the forum's best practices please raise.
Thanks in advance,