1
\$\begingroup\$

I've written a function to create an HTML tree from JSON.

async function traverseTree (list, node) {
  const li = document.createElement('li')
  if (node.kids) {
    const
      ul = document.createElement('ul'),
      span = document.createElement('span')
    span.innerText = node.name
    li.appendChild(span)
    li.appendChild(ul)
    list.appendChild(li)
    for (const kid of node.kids) {
      traverse(ul, kid)
    }
    return list
  }
  li.innerText = node.name
  list.appendChild(li)
  return list
}

;(async function(){
  const
    tree = await readJson('./tree.json'),
    list = document.createElement('ul'),
    output = await traverseTree(list, tree)
  console.log(output)
})()

The issue is that I don't want to display the root node in the HTML tree since it is already displayed elsewhere, therefore I modified the code to exclude it.

async function traverseTree (list, node, isNotRoot) { // additional parameter
  const li = document.createElement('li')
  if (node.kids) {
    let ul = list                                     // now let
    if (isNotRoot) {                                  // additional conditional
      const span = document.createElement('span')
      ul = document.createElement('ul')               // additional assignment of value
      span.innerText = node.name
      li.appendChild(span)
      li.appendChild(ul)
      list.appendChild(li)
    }
    for (const kid of node.kids) {
      traverseTree(ul, kid, true)                     // additional argument
    }
    return list
  }
  li.innerText = node.name
  list.appendChild(li)
  return list
}

I'm wondering if there is a better, cleaner way to go about this?

\$\endgroup\$
3
\$\begingroup\$

Let's separate it by responsibilities into renderKids, renderName, traverseTree so that:

  1. the main function can start explicitly from where you want
  2. functions become immutable, free of side-effects
  3. less repeated code
  4. a single return is used
  5. hopefully, the intent and flow become more obvious
function renderTree(json) {

  function renderKids(kids) {
    const ul = document.createElement('ul');
    for (const kid of kids) {
      ul.appendChild(traverseTree(kid))
    }
    return ul;
  }

  function renderName(name) {
    const span = document.createElement('span');
    span.textContent = name;
    return span;
  }

  function traverseTree(node) {
    const li = document.createElement('li');
    if (node.kids) {
      li.appendChild(renderName(node.name));
      li.appendChild(renderKids(node.kids));
    } else {
      li.textContent = node.name;
    }
    return li;
  }

  return renderKids(json.kids || []);
}

Usage: const output = renderTree(await readJson('./tree.json'))

Note, traverseTree is not asynchronous so there's no need for async keyword.

\$\endgroup\$
  • 2
    \$\begingroup\$ Modularizing code makes everything better. \$\endgroup\$ – SpeedOfRound Nov 8 at 20:58
  • \$\begingroup\$ why not make the function asynchronous? \$\endgroup\$ – PrimitiveNom Nov 12 at 22:37
  • \$\begingroup\$ man, although your code is cleaner, its wayyy harder to follow the logic, partly due to the fact that the names youve chosen for your nested functions are misnomers in certain contexts \$\endgroup\$ – PrimitiveNom Nov 12 at 22:49
  • \$\begingroup\$ and theres issues using your code because it treats the top level folders no differently than children folders \$\endgroup\$ – PrimitiveNom Nov 12 at 22:57
  • \$\begingroup\$ I can't agree. The code became so simple compared to the original spaghetti with side effects that "way harder" is an obvious exaggeration. The rest of your comments feels like a needless nitpicking and exaggeration as well. As for the "it treats the top level folders no differently", even without seeing the actual data I can't agree as it doesn't render the node name of the root which in my understanding was the entire point of the question. Anyway, this code was an example. \$\endgroup\$ – wOxxOm Nov 13 at 4:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.