javascript:カスタムイベントを使ったイベントハンドラの実装


動機、やること

  • javascriptに(いつからか)CustomEventというユーザ定義のイベント発行機能が存在する(していた)ことを知った
  • オブジェクト間のメッセージング処理に使いたい
  • 恒例のIE独自仕様やイベントオブジェクトのdetailプロパティにカスタムデータを渡す仕様などは、僕の脳内の希少なワークスペースからご退出いただきたい
  • => CustomEventをラッパしたイベント処理を実装しよう!

開発環境

前提として環境は以下のような感じ。

  • npm
  • webpack3
  • babel
  • es6っぽく

実装(ライブラリ)

以下の実装全体をgithubにアップしました。差分がありましたらgithubを正とさせてください。。

イベント処理の実装本体はこんな感じ。

event.js
/**
 * 指定したkey名のカスタムイベントを送信する
 * @method send
 * @param  {string} key   カスタムイベント名
 * @param  {object} value データ
 */
export const dispatch = (key, value) => {
    let ev;
    try {
        ev = new CustomEvent(key, {detail: value});
    } catch(e) { // for IE
        ev = document.createEvent('CustomEvent');
        ev.initCustomEvent(key, false, false, value);
    }
    document.getElementsByTagName('body')[0].dispatchEvent(ev);
};

/**
 * カスタムイベントのリスナーを登録する
 * @method listen
 * @param  {string}   key      カスタムイベント名
 * @param  {Function} callback イベントリスナーから呼ばれる関数
 */
export const listen = (key, callback) => {
    document.getElementsByTagName('body')[0].addEventListener(key, callback);
};

今回はクラス間のイベント送受信が目的だったため、HTML上で確実に存在するbodyタグを固定で使用してイベント発行を行っています。

また、IEだけCustomEventコンストラクタが使えない独自仕様なので、IEの実装を吸収してあげます。
#こちら、出典サイトを忘れてしまったので(qiita内だったような)、ご存知の方は名乗り上げていただければ幸いです(他薦も可)。

実装(イベントリスナ側)

イベントリスナ側の実装は以下のような感じ。

listener.js
import { listen } from './event.js';
listen('app:ping', (e) => alert(`ping - ${e.detail}`));

実装(イベント発行側)

イベント発行側はこんな感じ。
ボタンのclickイベントでカスタムイベントを発行します。

dispatcher.js
import { dispatch } from './event.js';
document.getElementByTagName('button')[0].onclick = () =>  dispatch('app:ping', 'pong');

実装(その他)

ビルドのエントリポイントになるapp.jsを作成します

app.js
require('./listener.js');
require('./dispatcher.js');

ついでにwebpack.config.js

webpack.config.js
const path = require('path');
const webpack = require('webpack');

module.exports = {
    entry: {
        'app': './app.js'
    },
    output: {
        path: path.resolve(__dirname, "./"),
        filename: 'static/js/[name].js',
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: [/node_modules/],
                use:[{
                    loader: "babel-loader",
                    options:{
                        presets:[
                            ['env', {'modules': false}]
                        ]
                    }
                }]
            },
        ]
    },
    devServer: {
        contentBase: path.resolve(__dirname, "./"),
        port:3000,
    },
    devtool: 'source-map',
};

package.jsonを適当に書いておきます。
npm initで生成し、依存関係を追加するなどしておいてください。
今回はbabelとwebpack、およびwebpack-dev-serverを使います。

package.json
{
    "name": "js-customevent",
    "version": "0.0.1",
    "description": "CustomEvent wrapper",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "start": "node_modules/.bin/webpack-dev-server"
    },
    "author": "",
    "license": "ISC",
    "devDependencies": {
        "babel-core": "^6.26.0",
        "babel-loader": "^7.1.2",
        "babel-preset-env": "^1.6.1",
        "webpack": "^3.10.0",
        "webpack-dev-server": "^2.11.1"
    }
}

HTMLは最低限こんな感じ。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
</head>
<body>
    <button type="button">ボタン</button>
</body>
<script src="/static/js/app.js"></script>
</html>

上記ファイルをすべて同じフォルダに格納してください。

動作確認

上記準備が整った後で、ルートディレクトリ下で以下を実行します。

$ npm install
$ npm start

ブラウザから、http://localhost:3000/ にアクセスしてください。
ボタンを押して、以下のようなalertが表示したら動作確認完了です。

dispatch.js側で渡した「pong」というメッセージがlistener.jsで実行したイベントリスナに捕捉されて、alertに反映しました。

まとめ

javascriptのCustomEventを使ったイベントハンドリングについてご紹介しました。ご参考になれば幸いです。

こちらの現場からは以上です。