[w3c/webcomponents] How to wait for will-be custom elements to be upgraded? (#558)

In my experiences so far, I define custom elements, and the `<body>` of the page is empty. At some point in the future after the custom elements are defined and after the document is loaded, I use a view layer (f.e. React, Meteor Blaze, etc) to render some components which results in elements being added into the `<body>`. This works really well because by the time my custom elements get placed into the DOM (indirectly, by the view layers, not by me), then the registered Custom Element classes have their life cycle callbacks (I'm on v0, so createdCallback, attachedCallback, detachedCallback, and attributeChangedCallback) fired as expected and in the correct order.

It is a completely different story when we're not using a view layer. Suppose we're sending HTML markup from the server. The HTML markup is parsed at some point in time which can possibly *before* the Custom Element classes are ever registered. What can happen is that when the Custom Element classes are finally registered, the DOM tree may already be created, which causes attachedCallbacks to fire in possibly the incorrect order because if there are multiple custom element classes that should be registered, then what happens is that the `attachedCallback` of one element may be called but not that of the other elements that are not yet upgraded, which causes problems with a framework that defines multiple elements.

Another thing is that if Custom Element classes are registered after the elements already exist in DOM, then their `attributeChangedCallback` methods will not be fired with the initially set attribute values (if any). Note, I am testing this with Chrome's native v0 implementation, and that is the behavior that I observe.

Let me explain how the problems can be solved by showing you some hacks I've written:

The following is an excerpt from a class, with some code removed for simplicity just to show the problem. What you'll see is that in the `attachedCallback` I've implemented some code that will manually call `attributeChangedCallback` in the case that there might be pre-existing attributes on the element once it has been upgraded (Chrome's implementation does not call `attributeChangedCallback` in this case). You'll also notice that I've written a polyfill for my [`childConnectedCallback` idea](https://github.com/w3c/webcomponents/issues/550), and in the implementation you'll see that I'm using `setTimeout`, which is an ugly and horrible hack in order to defer some code so that the code can be executed after upgrade of `this` element's children has been completed, otherwise the callbacks may be executed before the child elements are upgraded (i.e. before the child element classes are registered):

```js
        attachedCallback() {

            // Handle any nodes that may have been connected before `this` node
            // was created (f.e. child nodes that were connected before the
            // custom elements were registered and which would therefore not be
            // detected by the following MutationObserver).
            if (this.childNodes.length) {
                console.log(` ------ ${this.nodeName} has children!!!`)

                // Timeout needed in case the Custom Elements classes are
                // registered after the elements are already defined in the DOM
                // but not yet upgraded.
                setTimeout(() => {
                    for (let node of this.childNodes) {
                        this.childConnectedCallback(node)
                    }
                }, 5)
            }

            // TODO issue #40
            // Observe nodes in the future.
            // This one doesn't need atimeout since the observation is already
            // async.
            const observer = new MutationObserver(changes => {
                for (let change of changes) {
                    if (change.type != 'childList') continue

                    for (let node of change.addedNodes)
                        this.childConnectedCallback(node)

                    for (let node of change.removedNodes)
                        this.childDisconnectedCallback(node)
                }
            })
            observer.observe(this, { childList: true })

            // fire this.attributeChangedCallback in case some attributes have
            // existed before the custom element was upgraded.
            if (this.hasAttributes())
                for (let attr of this.attributes)
                    if (this.attributeChangedCallback)
                        this.attributeChangedCallback(attr.name, null, attr.value)
        }
```

Subclasses of the class shown above implement `childConnectedCallback`, and rely on their children being instances of my framework's custom element classes.

Again, this all works fine if my classes are registered before any of these elements are ever placed into the DOM. But, if the elements exist prior to registration, they will need to be upgraded, and then my code would not work without the `setTimeout` hack and the manual calling of the `attributeChangedCallback` methods.

This has led me to ask the following question on stackoverflow: [How to wait for Custom Element reference to be “upgraded”?](http://stackoverflow.com/questions/39196503/how-to-wait-for-custom-element-reference-to-be-upgraded).

I believe if there were some official way to wait for the upgrade of an element if all you have is a reference to that element (without the ability to modify that element's code prior), then it might make it easier to write code in a way that isn't so hacky as in my above example.

It may be possible that an element is never registered, so maybe there would also need to be a way to cancel the waiting for upgrade, in order not to leak memory.

Assume that the custom elements that I will await to be upgraded are third party elements. I don't want to monkey patch the `createdCallback` of the element whose reference I have, as that would also be hacky.

Is there any way to do something after an element has been upgraded?

Is there any other recommended approach?

Previously, I simply placed logic into my child elements that on `attachedCallback` would look for their parents in order to create a connection with their parents, but this fails badly with closed shadow trees as described in https://github.com/w3c/webcomponents/issues/527, so I've since changed my API so that it is parents that observe children in order to create connections with the children.

## However!

The parent cannot create a connection with the children when the parent class is registered and the child class is not yet registered, which is why I've written the above hacks. This scenario seems to happen due to the fact that element registration seems to be synchronous.

Suppose we have this code:

```js
document.registerElement('some-el', SomeElement)
document.registerElement('other-el', OtherElement)
```

The first line will cause `some-el` elements to be upgraded synchrously, and those elements will therefore execute logic immediately, and if that logic depends on child elements being `other-el`, then the logic will fail. However, if I defer the logic, then that gives a chance for the registration of `other-el` to happen first, and then the logic in the parent will succeed.

Once again, *this is not a problem at all* when the elements are registered before any of the elements are ever placed into the DOM.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/webcomponents/issues/558

Received on Monday, 29 August 2016 20:22:57 UTC