Last active
April 22, 2023 13:41
-
-
Save ervinne13/2e7de9381886c0178261251f60eb4a57 to your computer and use it in GitHub Desktop.
A simple function that generates an unordered list based on a tree of nodes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
Instructions: | |
You may just copy the whole code and execute this on the browser console or include this to a node | |
project that has [JSDOM](https://github.com/jsdom/jsdom) to run it in the terminal. | |
Requirements: | |
A node is either a string, or a JS object of the form {"name": "a name", "nodes": [more, nodes]} | |
Write a function in javascript that converts an array of nodes into an unordered list in HTML | |
function mySample(arrayOfNodes) {...; return html} | |
For example: | |
["one", {name: "two", nodes: [{name: "2.1", nodes: ["2.1a")}, "two point two"|}, "three"] | |
=> | |
• one | |
• two | |
• 2.1 | |
• 2.1a | |
• two point two | |
• three | |
Answer: This can resolved with simple recursion. We can either manually build the HTML string, | |
or continuously add nodes of element objects to the tree. I'm more comfortable doing the latter | |
if we're dealing with vanilla. | |
*/ | |
/** | |
* Creates an unordered list element object based on the array of nodes. | |
* An HOF that contains recursive functionality inside. | |
* | |
* @param {String|Object} nodes | |
* @param {Object} parent Element that can either be the unordered list, or | |
* @returns | |
*/ | |
const createListElement = (nodes = [], parent = null) => { | |
const SAFETY_LIMIT = 100; // Arbitrary count, but I'm just more comfortable to always use a recursion stopper in case I mess up when developing. | |
let recursionCount = 0; | |
const recursiveCreateUL = (nodes = [], parent = null) => { | |
assertInfiniteRecursionSafe(nodes); | |
if (!parent) { | |
return recursiveCreateUL(nodes, document.createElement("ul")); | |
} | |
for (const node of nodes) { | |
let li; | |
if (typeof node === "object") { | |
li = nodeObjToLi(node); | |
} else if (typeof node === "string") { | |
li = stringToLi(node); | |
} else { | |
const e = new Error("Invalid node. Nodes can only be strings or objects"); | |
e.node = node; | |
throw e; | |
} | |
parent.appendChild(li); | |
} | |
return parent; | |
}; | |
const nodeObjToLi = (node) => { | |
assertNodeObjectIsValid(node); | |
const li = document.createElement("li"); | |
li.innerHTML = node.name; | |
li.appendChild(recursiveCreateUL(node.nodes)); | |
return li; | |
} | |
const stringToLi = (text) => { | |
const li = document.createElement("li"); | |
li.innerHTML = text; | |
return li; | |
} | |
const assertInfiniteRecursionSafe = (nodes) => { | |
if (recursionCount++ >= SAFETY_LIMIT) { | |
// Ideally I have at least a generic Error object I can put other variables on but it's kinda | |
// overkill for now, I really just want to include the nodes/node that caused the fault so | |
// let's just do it this way for now. | |
const e = new Error("Recursion limit reached. A faulty node list might have been provided."); | |
e.nodes = nodes; | |
throw e; | |
} | |
} | |
const assertNodeObjectIsValid = (node) => { | |
if (!(node?.name && Array.isArray(node?.nodes))) { | |
const e = new Error("Node object is invalid. It must have a name and an array of nodes"); | |
e.node = node; | |
throw e; | |
} | |
} | |
return recursiveCreateUL(nodes, parent); | |
}; | |
try { | |
// Try to change the input too, or maybe deliberately break it by passing non object, sudden arrays, or just incorrect object keys | |
createListElement(["one", { name: "two", nodes: [{name: "2.1", nodes: [ "2.1a" ] }, "two point two" ] }, "three"]); | |
} catch(e) { | |
// things we can report to the user | |
console.error(e); | |
// things we keep only on our internal logs, but i'm outputting everything here as we don't have | |
// a logging system in place | |
if (e.node) { | |
console.error({ node: e.node }) | |
} | |
if (e.nodes) { | |
console.error({ nodes: e.nodes }) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
"Show or talk about how to enhance the code to change the bullet symbols to reflect the level of the item."
With the way the function is designed, it's very simple, just add an
ol
element as the parent instead of using anull
default:createListElement(["one", { name: "two", nodes: [{name: "2.1", nodes: [ "2.1a" ] }, "two point two" ] }, "three"], document.createElement("ol"));