- 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