- From: Joe Pea <notifications@github.com>
- Date: Fri, 01 Feb 2019 10:57:27 -0800
- To: w3c/webcomponents <webcomponents@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <w3c/webcomponents/issues/787/459829842@github.com>
Alright, the following is the final solution with `MutationObserver` which seems to work in all the cases where the custom elements are defined BEFORE they are used, cleans itself up, and triggers `childConnectedCallback` and `childDisconnectedCallback` following the same pattern as `connectedCallback` and `disconnectedCallback` wherein they are only called when element is connected/disconnected into/from a context (document or shadow root). Don't mind the `Class()` syntax which is using class inheritance from [lowclass](https://github.com/trusktr/lowclass). The `Mixin()` helper simply makes a mixin so that the class has a static `mixin` method). Other than this, you can imagine it using plain `class` syntax. The implementation of `observeChildren` is [here](https://github.com/trusktr/infamous/blob/b985ec7bf8a1f1f8ace2d4051d7d522f9a1ce988/src/core/Utility.js#L15-L83), which is just using `MutationObserver` with `childList: true` to call the connected and disconnected callbacks that are passed into `observeChildren`. ```js import Class from 'lowclass' import Mixin from 'lowclass/Mixin' import { observeChildren } from '../core/Utility' const childConnectedEvent = new CustomEvent('child-connected', { bubbles: false, composed: false }) const childDisconnectedEvent = new CustomEvent('child-disconnected', { bubbles: false, composed: false }) export default Mixin(Base => Class('WithChildren').extends(Base, ({ Super, Private, Public }) => ({ // TODO, if the polyfills cover this remove it, isConnected is already part // of the window.Node class (in the specs) and serves the same purpose. // https://github.com/webcomponents/webcomponentsjs/issues/1065 isConnected: false, constructor(...args) { const self = Super(this).constructor(...args) Private(self).__createObserver() return self }, connectedCallback() { this.isConnected = true Super(this).connectedCallback && Super(this).connectedCallback() const priv = Private(this) // NOTE! This specifically handles the case that if the node was previously // disconnected from the document, then it won't have an __observer when // reconnected. This is not the case when the element is first created, // in which case the constructor already created the __observer. // // So in this case we have to manually trigger childConnectedCallbacks if (!priv.__observer) { const currentChildren = this.children Promise.resolve().then(() => { for (let l=currentChildren.length, i=0; i<l; i+=1) { this.childConnectedCallback && this.childConnectedCallback(currentChildren[i]) } }) } Private(this).__createObserver() }, disconnectedCallback() { this.isConnected = false Super(this).disconnectedCallback && Super(this).disconnectedCallback() // Here we have to manually trigger childDisconnectedCallbacks // because the observer will be disconnected. const lastKnownChildren = this.children Promise.resolve().then(() => { for (let l=lastKnownChildren.length, i=0; i<l; i+=1) { this.childDisconnectedCallback && this.childDisconnectedCallback(lastKnownChildren[i]) } }) Private(this).__destroyObserver() }, // private fields private: { __observer: null, __handledChildren: [], __createObserver() { if (this.__observer) return const self = Public(this) this.__observer = observeChildren( self, child => { if (!self.isConnected) return if (!this.__handledChildren.includes(child)) this.__handledChildren.push(child) self.childConnectedCallback && self.childConnectedCallback(child) }, child => { if (!self.isConnected) return const indexOfChild = this.__handledChildren.indexOf(child) if (indexOfChild < 0) this.__handledChildren.splice(indexOfChild, 1) self.childDisconnectedCallback && self.childDisconnectedCallback(child) }, true ) }, __destroyObserver() { if (!this.__observer) return this.__observer.disconnect() this.__observer = null }, }, }))) ``` And here's how to use it: ```js import WithChildren from './WithChildren' class MyElement extends WithChildren.mixin(HTMLElement) { childConnectedCallback(child) { ... } childDisconnectedCallback(child) { ... } } ``` -- 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/787#issuecomment-459829842
Received on Friday, 1 February 2019 18:57:49 UTC