开发者

jQuery first parent containing all children

How would I got about selecting the first parent of a set of elements that contains ALL of those elements?

For example:

<body>
 <dl>
  <dt>Items:</dt>
  <dd>
   <ul>
    <li>Item 1<div class="item-info">...<开发者_运维技巧/div></li>
    <li>Item 2<div class="item-info">...</div></li>
    <li>Item 3<div class="item-info">...</div></li>
   </ul>
  </dd>
 </dl>
</body>

I want something like this:

$('.item-info').commonParent();

and it would return the equivalent of:

[$('ul')]

Is there an easy way to do this with jQuery selectors? Or am I gonna have to write a plugin?


If you are actually looking for lowest common ancestor (See this working in a fiddle):

jQuery.fn.commonAncestor = function() {
  var parents = [];
  var minlen = Infinity;

  $(this).each(function() {
    var curparents = $(this).parents();
    parents.push(curparents);
    minlen = Math.min(minlen, curparents.length);
  });

  for (var i in parents) {
    parents[i] = parents[i].slice(parents[i].length - minlen);
  }

  // Iterate until equality is found
  for (var i = 0; i < parents[0].length; i++) {
    var equal = true;
    for (var j in parents) {
      if (parents[j][i] != parents[0][i]) {
        equal = false;
        break;
      }
    }
    if (equal) return $(parents[0][i]);
  }
  return $([]);
}

Example

<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="jquery.commonancestor.js"></script>


$(function() {
  console.log($(".item-info").commonAncestor());
});
</script>
<body>
 <dl>
  <dt>Items:</dt>
  <dd>
   <ul>
    <li>Item 1<b><div class="item-info">...</div></b></li>
    <li>Item 2<u><div class="item-info">...</div></u></li>
    <li>Item 3<i><div class="item-info">...</div></i></li>
   </ul>
  </dd>
 </dl>
</body>

This has not been tested rigorously, so please point out any errors you see.

EDIT Was returning parent instead of $(parent)

EDIT Wasn't working in IE8


I assume the point is that the .item-info elements are (potentially) spread out throughout the page.

If that's right, try this: http://jsfiddle.net/EJWjf/1/

var $items = $('.item-info');   // Cache all the items in question
var total = $items.length;      // Cache the total number
var parent;                     // Will store the match

$items.first()           // Grab the first item (an arbitrary starting point)
    .parents()           // Get all of its parent elements
    .each(function() {
            // Iterate over each parent, finding the .info-item elements
            //    it contains, and see if the quantity matches the total
        if($(this).find('.item-info').length == total) {
            parent = this;  // If so, we found the closest common ancestor so
            return false;   //     store it and break out of the loop
        }
    });

alert(parent.tagName);

Here's a function version: http://jsfiddle.net/EJWjf/2/

function findCommon(selector) {
    var $items = $(selector);   
    var total = $items.length;      
    var parent;                   

    $items.first()
        .parents()
        .each(function() {
            if($(this).find(selector).length == total) {
                parent = this;
                return false;
            }
        });

    return $(parent);
}

var result = findCommon('.item-info');


Have you forgotten? ... It's just Javascript ...

$('.item-info').get(0).parentElement


Here's my version. It performs best of all the jQuery-plugin type answers on the page. It is the only one that handles an empty jQuery object $() without error. It and Jamie Wong's answer are the only ones that handle a single-item jQuery object without error.

I built a jsfiddle showcasing all the versions' output. You can click the Preset buttons to easily see different test cases, or enter your own selector and click Find to see the result.

jQuery.fn.reverse = function() {
   return Array.prototype.reverse.call(this);
};

jQuery.fn.commonAncestor = function() {
   var i, l, current,
      compare = this.eq(0).parents().reverse(),
      pos = compare.length - 1;
   for (i = 1, l = this.length; i < l && pos > 0; i += 1) {
      current = this.eq(i).parents().reverse();
      pos = Math.min(pos, current.length - 1);
      while (compare[pos] !== current[pos]) {
         pos -= 1;
      }
   }
   return compare.eq(pos);
};

In the case where one of the elements is the ancestor of another, all the answers given so far return the parent of the ancestor element. Both the links above include a version of my function where if for example there is a div on the page, $('body, div').commonAncestor() will return body instead of html

Note: this function assumes that all items in the jQuery set are in the current document. It would take special, and strange, effort to get elements from two different documents into the same jQuery object--and in this case my function will fail (and so will all the others on the page). This could be remedied starting with changing the for condition pos > 0 to pos >= 0, making the while loop handle hitting -1, and possibly changing return compare.eq(pos) to return $(compare[pos]) (since the eq() function uses negative indices to index backwards from the end instead of returning empty).


This one's significantly faster than all the others (including ErikE's) especially for lots of elements:

jQuery.fn.commonAncestor = function() {
  if (!this.length)
    return null;

  var parent = this[0].parentNode;
  for (var i = this.length - 1; i >= 1; --i) {
    var elt = this[i];
    if (elt.contains(parent))
      parent = elt.parentNode;
    else
      while (!parent.contains(elt))
        parent = parent.parentNode;
  }
  return parent;
};


8 years later, and faced with the same problem I ended up here. None of these answers were exactly what I was looking for, so I ended up with this. It might not be the absolute fastest, but it's small and straightforward:

$.fn.commonParents = function () {
  var $children = this.toArray();
  // reduce the parents to those where
  return this.parents().filter(function (i, parent) {
    // every child
    return $children.every(function (child) {
      // is contained by a that parent
      return $.contains(parent, child);
    });
  });
};

To get the closest:

var $closestCommonParent = $elements.commonParents().first();


Are you looking for any of these?

$("element").parents() // return all parents until reaches 'html'

$("element").parent() // return the first parent

$("element").closest("parent") // return the closest selected parent


I believe this is what you're looking for:

jQuery

The first four lines contain the code that does the actual grabbing of the ULs. The rest is just to display the content of the matched ULs.

<script type="text/javascript">
  $(document).ready(function() {
    // The code that grabs the matching ULs
    var ULsWithAll=$("ul").has(".item-info").filter( function(index) { // Grab all ULs that has at least one .item-info
      var ChildrenOfUL=$(this).children("li");  // Grab the children

      return ChildrenOfUL.length===ChildrenOfUL.has(".item-info").length;  // Are all of them .item-info
    });

    $(ULsWithAll).each(function(){ // Output the ULs that were found
      alert($(this).html());
    })
  });
</script>

HTML

<dl>
  <dt>Items 1:</dt>
  <dd>
    <ul>
      <li>Item 1-1<div class="item-info">...</div></li>
      <li>Item 1-2<div class="item-info">...</div></li>
      <li>Item 1-3<div class="item-info">...</div></li>
    </ul>
  </dd>
  <dt>Items 2:</dt>
  <dd>
    <ul>
      <li>Item 2-1<div class="item-info">...</div></li>
      <li>Item 2-2<div class="item-info">...</div></li>
      <li>Item 2-3<div class="item-info">...</div></li>
    </ul>
  </dd>
  <dt>Items 3:</dt>
  <dd>
    <ul>
      <li>Item 3-1<div class="item-info">...</div></li>
      <li>Item 3-2<div class="item-info">...</div></li>
      <li>Item 3-3<div>...</div></li>
    </ul>
  </dd>
</dl>

The above jQuery code will return the ULs for the first and second hit (the third one is a dud). Check it at http://jsfiddle.net/ksTtF/

Another example (without the alert): http://jsfiddle.net/vQxGC/


so... I know this is an old thread, but I needed this exact functionality, and needed it to work in the beautiful chain that is jQuery;) so here's what I came up with:

jQuery.fn.firstParent=function (f){
    //Set the variables for use;
    s=$(this);
    var i,j;
    var p=[];
    var pv=true;

    //make sure there is something to itterate over
    if(s.length>0){

        //build a multidimentional array of parents
        s.each(function(){p.push($(this).parents());});

        //itterate through the parents of the first element starting at the closest
        for(i=0;i<p[0].length;i++){

            //itterate through all elements after the first
            for(j=1;j<s.length;j++){

                //look for the current parent of the first element in each array
                pv=($.inArray(p[0][i],p[j]) != -1);

                //if we are looking at the last element and it matchs, return that parent
                if(pv==true && j==s.length-1) return typeof(f)=='string'?$(p[0][i]).find(f):$(p[0][i]);
            }
        }
    }
    return false;
}

there is also a jsfiddle here


Inspired from a mix of the other answers on the quiestion, I've created a version that I found fitted my requirements. http://jsfiddle.net/qDRv5/ (Note that it writes the found parent to the console)

jQuery.fn.firstParent=function (f){
    //Set the variables for use;
    s=$(this);
    var i,j;
    var p=[];
    var pv=true;

    //make sure there is something to itterate over
    if(s.length>0){

        //build a multidimentional array of parents
        s.each(function(){p.push($(this).parents());});

        //itterate through the parents of the first element starting at the closest
        for(i=0;i<p[0].length;i++){

            //itterate through all elements after the first
            for(j=1;j<s.length;j++){

                //look for the current parent of the first element in each array
                pv=($.inArray(p[0][i],p[j]) != -1);

                //if we are looking at the last element and it matchs, return that parent
                if(pv==true && j==s.length-1) return typeof(f)=='string'?$(p[0][i]).find(f):$(p[0][i]);
            }
        }
    }
    return false;
}

I start by looking for the element the least steps way from root, because I know for sure that this element will not have a common ancestor further away from root. I then step back from parent at a time, from that elements an checks if that parent elements has all the other elements as children. When I find a parent elements that has all the elements as children, my mission i complete.


I would do something like this: filter out selector parents, which contain every element in the selector's set.

jQuery.fn.commonParent = function() {
  var parent = this.parents();
  this.each(function() {
    parent = parent.has(this);
  });
  return parent.first();
};
0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜