简体   繁体   中英

Add level classes to nested list with Javascript Vanilla

Im trying to add level clases to a list with nested list items. I want the following structure.

<ul class="parent parent-level-1">
  <li class="child child-level-1"><a>Title level 1</a></li>
  <li class="child child-level-1"><a>Title level 1</a>
    <ul class="parent parent-level-2">
      <li class="child child-level-2"><a>Title level 2</a>
        <ul class="parent parent-level-3">
          <li class="child child-level-3"><a>Title level 3</a></li>
          <li class="child child-level-3"><a>Title level 3</a></li>
        </ul>
      </li>
      <li class="child child-level-2"><a>Title level 2</a>
        <ul class="parent parent-level-3">
          <li class="child child-level-3"><a>Title level 3</a></li>
          <li class="child child-level-3"><a>Title level 3</a></li>
          <li class="child child-level-3"><a>Title level 3</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li class="child child-level-1"><a>Title level 1</a>
    <ul class="parent parent-level-2">
      <li class="child child-level-2"><a>Title level 2</a></li>
    </ul>
  </li>
  <li class="child child-level-1"><a>Title level 1</a></li>
  <li class="child child-level-1"><a>Title level 1</a>
    <ul class="parent parent-level-2">
      <li class="child child-level-2"><a>Title level 2</a></li>
    </ul>
  </li>
</ul>

As you can see there are some nested list inside the parent list that at the same time may content another nested lists. I tried the following Javascript functio where parent is the parent of the main list and level an integer for the first level, could be 0 or 1.

function addLevelClass(parent, level) {
  const iteration = parent.querySelectorAll("ul");

  const firstUL = parent.querySelector("ul");
  firstUL.classList.add("parent", "parent-level-" + level);

  const firstItems = parent.querySelectorAll("li");

  console.info(firstItems);

  // No point if no list items.
  if (!firstItems.length) {
    return;
  }

  //loop trough fisrt-level li
  for (let c = 0; c < firstItems.length; c++) {
    const childrenLi = firstItems[c];

    //add a child class to the li
    childrenLi.classList.add("child", "child-level-" + level);
  }

  // No point if no list items.
  if (!iteration.length) {
    return;
  }
  for (let g = 0; g < iteration.length; g++) {
    const levelIncrement = level + g;

    const parentUL = iteration[g].querySelector("ul");

    //add a parent class to the ul
    parentUL.classList.add(
      "parent",
      "parent-level-" + (level + levelIncrement)
    );

    //fetch all the li's that are direct children of the ul
    const childItems = parentUL.querySelectorAll("li");

    // No point if no list items.
    if (!childItems.length) {
      return;
    }

    //loop trough each li
    for (let c = 0; c < childItems.length; c++) {
      const childrenLi = childItems[c];

      //add a child class to the li
      childrenLi.classList.add(
        "child",
        "child-level-" + (level + levelIncrement)
      );
    }
  }
}

The function gets lost somewhere in the 3er level and fails setting levels for the rest of the list.

The best way to understand this problem is that the HTML structure is a Tree data structure (actually it's a DAG, if you wish to research further).

These problems are part of data structures CS101 (and how to traverse/search). The solution provided is DFS.

However, to approach this problem you need to consider recursion .

ie given a tree structure of the following

| root
|
|- leaf node
|- node with children
 |- leaf node

So the idea is where you find the root node first. Here you are looking for document.querySelector('ul')

then you're going to look for leaf nodes within the root ul . here you'll do something like document.querySelector('ul > li') . this give you the existing child nodes from the root.

from there - you'll need to switch the parent with the child to perform the action recursively.

i'll provide an example below:

 // helper fn const findChildren = el => [...el.children].filter(el => el.tagName === "LI") const root = document.querySelector("ul"); const children = findChildren(root); const traverse = (baseNode, children, iter = 1) => { // process baseNode here with iter here baseNode.classList.add(`list-${iter.toString()}`) for (child of children) { child.classList.add(`child-${iter.toString()}`) let hasGranChild = [...child.children].filter(el => el.tagName === "UL").length if (hasGranChild) { let newChild = [...child.children].find(el => el.tagName === "UL"); const granChildren = findChildren(newChild) traverse(newChild, granChildren, iter + 1) } } } traverse(root, children)
 <ul> <li><a>Title level 1</a></li> <li><a>Title level 1</a> <ul> <li><a>Title level 2</a> <ul> <li><a>Title level 3</a></li> <li><a>Title level 3</a></li> </ul> </li> <li><a>Title level 2</a> <ul> <li><a>Title level 3</a></li> <li><a>Title level 3</a></li> <li><a>Title level 3</a></li> </ul> </li> </ul> </li> <li><a>Title level 1</a> <ul> <li><a>Title level 2</a></li> </ul> </li> <li><a>Title level 1</a></li> <li><a>Title level 1</a> <ul> <li><a>Title level 2</a></li> </ul> </li> </ul>

Result

<ul class="list-1">
  <li class="child-1"><a>Title level 1</a></li>
  <li class="child-1"><a>Title level 1</a>
    <ul class="list-2">
      <li class="child-2"><a>Title level 2</a>
        <ul class="list-3">
          <li class="child-3"><a>Title level 3</a></li>
          <li class="child-3"><a>Title level 3</a></li>
        </ul>
      </li>
      <li class="child-2"><a>Title level 2</a>
        <ul class="list-3">
          <li class="child-3"><a>Title level 3</a></li>
          <li class="child-3"><a>Title level 3</a></li>
          <li class="child-3"><a>Title level 3</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li class="child-1"><a>Title level 1</a>
    <ul class="list-2">
      <li class="child-2"><a>Title level 2</a></li>
    </ul>
  </li>
  <li class="child-1"><a>Title level 1</a></li>
  <li class="child-1"><a>Title level 1</a>
    <ul class="list-2">
      <li class="child-2"><a>Title level 2</a></li>
    </ul>
  </li>
</ul>

within the recursive nature of the function, you can pass in the current "level" within the function arguments. By setting the starting iter = 1 you don't care how "deep" the tree goes. And as you use CSS selectors direct child (ie el1 > el2 ) you're asking, can you give me the direct children of el1 which are el2 ?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM