【问题标题】:QuerySelector not finding template on HTML importQuerySelector 在 HTML 导入时找不到模板
【发布时间】:2016-06-06 19:10:03
【问题描述】:

我目前正在尝试学习如何使用最新的稳定版 Chrome 52 使用 Web 组件(不使用 Polymer)(我也尝试过使用 Chrome 52 上的 webcomponents.js polyfill)。但是,当我这样做时,我似乎收到了 querySelector 错误。当我尝试通过document.querySelector('#template') 在控制台中获取(诚然命名不佳的模板 ID)时,它为空并且无法找到它。

我正在使用this guide,尽管使用了一些 ES6 语法。 (我也试过直接复制粘贴,还是一样的问题)

我也尝试在 shadowDOM 中搜索,但它也不存在。

view.html

<template id="template">
  <style>
  </style>
  <div class="container">
    <h1>WZView</h1>
  </div>
</template>
<script>
"use strict";

class WZView extends HTMLElement {

  createdCallback () {
    var root = this.createShadowRoot();
    var template = document.querySelector('#template');
    root.appendChild(document.importNode(template.content, true));
  }

}

document.registerElement('wz-view', WZView);
</script>

index.html

<!DOCTYPE html>
<html>
<head>
<!--<script src="/bower_components/webcomponentsjs/webcomponents.js"></script>-->
<link rel="import" href="view.html">
</head>
<body>
  <wz-view></wz-view>
</body>
</html>

控制台:

view.html:16 Uncaught TypeError: Cannot read property 'content' of null
> document.querySelector('#template')
null

【问题讨论】:

  • 也许
  • 感谢艾莉莎。学到了一些新东西 :) 如果你使用 document.querySelectorAll('#template') 你的代码能工作吗?
  • 和以前一样的错误
  • 如果我在普通网页中创建模板元素,可以使用querySelector来获取元素。您的模板元素在文档对象中不存在,因为它在导入中。试试document.currentScript.ownerDocument.querySelector('#template')

标签: javascript html google-chrome web-component


【解决方案1】:

在导入的 HTML 内的&lt;script&gt;s 中,不要使用document.querySelector(...)

用途:

// while inside the imported HTML, `currentDocument` should be used instead of `document`
var currentDocument = document.currentScript.ownerDocument;
...
// notice the usage of `currentDocument`
var templateInsideImportedHtml = currentDocument.querySelector('#template');

示例(修正问题中的示例)

var currentDocument = document.currentScript.ownerDocument; // <-- added this line

class WZView extends HTMLElement {
    createdCallback () {
        var root = this.createShadowRoot();
        var template = currentDocument.querySelector('#template'); // <-- changed this line
        root.appendChild(document.importNode(template.content, true));
    }
}

兼容性:

仅限IE 11 won't support it。大多数browsers (including Edge) implement it,以及IE 10 and below there is a polyfill

【讨论】:

  • 这似乎不适用于从导入中导入(即文档 -> 导入 -> 导入)。我在第二次导入中看到的是 currentScript 与第一次导入相同。这是在 Chromium 59 上
  • @tjb1982 你在其他浏览器中测试过吗?很难说这是错误还是设计行为。
  • 这对我不起作用。获取:未捕获的类型错误:无法读取 null 的属性“ownerDocument”
【解决方案2】:

更新:我原来的答案是垃圾。我的问题是我试图从类内部中的方法获取currentScript.ownerDocument,而不是在当前文档中主动运行的脚本中(例如,在我定义的IIFE中) 类,因此,脚本将在模板旁边运行)。可以从另一个脚本调用一个方法,此时的“currentScript”(即,可能是一个完全不同的文档,特别是如果您像我一样从其他导入导入)。

所以这很糟糕:

class Foo extends HTMLElement {
    constructor() {
        const template = document.currentScript.ownerDocument.querySelector("template");
        // do something with `template`
    }

}

这样更好:

(() => {

const _template = document.currentScript.ownerDocument.querySelector("template");

class Foo extends HTMLElement {
    constructor() {
        // do something with `_template`
    }
}

})();

希望能帮助像我这样愚蠢的人。


原答案:

我在尝试从某个深度的导入层次结构中访问模板时也遇到了问题。在这种情况下,currentScript 的建议对我不起作用:在 Chrome/Chromium 中,currentScript 总是指第一次导入,但从不指任何更深层次的导入(正如我在对@acdcjunior 答案的评论中提到的那样),而在 Firefox(通过 polyfill)中,currentScriptnull

所以我最终做的是类似于@Caranicas 的回答。我创建了一个实用函数来查找导入的文件,在 IIFE 中的类之外调用它一次,然后将其作为类的属性,如下所示:

index.html:

    var _resolveImport = function(file) {
        return (function recur(doc) {
            const imports = doc.querySelectorAll(`link[rel="import"]`);
            return Array.prototype.reduce.call(imports, function(p, c) {
                return p || (
                    ~c.href.indexOf(file)
                        ? c.import
                        : recur(c.import)
                );
            }, null);
        })(document);
    }

src/app.html:

<link rel="import" href="src/component.html">
<template>...</template>
<script>
((global) => {

    const _import = global._resolveImport("src/app.html");

    class App extends HTMLElement {

        static get import() {
            return _import;
        }

        connectedCallback() {
            this.render();
            this.$component = new global.Component();
        }

        render() {
            let template = this.constructor.import.querySelector("template");
            //...
        }

        //...
    }
})(this);
</script>

src/component.html:

<template>...</template>
<script>
((global) => {

    const _import = _resolveImport("src/component.html");

    class Component extends HTMLElement {

        static get import() {
            return _import;
        }

        render() {
             let template = this.constructor.import.querySelector("template");
             //...
        }

        //...
    }
    global.Component = Component;
})(this);
</script>

_resolveImport 很昂贵,因此最好不要为每次导入多次调用此函数,而仅用于实际需要它的导入。

【讨论】:

    【解决方案3】:

    我遇到了同样的问题,我一直在搞砸,直到我找到了一些有用的东西。

    如果您使用document.querySelector('link[rel=import]'),您可以获得当前导入。添加.import 将为您提供导入的文档,然后您可以使用它来查询您的选择器

    var template = document.querySelector('link[rel=import]').import.querySelector('#template');
    

    编辑:

    这很脆弱,为了进行 2 种不同的导入,它有点困难。

    我把它分解成它自己的功能。首先,您需要使用querySelectorAll 获取页面上的所有导入。然后使用 map 您可以将实际模板值插入到数组中,然后使用快速过滤器删除空值,您可以获取第一个也是唯一一个元素,这将是正确的模板。

    getImportedTemplate() {
    
        const imports = document.querySelectorAll('link[rel=import]');
    
        return Array.from(imports).map( (link) => {
            return link.import.querySelector('#myTemplate');
        }).filter( (val) => {
            return val !== null;
        })[0];
    }
    
    createdCallback() {
        var imported = this.getImportedTemplate();
        var content = imported.content;
        this.appendChild(document.importNode(content, true));
    }
    

    注意:

    我可以使用 filter 作为唯一的数组操作来代替 map,但这只会给我一个包含链接的数组,所以我必须要么有另一个变量才能在该过滤器中捕获它操作,或再次运行querySelector

    【讨论】:

      【解决方案4】:

      使用 polyfill HTML 导入 (npm @webcomponents/html-imports ^1.2),组件 &lt;template&gt; 最终被放置在主文档标题中的某个位置。对于原生 HTML 导入,它最终被放置在一个单独的文档中。在这两种情况下找到模板的可靠方法是:

      [my-component.html]
      <template id="my-component">
        ...
      
      <script>
        ...
        const script = document.currentScript;
        const template = script.ownerDocument.querySelector('template#my-component');
        ...
        customElements.define('my-component', ...);
      

      为每个模板分配一个唯一的 id,例如组件名称,以便在 polyfill 的情况下选择正确的模板(guide 在这方面可能有点过于简单)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-03-31
        • 2018-02-23
        • 2012-02-13
        • 1970-01-01
        • 2016-10-06
        • 1970-01-01
        • 1970-01-01
        • 2016-07-05
        相关资源
        最近更新 更多