How to define a more customElements for same Class - javascript

I jsut tried to define new Custom Elements.
// the class
class MyEl extends HTMLElement {
constructor() {
// mandatory super call to upgrade the current context
super();
// any other Class/DOM related procedure
this.setAttribute('custom-attribute', 'value');
this.innerText = 'new element';
}
}
// its mandatory definition
customElements.define('my-el', MyEl);
// the JS way to create a custom element
const newMe = new MyEl();
document.body.appendChild(newMe);
Defining a new element of the same class will cause an error:
"Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': this constructor has already been used with this registry"
But i want to define new elements for same class.
Could you suggest a way to display multiple items from the same class (MyEl).
Thank in advance!

Just try with an anonymous constructor and extend your MyEl:
class MyEl extends HTMLElement {
// your definitions
}
customElements.define("my-better-element", class extends MyEl { })
customElements.define("my-second-element", class extends MyEl { })

customElements.define('my-el', MyEl) must be called only once to tell the browser you have a new <my-el> tag (aka to define a custom element).
Then you can create multiple elements just by using new:
const newMe1 = new MyEl();
const newMe2 = new MyEl();
Or createElement():
const newMe1 = document.createElement( 'my-el' )
const newMe2 = document.createElement( 'my-el' )
Or, in the html code:
<my-el></my-el>
<my-el></my-el>

Related

Embedding a custom HTMLElement into another custom HTMLElement

I'm wondering what best practices are for creating an object that extends HTMLElement and embedding it within another object that extends HTMLElement. I'm interested in an implementation that creates these objects and adds them to the DOM programmatically.
Use case: I can create e.g. a Menu component and re-use it throughout my app.
The below code works but I think it can be optimized.
Edit: It was pointed out (and I agree) that there isn’t really a question here.
Here’s a question: I only want to call customElements.define() once per element. What is the scope of this call? I tried to call it in the TodoMenu constructor and then use that element within Todo to no avail.
class Todo extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const wrapper = document.createElement('div');
customElements.define("todo-menu", TodoMenu);
const todoMenu = document.createElement("todo-menu");
wrapper.appendChild(todoMenu);
shadow.appendChild(wrapper);
}
}
class TodoMenu extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const wrapper = document.createElement('div');
// Some UI stuff
shadow.appendChild(wrapper);
}
}
customElements.define("todo", Todo);
const todoItem = document.createElement(Todo);
document.getElementById('test-todo2').appendChild(todoItem);

Disadvantage of building a custom element within the constructor?

I understand the advantages of templates in terms of performance when designing custom elements, but for structures which are only used in one element I am struggling to understand the disadvantage of building the html within the constructor() of the element class definition itself.
Put another way, what is the disadvantage of doing something like this:
const myTemplate = document.createElement("template");
myTemplate.innerHTML = `<p>my text</p>`;
customElements.define( 'my-elem', class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(myTemplate.content.cloneNode(true));
}
})
over this:
customElements.define( 'my-elem', class extends HTMLElement {
constructor() {
super();
let myP = document.createElement("p");
let myText = document.createTextNode("my text");
myP.appendChild(myText);
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(myP);
}
})
... when the latter option both (a) preserves the advantages of using createElement, and (b) helps prevent potential encapsulation issues introduced by defining the template outside the scope of the element definition?
I'm also aware that I could build the template with createElement instead of innerHTML in the example above, but that has the potential to introduce even more variables defined outside the scope of the element definition.
It is a subtle difference and boils down to requirements and/or team/personal preference.
I would do:
customElements.define( 'my-elem', class extends HTMLElement {
constructor() {
let myP = document.createElement("p");
let myText = document.createTextNode("my text");
myP.append(myText);
// MDN docs are wrong, you can put code *before* super
super() // create and return 'this'
// create and return this.shadowRoot
.attachShadow({ mode: "open" })
.append(myP);
}
})
customElements.define( 'my-elem2', class extends HTMLElement {
constructor() {
super()
.attachShadow({ mode: "open" })
.innerHTML = "Hello! Component";
}
})
<my-elem></my-elem>
<my-elem2></my-elem2>

Custom Element Illegal Constructor

This code gives an "Illegal Constructor" Error, can anybody tell me why?
class MyCustomElement extends HTMLElement {
constructor(){
super();
// Other things
}
}
const myFunc = () => {
const instance = new MyCustomElement();
console.log(instance);
}
myFunc();
After hours of searching I found that you MUST register the custom element BEFORE you can create an instance of it. I don't think this is in the spec, but this is the case for all browsers, also the error message sucks. I wish chrome would have just said "You must register a custom element before instantiating it" rather than "Illegal Constructor", which tells us almost nothing about what actually went wrong.
class MyCustomElement extends HTMLElement {
constructor(){
super();
// Other things
}
}
const myFunc = () => {
const instance = new MyCustomElement();
console.log(instance);
}
// Add this and it will start working
window.customElements.define('my-custom-element', MyCustomElement);
myFunc();
Note that you can create a custom element before it is defined by using document.createElement().
This element will be created as an unknown element and then only upgrade to a custom element when defined.
class MyCustomElement extends HTMLElement {
constructor(){
super()
console.log( 'created' )
}
}
const myFunc = () => {
const instance = document.createElement( 'my-custom-element' )
console.log( instance )
document.body.appendChild( instance )
}
myFunc()
customElements.define( 'my-custom-element', MyCustomElement )

How to access custom element from within a linked javascript file?

If I had a script like this
<template id="x-foo-from-template">
<script src="/js/main.js"></script>
</template>
<script>
customElements.define('my-header', class extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
const t = document.currentScript.ownerDocument.querySelector('#x-foo-from-template');
const instance = t.content.cloneNode(true);
shadowRoot.appendChild(instance);
// set up title
var title = this.getAttribute("title");
var div = document.createElement("div");
div.innerText = title;
shadowRoot.appendChild(div);
}
});
</script>
From within main.js how can I access the custom element which is equivalent to this in the constructor()?
Thanks
You cannot do that as explained in this thread: The currentScript property will return null.
Instead you should load the script outside of the <template>, and invoke a function defined in the script from your custom element callbacks connectedCallback() or constructor().
You must access currentScript outside of the component. I use this:
var frag = (document.currentScript||document._currentScript).ownerDocument.querySelector('template').content;
I use (document.currentScript||document._currentScript) to handle polyfills.
Then in your constructor you would use:
const instance = frag.cloneNode(true);
At this point frag is a document-fragment and can contain any number of children. All of these will be replicated by the call to cloneNode so that each instance of your Web Component has its own copy of DOM.

Node Getting error using new with ES6 class stored in variable

I have the following master ES6 class that holds references to other ES6 classes that are dynamically called later in my code like so:
class Root {
constructor() {
this.refs = {
ref1: require('ref1')
ref2: require('ref2')
}
}
}
module.exports = exports = new Root;
ref1 and ref2 export new instance of the class Ref1 and class Ref2.
Later down the line, I attempt to make an instance of one of these references classes by doing:
const Root = require('root');
class other {
someFunc() {
var ref = new Root.refs['ref1'](value);
}
}
This always ends with the following error:
TypeError: this.actions[action.action] is not a constructor
How can I properly make a reference to these ref classes?
You are missing () after new Root.
If the export is set with default you might also want to try with require('ref1').default
You are not exporting the class Root you are exporting an instance of class Root by using the new keyword.
Change it to say module.exports = Root;

Categories