【问题标题】:Vanilla JS Selector for an element that has a child element with a certain attributeVanilla JS 选择器,用于具有具有特定属性的子元素的元素
【发布时间】:2017-09-15 15:52:18
【问题描述】:

我正在尝试构建一个标签系统。如果我有以下 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.

我做错了什么?

【问题讨论】:

  • 使选择器的目标是a,然后你想要的li是父级-例如document.querySelector('.wizard-tabs &gt; ul &gt; li &gt;a[data-formstep="'+newStep+'"]').parentElement
  • 这似乎是一个不必要的额外步骤。必须有一种方法可以根据锚点的属性而不是锚点来选择 LI。
  • 但是:has在任何浏览器中都不受支持,所以这实际上是一个必要的额外步骤——争论浏览器支持或不支持什么是没有意义的
  • 哦,好的。我没有意识到这一点。我在 jquery(我正在尝试使用 vanilla)中看到 :has() 并认为它最初是一个 JS 选择器。
  • MDN documentation for :has - 我发现 MDN 文档非常好,因为即使 firefox 不支持该功能(但)MDN 也会记录它,因为它是一个新兴标准

标签: javascript css-selectors


【解决方案1】:

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

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

解决方法: 正如 Jaromanda 所说,只需引用 &lt;a&gt;,然后得到它的 .parentElement

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

【讨论】:

  • ("parent:has(tag)")('parent&gt;tag').parentElement 不同 has() 确实针对所有后代,如果是 has(&gt;tag) 那么是的,它将是相同的。
  • 然而,@Kaiido ("parent:has(tag)")('parent tag') 相同,然后向上遍历 parentElement 链到 parent ... 是的,它更复杂,但在给出的示例中,您的没有意义
【解决方案2】:

:has() 截至 2017 年是非标准的 jQuery 选择器仅限。它在规范中的状态是未知的,不存在任何实现,您不应该假设它在准备好之前会一直实现。

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

【讨论】:

  • 目前是an experimental technology - 所以,“非标准”是夸大其词
  • @Jaromanda X:自 FPWD 以来近 六年,它一直是“实验性的”,没有本机实现。这六年与 jQuery 出现的过去十年没有什么不同。
  • 链接的草稿 css 文档的日期为 Selectors Level 4 Editor’s Draft, 23 August 2017 - 不知道你从哪里得到 6 年 - MDN 页面仅在 Jan 10, 2016, 3:50:50 PM 之后才存在所以......再次......你在做什么关于它是实验性的 6 年?你来自未来吗?
  • @Jaromanda X:对不起,我记错了,:has() was only added to the spec in 2014/2015, replacing the subject indicator from the FPWD(顺便说一下,这意味着第一个公共工作草案)。所以大约三年,而不是六年。我的观点仍然是,我不会屏住呼吸进行实施,因为在这三年中没有取得任何进展(除非您将重命名选择器配置文件视为进展)。
【解决方案3】:

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

(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'));
<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>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-06
    • 1970-01-01
    • 2011-10-13
    • 2010-11-02
    • 2012-11-30
    • 2011-10-31
    • 1970-01-01
    • 2022-01-03
    相关资源
    最近更新 更多