Queryselector Not Finding Template On Html Import
Solution 1:
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 lineclassWZViewextendsHTMLElement {
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.
Solution 2:
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:
classFooextendsHTMLElement {
constructor() {
const template = document.currentScript.ownerDocument.querySelector("template");
// do something with `template`
}
}
and this is better:
(() => {
const _template = document.currentScript.ownerDocument.querySelector("template");
classFooextendsHTMLElement {
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 (functionrecur(doc) {
const imports = doc.querySelectorAll(`link[rel="import"]`);
returnArray.prototype.reduce.call(imports, function(p, c) {
return p || (
~c.href.indexOf(file)
? c.import
: recur(c.import)
);
}, null);
})(document);
}
src/app.html:
<linkrel="import"href="src/component.html"><template>...</template><script>
((global) => {
const _import = global._resolveImport("src/app.html");
classAppextendsHTMLElement {
staticgetimport() {
return _import;
}
connectedCallback() {
this.render();
this.$component = newglobal.Component();
}
render() {
let template = this.constructor.import.querySelector("template");
//...
}
//...
}
})(this);
</script>
src/component.html:
<template>...</template><script>
((global) => {
const _import = _resolveImport("src/component.html");
classComponentextendsHTMLElement {
staticgetimport() {
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.
Solution 3:
I ran into the same issue, I kept messing around until I got something that worked.
If you use document.querySelector('link[rel=import]')
you can get the current import. Adding .import
to that will give you the imported document, which you can then use to query your selector
var template = document.querySelector('link[rel=import]').import.querySelector('#template');
EDIT:
This was brittle, in order to do 2 different imports it was a bit more difficult.
I broke it out into its own function. First you need to get all the imports on the page with querySelectorAll
. Then using map you can insert the actual template value into an array, and then a quick filter to remove the null values and you can grab the first and only element and that will be the correct template.
getImportedTemplate() {
const imports = document.querySelectorAll('link[rel=import]');
returnArray.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));
}
Note:
I could have used filter as the only array operation, in lieu of map, but that would only only give me an array with the link in it, so I would have have to either have another variable to catch it in that filter operation, or run querySelector
again.
Solution 4:
With polyfilled HTML imports (npm @webcomponents/html-imports ^1.2), the component <template>
ends being placed somewhere in the main document header. With native HTML imports, it ends being placed in a separate document. A reliable way to find the template in both cases is this:
[my-component.html]
<templateid="my-component">
...
<script>
...
const script = document.currentScript;
const template = script.ownerDocument.querySelector('template#my-component');
...
customElements.define('my-component', ...);
Assign each template a unique id, for example the component name, to select the correct template in the polyfilled case (the guide might be a little too simple in that regard)
Post a Comment for "Queryselector Not Finding Template On Html Import"