UNPKG

joicomponents

Version:

Design patterns to build native web components

144 lines (118 loc) 6.87 kB
# Mixin: ChildrenChange ## External vs. internal stimuli The reason `SlotchangeMixin` is unaffected by the presence of `.shadowRoot` and `<slot>` elements, is that it reacts only to **outside** stimuli. `SlotchangeMixin` only triggers `slotchangeCallback(...)` when a *potentially slottable* element has been added or removed (and the first time the element connects to the DOM). This does mean that `slotchangeCallback(...)` is **NOT** triggered: * when a new `<slot>` element is added to the shadowDOM, * that results in new slotted elements being transposed to the flattenedDOM. This goes counter to `slotchange` event semantic. However. Changes of the shadowDOM should be done within the custom element. And so it would be clearer to call functions that should trigger after such a change from the same place where the shadowDOM is changed, instead of indirectly triggering the same functions via a callback. Changes of *potentially slottable* nodes however are not driven from inside the element and thus is well served triggered from a callback. On the other side, `slotchange` and `ShadowSlotchangeMixin` are triggered from inside the element. This requires the element to have a shadowRoot and corresponding `<slot>` elements. And this means that *both* *internal* changes the element itself triggers callbacks/events (which you likely do not need and thus need to disregard) *and* *external* changes (which you likely need to be notified of/react to). But *not all* external changes of *potentially slottable* nodes are triggered. It is my opinion that for web components externally initialized changes of *potentially slottable* elements is what the element needs to be notified of in many use-cases. `slotchange` means *only actually slotted elements from the inside*. But it should mean *all potentially slottable elements from the outside*. ## `SlotchangeMixin` [`SlotchangeMixin`](../../../../../src/slot/SlottableMixin.js) implements a different strategy to observe `slotchange` events. Instead of attaching event listeners for `slotchange` events on the `.shadowRoot`, `SlotchangeMixin` uses `MutationObserver` to **observe the `childNodes` of the `host` node**. `SlotchangeMixin` then categorizes these nodes by `slot`-attributes, and then in addition attaches `slotchange` listeners to any chained `<slot>` element child nodes of its `host` element. Phu.. Its a mouthful. However, although the implementation is a lot less intuitive, both `SlotchangeMixin` and `ShadowSlotchangeMixin` function similarly. Except for one small, but beautiful little difference: `SlotchangeMixin` reacts to external, not internal stimuli. ## Example: `<red-frame>` using `SlotchangeMixin` In this example we remake the `<red-frame>` `SlotchangeMixin`. ```html <script type="module"> import {SlotchangeMixin} from "./SlottableMixin.js"; class RedFrame extends SlotchangeMixin(HTMLElement) { constructor(){ super(); this.attachShadow({mode: "open"}); this.shadowRoot.innerHTML = `<style> :host { display: inline-block; border: 10px solid red; } </style> <div id="count"></div> <slot></slot>`; } slotchangedCallback(slotName, newAssignedNodes) { //[1] const div = this.shadowRoot.getElementById("count"); div.innerText = newAssignedNodes.length; } } customElements.define("red-frame", GreenFrame); </script> <red-frame> <img src="tomato.jpg" alt="tomato"> <img src="strawberry.jpg" alt="strawberry"> </red-frame> <script> setTimeout(function(){ const cherry = document.createElement("img"); cherry.src = "cherry.jpg"; cherry.alt = "cherry"; document.querySelector("red-frame").appendChild(cherry); }, 2000); </script> ``` 1. All the functionality of efficiently and harmonically listening for `slotchange` events are encapsulated in `SlotchangeMixin`. If you need to distinguish between different `<slot>` elements, use the first argument `slotName` of the callback method. ## References * https://github.com/webcomponents/gold-standard/wiki/Content-Changes * https://github.com/webcomponents/gold-standard/wiki/Content-Assignment * https://github.com/webcomponents/gold-standard/wiki/Detachment * https://www.polymer-project.org/2.0/docs/devguide/shadow-dom#observe-nodes * https://www.polymer-project.org/2.0/docs/api/classes/Polymer.FlattenedNodesObserver * https://github.com/w3c/webcomponents/issues/493#issuecomment-218077582 * https://dom.spec.whatwg.org/#mutation-observers * https://github.com/whatwg/dom/issues/126 ## Acknowledgments Many thanks to Jan Miksovsky and the Elix project for input and inspiration. <!-- If you that `SlotchangeMixin.slotchangeCallback(...)` better serves the needs of a custom element whereas `slotchange` and `ShadowSlotchangeMixin` is better suited for `slotchangeCallback(...)` reacts to changes in the environment of the custom element, not changes that can be affected from within the shadowDOM. This means that `slotchangeCallback(...)` will not necessarily trigger when you add or remove `<slot>` elements inside the shadowDOM. Changes of the shadowDOM of an element thus cannot trigger `slotchangeCallback(...)`. But, at the same time, the `slotchangeCallback(...)` gives you you are not dependent on any As changes inside the element does not affect which elements are currently *slottable*, `slotchangeCallback(...)` does not trigger. `slotchange` event reacts to changes from within the shadowDOM of the element. If there are no `<slot>` that correspond to the slot name that changes, or no `<slot>` element at all, then no `slotchange` event neither. This is If you add a `<slot>` element inside the shadowDOM, and then connect that element or another `<slot>` element with the same `name` attribute back into the shadowDOM, then `slotchangeCallback(...)` will not be triggered, whereas a `slotchange` event should be triggered in Chrome (and maybe Safari, I don't know). When the missing initial `slotchange` event in Safari is triggered, then the caching of previously `assignedNodes` for each slot name can be skipped, and that will enable such changes of the shadowDOM to trigger `slotchangeCallback(...)` too. As of right now, the solution is just to know that if you remove and then add `<slot>` elements inside the shadowDOM, that will not trigger `slotchangeCallback()` unless the values have actually changed. As such changes should be done from within the component, both solutions are -->