Custom Element Illegal Constructor - javascript

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 )

Related

Function-binding with super keyword in javascript

I would like to call "super" from a bound function.
Here is my use case: I have many child classes from different parents. I'd like to bind the same function to all of them (instead of copy pasting it). That function needs to call the "super" version of that same function.
Example:
class Parent {
func() {
console.log("string1");
}
}
function boundFunc() {
super.func();
console.log(this.string2);
}
class Child extends Parent {
constructor() {
super();
this.string2 = "string2"
this.func = boundFunc.bind(this);
}
}
const child = new Child();
child.func();
I would like to obtain result:
string1
string2
I get this result instead (unsurprisingly, I'd day):
"SyntaxError: 'super' keyword unexpected here".
I've tried to pass the super function as an argument to bind. Like this:
function bindedFunc(thisArg, oriFunc) {
oriFunc();
console.log(this.string2);
}
class Child extends Parent {
constructor() {
super();
this.string2 = "string2"
this.func = bindedFunc.bind(this, super.func);
}
}
Result (oriFunc happens to be undefined):
TypeError: oriFunc is not a function
Any solution? Thank you
Instead of super, you can use Object.getPrototypeOf twice: once to navigate from the instance to its internal prototype (which is Child.prototype), and once to navigate from that to its internal prototype (which is Parent.prototype):
class Parent {
func() {
console.log("string1");
}
}
function boundFunc() {
Object.getPrototypeOf(Object.getPrototypeOf(this)).func();
console.log(this.string2);
}
class Child extends Parent {
constructor() {
super();
this.string2 = "string2"
this.func = boundFunc.bind(this);
}
}
const child = new Child();
child.func();

How to modify getters/setters on prototypes?

For functions on prototypes I used to do something like:
var attachShadow = HTMLElement.prototype.attachShadow
HTMLElement.prototype.attachShadow = function (option) {
var sh = attachShadow.call(this, option)
console.log('ShadowDOM Attached!')
return sh
}
On this example, I modified the attachShadow method inside the HTMLElement prototype to get me notified when a new shadowDOM get attached to an element.
Now I wanna do something similar with ShadowRoot.prototype.adoptedStyleSheets, but this time adoptedStyleSheets is a getter/setter, var adoptedStyleSheets = HTMLElement.prototype.adoptedStyleSheets will cause an error: Uncaught TypeError: Illegal invocation.
I'm not sure what to do, How to modify getters/setters on prototypes?
You don't want to retrieve the value inside adoptedStyleSheets (which obviously throws an error when called from the prototype) but its property descriptor in order to reuse it in your own adoptedStyleSheets:
(function () {
const oldAdoptedStyleSheetsGetter = Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'adoptedStyleSheets');
Object.defineProperty(ShadowRoot.prototype, "adoptedStyleSheets", {
get: function () {
console.log('adoptedStyleSheets was accessed!');
return oldAdoptedStyleSheetsGetter.get.call(this)
},
});
})();
customElements.define('web-component', class extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `Hi I'm a web component`;
console.log('this.shadowRoot.adoptedStyleSheets:', this.shadowRoot.adoptedStyleSheets);
}
});
<web-component></web-component>

Implementing JS decorator to wrap class

I'm trying to wrap class constructor and inject to some logic by using class decorator. Everything worked fine until I have tried to extend wrapped class: Extended class don't have methods in prototype.
function logClass(Class) {
// save a reference to the original constructor
const _class = Class;
// proxy constructor
const proxy = function(...args) {
const obj = new _class(...args);
// ... add logic here
return obj
}
// copy prototype so intanceof operator still works
proxy.prototype = _class.prototype;
// return proxy constructor (will override original)
return proxy;
}
#logClass
class Base {
prop = 5;
test() {
console.log("test")
}
}
class Extended extends Base {
test2() {
console.log("test2")
}
}
var base = new Base()
base.test()
var ext = new Extended()
console.log(ext.prop)
ext.test()
ext.test2() // TypeError: ext.test2 is not a function
Okay so I tried to figure out what is "wrong" with your code, but I was not able to make it work because it didn't typecheck. So, as a last resort, I'm posting a partial answer of my attempt, which works (with some quirks) so I can help other users who are more savvy with TypeScript.
First of all, the quirks: class decorators in TS cannot modify the structure of a type, so if you wanted to, for example, add a method to the decorated class, you would be able to do it but you would have to eat up/suppress unavoidable type errors (TS2339) when calling those methods.
There is a work around for this in this other question: Typescript adding methods with decorator type does not exist, but you would lose this current clean syntax for decorators if you do this.
Now, my solution, taken more or less directly from the documentation:
function logClass<T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
super(args);
// ...add programmatic logic here
// (`super` is the decorated class, of type `T`, here)
}
// ...add properties and methods here
log(message: string) { // EXAMPLE
console.log(`${super.constructor.name} says: ${message}`);
}
}
}
#logClass
class Base {
prop = 5;
test() {
console.log("test");
}
constructor() {}
}
class Extended extends Base {
test2() {
console.log("test2");
}
}
var base = new Base();
base.test();
var ext = new Extended();
console.log(ext.prop);
//base.log("Hello"); // unavoidable type error TS2339
ext.test();
ext.test2();

How to define all custom elements before any constructors are called?

I have two custom elements: One and Two. One has a function. Two has a child of One and tries to call that function in its constructor. It says that the function does not exist if I call customElements.define() on Two before One. However, if I define One before Two, it works just fine.
In my actual project, I do not have control of the order in which they are defined, and by default they are being defined in the wrong order.
I tried calling the function in the connectedCallback(), but this also failed.
When exactly is the constructor called?
Is there any way that I can make sure they are all defined before any constructors are called?
class One extends HTMLElement {
constructor() {
super()
console.log('one constructor')
}
myFunc() {
console.log('it worked!')
}
}
class Two extends HTMLElement {
constructor() {
super()
console.log('two constructor')
this.innerHTML = '<my-one></my-one>'
this.myOne = document.querySelector('my-one')
// this part fails
this.myOne.myFunc()
}
connectedCallback() {
// this also fails
this.myOne.myFunc()
}
}
// this works
// customElements.define("my-one", One)
// customElements.define("my-two", Two)
// this breaks
customElements.define("my-two", Two)
customElements.define("my-one", One)
<my-two></my-two>
It is all about the life-cycle of a Web Component and when a tag is upgraded from an HTMLUnknownElement to your actual Component
A component is defined in two steps.
1) Definition of a class
2) Calling customElements.define
Yes these two can be written together:
customElements.define('dog-food', class extends HTMLElement{});
But the class definition still happens before customElements.define is called.
Elements are only upgraded to a custom element when two things have happened:
1) The Custom Element must be defined by using customElements.define and
2) The Custom Element must either
1) be instantiated using document.createElement or new MyElement
2) be added to the DOM tree
This example has the element placed into the DOM but it is not defined for 1 second.
I display the constructor before it is defined and then, again, after it is defined.
class One extends HTMLElement {
constructor() {
super();
console.log('one constructor');
}
connectedCallback() {
this.innerHTML = "I have upgraded";
}
}
let el = document.querySelector('my-one');
setTimeout(() => {
customElements.define("my-one", One);
console.log('after: ', el.constructor.toString().substr(0,30));
}, 1000);
console.log('before: ', el.constructor);
<my-one>I am just a simple element</my-one>
In your code you use innerHTML to "load" <my-one>. But since <my-two> may not "really" be in the DOM by the time the constructor is called then the innerHTML will not be in the DOM and, thus, <my-one> will not get upgraded yet.
One thing you can do is to wait until the <my-two> component is really placed into the DOM by waiting to change the innerHTML in the connectedCallback function:
class One extends HTMLElement {
constructor() {
super();
console.log('one constructor');
}
myFunc() {
console.log('it worked!');
}
}
class Two extends HTMLElement {
constructor() {
super();
console.log('two constructor');
}
connectedCallback() {
this.innerHTML = '<my-one></my-one>';
setTimeout(() => {
this.myOne = document.querySelector('my-one');
this.myOne.myFunc();
});
}
}
customElements.define("my-two", Two)
customElements.define("my-one", One)
<my-two></my-two>
You will notice that I had to still place the call to the function in <my-one> into a setTimeout call. This is because the <my-one> element can not be upgraded until AFTER your connectedCallback function exists. Upgrading needs a chance to run and it will not run in the middle of your function.
Another way to do it is by calling the constructor for <my-one> directly:
class One extends HTMLElement {
constructor() {
super();
console.log('one constructor');
}
connectedCallback() {
this.innerHTML = "one";
}
myFunc() {
console.log('it worked!');
}
}
class Two extends HTMLElement {
constructor() {
super();
console.log('two constructor');
}
connectedCallback() {
customElements.whenDefined('my-one').then(() => {
this.myOne = document.createElement('my-one');
this.append(this.myOne);
this.myOne.myFunc();
});
}
}
customElements.define("my-two", Two);
customElements.define("my-one", One);
<my-two></my-two>
Here you will notice that I had to add a call to customElements.whenDefined. This will wait until <my-one> to actually be defined before it attempts to instantiate it. Once it is defined then you can create it and call the member function right away.
One last thing. There are rules for what you should and should not do while in a constructor for a Web Component. They are defined here https://w3c.github.io/webcomponents/spec/custom/#custom-element-conformance
But one thing I will point out is that you are not supposed to touch or change any attributes or child elements in the constructor. Mainly because there are no attribute or any child elements when the Web Component is constructed. Those are changed and added after the fact.

How to define a more customElements for same Class

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>

Categories