UNPKG

joicomponents

Version:

Design patterns to build native web components

224 lines (179 loc) 8.6 kB
# Pattern: AutoAttribute The AutoAttribute pattern uses the BatchedCallbacks pattern to add or alter an attribute on a web component *automatically*. The AutoAttribute serves 3 purposes: 1. *reactive in JS*. When the auto attribute is either added, altered, or removed from the host element, this will trigger an `attributeChangedCallback(...)` on the web component. If this callback was the *only* purpose of the AutoAttribute, then the AutoAttribute would be overkill and the FunctionalMixin pattern should be used instead. However, the AutoAttribute pattern *uses* an HTML attribute to *indirectly* trigger the `attributeChangedCallback(...)` because the HTML attribute can *also* be: 2. *read from CSS*. CSS selectors can use HTML attributes to activate/deactivate different sets of CSS rules. This way, the web component can both choose between a set of CSS rules for its content internally, and a set of CSS rules can be set on the component externally. 3. *set in HTML*. This means that the AutoAttribute behavior can be read from HTML and also intuitively controlled, started and stopped directly in the HTML template. The AutoAttribute pattern is a mechanism to enable web components to automatically react to changes. By using an HTML attribute to communicate changes, both JS reactions and CSS rules can be selected, while the mechanism can be read and controlled via the DOM. ## The API of the AutoAttribute pattern The AutoAttribute is implemented as a mixin that reads two HTML attributes: 1. `auto-attributename` activates/deactivates the AutoAttribute on the element. 2. `_attributename` is the attribute value that is automatically updated when the `auto-attributename` attribute is there. How the web component should activate the mixin in response `auto-attributename` varies and depends on how the AutoAttribute mixin works and what it does. The developer of the web component should often, but not always, expect to: 1. *observe* the `auto-attributename` attribute, and 2. in `attributeChangedCallback(...)` react to the `auto-attributename` by calling a method inherited from the mixin. When activate, the AutoAttribute mixin will *automatically* set or remove the `_attributename` on the host element. When the `_attributename` is added/removed on the web component, two things happen: 1. a `attributeChangedCallback(...)` for the `_attributename` is called. 2. CSS selectors for the host element that use this attribute can be turned on/off. Internally, CSS selectors that use the AutoAttribute value look like: 1. `* {...}` (default CSS rules, no `_attributename`) 2. `:host([_attributename]){...}` (CSS rules when the `_attributename` is set) Externally, the CSS selectors look like: 1. `#el {...}` (default CSS rules, applies to all elements, regardless of `_attributename` state) 2. `#el:not([_attributename]) {...}` (default CSS rules, no `auto-attribute`) 3. `#el[_attributename]{...}` (CSS rules for when `auto-attribute` is set without any specified step value) Commonly, the mixin batches the AutoAttribute processing, ie. observing changes and applying them to all elements that subscribe to it as a *single* synchronous process. This yields efficiency. However, when the AutoAttribute runs and in which order these batched elements are processed, depend on the element and/or system property being observed. ## Example: OnlineAutoAttributeMixin This example shows a mixin that adds an AutoAttribute `_online="1"` on the host element of a web component whenever the browser is online, and an `_online="0"` whenever the browser is offline. The AutoAttribute is controlled by the `auto-online` attribute. The OnlineAutoAttributeMixin does not automatically react to the `auto-online` attribute. It is left up to the web components that inherits from the OnlineAutoAttributeMixin to: * remove or keep the `_online` attribute (and value) when `auto-online` is removed from an element, and * whether or not to immediately trigger the processing of `_online` attribute when `auto-online` is set on an element. ```javascript const batch = []; function addToBatch(el) { const index = batch.indexOf(el); if (index >= 0) return; batch.push(el); } function removeFromBatch(el) { const index = batch.indexOf(el); if (index >= 0) batch.splice(index, 1); } function runBatchProcess() { for (let el of batch) el.updateAutoOnline(); } let onlineState = navigator.onLine ? 1 : 0; window.addEventListener("online", function connecting() { onlineState = 1; runBatchProcess(); }); window.addEventListener("offline", function disConnecting() { onlineState = 0; runBatchProcess(); }); export function OnlineAutoAttributeMixin(type) { return class OnlineAutoAttributeMixin extends type { connectedCallback() { super.connectedCallback && super.connectedCallback(); addToBatch(this); } disconnectedCallback() { super.disconnectedCallback && super.disconnectedCallback(); removeFromBatch(this); } updateAutoOnline() { this.hasAttribute("auto-online") && this.setAttribute("_online", onlineState); } }; } ``` ## Demo: Web traffic lights In this demo we apply the `OnlineAutoAttributeMixin` to a web component called `<traffic-light>`. The `<traffic-light>` element will have a border that is: 1. grey, when the `OnlineAutoAttributeMixin` is inactive and no `_online` attribute is set, 2. red, when the `OnlineAutoAttributeMixin` is active and the browser is offline, or `_online="0"` attribute, 3. green, when the `OnlineAutoAttributeMixin` is active and the browser online, or `_online="1"` attribute,. The `<traffic-light>` element observes the `auto-online` attribute: whenever the `auto-online` attribute is added and removed, the `<traffic-light>` updates the value of its `_online` attribute. This means that: 1. The user of the element can fix the `_online` attribute both statically and dynamically, but that 2. the `_online` attribute value will be overwritten by the system whenever: 1. the `auto-online` attribute is added (including at startup), 2. the `auto-online` attribute is removed, 3. the browser online/offline state changes *and* an `auto-online` attribute is set on the element. The colors of the `<traffic-light>` element can be specified as CSS variables `--color-offline`, `--color-online`, `--color-inactive`. ```html <script type="module"> import {OnlineAutoAttributeMixin} from "../../src/mixin/OnlineAutoAttributeMixin.js"; class TrafficLight extends OnlineAutoAttributeMixin(HTMLElement) { constructor() { super(); this.attachShadow({mode: "open"}); this.shadowRoot.innerHTML = ` <style> div { border: 10px solid var(--color-inactive, darkgrey); } :host([_online="0"]) div{ border-color: var(--color-offline, red); } :host([_online="1"]) div{ border-color: var(--color-online, green); } </style> <div> <slot></slot> </div> `; } static get observedAttributes() { return ["auto-online"]; } attributeChangedCallback(name, oldValue, newValue) { if (name === "auto-online") { if (newValue !== null) this.updateAutoOnline(); else this.removeAttribute("_online"); } } } customElements.define("traffic-light", TrafficLight); </script> <style> #two { --color-offline: darkblue; --color-online: lightblue; } #three[_online="1"] { --color-online: pink; } </style> <traffic-light id="one" auto-online>one</traffic-light> <traffic-light id="two" auto-online>two</traffic-light> <traffic-light id="three">three</traffic-light> <traffic-light id="four" _online="0">four</traffic-light> <script> function alterAttributesDynamically() { document.querySelector("#one").removeAttribute("auto-online"); document.querySelector("#three").setAttribute("auto-online", ""); } </script> <button onclick="alterAttributesDynamically()">Remove auto-online from #one. Add auto-online to #three</button> ``` The example above is for demonstration purposes. It is unlikely that the altering the appearance of an element based on the online/offline status would best be served with a) a mixin, b) coordinated control both inside and outside the element. The purpose of this example is to explain the mechanics of the AutoAttribute pattern as simple as possible. The AutoAttribute's true value comes when applied to more complex use cases. ## References *