Select a div 如果它包含所有指定的元素

Select a div if it contains all specified elements

我正在尝试 select 包含 tagOnetagTwo span 元素的 .item div。

我的div结构如下:

<div id="items">

    <div id="block1" class="item">
        <span class="tagOne tag">One</span>
        <span class="tagTwo tag">Two</span>
    </div>

    <div id="block2" class="item">
        <span class="tagOne tag">Java</span>
    </div>

</div>

使用以下 jQuery 我能够分别找到标签(及其父 div 的标签)。

var blocks =  $('#items .item');

blocks.filter('.item').find('[class*="tagOne"]').parent();
blocks.filter('.item').find('[class*="tagTwo"]').parent();

但是,一旦我尝试将它们组合起来以将范围缩小到同时包含它们div,我就没有得到任何结果,而且我似乎无法找出原因!

blocks.filter('.item').find('[class*="tagOne"][class*="tagTwo"]');

我的理解是,逗号语法将创建一个 OR 表达式,而不创建一个 AND 表达式。我在 AND 表达式之后,因为我只想 return 包含所有条件的 div,或者什么都没有。


注意:我这样做是因为我正在创建一个基于标签的切换过滤器,并且标准(即 tagOne, tagTwo)是标签 select 的串联由用户编辑(未显示),因此最好尝试在一次操作中完成。


编辑: 将重复的 id 移动到 class 名称而不是使其有效并相应地调整 JavaScript 代码。

试试这个

 blocks.filter('.item').find('[id="tagOne"],[id="tagTwo"]');

首先,ID 应该是唯一的。现在,标记包含两个 ID 为 tagOne 的元素,这是无效标记。

您可以使用 class 代替 ID。

  1. Select 两个元素中的任何一个(.tagOne.tagTwo 在这种情况下)
  2. 使用 siblings() 到 select 具有另一个 class
  3. 的兄弟元素
  4. 使用 closest() 到 select 最近的祖先匹配 selector.

上面的步骤 #1、#2 和 #3 将 select 只有那些同时具有 .tagOne.tagTwo 作为后代的 .item 元素。

代码:

$('.tagOne') // Select one of the element
  .siblings('.tagTwo') // Get second element if it is sibling
  .closest('.item') // Get the closest ancestor

$('.tagOne') // Select one of the element
  .siblings('.tagTwo') // Get second element if it is sibling
  .closest('.item') // Get the closest ancestor
  .addClass('selected'); // For Demo purpose
.item {
  color: red;
}
div.selected {
  color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="items">

  <div id="block1" class="item">
    <span class="tagOne tag">One</span>
    <span class="tagTwo tag">Two</span>
  </div>

  <div id="block2" class="item">
    <span class="tagOne tag">Java</span>
  </div>

  <div id="block3" class="item">
    <span class="tagTwo tag">I Love JavaScript</span>
  </div>

</div>


您也可以使用 filter 如下。

  1. 使用 filter()
  2. 遍历所有 .item 元素
  3. 使用context selector检查当前.item是否有后代.tagOne.tagTwo
  4. 在 jQuery 对象上使用 length 属性 来获取由 selector.
  5. 编辑的元素数 select

代码:

$('.item').filter(function() {
  return $('.tagOne', this).length && $('.tagTwo', this).length;
})

// Fiddle: https://jsfiddle.net/tusharj/8tuu1wxs/1/

// Iterate over all elements having item class
$('.item').filter(function() {
  return $('.tagOne', this).length && $('.tagTwo', this).length;
}).addClass('selected');
.item {
  color: red;
}
.selected {
  color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="items">

  <div id="block1" class="item">
    <span class="tagOne tag">One</span>
    <span class="tagTwo tag">Two</span>
  </div>

  <div id="block2" class="item">
    <span class="tagOne tag">Java</span>
  </div>

  <div id="block3" class="item">
    <span class="tagTwo tag">I Love JavaScript</span>
  </div>

</div>


如果元素的sequence/order是固定的,CSSgeneral sibling selector ~ or adjacent sibling selector +就可以了

$('.tag1 ~ .tag2').closest('.item')

$('.tag1 + .tag2').closest('.item')

// Fiddle: https://jsfiddle.net/tusharj/amdoLfou/1/

$('.tag1 ~ .tag2') // + can also be used instead of ~
  .closest('.item') // Get closest ancestor
  .css('color', 'blue'); // For Demo purpose
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="items">

  <div id="block1" class="item">
    <span class="tag1 tag">One</span>
    <span class="tag2 tag">Two</span>
  </div>

  <div id="block2" class="item">
    <span class="tag1 tag">Java</span>
  </div>

</div>

尝试使用 has jquery has selector 它搜索所选节点是否有某些子节点: https://jsfiddle.net/96gbf7xg/

虽然您已经接受了一个答案,但我觉得这个问题值得一个简单的 JavaScript,而不是简单的 jQuery 解决方案。因此,考虑到这一点,我想提供以下方法(它确实使用了一些 ECMAScript 6 features,因此确实需要一个相当现代的浏览器):

// using an Immediately-Invoked Function Expression syntax,
// so that the enclosed function will be executed when
// encountered, rather than requiring the user to call it
// explicitly (this would need to run in a DOMReady callback
// or once the DOM has been constructed, however):
(function hasAll(opts) {

  // setting the default settings for the function:
  var settings = {
    // a CSS Selector string to identify the ancestor
    // element that you wish to identify:
    'ancestorSelector': 'div',

    // an array of CSS Selectors to identify the
    // descendants by which the ancestor should
    // be found:
    'descendantSelectors': []
  }

  // looking at the named (not inherited) properties
  // of the opts Object supplied by the user:
  for (var property in opts) {

    // if the opts Object has a given property
    // name we set the corresponding property
    // of the settings Object to be equal to that
    // property-value:
    if (opts.hasOwnProperty(property)) {
      settings[property] = opts[property];
    }
  }

  // finding all the elements represented by the first selector
  // of the user-supplied selectors contained within an element
  // matching the ancestor selector:
  var firstElements = document.querySelectorAll(
      settings.ancestorSelector + ' ' + settings.descendantSelectors[0]
    ),

  // converting the NodeList returned by document.querySelectorAll()
  // into an Array, using Array.from:
    arrayOfFirsts = Array.from(firstElements),

  // here we iterate over that Array, using Array.prototype.filter():
    hasSiblings = arrayOfFirsts.filter(function(n) {

      // we look for the parentNode of the current node (n):
      var p = n.parentNode;

      // we use Array.prototype.every() to ensure that every
      // selector in the descendantSelectors Array returns
      // a Node (document.querySelector() returns only the
      // first node matching the given selector, or null if
      // there is no element matching that selector).
      // if Array.prototype.every() returns true (all elements
      // of the Array match the supplied test) then the current
      // node (n) is retained in the array returned by
      // Array.prototype.filter():
      return settings.descendantSelectors.every(function(selector) {

        // Array.prototype.every() returns a Boolean,
        // true : if all elements of the Array match
        //        the supplied test/assessment,
        // false: if *any* of the elements of the Array
        //        fail to match.
        // this is the test that we're matching against:
        return p.querySelector(selector) !== null;
      });
    });

  // here we iterate over the hasSiblings Array, and use
  // Array.prototype.map() to form a new Array, using
  // an Arrow function to take the current node (n)
  // and find, and return, the closest element to that
  // node which matches the supplied settings.ancestorSelector:
  var found = hasSiblings.map(n => n.closest(settings.ancestorSelector));

  // returning that array to the calling context:
  return found;

})({
  // this is the 'opts' Object that we're passing to the
  // IIFE-contained function:
  'ancestorSelector': '.item',
  'descendantSelectors': ['.tagOne', '[data-demo]']

// using Array.prototype.forEach() to iterate over the
// returned elements, to add the class 'hasAll' to the
// the classList (the list of class-names) of the given
// node (n):
}).forEach(n => n.classList.add('hasAll'));

(function hasAll(opts) {
  var settings = {
    'ancestorSelector': 'div',
    'descendantSelectors': []
  }

  for (var property in opts) {
    if (opts.hasOwnProperty(property)) {
      settings[property] = opts[property];
    }
  }

  var firstElements = document.querySelectorAll(
      settings.ancestorSelector + ' ' + settings.descendantSelectors[0]
    ),
    arrayOfFirsts = Array.from(firstElements),
    hasSiblings = arrayOfFirsts.filter(function(n) {
      var p = n.parentNode;
      return settings.descendantSelectors.every(function(selector) {
        return p.querySelector(selector) !== null;
      });
    });
  var found = Array.from( hasSiblings.map(n => n.closest(settings.ancestorSelector)) );
  return found;

})({
  'ancestorSelector': '.item',
  'descendantSelectors': ['.tagOne ~ .tagTwo']
}).forEach(n => n.classList.add('hasAll'));
div {
  width: 50%;
  margin: 0.5em auto;
  border: 2px solid #000;
  border-radius: 1em;
  padding: 0.5em;
  box-sizing: border-box;
}

.hasAll {
  border-color: #f90;
}

.hasAll span {
  color: #f90;
  font-weight: bold;
}
<div id="items">

  <div id="block1" class="item">
    <span class="tag tagOne">One</span>
    <span class="tag tagTwo">Two</span>
  </div>

  <div id="block2" class="item">
    <span class="tag tagOne">Java</span>
  </div>

  <div id="block3" class="item">
    <span class="tag tagOne" data-demo="false">tag-one</span>
    <span class="tag tagTwo">tag-two</span>
    <span class="tag" data-demo="true">tag-three</span>
  </div>

</div>

JS Fiddle demo.

请注意,使用上述函数,如果祖先元素的任何后代或其后代的兄弟元素与多个选择器匹配,则该祖先元素将被匹配。

参考文献: