Convert paths into a nested unordered list using JavaScript:

Viewed 135

I have a list with paths as below:

<ul class="list">
    <li class="item">/animal/dog/treats/biscuits</li>
    <li class="item">/animal/dog/treats/cookies</li>
    <li class="item">/clothes/shirts</li>
</ul>

I want to convert each of the paths in the above list into a nested unordered list as shown below using JavaScript:

<ul>
    <li>animals
        <a href="/animal">Shop</a>
        <ul>
            <li>dogs
                <a href="/animal/dog">Shop</a>
                <ul>
                    <li>treats
                        <a href="/animal/dog/treats">Shop</a>
                        <ul>
                            <li>
                                <a href="/animal/dog/treats/biscuits">biscuits</a>
                            </li>
                            <li>
                                <a href="/animal/dog/treats/cookies">cookies</a>
                            </li>
                        </ul>
                    </li>
                </ul>
            </li>
        </ul>
    </li>
    <li>clothes
        <a href="/clothes">Shop</a>
        <ul>
            <li><a href="/clothes/shirts">shirts</a></li>
        </ul>
    </li>
</ul>

Here is the expected output: output

If anyone could please steer me in the right for achieving this using JavaScript, I'd really appreciate it. Thank you!

2 Answers

Here's some vanilla JavaScript to do exactly what you specified.  I added a space between 'shop' and the list entry names to stop them from generating right up against each other.  Hope this helps!

let list  = document.querySelector('#list'); // get list
let items = list.querySelectorAll('[class*="item"]'); // get all items in list
let newList = document.createElement('ul');

items.forEach( item => {

  const paths      = item.innerText.split('/');
  paths.splice(0,1); // remove first element since it's empty
  
  let   dir        = newList;
  let   globalPath = "";
  
  for( path of paths ){
  
    globalPath = globalPath + '/' + path;
    
    if( dir[path] == undefined ){
      
      let subList = document.createElement('li'); // begin sublist
        subList.innerText = path + ' '; // add space between the list entry and href
        
        let href = document.createElement('a'); // begin href
          href.href = globalPath;
        subList.appendChild( href ); // end href
        
        if( path != paths[paths.length-1] ){ // prep another ul to go deeper in the list
          href.innerText = "shop";
          let ul = document.createElement('ul');
          subList.appendChild( ul );
          dir[path] = ul;
        }
      dir.appendChild(subList); // end sublist
      
    }
    
    dir = dir[path]; // dive deeper
    
  }
  
} );

list.innerHTML = newList.innerHTML; // replace old list with new list
<ul class="list" id="list">
    <li class="item">/animal/dog/treats/biscuits</li>
    <li class="item">/animal/dog/treats/cookies</li>
    <li class="item">/clothes/shirts</li>
</ul>

A Saturday afternoon/evening with nothing to do ... and this is what I put together. Not the first answer, but yet another one. ;-)

// convert the menu object into the nested <ul> structure:
function buildShop(pth,mn){
 return Object.entries(mn).sort(([k],[l])=>k.localeCompare(l))
  .map(([sh,o])=>
  '<li>'+
   (Object.entries(o).length
    ? sh+' <a href="'+pth+sh+'">Shop</a><ul>'+ buildShop(pth+sh+'/',o)+'</ul>'
    : '<a href="'+pth+sh+'">'+sh+'</a>')+
  '</li>').join("\n")
}

// Create a "menu" object containing all shops and items:
const menu={};
document.querySelectorAll("ul.list>li").forEach(el=>
 el.textContent.substr(1).split("/")
 .reduce((m,sh)=>m[sh]=m[sh]||{},menu)
);

document.querySelector("ul.list").innerHTML=buildShop('/',menu);
<ul class="list">
<li class="item">/animal/dog/collar</li>
<li class="item">/animal/dog/treats/biscuits</li>
<li class="item">/animal/dog/treats/cookies</li>
<li class="item">/clothes/shirts</li>
<li class="item">/animal/dog/leash</li>
</ul>

I just added alphabetical sorting for each level. This way it doesn't matter in which order the paths were defined in the first place.

Related