QuerySelector not finding template on HTML import
In the <script>
s inside the imported HTML, don't use document.querySelector(...)
.
Use:
// 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');
Example (fixing the example in the question):
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));
}
}
Compatibility:
Only IE 11 won't support it. Most browsers (including Edge) implement it, and for IE 10 and below there is a polyfill.
Update: My original answer is garbage. My problem was that I was trying to obtain the currentScript.ownerDocument
from a method inside the class, instead of in a script actively running in the current document (e.g., in an IIFE where I define the class and, hence, where the script would be running alongside of the template). A method may be called from another script, the "currentScript" at that point (i.e., possibly a different document altogether, especially if you're importing from other imports, like I was).
So this is bad:
class Foo extends HTMLElement {
constructor() {
const template = document.currentScript.ownerDocument.querySelector("template");
// do something with `template`
}
}
and this is better:
(() => {
const _template = document.currentScript.ownerDocument.querySelector("template");
class Foo extends HTMLElement {
constructor() {
// do something with `_template`
}
}
})();
Hopefully that helps someone else who is dumb like me.
Original answer:
I also encountered problems trying to gain access to templates from an import hierarchy of some depth. The currentScript
suggestion didn't work for me in this case: in Chrome/Chromium, the currentScript
always referred to the first import, but never to any of the deeper imports (as I mentioned in a comment to @acdcjunior's answer), and in Firefox (via polyfill), the currentScript
was null
.
So what I ended up doing was something similar to @Caranicas's answer. I created a utility function that finds the imported file, call it once outside of the class in an IIFE, and then made it a property of the class, like this:
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
is expensive, so it's a good idea not to call this more than once for each import, and only for imports that actually need it.