iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0
Modern Web

web component - 次世代網頁技術的重要拼圖系列 第 22

web component 的實做- 多選下拉選單組件

  • 分享至 

  • xImage
  •  

相較於昨天的單選下拉選單,多選下拉選單組件要修改的只有以下幾個組件:下拉選單的內容組件,以及把所有組件串起來的組件。同時還要追加一個顯示個別資料用的組件。

這篇文章沒有完成的部分

還沒有處理optiongroup元素的部分
還沒有時間改成headless的無UI組件

實做

HTML

<body>
    <form id="form">
        <my-multi-select>
            <select multiple>
                <option value="111" selected>111</option>
                <option value="222"  selected>222</option>
                <option value="333">333</option>
                <option value="444">444</option>
                <option value="555">555</option>
                <option value="666">666</option>
            </select>
        </my-multi-select>
        <button type="submit">Submit</button>
    </form>
    <script type="module">
        import { MyMultiSelect } from './select.js'
        customElements.define("my-multi-select", MyMultiSelect);
        const FormNode = document.getElementById("form");
        FormNode.addEventListener("submit", (e) => {
            e.preventDefault();
            console.log(FormNode.elements[0].value);
        });
    </script>
</body>

下拉選單的內容組件

因為需要顯示多重選擇。所以下拉選單的內容組件中需要加入一個checkbox,用來表示該選單是否有選擇。

const multiOptionStyle = `
    // CSS的部分省略
    `
class MyMultiSelectOption extends HTMLElement {
    optionCheckbox = document.createElement('input');
    constructor() {
        super()
        this.attachShadow({mode: 'open'})
        const styleSheet = document.createElement('style')
        styleSheet.innerHTML = multiOptionStyle
        this.shadowRoot.appendChild(styleSheet)
        this.shadowRoot.appendChild(this.render())
    }
    static get observedAttributes() {
        return ['selected'];
    }
    attributeChangedCallback(name, oldValue, newValue) {
        if (name === 'selected') {
            if (newValue !== null) {
                this.optionCheckbox.setAttribute('checked', '');
                this.optionCheckbox.checked = true;
            } else {
                this.optionCheckbox.removeAttribute('checked');
                this.optionCheckbox.checked = false;
            }
        }
    }
    render() {
        this.optionCheckbox.setAttribute('type', 'checkbox');
        this.optionCheckbox.classList.add('checkbox');
        const selected = this.hasAttribute('selected');
        if (selected) {
            this.optionCheckbox.setAttribute('checked', '');
            this.optionCheckbox.checked = true;
            this.optionCheckbox.setAttribute('value', this.getAttribute('value'));
        }
        const optioncontent = document.createElement('div');
        optioncontent.classList.add('content');
        optioncontent.innerHTML = `<slot></slot>`;
        const optionContainer = document.createElement('label');
        optionContainer.classList.add('container');
        optionContainer.appendChild(this.optionCheckbox);
        optionContainer.appendChild(optioncontent);
        return optionContainer;
    }
}

顯示個別資料用的組件

這個組件就只是用來 顯示個別資料

const multiChipStyle = `
    // CSS的部分省略
`
class MyMultiSelectChip extends HTMLElement {
    constructor() {
        super()
        this.attachShadow({mode: 'open'})
        const styleSheet = document.createElement('style')
        styleSheet.innerHTML = multiChipStyle
        this.shadowRoot.appendChild(styleSheet)
        this.shadowRoot.appendChild(this.render())
    }
    render() {
        return document.createElement('slot')
    }
}

所有組件串起來的組件

因為有多選,會需要以下幾個功能

  1. 選擇項目時,要根據己選擇的項目來注意新增或刪除己選擇的項目
  2. 根據己經選擇的項目,渲染資料。

重點部分

class MyMultiSelect extends HTMLElement {
    
    ...
    
    selectValueSet = new Set()
    haslock = false
    
    ...
    // 選擇項目的事件: 變成需要設定己選擇的項目,之後再來重新渲染資料
    selectOption(option) {
        return (e) => {
            if (!this.haslock) {
                this.haslock = true
                const item = this.optionMap.get(option)
                option.selected = true
                item.selected = true
                this.setValue(item)
                this.renderSelectContent()
            }
        }
    }
    // 設定己選擇的項目的事件
    setValue(item) {
        if (this.selectValueSet.has(item)) {
            this.selectValueSet.delete(item)
            return false
        } else {
            this.selectValueSet.add(item)
            return true
        }
    }
    // 重新渲染資料用的事件
    renderSelectContent() {
        this.selectContentNode.innerHTML = ''
        for (let item of this.selectValueSet) {
            const chip = document.createElement('my-multi-select-chip')
            chip.innerHTML = item.innerHTML
            this.selectContentNode.appendChild(chip)
        }
        requestAnimationFrame(() => {
            this.haslock = false
        })
    }
}

上一篇
web component 的實做- 單項下拉選單組件
下一篇
web component 的實做- virtualized list
系列文
web component - 次世代網頁技術的重要拼圖30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言