jQuery首个包含所有子元素的父元素

12

如何选择一组元素的第一个父元素,该组元素包含所有这些元素?

例如:

<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>
我想要这样的东西:
$('.item-info').commonParent();
并且它将返回相当于以下内容:
[$('ul')]

有没有使用jQuery选择器的简单方法来做到这一点?还是我必须编写一个插件?


4
您是否正在寻找所有元素的最近公共祖先? - Jamie Wong
父类可以包含其他类吗?还是你只想要只包含相同类的父类?$(".item-info").parents("ul:first")将会给你所有包含这些子元素的(ul)元素,并自动过滤重复项,所以在这种情况下,你只会得到一个ul。 - Andir
是的,我正在寻找一个最近公共祖先的通用版本。 - rossipedia
出于好奇,你为什么需要这样做?我真的想不到任何有用的应用。 - Jamie Wong
总之,我的问题是动态插入一个显示/隐藏所有按钮,但按部分分组,如果我决定使用不同的标签来列出我的项目列表,我不想改变我的Javascript代码。 - rossipedia
11个回答

9

如果您正在寻找最近公共祖先(可以在这个示例中查看效果):

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 $([]);
}

示例

<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>

这部分还没有经过严格的测试,如有任何错误请指出。

编辑:原本返回的是parent而不是$(parent)。

编辑:在IE8中无法正常工作。


这似乎可以起作用,不过我在想为什么要使用所有的切片(slicing)呢?是因为.parents()按相反的顺序返回父元素,最后是html吗? - rossipedia
切片操作需要将所有节点带到DOM树的相同高度。如果最靠近根节点(html)的元素距离根节点有3个节点,则最低公共祖先不能超过距离根节点3个节点。此外,对任何比那更远的东西进行相等性检查是毫无意义的,因为距离根节点3个节点的元素不可能等于距离根节点4个节点的元素。切片将父节点列表降至距离根节点的最小距离。 - Jamie Wong
最初的 $(this) 不需要,因为在 jQuery 函数中,this 已经是一个 jQuery 对象了。此外,自 jQuery 1.4(或那个时候)起,一个空的 jQuery 对象只需要 $() 而不是 $([]) - ErikE
如果您对一些比较信息和导致函数失败的边缘情况感兴趣,请查看我的答案。 - ErikE
IE8在使用for(var i in parents)时会迭代数组方法。因此,请将其替换为for (var i=0; i<parents.length; i++),以仅迭代数组项。 - Pherrymason

2
我假设的重点是.item-info元素可能分散在整个页面中。如果是这样,请尝试以下内容: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);

这里有一个函数版本: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');

如果他只使用一个选择器,这是一个不错的简单解决方案。但需要复制/先前了解选择器。 - Jamie Wong
@Jamie - 请解释你的意思。你必须在某个时候知道选择器。它可以在一个变量中共享,没有理由不这样做。或者我没有理解你的意思? - user113716
@Jamie - 这是一个函数版本:http://jsfiddle.net/EJWjf/2/ 只需传入选择器即可。虽然你对于多个选择器的说法是正确的。但那需要更多的关注。不过应该很容易实现。 - user113716
@Jamie - 添加了一个插件版本,仅用于概念验证。 ;o) http://jsfiddle.net/EJWjf/5/ - user113716
我想在我的比较函数中包含您的功能(请参见我的答案),但它不适用于通用 jQuery 插件。 - ErikE
显示剩余4条评论

1
这是我的版本。它是页面上所有jQuery插件类型答案中表现最佳的一个。它是唯一一个可以处理空的jQuery对象$()而不出错的插件。和Jamie Wong的答案一样,它也是唯一一个可以处理单个jQuery对象而不出错的插件。
我创建了一个jsfiddle展示所有版本的输出。您可以点击预设按钮轻松查看不同的测试用例,或者输入自己的选择器并单击“查找”以查看结果。
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);
};

在一个元素是另一个元素的祖先的情况下,到目前为止给出的所有答案都返回祖先元素的父元素。上面的链接都包含了我的函数的一个版本,如果例如页面上有一个div,$('body, div').commonAncestor()将返回body而不是html
注意:此函数假定jQuery集合中的所有项都在当前文档中。要将来自两个不同文档的元素放入同一个jQuery对象中需要特殊而奇怪的努力--在这种情况下,我的函数将失败(页面上的所有其他函数也将失败)。可以通过从更改for条件pos > 0pos >= 0开始进行纠正,使while循环处理-1的情况,并可能将return compare.eq(pos)更改为return $(compare[pos])(因为eq()函数使用负索引从末尾向后索引而不是返回空)。

1
这个比其他所有的都快得多(包括ErikE的),尤其是对于大量元素的情况:

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;
};


1

你忘了吗?...这只是Javascript...

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

我知道这只是JavaScript。然而,我遇到的问题并不像那么简单。我的jQuery集合可能包含一个单独的列表项、一个输入框、一个span和一个链接,我想获取包含它们所有的第一个父对象,无论它们在文档中的位置如何。 - rossipedia

1

8年后,当我再次面临同样的问题时,我最终来到了这里。这些答案都不完全符合我的要求,所以我最终选择了这个。它可能不是绝对最快的,但它很简单明了。

$.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);
    });
  });
};

获取最接近的:
var $closestCommonParent = $elements.commonParents().first();

0

受其他答案的启发,我创建了一个版本,我发现它符合我的要求。 http://jsfiddle.net/qDRv5/(请注意,它会将找到的父级写入控制台)

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;
}

我首先寻找距离根节点最近的元素,因为我确定这个元素不会有一个更远离根节点的共同祖先。 然后,我一次从父级向后退,从该元素开始检查其父级元素是否具有所有其他元素作为子元素。当我找到一个父级元素具有所有元素作为子元素时,我的任务就完成了。

Ricki,这个函数在某些情况下会失败。查看此jsfiddle链接以获取一些测试用例(单击Presets)。请查看我的回答获取更多详细信息。 - ErikE

0

我知道这是一个旧的线程,但我需要这个确切的功能,并且需要它在美丽的jQuery链中工作;) 所以这就是我想出来的:

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;
}

这里还有一个 jsfiddle 链接


Trey,这个函数在某些情况下会失败。请参见此Fiddle获取一些测试用例(单击 Presets)。有关更多详细信息,请参见我的答案。 - ErikE

0
你是否在寻找以下任何一项?
$("element").parents() // return all parents until reaches 'html'

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

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

最接近的元素是closest("ul")或parents("ul"),因为没有"parent"元素。也就是说,除非OP实际上正在寻找最接近的公共元素,否则这是另一个问题。 - Andir
2
我认为他正在寻找最近的共同祖先,如果是这样,那么这个方法不适用。 - You
是的,当我看到这个问题时,我也在考虑$('.item-info').parent()或$('.item-info').closest('ul')。 - a7drew

0

我相信这就是你要找的内容:

jQuery

前四行代码包含了实际抓取UL的代码,其余部分只是为了显示匹配的UL的内容。

<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>

上述jQuery代码将返回第一次和第二次命中的UL(第三个是无用的)。 在http://jsfiddle.net/ksTtF/上进行检查。

另一个例子(没有警报):http://jsfiddle.net/vQxGC/


这基本上就像帕特里克的答案,只不过更长、更难理解,并且有更多的内容重复——也就是说,如果你想要重用它,它就没用了。 - Jamie Wong
@Jamie Wong - 实际执行操作的代码只有五行。当然,你可以通过将 $(this).children("li") 放入变量中来减少一个 jQuery 元素的获取。 - Gert Grenander
好的,我的长度问题无效了,但其他问题仍然存在。 - Jamie Wong
改为使用过滤器。更加高效。 - Gert Grenander
我没有在测试用例中包含你的函数,因为它不适合作为jQuery插件。 - ErikE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接