Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proxy a WebComponent's constructor that extends HTMLElement

So, in a library that I'm creating that uses custom elements, you obviously need to define the class in the CustomElementsRegistry before you may instantiate it.

As of right now, this is being solved with a decorator:

class Component extends HTMLElement {

    static register (componentName) {
        return component => {
            window.customElements.define(componentName, component);
            return component;
        }
    }
}

@Component.register('my-element')
class MyElement extends Component { }

document.body.appendChild(new MyElement());

This works, however, I would like to automatically register the custom element upon instantiation of the class (so that the author does not have to add the decorator to every single component that they write). This could maybe be accomplished via a Proxy.


My problem, though, is that when I attempt to use a Proxy on the constructor, and attempt to return an instance of the target, I still get Illegal Constructor, as if the element has never been defined in the registry.

This obviously has to do with the way I am instantiating the class inside of the proxy, but I'm unsure of how to do it otherwise. My code is as follows:

Please run in latest Chrome:

class Component extends HTMLElement {

    static get componentName () {
        return this.name.replace(/[A-Z]/g, char => `-${ char.toLowerCase() }`).substring(1);
    }
}

const ProxiedComponent = new Proxy(Component, {

    construct (target, args, extender) {
        const { componentName } = extender;
  	
        if (!window.customElements.get(componentName)) {
            window.customElements.define(componentName, extender);
        }
    
        return new target(); // culprit
    }
});

class MyElement extends ProxiedComponent { }

document.body.appendChild(new MyElement());

How can I continue the chain of inheritance inside of the proxy without losing the context of the fact that I'm instantiating the MyElement class so that it doesn't throw the Illegal Constructor exception?

like image 317
ndugger Avatar asked Dec 12 '17 19:12

ndugger


People also ask

What is extends HTMLElement?

Extending HTMLElement ensures the custom element inherits the entire DOM API and means any properties/methods that you add to the class become part of the element's DOM interface. Essentially, use the class to create a public JavaScript API for your tag.

Can you use custom elements in HTML?

One of the key features of the Web Components standard is the ability to create custom elements that encapsulate your functionality on an HTML page, rather than having to make do with a long, nested batch of elements that together provide a custom page feature.

What are autonomous custom elements?

Autonomous custom elements are new HTML tags, defined entirely by the author. They have none of the semantics of existing HTML elements, so all behaviors need to be defined by the author. Customized built-ins extend existing HTML elements with custom functionality. They inherit semantics from the elements they extend.

What is custom element?

Custom Elements allow web developers to define new types of HTML elements. The spec is one of several new API primitives landing under the Web Components umbrella, but it's quite possibly the most important. Web Components don't exist without the features unlocked by custom elements: Define new HTML/DOM elements.


1 Answers

There were 2 problems:

  • new target() created LibElement instance, that is not registered as custom element. And here you got Illegal Constructor error.
  • even if you register LibElement resulting DOM element will be <lib-element>, cause you call new target and at this point javascript have no idea about child class.

The only way i found is to use Reflect API to create right instance of object.

class LibElement extends HTMLElement {
    static get componentName () {
        return this.name.replace(/[A-Z]/g, char => `-${ char.toLowerCase() }`).substring(1);
    }
}

const LibElementProxy = new Proxy(LibElement, {
    construct (base, args, extended) {
        if (!customElements.get(extended.componentName)) {
            customElements.define(extended.componentName, extended);
        }
    
        return Reflect.construct(base, args, extended);
    }
});

class MyCustomComponent extends LibElementProxy {}
class MyCustomComponentExtended extends MyCustomComponent {}

document.body.appendChild(new MyCustomComponent());
document.body.appendChild(new MyCustomComponentExtended());

And I really liked this idea of proxied constructor for auto-registration of custom elements )

like image 156
Gheljenor Avatar answered Oct 17 '22 02:10

Gheljenor