Search code examples
javascriptjqueryhtmljquery-selectorsdom-traversal

Select common parent/ancestor of nested elements with jQuery and CSS Selector


I am looking to select the common parent of multiple nested elements for which I only know the inner text.

For example, in the following code:

<unknown>       
    <unknown class="unknown">
        ....
        <unknown>
            <unknown>Sometext</unknown>
        </unknown>
        <unknown>
            <unknown>Sometext</unknown>
        </unknown>
        <unknown>
            <unknown>Sometext</unknown>
        </unknown>
        ....
    </unknown>
</unknown>

I would like to get the closest element (common parent) that has class unknown in this scenario. I do not know the actual tags or class names. I only know that the nest element contains the "Sometext". I know this can be done through a loop using jQuery/Javascript, but is there a CSS selector that I can use with jQuery to find this? I tried using a combination of closest(), parents(), parentsUntil() but I can't seem to get to this element.

Thank you!


Solution

  • First, you need to ensure you only match the leaf nodes (nodes with no child nodes), so use:

    :not(:has(*))
    

    So to find all the exact matches (just the leaf nodes), use:

    var matches = $(':not(:has(*))').filter(function () {
        return $(this).text() == "Sometext";
    });
    

    or just using a combined filter on all elements (with an added check for 0 children):

    var matches = $('*').filter(function () {
         return !$(this).children().length && $(this).text() == "Sometext";
    });
    

    Note: I have not yet tested which of these two options is fastest.

    Then you need to find the first ancestor (of the first match), that contains all the matches:

    var commonparent = matches.first().parents().filter(function () {
        return $(this).find(matches).length == matches.length;
    }).first();
    

    JSFiddle: http://jsfiddle.net/TrueBlueAussie/v4gr1ykg/

    Based on David Thomas' suggestion, here it is as a pair of jQuery extensions (commonParents() and commonParent()) that may be of future use for people:

    To find all common parents of a jQuery collection use `commonParents()':

    $.fn.commonParents = function (){
        var cachedThis = this;
        return cachedThis.first().parents().filter(function () {
            return $(this).find(cachedThis).length === cachedThis.length;
        });
    };
    

    JSFiddle: (commonParents): http://jsfiddle.net/TrueBlueAussie/v4gr1ykg/3/

    To find the closest common parent of a jQuery collection use commonParent():

    $.fn.commonParent = function (){
        return $(this).commonParents().first();
    };
    

    JSFiddle: (commonParent): http://jsfiddle.net/TrueBlueAussie/v4gr1ykg/2/

    Notes:

    • jQuery optimises the combined use of first() in commonParent with the commonParents filter() and it only calls the code in commonParents until a first match is made, so commonParent does not need to be made more efficient.