--- title: "Open-WC 筆記" path: "Open-WC 筆記" --- {%hackmd @RintarouTW/About %} # Open-WC 筆記 Open Web Component - open-wc depend on LitElement(successor of Polymer). - Light weight. - shadow dom : 看來終於解決 naming polution 的問題了 - customElement - customEvent (finally...) ## Shadow DOM ### LitElement 實現方式 ```javascript const header = document.createElement('header'); const shadowRoot = header.attachShadow({mode: 'open'}); shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>'; // Could also use appendChild(). // header.shadowRoot === shadowRoot // shadowRoot.host === header ``` <center> ```graphviz digraph { host ->shadowRoot } ``` </center> shadowRoot 即 Shadow DOM 之 document, 支援 getElementById(), querySelector(),...。 ### Custom Element 使用 Shadow DOM ```javascript // Use custom elements API v1 to register a new HTML tag and define its JS behavior // using an ES6 class. Every instance of <fancy-tab> will have this same prototype. customElements.define('fancy-tabs', class extends HTMLElement { function Object() { [native code] }() { super(); // always call super() first in the constructor. // Attach a shadow root to <fancy-tabs>. const shadowRoot = this.attachShadow({mode: 'open'}); shadowRoot.innerHTML = ` <style>#tabs { ... }</style> <!-- styles are scoped to fancy-tabs! --> <div id="tabs">...</div> <div id="panels">...</div> `; } ... }); ``` ## `<slot></slot>` Light DOM 組件用戶編寫的標記。該 DOM 不在組件 shadow DOM 之內。 它是元素實際的子項。 ```javascript <button is="better-button"> <!-- the image and span are better-button's light DOM --> <img src="gear.svg" slot="icon"> <span>Settings</span> </button> ``` Shadow DOM 該 DOM 是由組件的作者編寫。Shadow DOM 對於組件而言是本地的,它定義內部結構、作用域 CSS 並封裝實現詳情。它還可定義如何渲染由組件使用者編寫的標記。 ```javascript #shadow-root <style>...</style> <slot name="icon"></slot> <span id="wrapper"> <slot>Button</slot> </span> ``` Browser Render 會通過 `<slot></slot>` 來組合 Light DOM 和 Shadow DOM ```javascript <button is="better-button"> #shadow-root <style>...</style> <slot name="icon"> <img src="gear.svg" slot="icon"> </slot> <slot> <span>Settings</span> </slot> </button> ``` Shadow DOM 使用 `<slot >` 元素將不同的 DOM 樹組合在一起。Slot 是組件內部的佔位符,用戶可以使用自己的標記來填充。 通過定義一個或多個 slot,您可將外部標記引入到組件的 shadow DOM 中進行渲染。 這相當於您在說“在此處渲染用戶的標記”。 如果 `<slot>` 引入了元素,則這些元素可“跨越” shadow DOM 的邊界。 這些元素稱爲分佈式節點。從概念上來看,分佈式節點似乎有點奇怪。 Slot 實際上並不移動 DOM;它們在 shadow DOM 內部的其他位置進行渲染。 組件可在其 shadow DOM 中定義零個或多個 slot。Slot 可以爲空,或者提供回退內容。 如果用戶不提供 light DOM 內容,slot 將對其備用內容進行渲染。 [深入 Shadow DOM](https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=zh-tw) ## Open WC Custom Element ```javascript import { LitElement, html } from "lit-element"; class UpdateArraysAndObjects extends LitElement { static get properties() { return { myArray: { type: Array }, myObject: { type: Object } }; } constructor() { super(); this.myObject = { id: 1, text: "foo" }; this.myArray = [{ id: 1 }, { id: 2 }]; } render() { } } customElements.define("update-arrays-and-objects", UpdateArraysAndObjects); ``` where customElements.define(custom-tag-name, element_class_name); ## Attribute and Property - Attribute: html tag attribute ex: `<div id="id">` - Property: javascript object property ## Render Style ```javascript import { LitElement, html, css } from "lit-element"; class RenderStyles extends LitElement { /** * Styles should be added as a static getter. They are evaluated once, and then added * in the element's shadow dom. * * Shadow dom takes care of scoping the CSS of your element to only affect your * element's template, and not the element outside. For an in-depth explanation * of shadow dom, see: https://github.com/praveenpuglia/shadow-dom-in-depth */ static get styles() { return css` :host { display: block; } .message { color: blue; } `; } render() { return html` <div class="message">Hello world!</div> `; } } customElements.define("render-styles", RenderStyles); ``` ## LitElement Oberserve Scope LitElement only watch on Array and Object changes, not it's content. Use this.requestUpdate() instead after mutating the content directly. ```javascript // this.myArray.push(newItem); // this.myObject.id = newId; // this.requestUpdate(); ``` ```javascript import { LitElement, html } from "lit-element"; class UpdateArraysAndObjects extends LitElement { static get properties() { return { myArray: { type: Array }, myObject: { type: Object } }; } constructor() { super(); this.myObject = { id: 1, text: "foo" }; this.myArray = [{ id: 1 }, { id: 2 }]; } render() { return html` <h3>Array items</h3> <ul> ${this.myArray.map( item => html` <li>${item.id}</li> ` )} </ul> <button @click=${this._addArrayItem}>Add array item</button> <h3>Object</h3> <div><strong>${this.myObject.id}</strong>: ${this.myObject.text}</div> <button @click=${this._updateObjectId}>Add object item</button> `; } /** * If you mutate an array directly, LitElement will not detect * the change automatically. * * The recommended approach is to use immutable data patterns. * You can easily append an array item using array spread syntax: */ _addArrayItem() { const newId = Math.round(Math.random() * 100); const newItem = { id: newId }; this.myArray = [...this.myArray, newItem]; /** * An alternative method is to mutate the array and then call * requestUpdate() to notify LitElement the property changed. */ // this.myArray.push(newItem); // this.requestUpdate(); } /** * If you mutate an object directly, LitElement will not detect * the change automatically. * * The recommended approach is to use immutable data patterns. * You can easily update an object's property using the object * spread syntax: */ _updateObjectId() { const newId = Math.round(Math.random() * 100); this.myObject = { ...this.myObject, id: newId }; /** * An alternative method is to mutate the object and then call * requestUpdate() to notify LitElement the property changed. */ // this.myObject.id = newId; // this.requestUpdate(); } } customElements.define("update-arrays-and-objects", UpdateArraysAndObjects); ``` ###### tags: `html` `node` `open-wc` `lit-element`