Matt K
Matt K

Reputation: 7337

Using jQuery is there a way to find the farthest (deepest, or most nested) child element?

If I have a set of elements:

<div class="start">
    <div>
        <span>Text 1</span>
    </div>
</div>

<div class="start">
    <span>
        <div>
            <span>Text 2</span>
        </div>
        <div>
            <div>
                <span>Text 3</span>
            </div>
        </div>
    </span>
</div>

What is the best way (using jQuery) to retrieve the most nested child elements (in this case the spans "Text 1" and "Text 3") without knowing what the element structure will be beforehand specifically?

Here's a jsFiddle I'm working with.

Also, I apologize if this has been asked before, but I couldn't find anything like this question specifically.

Upvotes: 2

Views: 9134

Answers (5)

Methos
Methos

Reputation: 14444

Another method(possibly not so fast) with minimal lines of code could be as follows:

var all_matched_elements = $(":contains('" + text_to_search + "')");
var all_parent_elements = $(all_matched_elements).parents();
var all_deepest_matches = $(all_matched_elements).not(all_parent_elements);

This method is especially useful if your text is inside an element that also has other children elements as follows:

<div class="start">
    <div>
        <div>Text 1<span>Alpha Beta Gamma</span></div>
    </div>
</div>

However, the above code would not work for a DOM that looks like following:

<div class="start">
        <div>
            <div id="zzz">search-text
                <div id="aaa">search-text</div>
            </div>  
        </div>
 </div>

In above case, it would only select inner-most with id "#aaa". It would drop id "#zzz". If one wants to also choose with id "#zzz" then code at the start has to be modified to drop elements from "all_parent_elements" whose direct text also matches search text.

Upvotes: 2

jfriend00
jfriend00

Reputation: 708096

Here's an implementation that uses a treeWalk function I had written earlier and then wraps it in a jquery method that finds the deepest descendant of each item in the passed in jQuery object and returns a new jQuery object containing those nodes.

A solution with recursion and lots of jQuery can be done with lots less code, but it will likely be slower. This is based on a generic native JS tree walk function that walks a tree.

Working demo with more complicated HTML test case than the OP's HTML: http://jsfiddle.net/jfriend00/8tC3a/

$.fn.findDeepest = function() {
    var results = [];
    this.each(function() {
        var deepLevel = 0;
        var deepNode = this;
        treeWalkFast(this, function(node, level) {
            if (level > deepLevel) {
                deepLevel = level;
                deepNode = node;
            }
        });
        results.push(deepNode);
    });
    return this.pushStack(results);
};

var treeWalkFast = (function() {
    // create closure for constants
    var skipTags = {"SCRIPT": true, "IFRAME": true, "OBJECT": true, "EMBED": true};
    return function(parent, fn, allNodes) {
        var node = parent.firstChild, nextNode;
        var level = 1;
        while (node && node != parent) {
            if (allNodes || node.nodeType === 1) {
                if (fn(node, level) === false) {
                    return(false);
                }
            }
            // if it's an element &&
            //    has children &&
            //    has a tagname && is not in the skipTags list
            //  then, we can enumerate children
            if (node.nodeType === 1 && node.firstChild && !(node.tagName && skipTags[node.tagName])) {                
                node = node.firstChild;
                ++level;
            } else if (node.nextSibling) {
                node = node.nextSibling;
            } else {
                // no child and no nextsibling
                // find parent that has a nextSibling
                --level;
                while ((node = node.parentNode) != parent) {
                    if (node.nextSibling) {
                        node = node.nextSibling;
                        break;
                    }
                    --level;
                }
            }
        }
    }
})();

var deeps = $(".start").findDeepest();

deeps.each(function(i,v){
    $("#results").append(
        $("<li>").html($(v).prop("tagName") + " " + $(v).html())
    );
});

Upvotes: 4

Fabricio
Fabricio

Reputation: 839

I think this will work for you.

var deepestLevel = 0;
var deepestLevelText = "";

function findDeepNested(element, currentLevel) {
    if ((element.children().length == 0) && (deepestLevel < currentLevel)) {
        // No children and current level is deeper than previous most nested level
        deepestLevelText="<li>" + element.text() + "</li>";
    }
    else { // there are children, keep diving
        element.children().each( function () {
            findDeepNested($(this), currentLevel + 1);
        });
    }
}

$(".start").each( function () {
    deepestLevel = 0;
    deepestLevelText = "";
    findDeepNested($(this), 0);
    $("#results").append(deepestLevelText);
});

Fiddle

Upvotes: 0

Lepidosteus
Lepidosteus

Reputation: 12047

You need to use the :last filter

$('.start').find(':last');

Working fiddle: http://jsfiddle.net/BmEzd/1/

Upvotes: 3

Maciej Treder
Maciej Treder

Reputation: 12350

<script type="text/javascript">
    function findFarthestNode(node)
    {
        if(node.parent().length && node.parent().prop("tagName") != "BODY")
            return findFarthestNode(node.parent());
        return node;
    }
    jQuery(document).ready(function() {
        node = jQuery("span");
        farthest = findFarthestNode(node);
        console.log(farthest);
    });
</script>

Upvotes: -1

Related Questions