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

How to query elements within shadow DOM from outside in Dart?

如何 select 影子 DOM 中的节点?考虑以下示例:

"unshadowed"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()" 之类的评论中,我展示了 select 或我尝试从外部 运行 任何阴影 DOM。

我已经阅读了以下文章:http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/?redirect_from_locale=ru 并且根据文章中所说的事实,查询如:document.querySelector('app-element::shadow h2'); 在 JS 中应该按预期工作。但是在 Dart 中它不起作用。

我做错了什么?

Update2(来自评论)

如果您使用自定义 main,请确保在尝试与 Polymer 元素交互之前正确初始化 Polymer(有关详细信息,请参阅 how to implement a main function in polymer apps)。

我通常建议避免自定义 main 并创建一个 app-element(或您喜欢的任何名称)并将您的初始化代码放入 attached(确保调用 super.attached();)或在 ready() 中(不需要 super 调用)。

原创

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

这应该有效:

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

伪选择器 ::shadow 和组合器 /deep/ 在 firefox 上不起作用。

使用.shadowRoot

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

使用 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);

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

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

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