Basically, I want to querySelect a <template> from javascript and I keep getting null.
JavaScript file:
class MyImportWebcomponent extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: "open" });
const template = document.querySelector("my-import-webcomponent-template");
const instance = template.content.cloneNode(true);
shadowRoot.appendChild(instance);
}
}
customElements.define("my-import-webcomponent", MyImportWebcomponent);
Template object from my vanilla webcomponent
<template id="my-import-webcomponent-template">
<div id="mydiv" name="mydiv">
<p>Trying html importing another javascript file</p>
</div>
</template>
<script src="/to-try/my-import-webcomponent.js"></script>
index.html
<my-import-webcomponent></my-import-webcomponent>
<link rel="import" href="./to-try/my-import-webcomponent.html" />
The main issue is that document.querySelector("my-import-webcomponent-template") returns undefinied.
IN case it adds something usefull, if I try keep both javascript and html in same file and instead of querySelector I create straight the element, I successfully can do it.
All in a single file
const templateString = `<div id="mydiv" name="mydiv"><p>Trying html plus javascript in same file</p></div>`;
const template = document.createElement("template");
template.innerHTML = templateString;
export class MyCompleteWebcomponent extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.appendChild(template.content.cloneNode(true));
}
}
customElements.define("my-complete-webcomponent", MyCompleteWebcomponent);
My question would be exact the same as queryselector if it wasn't for two reasons: (1) they seemed to rely on Polifys which it isn't my case and (2) the answer accepted is based on document.currentScript.ownerDocument which demands an old library as far as I know.
*** edited after suggestion to use instead of
<!-- <link rel="import" href="./to-try/my-import-webcomponent.html" /> -->
<script type="module" src="./to-try/my-import-webcomponent.js"></script>
<my-import-webcomponent></my-import-webcomponent>
Nothing changed at all
*** edited after recommended to add "#". No changes at all
If you want to load HTML files with <link rel="import"> then you'll need to load the HTML Imports library before.
<script src="https://raw.githubusercontent.com/webcomponents/html-imports/master/html-imports.min.js"></script>
<link rel="import" href="./to-try/my-import-webcomponent.html">
...
Related
When I try to append template to the shadow DOM, it only shows as a "#documentFragment", and never renders or copies the actual elements structured within the template.
I spent hours trying to figure it out. The solution I found was to use:
template.firstElementChild.cloneNode(true);
instead of:
template.content.cloneNode(true);
then, and only then, everything works as expected.
My question is, am I doing something wrong?
const template = document.createElement('template');
const form = document.createElement('form');
const gateway = document.createElement('fieldset');
const legend = document.createElement('legend');
gateway.appendChild(legend);
const username = document.createElement('input');
username.setAttribute('type', 'email');
username.setAttribute('name', 'username');
username.setAttribute('placeholder', 'email#address.com');
username.setAttribute('id', 'username');
gateway.appendChild(username);
const button = document.createElement('button');
button.setAttribute('type', 'button');
button.innerHTML = 'Next';
gateway.appendChild(button);
form.appendChild(gateway);
template.appendChild(form);
class UserAccount extends HTMLElement {
constructor() {
super();
const shadowDOM = this.attachShadow({
mode: 'open'
});
const clone = template.firstElementChild.cloneNode(true);
// This does not work
// const clone = template.content.cloneNode(true);
shadowDOM.appendChild(clone);
shadowDOM.querySelector('legend').innerHTML = this.getAttribute('api');
}
}
window.customElements.define('user-account', UserAccount);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<!-- <link rel="stylesheet" href="./css/main.css"> -->
<script src="./js/user-account.js" defer></script>
<title>Title</title>
</head>
<body>
<user-account api="/accounts"></user-account>
</body>
</html>
TEMPLATES are only interesting if you need to make multiple copies or want to work in plain HTML + CSS as much as possible.
Many Web Components show the usage:
const template = document.createElement("template");
template.innerHTML = "Hello World"
and then do:
constructor() {
super();
this._shadowRoot = this.attachShadow({ mode: "open" });
this._shadowRoot.appendChild(template.content.cloneNode(true));
}
Which, because the template is only used as a single "parent" container, you can write as:
constructor() {
super().attachShadow({ mode: "open" }).innerHTML = "Hello World";
}
Note: super() returns this, and attachShadow() sets and returns this.shadowRoot ... for free
Two types of TEMPLATES
You can create a <TEMPLATE> in DOM, or you can create a template in Memory
Templates in Memory
9 out of 10 Memory-templates can be done with other HTMLElements as container,
as is the case with your code, where FORM can be the main container. No need for a template container.
If you do build a template in memory, learn the value of append() over
(the often misused) appendChild()
In Memory templates are great for making (many) alterations (with code)
Templates in DOM
No need for trying to stuff HTML and CSS in JavaScript strings, you have a DOM in the HTML document!
Use the <TEMPLATE> HTML Element.
Add shadowDOM <slot> to the mix and you will spent less time debugging JavaScript and more time writing semantic HTML.
DOM Templates are great for easy HTML and CSS editting (in your IDE with syntax highlighting) of more static HTML/CSS structures
Here are both types of TEMPLATES with your code, which one is easier for a developer?
const form = document.createElement('form');
const gateway = document.createElement('fieldset');
const legend = document.createElement('legend');
const username = document.createElement('input');
username.setAttribute('type', 'email');
username.setAttribute('name', 'username');
username.setAttribute('placeholder', 'email#address.com');
username.setAttribute('id', 'username');
const button = document.createElement('button');
button.setAttribute('type', 'button');
button.innerHTML = 'Next';
gateway.append(legend,username,button);
form.appendChild(gateway);
class Form extends HTMLElement {
constructor(element) {
super().attachShadow({mode:'open'}).append(element);
}
connectedCallback() {
this.shadowRoot.querySelector('legend').innerHTML = this.getAttribute('api');
}
}
window.customElements.define('form-one', class extends Form {
constructor() {
super(form)
}
});
window.customElements.define('form-two', class extends Form {
constructor() {
super(document.getElementById("FormTwo").content);
}
});
<template id="FormTwo">
<form>
<fieldset>
<legend></legend>
<input type="email" name="username" placeholder="email#address.com" id="username">
<button type="button">Next</button>
</fieldset>
</form>
</template>
<form-one api="/accounts"></form-one>
<form-two api="/accounts"></form-two>
Note:
In the above code the <TEMPLATE>.content is moved to shadowDOM.
To re-use (clone) the <TEMPLATE> the code must be:
super(document.getElementById("FormTwo").content.cloneNode(true));
Why your template.content failed
Your code failed because with
const template = document.createElement('template');
const form = document.createElement("form");
template.appendChild(form);
template has no content
TEMPLATE isn't a regular HTMLElement, you have to append to .content
const template = document.createElement('template');
const form = document.createElement("form");
template.content.appendChild(form);
will work
Most Web Component examples show:
const template = document.createElement("template");
template.innerHTML = "Hello World"
innerHTML sets .content under the hood
Which explains why instead of:
template.content.appendChild(form);
you can write:
template.innerHTML = form.outerHTML;
A 'template' element is a special element that doesn't actually render right away(reference). This is why appending the template produces nothing.
template.firstElementChild.cloneNode means "get the child of the template (i.e. the form) and clone it", which is the same as just appending the form, which works (below).
const template = document.createElement('template');
const form = document.createElement('form');
const gateway = document.createElement('fieldset');
const legend = document.createElement('legend');
gateway.appendChild(legend);
const username = document.createElement('input');
username.setAttribute('type', 'email');
username.setAttribute('name', 'username');
username.setAttribute('placeholder', 'email#address.com');
username.setAttribute('id', 'username');
gateway.appendChild(username);
const button = document.createElement('button');
button.setAttribute('type', 'button');
button.innerHTML = 'Next';
gateway.appendChild(button);
form.appendChild(gateway);
template.appendChild(form);
class UserAccount extends HTMLElement {
constructor() {
super();
const shadowDOM = this.attachShadow({
mode: 'open'
});
shadowDOM.appendChild(form);
shadowDOM.querySelector('legend').innerHTML = this.getAttribute('api');
}
}
window.customElements.define('user-account', UserAccount);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<!-- <link rel="stylesheet" href="./css/main.css"> -->
<script src="./js/user-account.js" defer></script>
<title>Title</title>
</head>
<body>
<user-account api="/accounts"></user-account>
</body>
</html>
My code works fine when I add my module straight into the html code, but it won't load it when I try to first import the module to a different javascript file.
I've tried exporting everything, from my module.
HTML:
<html>
<head>
<title>
Hello world
</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1>Tradingpost, done by no css gang.</h1>
<div id="sidenav">Here be the links to other pages</div>
<br>
<footer id="footer">
Done whenever. Copyright is mine.
</footer>
<script src="js/app.js"></script>
</body>
</html>
app.js:
import * as sidebar from "./visualModules/sidebarmodule"
function cumulator() {
sidebar.createSidebar()
}
sidebarmodule.js:
function sidebarAdder(pages) {
const sidebar = document.getElementById("sidenav")
const list = document.createElement("UL")
for(index = 0; index < pages.length; index++) {
const ul = document.createElement("LI")
const a = document.createElement("A")
a.setAttribute("href", "/" + pages[index])
a.innerHTML = "" + pages[index]
ul.appendChild(a)
list.appendChild(ul)
}
sidebar.appendChild(list)
}
export function createSidebar() {
var pages = [
"home",
"tradingpost"
]
sidebarAdder(pages)
}
It should add elements to the div. But it wont do it unless I straight up use the sidebarmodule.js. I want to do it through the app.js
EDIT
Found the problem!
Didn't initialize index in the for loop.
EDIT2
And the type="module" which needed to be added
When you load your app.js in your html file, try to add:
<script type="module" src="js/app.js"></script>
That should work when you want to use ESModules. But please update us regardless :)
Update:
Ok after creating a project myself using your HTML and JS, I found a few errors.
First: When using ESModules, you can't use any functions in the JS through your HTML, you will have to inject everything from the app.js.
index.html:
<body>
<div id="sidenav">
Here be the links to other pages
</div>
<br>
<footer id="footer">
Done whenever. Copyright is mine.
</footer>
<script type="module" src="js/app.js"></script>
app.js:
import { createSidebar } from './visualModules/sidebarmodule.js';
cumulator();
function cumulator() {
createSidebar()
}
Notice two things: at the end of the import, since we are not using a compiler, the modules do not recognize files without their extension. So I had to add .js to sidebarmodule. Secondly, I had to invoke cumulator function within the app.js file (like I said earlier, you cannot use any module functions outside their scope. There are no global variables with ESModules).
sidebarmodule.js:
function sidebarAdder(pages) {
const sidebar = document.getElementById("sidenav")
const list = document.createElement("UL")
for(var index = 0; index < pages.length; index++) {
const ul = document.createElement("LI")
const a = document.createElement("A")
a.setAttribute("href", "/" + pages[index])
a.innerHTML = "" + pages[index]
ul.appendChild(a)
list.appendChild(ul)
}
sidebar.appendChild(list)
}
export function createSidebar() {
var pages = [
"home",
"tradingpost"
]
sidebarAdder(pages)
}
You did not declare index inside your for loop, so I just added a var.
Hope this helps.
import is asynchronous in Javascript (in a browser, not Node.js) so you're calling createSidebar() before the module is loaded. You can use import to return a promise so you can execute code once it is completed.
Remove the embedded Javascript from your html, but leave the link to app.js. Then change app.js to this...
import("./visualModules/sidebarmodule")
.then((sidebar) => {
sidebar.createSidebar();
});
I want to use inheritance between classes that are in separate .js (but same folder), but i have this errors
Uncaught SyntaxError: Unexpected identifier
Uncaught ReferenceError: TableWithFunction is not defined
Here goes my code:
// Table.js
export default class Table {
constructor(name, h_cols) {
table_id = name + 'table';
hidden_cols = h_cols;
table = setTable();
}
setTable() {...};
setColumnDefs() {...};
{
// TableWithFunction.js
import Table from 'Table';
class TableWithFunction extends Table {
constructor (name, h_cols) {
super(name, h_cols);
selected_id = name + 'Selected';
}
//More methods ..
}
//test.blade.php (im using laravel framework)
<script src="{{ url('js/TableWithFunction.js') }}" ></script>
<script>
roleTable = new TableWithFunction('role',[2, 4]);
userTable = new TableWithFunction('user',[3]);
</script>
I also tried to do import Table from 'Table.js'; instead of import Table from 'Table';
And i tried changing Table.js using class Table { ... } export default Table instead of export default class Table {...} and it didn't fix it, of course.
To use ES6 modules in a capable browser the importing script needs to be included with type="module".
<script src="{{ url('js/TableWithFunction.js') }}" type="module"></script>
This prevents browsers not ready for modules to load code which would only throw an error.
The opposite is also available:
<script src="only-for-ie.js" nomodule></script>
This script will be ignored by browsers capable of handling ES6 modules.
I'm, working on server-side rendering in a React app and I have the following JavaScript code. My <FullPage action={this.handler}/> component has an action property that is set to a function. When that function gets called, I want to set a variable (or even some state) in this component here. I can't figure out how to declare handler
export default (req) => {
var myVar =
<Router location={req.path} context={{}}>
<FullPage action={this.handler}/>
</Router>
const content = renderToString(
myVar
);
return `
<html>
<head>
<link rel="stylesheet" href="App.css">
</head>
<body>
<div id="root">${content}</div>
<script src="bundleclient.js"></script>
</body>
</html>
`;
};
In my index.html I import an external HTML file with an Template, Shadow DOM etc. A custom web Component.
// index.html
...
<script src="//cdnjs.cloudflare.com/ajax/libs/polymer/0.3.4/platform.js"></script>
<link rel="import" href="/html-components/userlogin-header.html" >
<head>
<body>
<userlogin-header username="Test User"userimage="http://domain.com/img.jpg"></userlogin-header>
...
And the other file userlogin-header.html:
// userlogin-header.html
<template id="userlogin-header">
<div class="imgbox">
<img src="" class="userimage">
</div>
<div class="userinfo">
<div class="name"><span class="username"></div>
</div>
</template>
<script>
var doc = this.document.currentScript.ownerDocument,
UserLoginProto = Object.create( HTMLElement.prototype );
UserLoginProto.createdCallback = function() {
var template = doc.querySelector( "#userlogin-header" ),
box = template.content.cloneNode( true );
this.shadow = this.createShadowRoot();
this.shadow.appendChild( box );
var username = this.shadow.querySelector( '.userinfo .username' );
username.innerHTML = ( this.getAttribute( 'username' ) || 'Unbekannt' );
var imageurl = this.shadow.querySelector( 'img.userimage' );
imageurl.src = 'https://secure.gravatar.com/avatar/' + this.getAttribute( 'userimage' ) + '1?s=40&d=http://s3-01.webmart.de/web/support_user.png';
};
var Xuserlogin = doc.registerElement( 'userlogin-header', { 'prototype' : UserLoginProto } );
</script>
The problem is that there is the following error on call index.html
Uncaught TypeError: Cannot read property 'content' of null
If I enable HTML Import in my Chrome everything works correctly. But then I disable this and use platform.js instead there is this error.
Is there any solution for this problem? I do not want to use the whole polymer framework.
This is a symptom of this caveat of the polyfill.
In a native HTML Imports, document.currentScript.ownerDocument
references the import document itself. In the polyfill use
document._currentScript.ownerDocument (note the underscore).
Once you change that, you also need to use document.registerElement instead of doc.registerElement. You want to register the element such that it's visible to the importing document, not the imported one.
var Xuserlogin = document.registerElement(...);
Here's a working plunk.