WebComponent

Advantages: Native components, no framework required, good performance and less code Disadvantages: compatibility issues

Componentization benefits: high cohesion, reuse, composability

The core technology

  • custom elements: a set ofJavaScript API, allow definitioncustom elementsAnd its behavior, which can then be used as needed in the user interface
  • Shadow DOM: a set ofJavaScript API, used to encapsulateDOM tree attached to the element (rendered separately from the main document DOM)And control its associated functions. This way, you can keep the functionality of an element private, and it can be scripted and styled without worrying about running afoul of the rest of the document
  • HTML templates:<template>andslotElement allows the user to write markup templates that are not displayed on rendered pages. They can then be reused many times as a basis for defining the structure of the element

Implement custom Button components

HTML template template

The content in template is defined as a Button component style, and slot takes the content from a custom component and inserts it into the template

 <comp-button type='primary'>I'm a button</comp-button>
 <template id="btn">
     <button class="comp-btn">
         <slot>I'm the default</slot>
     </button>
 </template>
Copy the code

Component implementation

class CompButton extends HTMLElement {
    constructor() {
        super(a);// Create a shadow, where this is comp-button
        const shadow = this.attachShadow({
            mode: 'open'
        });
        // Find the template
        const temp = document.getElementById('btn');
        // Clone the template to ensure the reuse of the template
        const cloneTemp = temp.content.cloneNode(true);
        // Set some properties
        const style = document.createElement('style');
        const type = this.getAttribute('type') | |'default'
        const styleList = {
            'primary': {
                background: '#409eff'.color: '#fff'
            },
            'default': {
                background: '# 909399'.color: '#fff'
            }
        }
        style.textContent = `
                    .comp-btn{
                        outline:none;
                        border:none;
                        border-radius:4px;
                        padding:5px 10px;
                        display:inline-flex;
                        background:var(--background-color,${styleList[type].background});
                        color:var(--text-color,${styleList[type].color});
                        cursor:pointer
                    }
                `
        // Add style to shadow
        shadow.appendChild(style);
        // Template is mounted to shadowshadow.appendChild(cloneTemp); }}Copy the code

Define custom components

// Define a custom component
window.customElements.define('comp-button', CompButton);
Copy the code

WebComponent life cycle

  • connectedCallback: called when a Custom Element is first inserted into the document DOM
  • disconnectedCallback: is called when a Custom Element is removed from the document DOM
  • adoptedCallback: Called when a custom Element is moved to a new document (move into an iframe)
  • attributeChangedCallback: Called when a Custom Element is added or removed, or its attributes are modified

Implementation similar to Collapse Collapse panel

├─ index.html ├─ index.js ├── ly collapse.js ├─ ly collapse.js

Results show

index.html

<! -- * @Author: dfh * @Date: 2021-02-02 07:58:07 * @LastEditors: dfh * @LastEditTime: 2021-02-02 09:02:54 * @Modified By: dfh * @FilePath: /day15-webcomponent/2.index.html -->
<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
</head>

<body>
    <ly-collapse>
        <ly-collapse-item title="Consistency" name="1">
            <div>Consistent with real life: consistent with the flow and logic of real life, follow the language and concepts used by users;</div>
            <div>Consistency in the interface: All elements and structures must be consistent, such as design style, ICONS and text, element placement, etc.</div>
        </ly-collapse-item>
        <ly-collapse-item title="Feedback Feedback" name="2">
            <div>Control feedback: users can clearly perceive their own operations through interface styles and interactive effects;</div>
            <div>Page feedback: After operation, the current state is clearly displayed through changes in page elements.</div>
        </ly-collapse-item>
        <ly-collapse-item title="Efficiency Efficiency" name="3">
            <div>Simplified process: design simple and intuitive operation process;</div>
            <div>Clear and explicit: clear language expression and clear meaning, so that users can quickly understand and make decisions;</div>
            <div>Help users identify: simple and straightforward interface, let users quickly identify rather than recall, reduce user memory burden.</div>
        </ly-collapse-item>
        <ly-collapse-item title="Controllable Controllability" name="4">
            <div>User decision-making: Operation suggestions or safety tips can be provided to users according to scenarios, but decisions cannot be made on behalf of users.</div>
            <div>Controllable result: Users can perform operations freely, including undo, rollback, and termination of the current operation.</div>
        </ly-collapse-item>
    </ly-collapse>
    <! -- Template has no real meaning and will not be rendered to the page -->
    <template id='collapse-tmpl'>
        <div class="collapse">
            <slot></slot>
        </div>
    </template>
    <template id='collapse-item-tmpl'>
        <div class='title'></div>
        <div class='content'>
            <slot></slot>
        </div>
    </template>
    <! -- type='module' -->
    <script src="./index.js" type='module'></script>
</body>

</html>
Copy the code

index.js

/* * @Author: dfh * @Date: 2021-02-02 07:59:52 * @LastEditors: dfh * @LastEditTime: 2021-02-02 10:19:58 * @Modified By: dfh * @FilePath: /day15-webcomponent/index.js */
import LyCollapse from './ly-collapse.js';
import LyCollapseItem from './ly-collapse-item.js';

window.customElements.define('ly-collapse', LyCollapse);
window.customElements.define('ly-collapse-item', LyCollapseItem);

// Set the default state: 1 display, other hidden
const defaultActive = ['1'];
// Get collapse elements
const collapseEl = document.querySelector('ly-collapse');
// Pass data to the child
collapseEl.setAttribute('active'.JSON.stringify(defaultActive));

// Listen for custom events
collapseEl.addEventListener('changeName'.e= > {
    const {
        isShow,
        name
    } = e.detail;
    if (isShow) {
        const index = defaultActive.indexOf(name);
        / / remove
        defaultActive.splice(index, 1);
    } else {
        / / add
        defaultActive.push(name);
    }
    collapseEl.setAttribute('active'.JSON.stringify(defaultActive));
});
Copy the code

ly-collapse.js

/* * @Author: dfh * @Date: 2021-02-02 07:59:33 * @LastEditors: dfh * @LastEditTime: 2021-02-02 10:01:01 * @Modified By: dfh * @FilePath: /day15-webcomponent/ly-collapse.js */
export default class LyCollapse extends HTMLElement {
    constructor() {
        super(a);// Create a shadow node
        const shadow = this.attachShadow({
            mode: 'open'
        });
        // Find the template
        const tmpl = document.getElementById('collapse-tmpl');
        // Clone template,true means clone includes descendant nodes
        const tmplClone = tmpl.content.cloneNode(true);
        / / style
        let style = document.createElement('style');
        //:host represents the shadow root element
        style.textContent = ` :host{ display:flex; border:3px solid #ebebeb; border-radius:5px; width:100%; } .collapse{ padding:10px; width:100%; } `
        // Associate the template root shadow
        shadow.appendChild(tmplClone);
        // Add styles
        shadow.appendChild(style);

        // Monitor slot changes
        const slot = shadow.querySelector('slot');
        slot.addEventListener('slotchange'.(e) = > {
            this.slotList = e.target.assignedElements();
            this.render(); })}// Monitor property changes
    static get observedAttributes() {
        return ['active']}attributeChangedCallback(key, oldVal, newVal) {
        if (key === 'active') { // Accept active change processing
            this.activeList = newVal;
            this.render(); }}render() {
        //activeList has a value and the child is loaded
        if (this.activeList && this.slotList) {
            this.slotList.forEach(child= > {
                child.setAttribute('active'.this.activeList); }); }}}Copy the code

ly-collapse-item.js

/* * @Author: dfh * @Date: 2021-02-02 07:59:46 * @LastEditors: dfh * @LastEditTime: 2021-02-02 10:24:42 * @Modified By: dfh * @FilePath: /day15-webcomponent/ly-collapse-item.js */
export default class LyCollapseItem extends HTMLElement {
    constructor() {
        super(a);const shadow = this.attachShadow({
            mode: 'open'
        });
        const tmpl = document.getElementById('collapse-item-tmpl');
        const tmplClone = tmpl.content.cloneNode(true);
        this.isShow = false; // Indicates whether to display
        const style = document.createElement('style');
        style.textContent = ` :host{ width:100%; border-bottom:1px solid #ebeef5; display:flex; flex-direction:column; padding:10px 0; color:black; } .title{ background:#f1f1f1; line-height:35px; font-size:18px; } .content{ font-size:14px; } `
        shadow.appendChild(style);
        shadow.appendChild(tmplClone);
        this.titleEl = shadow.querySelector('.title');

        // Set the click event for the title
        this.titleEl.addEventListener('click'.() = > {
            //dispatchEvent dispatches a custom event
            document.querySelector('ly-collapse').dispatchEvent(new CustomEvent('changeName', {
                detail: {
                    name: this.getAttribute('name'),
                    isShow: this.isShow
                }
            }))
        })
    }

    // Monitor property changes
    static get observedAttributes() {
        return ['active'.'title'.'name']}attributeChangedCallback(key, oldVal, newVal) {
        switch (key) {
            case 'title': // Accept the title attribute
                this.titleEl.innerHTML = newVal;
                break;
            case 'active':
                this.activeList = JSON.parse(newVal); // The value passed by the parent component
                break;
            case 'name':
                this.name = newVal;
                break;
            default:
                break;
        }
        if (this.name && this.activeList) {
            // Whether the active number contains name
            this.isShow = this.activeList.includes(this.name);
            //this.shadowRoot gets the shadow tree
            this.shadowRoot.querySelector('.content').style.display = this.isShow ? 'block' : 'none'; }}}Copy the code