如何在 Dart 中从外部查询 shadow DOM 中的元素?

问题描述 投票:0回答:6

如何在 shadow DOM 中选择节点?考虑以下示例:

“无阴影”DOM 的结构

<app-element>
  #shadow-root
    <h2></h2>
    <content>
      #outside shadow
      <h2></h2>
    </content>
    <ui-button>
      #shadow-root
        <h2></h2>
  </ui-button>
</app-element>

index.html

<body>
<app-element>
  <!-- OK: querySelect('app-element').querySelect('h2') -->
  <!-- OK: querySelect('app-element h2') -->
  <!-- There is no problem to select it -->
  <h2>app-element > content > h2</h2>
</app-element>
</body>

templates.html

<polymer-element name="ui-button" noscript>
  <template>
    <!-- FAIL: querySelect('app-element::shadow ui-button::shadow h2') -->
    <h2>app-element > ui-button > h2</h2>
  </template>
</polymer-element>

<polymer-element name="app-element" noscript>
  <template>
    <!-- FAIL: querySelect('app-element::shadow').querySelect('h2') -->
    <!-- FAIL: querySelect('app-element::shadow h2') -->
    <!-- FAIL: querySelect('app-element').shadowRoot.querySelect('h2') -->
    <h2>app-element > h2</h2>
    <content></content>
    <ui-button></ui-button>
  </template>
</polymer-element>

在像“OK: querySelect()”这样的评论中,我展示了我试图从任何阴影 DOM 外部运行的选择器。

我已经阅读了以下文章:http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/?redirect_from_locale=ru 并根据文章中所说的事实,查询像:JS 中的

document.querySelector('app-element::shadow h2');
应该按预期工作。但是在 Dart 中它不起作用。

我做错了什么?

css-selectors dart polymer dart-polymer shadow-dom
6个回答
49
投票

伪选择器

::shadow
和组合器
/deep/
在 Firefox 上不起作用。

使用

.shadowRoot

var shadowroot = app-element.shadowRoot;
shadowroot.querySelector('h2');

14
投票

Update2(来自评论)

如果您使用自定义 main,请确保在尝试与 Polymer 元素交互之前正确初始化 Polymer(有关更多详细信息,请参阅如何在 Polymer 应用程序中实现 main 函数)。

我通常建议避免自定义 main 并创建一个

app-element
(或您喜欢的任何名称)并将您的初始化代码放入
attached
(确保调用
super.attached();
)或
ready()
(不需要超级电话)。

原创

在这种情况下,它似乎不在影子 DOM 中,而是在孩子中。

这应该有效:

querySelector('h2');

只有当它在您的元素中时它才会在影子 DOM 中

<template>...</template>
当您将它包装在自定义元素的标签中时它不会出现。

<polymer-element name="some-element">
  <template>
    <!-- this becomes the shadow DOM -->
    <content>
     <!-- 
       what gets captureD by the content element becomes a child or some-element
       -->
     </content>
  </template>
</polymer-element>
<body>
  <some-element>
    <!-- these elements here are captured by the 
         content tag and become children of some-element -->
    <div>some text</div>
  </some-element>
</body>

更新

如果你想搜索

在当前元素的影子 DOM 中

shadowRoot.querySelect('h2');

在影子 DOM 内的元素的影子 DOM 内

shadowRoot.querySelector('* /deep/ h2');
shadowRoot.querySelector('ui-button::shadow h2');

来自当前元素之外

import 'dart:html' as dom;
...
dom.querySelector('* /deep/ h2');
// or (only in the shadow DOM of <app-element>)
dom.querySelector('app-element::shadow h2');
dom.querySelector('app-element::shadow ui-button::shadow h2');
// or (arbitrary depth)
dom.querySelector('app-element /deep/ h2');

6
投票

对于想要易于使用的解决方案的人

function $$$(selector, rootNode=document.body) {
    const arr = []
    
    const traverser = node => {
        // 1. decline all nodes that are not elements
        if(node.nodeType !== Node.ELEMENT_NODE) {
            return
        }
        
        // 2. add the node to the array, if it matches the selector
        if(node.matches(selector)) {
            arr.push(node)
        }
        
        // 3. loop through the children
        const children = node.children
        if (children.length) {
            for(const child of children) {
                traverser(child)
            }
        }
        
        // 4. check for shadow DOM, and loop through it's children
        const shadowRoot = node.shadowRoot
        if (shadowRoot) {
            const shadowChildren = shadowRoot.children
            for(const shadowChild of shadowChildren) {
                traverser(shadowChild)
            }
        }
    }
    
    traverser(rootNode)
    
    return arr
}

像这样使用它:

var nodes = $$$('#some .selector')

// use from a custom rootNode
var buttonsWithinFirstNode = $$$('button', nodes[0])

它会遍历根节点内的所有元素,所以速度不会很快,但很容易使用。


0
投票

使用 reduce 方法的纯香草辅助函数

function queryShadow([firstShadowSelector, ...restOfTheShadowSelectors], itemSelector) {
    const reduceFunction = (currShadow, nextShadowSelector) => currShadow.shadowRoot.querySelector(nextShadowSelector);    
    const firstShadow = document.querySelector(firstShadowSelector);
    const lastShadow = restOfTheShadowSelectors.reduce(reduceFunction,firstShadow);
    return lastShadow && lastShadow.querySelector(itemSelector);
}

并像这样使用它

const shadowSelectorsArr = ['vt-virustotal-app','file-view', '#report', 'vt-ui-file-card', 'vt-ui-generic-card'];
const foundDomElem = queryShadow(shadowSelectorsArr, '.file-id');
console.log(foundDomElem && foundDomElem.innerText);

0
投票

这是一个老问题,但令我惊讶的是没有通用的解决方案(不仅仅是在 Dart 中)。让我们为所有情况解决它!

// query elements even deeply within shadow doms. e.g.:
// ts-app::shadow paper-textarea::shadow paper-input-container
function querySelectorDeep(selector, root = document) {
  let currentRoot = root;
  let partials = selector.split('::shadow');
  let elems = currentRoot.querySelectorAll(partials[0]);
  for (let i = 1; i < partials.length; i++) {
    let partial = partials[i];
    let elemsInside = [];
    for (let j = 0; j < elems.length; j++) {
      let shadow = elems[j].shadowRoot;
      if (shadow) {
        const matchesInShadow = shadow.querySelectorAll(partial);
        elemsInside = elemsInside.concat([... matchesInShadow]);
      }
    }
    elems = elemsInside;
  }
  return elems;
}

尝试示例:

  1. 转到Google 的文字转语音演示
  2. 打开控制台并输入:
let sel = `ts-app::shadow 
           paper-textarea::shadow 
           paper-input-container 
           iron-autogrow-textarea::shadow
           textarea`;
textarea = querySelectorDeep(sel)?.[0];
textarea.value = 'If you see this, selector worked.';

注意:这也可以递归解决,但我只是用迭代来解决。


0
投票

上述解决方案对我不起作用,但这确实有效:

function $$(selector, element = document.body) {
  if (element.shadowRoot) {
    element = element.shadowRoot;
    const found = element.querySelector(selector);
    if (found) return found;
  }
  for (let i = 0; i < element.children.length; i++) {
    const child = element.children[i];
    const found = $$(selector, child);
    if (found) {
      return found;
    }
  }
  return null;
}

$$('.my-text-box')

一样使用
© www.soinside.com 2019 - 2024. All rights reserved.