Vanilla JS Selector,用于具有具有特定属性的子元素的元素

时间:2017-09-15 03:53:17

标签: javascript css-selectors

我正在尝试构建一个标签系统。如果我有以下HTML:

<div class="wizard-tabs">
  <ul>
    <li class="active"><a href="#" data-formstep="step1">Step 1</a></li>
    <li><a href="#" data-formstep="step2">Step 2</a></li>
    <li><a href="#" data-formstep="step3">Step 3</a></li>
  </ul>
</div>

如何获取具有data-formstep属性等于某个值的锚的列表项?我目前正在使用这个:

document.querySelector('.wizard-tabs > ul > li:has(a[data-formstep="'+newStep+'"])').classList.add("active")

但我收到一个错误说:

Failed to execute 'querySelector' on 'Document': '.wizard-tabs > ul > li:has(a[data-formstep="step1"])' is not a valid selector.

我做错了什么?

3 个答案:

答案 0 :(得分:0)

:has是一项实验性技术,目前在任何浏览器中都不支持。

请参阅 https://developer.mozilla.org/en-US/docs/Web/CSS/:has (感谢@ jaromanda-x)

修复: 正如Jaromanda所说,只需引用<a>然后获取它.parentElement

var li = document.querySelector('.wizard-tabs > ul > li >a[data-formstep="'+newStep+'"]').parentElement;

答案 1 :(得分:0)

截至2017年,

:has()是一个非标准的jQuery选择器。它在规范中的状态是未知的,没有实现存在,你应该假设它将在它准备好之前实现。

当前选择器语法中没有等效于:has()(这是将其添加到规范中的全部原因);您最好的选择是选择子元素本身并使用.parentElement来获取其父元素。

答案 2 :(得分:0)

这是has()选择器的一个非常粗略的polyfill,可能需要更多的测试,但这仍然有用:

&#13;
&#13;
(function() {

  // Can probably be improved
  // Will search for sequence ":has(xxx)" even if xxx contains other sets of func(yyy)
  function searchForHas(selector) {
    if (!selector) {return null;}
    const closed = [], valids = [], open = [], lastFour = ['', '', '', ''];

    selector.split('').forEach(char => {
      if (char == ')') {
        closed.push(open.pop()); // close the last open one
      }
      open.forEach(o => o.s += char); // add this char to all open ones
      if (char == '(') {
        open.push({
          s: ''
        }); // open a new one as an object so we can cross reference
        if (lastFour.join('') === ':has') {
          valids.push(open[open.length - 1]); // this one is interesting
        }
      }
      // update our ':has' sequence identifier
      lastFour.shift();
      lastFour.push(char);
    });
    if (!valids.length) {
      return null;
    } else {
      let str = selector.split(':has(' + valids[0].s + ')');
      return [str.shift(), valids[0].s, str.join('')]; // [pre, current_has, post]
    }
  }

  function has(that, sel_parts) {
    const matches = [...that.querySelectorAll(sel_parts[0])] // pre selector
      .filter(el => el.querySelector(sel_parts[1])); // which has one current_has
    return sel_parts[2] ? Array.prototype.concat.apply([], // if there is a post
      matches.map(el => [...el.querySelectorAll(':scope' + sel_parts[2])] // flatten all the matches
        .filter(el => !!el)
      )
    ) : matches;
  }
  // Overwrite the protos
  const dQS = Document.prototype.querySelector;
  Document.prototype.querySelector = function querySelector(selector) {
    const has_parts = searchForHas(selector);
    if (has_parts) {
      return has(this, has_parts)[0] || null;
    } else {
      return dQS.apply(this, [selector]);
    }
  };
  const dQSA = Document.prototype.querySelectorAll;
  Document.prototype.querySelectorAll = function(selector) {
    const has_parts = searchForHas(selector);
    if (has_parts) {
      let arr = has(this, has_parts);
      return arr && arr.length ? arr : null;
    } else {
      return dQSA.apply(this, [selector]);
    }
  };
  const eQS = Element.prototype.querySelector;
  Element.prototype.querySelector = function(selector) {
    const has_parts = searchForHas(selector);
    if (has_parts) {
      return has(this, has_parts)[0] || null;
    } else {
      return eQS.apply(this, [selector]);
    }
  };
  var eQSA = Element.prototype.querySelectorAll;
  Element.prototype.querySelectorAll = function(selector) {
    const has_parts = searchForHas(selector);
    if (has_parts) {
      let arr = has(this, has_parts);
      return arr && arr.length ? arr : null;
    } else {
      return eQSA.apply(this, [selector]);
    }
  };
})();

console.log(document.querySelector('.wizard-tabs > ul > li:has(a[data-formstep="step1"])'));
// and even
console.log(document.querySelector('li:has(a[data-formstep="step2"])>i'));
&#13;
<div class="wizard-tabs">
  <ul>
    <li class="active"><a href="#" data-formstep="step1">Step 1</a><i data-s="1"></i></li>
    <li><a href="#" data-formstep="step2">Step 2</a><i data-s="2"></i></li>
    <li><a href="#" data-formstep="step3">Step 3</a><i data-s="3"></i></li>
  </ul>
</div>
&#13;
&#13;
&#13;