jQuery find first level elements with a class but not nested elements inside

Viewed 101

This is the example HTML (ids will not be present, added here for demo purpose)

<div id="root" class="node">
    <div class="[any class that is not `node`]">
        <div class="[any class that is not `node`]">
            <div class="node" id="n0"></div>
        </div>
        <div class="node" id="n1"></div>
        <div class="node" id="n2"></div>
        <div class="node" id="n3"></div>
        <div class="node" id="n4">
            <div class="[any class that is not `node`]">
                <div class="node" id="n4_1"></div>
            </div>
            <div class="[any class that is not `node`]">
                <div class="node" id="n4_2"></div>
            </div>
        </div>
    </div>
    <div class="node" id="n5"></div>
    <div class="node" id="n6"></div>
    <div class="node" id="n7"></div>
    <div class="node" id="n8">
        <div class="[any class that is not `node`]">
            <div class="node" id="n8_1"></div>
        </div>
        <div class="[any class that is not `node`]">
            <div class="node" id="n8_2"></div>
        </div>
    </div>
</div>

Find first level nodes within the root using a function:

function findNextLevelNodes($elem) {
    // example code
    let selector = '[selector to include next nodes, but not nodes nested within those]';
    $elem.find(selector).each((i, node) => console.log($(node).attr('id')));
}

such that

let $root = $('#root');
findNextLevelNodes($root);
// should print n0, n1, n2, n3, n4, n5, n6, n7, n8
// should not print n4_1, n4_2, n8_1, n8_2

Seems simple enough but I can't find the selector that will get me this result.

Found similar questions, but was not satisfied with answers, surely this should be easy enough to do, without manually looping through all of the children

Similar questions -

jQuery find only not nested elements

  • this has a solution that relies on looping through all elements inside #root
  • node can be nested to any level, so I would prefer if I didn't have to recursively loop through each and every element inside #root

Selecting first instance of class but not nested instances via jQuery

  • answers depend on the classes/tags of non-node elements of html, so not applicable

EDIT

findNextLevelNodes needs to be efficient in my case since it would be called recursively for each child node, and if findNextLevelNodes itself has recursion through all children, or .parents call, I have a feeling it might not be efficient with large node trees

For now it seems, i'll have to alter the html classes to keep things efficient, kinda like

<div id="root" class="node l0">
    <div class="[any class that is not `node`]">
        <div class="[any class that is not `node`]">
            <div class="node l1" id="n0"></div>
        </div>
        <div class="node l1" id="n1"></div>
        <div class="node l1" id="n2"></div>
        <div class="node l1" id="n3"></div>
        <div class="node l1" id="n4">
            <div class="[any class that is not `node`]">
                <div class="node l2" id="n4_1"></div>
            </div>
            <div class="[any class that is not `node`]">
                <div class="node l2" id="n4_2"></div>
            </div>
        </div>
    </div>
    <div class="node l1" id="n5"></div>
    <div class="node l1" id="n6"></div>
    <div class="node l1" id="n7"></div>
    <div class="node l1" id="n8">
        <div class="[any class that is not `node`]">
            <div class="node l2" id="n8_1"></div>
        </div>
        <div class="[any class that is not `node`]">
            <div class="node l2" id="n8_2"></div>
        </div>
    </div>
</div>

// now this should work
function findNextLevelNodes($root, level) {
    return $root.find('.node.l' + level);
}

Seems a bit hacky, I'll wait for sometime to see if I find anything more elegant and decently efficient, otherwise will have to go with this.

2 Answers

you can reach that simply by using a recursive function that will search in direct node child using .children(), if it have a class "node" then go to next direct child otherwise go deep again Here is the function

function findNextLevelNodesByClass($elem, className) {

  let nodesArray = [];
  
  function getRecursiveNextDom($rootNode) {
    $rootNode.children().each((i, elemnt) => {
      if ($(elemnt).hasClass(className))
        nodesArray.push(elemnt)
      else
        getRecursiveNextDom($(elemnt));
    })
  }
  
  
  getRecursiveNextDom($elem,className);
  
  return nodesArray;
}

See working snippet :

function findNextLevelNodesByClass($elem, className) {

  let nodesArray = [];
  
  function getRecursiveNextDom($rootNode) {
    $rootNode.children().each((i, elemnt) => {
      if ($(elemnt).hasClass(className))
        nodesArray.push(elemnt)
      else
        getRecursiveNextDom($(elemnt));
    })
  }
  
  
  getRecursiveNextDom($elem,className);
  
  return nodesArray;
}


let $root = $('#root');
let result = findNextLevelNodesByClass($root, 'node');
//result.forEach( element => console.log(element.id))
console.log( result.map(function(y){return y.id}).join(','))
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="root" class="node">
  <div class="anyclass">
    <div class="anyclass">
      <div class="node" id="n0"></div>
    </div>
    <div class="node" id="n1"></div>
    <div class="node" id="n2"></div>
    <div class="node" id="n3"></div>
    <div class="node" id="n4">
      <div class="anyclass">
        <div class="node" id="n4_1"></div>
      </div>
      <div class="anyclass">
        <div class="node" id="n4_2"></div>
      </div>
    </div>
  </div>
  <div class="node" id="n5"></div>
  <div class="node" id="n6"></div>
  <div class="node" id="n7"></div>
  <div class="node" id="n8">
    <div class="anyclass">
      <div class="node" id="n8_1"></div>
    </div>
    <div class="anyclass">
      <div class="node" id="n8_2"></div>
    </div>
  </div>
</div>

i dont see others solutions than: from all .node, i am filtering .node that have only 1 parent .node (root at least)

$('#root .node').filter( (s,e) => $(e).parents(".node").length < 2); // or length == 1

i have written < 2 because root has a class node, else set < 1.

you could write too:

$('#root').find(".node").filter( (s,e) => $(e).parents(".node").length < 2);

or

$('.node').filter( (s,e) => $(e).parents(".node").length == 1);

if you add class indicating level,

$("#root .node.l1") will give you the result..and will be the more efficient

Related