开发者

How do I select the innermost element?

开发者 https://www.devze.com 2023-01-25 00:43 出处:网络
In jQuery, how would I descend as far as possible into the HTML tree? For simplicity, I only have one path going downward.

In jQuery, how would I descend as far as possible into the HTML tree?

For simplicity, I only have one path going downward.

(related but bonus: how do I find the deepest element with multiple downward paths?)

<html>  
  <table id="table0">  
    <tr>  
      <td 开发者_运维知识库id="cell0">  
        <div class"simple">  I want to change this information </div>  
      </td>
    </tr>  
  </table>  
</html>

I want to change the innermost HTML of the cell named cell0 but I don't necessarily know the names of all the classes inside. Is it possible to select this far without knowing these names?

Thanks much!


For single path just find the element that doesn't have child nodes:

$('body *:not(:has("*"))');

Or, in your more specific case $('#cell0 *:not(:has("*"))');

For multiple paths - what if there are multiple equally nested nodes? This solution will give you an array of all nodes with highest number of ancestors.

var all = $('body *:not(:has("*"))'), maxDepth=0, deepest = []; 
all.each( function(){ 
    var depth = $(this).parents().length||0; 
    if(depth>maxDepth){ 
        deepest = [this]; 
        maxDepth = depth; 
    }
    else if(depth==maxDepth){
        deepest.push(this); 
    }
});

Again, in your situation you probably want to get to table cells' deepest elements, so you're back to a one-liner:

$('#table0 td *:not(:has("*"))');

- this will return a jQuery object containing all the innermost child nodes of every cell in your table.


I'd do this through a single recursive function:

// Returns object containing depth and element
// like this: {depth: 2, element: [object]}
function findDeepestChild(parent) {

    var result = {depth: 0, element: parent};

    parent.children().each(
        function(idx) {
            var child = $(this);
            var childResult = findDeepestChild(child);
            if (childResult.depth + 1 > result.depth) {
                result = {
                    depth: 1 + childResult.depth, 
                    element: childResult.element};
            }
        }
    );

    return result;
}


This question has a simple and good answer when for jQuery. However, I was looking for an elegant solution without it. Since :has is not supported in any browser yet, I came up with this one here.

Array.from( document.querySelectorAll( 'body *' ) )
  .filter( e => !e.children.length );


Well, starting from a basis that you have no idea where this "deepest" node is, you could do something like this:

$.fn.deepest = function() {
  var depth = 0;
  this.find('*').each(function() {
    var d = $(this).parents().length;
    depth = Math.max(d, depth);
  });
  return this.find('*').filter(function() {
    return this.parents().length === depth;
  });
});

Then

var $deepest = $('body').deepest();

would (excepting the 12 bugs probably in my code) would be a jQuery object for the set of deepest elements.

edit — one of my dozen bugs is that this doesn't take into account the depth of the starting node(s) - that'd be a trick to figure out. Might be better to refactor it so that it finds the deepest of the originally-selected list:

$.fn.betterDeepest = function() {
  var depth = 0;
  this.each(function() {
    depth = Math.max(depth, $(this).parents().length);
  });
  return this.filter(function() { return $(this).parents().length === depth; });
});

and to get the deepest on the page you'd do:

var deepest = $('*').betterDeepest();


With a single path:

var elem = $('#table0'),
    next;
while ((next = elem.children(':first')).length > 0)
  elem = next;
// elem now points to the deepest child of table0

If I get time, I'll update my answer with the code for multiple paths.


The first thing I would do is run a depth first search (DFS) and keep track of what level I was at for each node in the DOM.

In order to save having to perform a full DFS every time you want to change something (assuming you'd like to do it multiple times), you could tag each DOM node with its level in the tree as the search is performed. (Then again, you could maybe do this level tagging during the generation of the table?) Once the search is completed and you've determined the deepest node, keep a reference to that node.

From that point, you should be able to quickly update what node is at the deepest level at any given time.


Perhaps not the most efficient, but you could do this:

Example: http://jsfiddle.net/5N3Y7/

var selector = 'body > *';
var $next = $(selector);
var $result = $next;

while( $next.length ) {
    $result = $next;
    selector += ' > *';
    $next = $(selector);
}

$result.css('background','orange');

or this:

Example: http://jsfiddle.net/5N3Y7/2/

(The results in the example appear to be at different levels, but this is because the browser is inserting the missing <tbody>.)

var $next = $('body');

while ($next.length) {
    $result = $next;
    $next = $result.children();
}

$result.css('background', 'orange');


It's not jquery specific, but this recursive solution can be tweaked to do whatever you want.

function findDeepest (elem) {
 var result = {maxDepth: 0, deepestElem: null}
 descend(elem, 0, result);
 if (result.maxDepth > 0)
  alert (result.deepestElem.tagName + " " + result.maxDepth);
 }


function descend(elem, depth, result) {    
 switch (elem.nodeType) {
    case 1: // ELEMENT_NODE
        if (depth > result.maxDepth) {
         result.maxDepth = depth;
         result.deepestElem = elem;
         }
        for (var i=0; i<elem.childNodes.length; i++)
           descend(elem.childNodes[i], depth + 1, result);
        break;
    case 3: // TEXT_NODE
    case 4: // CDATA_SECTION_NODE
    case 5: // ENTITY_REFERENCE_NODE
    case 8: // COMMENT_NODE
        // handle these cases as needed
    break;
    }
}


I think, this is shortest sample for you:

$('#cell0 :last').text('new information')

It's really change innermost element, and save tags structure


Very late to the party, but here's my generic solution.

var elements$ = $(':contains("your text")');
var result = elements$.filter((index, element) => 
        index === elements$.length || 
        ! $.contains(element, elements$.get(index+1))
    );

It's very fast and simple, because jQuery already does all the job for us.

Notice that elements$ is an ordered list of containers, where many contain the next one. Thus the innermost elements are the last element plus all the elements that don't contain the next one.

0

精彩评论

暂无评论...
验证码 换一张
取 消