封裝事件處理


ECMAScript 本質部份〉這一系列文件,之所以要作為實驗性質的文件,主要是想試試,如果有機會拋棄一些包袱的話,在瀏覽器上寫 JavaScript 等,應該要做什麼樣的思考與設計,而在這一系列中,拋棄的最大包袱就是 Internet Explorer,如果你想要考量 Internet Explorer 相容性,可以參考〈JavaScript 本質部份〉。

在事件這邊,由於拋棄了 Internet Explorer 事件模型,相對於〈JavaScript 本質部份〉中對應的文件,你會發現事件封裝時簡單多了,可以在一個獨立的 Evt-1.0.0.js 中撰寫事件模組:

// 主要在允許事件處理器以傳回 false 的方式停止預設行為
function addEvtOn(elem, evtType, handler, capture = false) {
    elem.addEventListener(evtType, evt => {
        let result = handler.call(evt.currentTarget, evt);
        if(result === false) {
            evt.preventDefault();
        }
        return result;
    }, capture);
}

function removeEvtOn(elem, evtType, handler, capture = false) {
    elem.removeEventListener(evtType, handler, capture);
}

export {addEvtOn, removeEvtOn};

capture 預設為 false 是合理的,在過去為了相容於 Internet Explorer 不支援事件捕捉階段,許多程式庫與應用都未曾考量過捕捉階段的作法,當然,如果不考量 Internet Explorer,也可以試著思考捕捉階段可能的應用場合。

原本的 XD-1.0.0.js 現在變成了 XD-1.1.0.js,主要修改為匯入了 Evt-1.0.0.js:

import {addEvtOn, removeEvtOn} from './Evt-1.0.0.js';

然後在 ElemCollection 類別上,新增了 addEvtremoveEvt 兩個方法:

class ElemCollection {

    ...

    // 新增事件處理
    addEvt(type, handler, capture = false) {
        this.elems.forEach(elem => addEvtOn(elem, type, handler, capture));
        return this;
    }

    // 移除事件處理
    removeEvt(type, handler, capture = false) {
        this.elems.forEach(elem => removeEvtOn(elem, type, handler, capture));
        return this;
    }
}

其餘程式碼不變。來看看〈封裝 DOM 操作〉中動態新增圖片的範例,現在可以改寫成底下(你的瀏覽器必須支援 ES6 模組):

<!DOCTYPE html>
<html>
<head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width">
</head>
<body>

    <input id="src" type="text"><button id="add">新增圖片</button>
    <div id="images"></div>

<script type="module">
    import x from './js/XD-1.1.0.js';

    let doc = x(document);

    doc.elemsById('add').addEvt('click', evt => {

        let img = x('img').toElemCollection()
                          .attr('src', doc.elemsById('src').val())
                          .addEvt('click', evt => {
                              let target = evt.target;
                              target.parentNode.removeChild(target);
                          });      

        doc.elemsById('images').append(img);   

    });
</script>

</body>
</html>

按我觀看結果

至於接下來的範例,也可以改寫為:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body>  
    容器一:
    <div id="container1">
        <img id="image" src="https://openhome.cc/Gossip/images/caterpillar_small.jpg"/>
    </div><br>
    容器二:
    <div id="container2"></div>  

<script type="module">
    import {elemsById} from './js/XD-1.1.0.js';

    let image = elemsById('image');

    image.addEvt('click', evt => {
        let c1 = elemsById('container1');
        let c2 = elemsById('container2');
        if(evt.target.parentNode === c1.get()) {
            c2.append(image);
        } else {
            c1.append(image);
        }
    });

</script>  
</body>
</html>

按我觀看結果

可以在 XD-1.1.0.jsEvt-1.0.0.js 分別下載完整的 .js 檔案。