5

I ran into a problem of targeting different nested levels.

I read that it is possible to use .children feature + for loop, but I failed to do that.

Let's say I want to have a function where you pass the nesting level and it will change some property of <li> element on that level only.

I wrote this function to add classes to all last <li> 

  function changeElement(){ 
  var lastLi = document.querySelectorAll('li:last-child'); 
    for(i = 0; i < lastLi.length; i++){ 
     lastLi[i].classList.add('last'); 
     lastLi[i].style.background = 'green'; 
    } 
  } 

But now I need to target <li> elements on specific nested level

  function changeElementOnLevel(level) {
  var lastLi = document.querySelectorAll('li:last-child');

  for (i = 0; i < lastLi.length; i++) {
    //if level is 1 than we should change ul.root > li:last-child
    //if level is 3 than we should change ALL <li:last-child> inside ul.root 
> li > ul > 
  }

}
changeElementOnLevel(3)
<ul class='root'>
  <li>Cat
    <ul>
      <li>Eats1</li>
      <li>Sleeps</li>
      <li>Snuggles</li>
      <li>Plays</li>
      <li>Meows</li>
    </ul>
  </li>
  <li>Dog
    <ul>
      <li>Eats2</li>
      <li>Sleeps</li>
      <li>Snuggles</li>
      <li>Plays</li>
      <li>Barks</li>
    </ul>
  </li>
  <li>Fish
    <ul>
      <li>Eats3</li>
      <li>Sleeps</li>
      <li>Swims</li>
      <li>Plays</li>
      <li>Swims</li>
    </ul>
  </li>

2
  • 1
    What you want is a little unclear. You say you want to pass a level, and that a property on that "li" will change. But you have a "ul", with "li" children, each which has a "ul", each which was several "li" children. It looks like you actually need to think recursively. Commented Feb 11, 2020 at 20:13
  • I posted an answer that works. Think recursion!!! Commented Feb 11, 2020 at 22:02

6 Answers 6

2

I consider that you have some nested ul tags, so in your example, the ul.root is level 1 and the inner ul is level 2 and if instead of 'Eats1' you have a ul tag, it will be level 3 and ... Use this function:

function changeElementOnLevel(level){
    let query = 'ul.root>li';
    for (let i = 1; i < level; i++) {
        query += '>ul>li'
    }
    query += ':last-child'
    var lastLi = document.querySelectorAll(query);
    for(i = 0; i < lastLi.length; i++){
        // your code 
    }
 }
Sign up to request clarification or add additional context in comments.

Comments

1

It's depends what exactly you want to do. Are you going to change inner HTML on click? What do you mean by saying third level? You have 3 levels, and 3 lists. More information needed in order to help you.

                

  var myElement = document.getElementsByClassName('root');
            var arrayOfPets = myElement[0].children; // we are getting first level, all animals

            var secondLevelArr = [];

            for(i = 0; i < arrayOfPets.length; i++){ 
                arrayOfPets[i].classList.add('last'); 
                arrayOfPets[i].style.background = 'green'; 

                var secondLevel = arrayOfPets[i].children[0].children;
                // Push results to array
                secondLevelArr.push(secondLevel);
               } // will add class and background to all of them
          // To add styles only to last element, you do not need to loop through them

          arrayOfPets[arrayOfPets.length - 1].style.background = 'red'; 

          for(i = 0; i < secondLevelArr.length; i++){

            secondLevelArr[i][0].style.color = "white";

            for(j = 0; j < secondLevelArr[i].length; j++){
                secondLevelArr[i][j].style.textDecoration = 'line-through';
            }

            secondLevelArr[i][secondLevelArr[i].length - 1].style.textDecoration = 'none';
          }
              
 <ul class='root'>
                <li>Cat
                  <ul>
                    <li>Eats1</li>
                    <li>Sleeps</li>
                    <li>Snuggles</li>
                    <li>Plays</li>
                    <li>Meows</li>
                  </ul>
                </li>
                <li>Dog
                  <ul>
                    <li>Eats2</li>
                    <li>Sleeps</li>
                    <li>Snuggles</li>
                    <li>Plays</li>
                    <li>Barks</li>
                  </ul>
                </li>
                <li>Fish
                  <ul>
                    <li>Eats3</li>
                    <li>Sleeps</li>
                    <li>Swims</li>
                    <li>Plays</li>
                    <li>Swims</li>
                  </ul>
                </li>
            </ul>

2 Comments

Hello, by changing the elements I mean adding new classes to them so I need to define which element code should work with
I've edited my code, please see if it is something you are looking for.
1

Here's what I came up with on the fly:

First, I'd make a small tweak to your HTML (I'm not sure what you have is actually valid...it might display how you like, but structurally it's going to cause problems). It is going to be difficult to set the value of an "li", if it has the value AND a nested list. If you reset the innerText or the innerHtml, you're going to completely overwrite the rest of the HTML in that tag, meaning you'll lose the nested list. You could work around this, but why bother, just close those tags predictably.

(Note I don't think any of the other answers address this issue).

So I'd first do this, notice how I close the "li" for Cat, Dog, and Fish:

          <ul class='root'>
            <li>Cat</li>
              <ul>
                <li>Eats1</li>
                <li>Sleeps</li>
                <li>Snuggles</li>
                <li>Plays</li>
                <li>Meows</li>
              </ul>
            <li>Dog</li>
              <ul>
                <li>Eats2</li>
                <li>Sleeps</li>
                <li>Snuggles</li>
                <li>Plays</li>
                <li>Barks</li>
              </ul>
              <li>Fish</li>
                <ul>
                  <li>Eats3</li>
                  <li>Sleeps</li>
                  <li>Swims</li>
                  <li>Plays</li>
                  <li>Swims</li>
                </ul>
            </ul>

Now you can select elements and set values very straightforwardly (and the HTML is sound); the selectors here basically say "give me the li and ul children that are ONLY direct descendants of whatever element I'm currently working with" (otherwise you will get all of them no matter how deeply nested, which you don't want).

This code gets you the desired result by working recursively on nested "li" and "ul" collections, note that it also works on the top level "li" collection:

    const top_layer = document.querySelectorAll ( '.root' );
    const the_new_val = 'THE NEW VAL'; 
    function setProps ( elems, level ) {
      Array.from ( elems ).forEach ( x => { 
        const li = x.querySelectorAll ( ':scope > li' );
        const ul = x.querySelectorAll ( ':scope > ul' );
        if ( Array.from ( li ).length >= level ) {
          li [ level ].innerText = the_new_val;
            setProps ( li [ level ].children, level );
        } 
        if ( Array.from ( ul ).length ) {
            setProps ( ul, level );
        }
      });
    }

setProps ( top_layer, 2 );

Yes, you could work with "children", but since we are directly interested in setting "li" values, which always appear in "ul" tags, the explicit selectors make it more obvious what's going on and would ignore any other children that may be around, feel free to make that change if you like.

The displayed result:

Displayed Result

Comments

0

It is not very clear what you are trying to achieve :) But you can try :nth-child() - CSS pseudo-class selector that allows you to select elements based on their index (source order) inside their container.

This is just an example:

function find(n) {
    // returns NodeList
    var liNodeList = document.querySelectorAll('li:nth-child(' + n + ')');
    console.log(li);

    // if you need to do something with those elements, you can iterate
    for (var i = 0; i < liNodeList.length; ++i) {
        var item = liNodeList[i];
        // do what you need with particular item
    }
}

Also, the right method is querySelectorAll(...). What you are using querySelectAll does not exist.

2 Comments

Hello, John! it is my first task from the DOM classes and Ii believe it should be done using the loop. So I code accepts a specific level from the user > goes to that level and add something(e.x add a new class to all last <li> on that level
I wrote this function to add classes to all last <li> function addClass(){ var lastLi = document.querySelectorAll('li:last-child'); for(i = 0; i < lastLi.length; i++){ lastLi[i].classList.add('last'); lastLi[i].style.background = 'green'; } } But now I need to target <li> elements on specific nested level
0

Try like below,

I had used mix of query selectors and traversal to achieve this,

function changeElementOnLevel(level) {
        var rootElement = document.querySelector(".root");

        let targetLi;
        if (level === 1) {
          targetLi = rootElement.children[rootElement.children.length - 1];
          let ul = targetLi.querySelector("ul"); // Since a ul is also there in level 1, I am expecting we need to change only li value not the ul
          targetLi.textContent = "changed Value";
          targetLi.append(ul);
        } else if (level === 3) {
          targetLi = rootElement.querySelector(
            "li:last-child ul li:last-child"
          );
          targetLi.textContent = "changed Value";
        }
      }

      changeElementOnLevel(3);
<ul class="root">
      <li>
        Cat
        <ul>
          <li>Eats1</li>
          <li>Sleeps</li>
          <li>Snuggles</li>
          <li>Plays</li>
          <li>Meows</li>
        </ul>
      </li>
      <li>
        Dog
        <ul>
          <li>Eats2</li>
          <li>Sleeps</li>
          <li>Snuggles</li>
          <li>Plays</li>
          <li>Barks</li>
        </ul>
      </li>
      <li>
        Fish
        <ul>
          <li>Eats3</li>
          <li>Sleeps</li>
          <li>Swims</li>
          <li>Plays</li>
          <li>Swims</li>
        </ul>
      </li>
    </ul>

Comments

0

This code resolved my issue:

var parent = document.querySelector('.root');

function setFirstItemClass(element, level){
    level = +level;
    if(level == 1){
        console.dir(element);
        if(element.children.length > 0){
            element.children[0].style.backgroundColor = 'red';
        }
    } else {
        if(element.children.length > 0){
            level--;
            for(child of element.children){
                setFirstItemClass(child, level);
            }
        }

    }
}

setFirstItemClass(parent, 3);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.