他のライブラリとの統合

サードパーティライブラリや、pure JavaScriptのコードと統合するには、仮想エレメントのconfig属性を使って行うことができます。

統合コードやヘルパー関数をコンポーネント内にカプセル化できるため、この方法が推奨です。

次のサンプルは、select2ライブラリ(jQuery用の拡張版<select>タグを提供するライブラリ)を統合した、シンプルなコンポーネントの例です。

var Select2 = {
    // 選択ボックスを返す
    view: function(ctrl, attrs) {
        var selectedId = attrs.value().id;
        //SELECTエレメントの拡張であるSelect2を作成します
        return m("select", {config: Select2.config(attrs)}, [
            attrs.data.map(function(item) {
                var args = {value: item.id};
                // 選択されているオプションを設定します
                if(item.id == selectedId) {
                    args.selected = "selected";
                }
                return m("option", args, item.name);
            })
        ]);
    },
    /**
    Select2 configファクトリこのドキュメントのパラメータは、`ctrl`引数のプロパティについて説明しています。
    @param {Object} data - <option>リストに追加されるデータ
    @param {prop} value - `data`内の選択したいアイテムのプロパティ
    @param {function(Object id)} onchange - 選択が変更された時に呼び出されるイベントハンドラ
        `id`は`value`と同じです。
    */
    // 注意: configはサーバサイドでは実行されません。
    config: function(ctrl) {
        return function(element, isInitialized) {
            if(typeof jQuery !== 'undefined' && typeof jQuery.fn.select2 !== 'undefined') {
                var el = $(element);
                if (!isInitialized) {
                    el.select2()
                        .on("change", function(e) {
                            var id = el.select2("val");
                            m.startComputation();
                            // 選択されているオプションを設定します
                            ctrl.data.map(function(d){
                                if(d.id == id) {
                                    ctrl.value(d);
                                }
                            });

                            if (typeof ctrl.onchange == "function"){
                                ctrl.onchange(el.select2("val"));
                            }
                            m.endComputation();
                        });
                }
                el.val(ctrl.value().id).trigger("change");
            } else {
                console.warn('ERROR: ページにjqueryとSelect2がロードされていません');
            }
        };
    }
};

var Dashboard = {
    controller: function() {
        var ctrl = this,
          //ユーザ一覧のリスト
          data = [
            {id: 1, name: "ジョン"},
            {id: 2, name: "メアリー"},
            {id: 3, name: "セニカ"}
          ];

        ctrl.data = data;
        // 現在のユーザはpropに格納します
        ctrl.currentUser = m.prop(data[1]);
        ctrl.changeUser = function(id) {
          console.log(id);
        };
    },

    view: function(ctrl) {
        return m("div", [
            m("label", "ユーザ:"),
            m.component(Select2, {
              data: ctrl.data,
              value: ctrl.currentUser,
              onchange: ctrl.changeUser
            })
        ]);
    }
};

m.mount(document.body, Dashboard);

select2.configは、与えられたコントローラに対応するconfig関数を作成するファクトリー関数です。テンプレートが散乱するのを避けるために、select2.view関数の外側にこの関数を定義します。

このファクトリ関数で作成されたconfig関数は、初期化関数だけを実行します。ただし、すでに初期化済みの場合は何もしません。この関数はMithrilの自動再描画システムから何度も呼び出されますし、描画のたびに何度もselect2を初期化するのはうれしくないため、このif文は大切です。

初期化コードはchangeイベントハンドラを定義します。仮想エレメントの中でこの属性を使用しなかった場合など、もしこのハンドラがMithrilのテンプレートで作成されていない場合は、手動で自動再描画システムに統合する必要があります。

統合するには関数の先頭でm.startComputationを呼び、関数の最後でm.endComputationを呼び出すだけです。もしスレッドの統合が終わっていなければ、これらの関数のペアをすべての非同期実行スレッドのコードに追加します。

例えば、もし非同期のウェブサービス呼び出しにm.requestを使っていれば、これ以上m.startComputation / m.endComputation呼び出しを追加する必要はありませんが、イベントハンドラには追加しなければなりません。

そうではなくjQueryを使ってウェブサービス呼び出しをしているのであれば、changeイベントハンドラ内にこの関数のペアを置く以外に、jQueryのajax呼び出しの前にm.startComputationを追加し、コールバックが完了した後にm.endComputation呼び出しを追加します。サンプルについては自動描画システムのガイドを参照してください。

configメソッドについて注意すべきことは、config関数の実行スレッド内では、m.redrawm.startComputationm.endComputationを呼び出さないことです。(実行スレッドは基本的に、他の非同期スレッドが実行する前に、ある程度の量のコードを含んでいます。)

Mithrilは技術的にこのユースケースをサポートしていないため、再描画が何度も行われてパフォーマンスが低下したりしますし、場合によってはアプリケーションコードが無限ループして、デバッグが極めて難しい状況になったりします。

サンプルのdashboardコンポーネントを見ると、どのようにselect2コンポーネントを使用するかが分かります。

統合コンポーネントを作成するときは、常にドキュメントを作成し、他の人がコンポーネントの初期化時にどの属性パラメータが使用できるか分かるようにすべきです。


レガシーコードとの統合

もし、個別のウィジェットを同じページ内の異なる場所に置きたい場合には、通常のMithrilアプリケーションと同じように、m.renderm.mountm.routeなどを使ってそれぞれのウィジェットを初期化することができます。

1つだけ注意すべきことがあるとしたら、単純にこれら複数の「島」を初期化してしまった時に、それぞれの初期化呼び出しがお互いを認識せずに、再描画処理が想定上に発生してしまう、ということを避けなければならない、ということです。レンダリングを初期化するには、それぞれの実行スレッドの中で、最初のウィジェットの初期化の前にm.startComputationを呼び出し、最後のウィジェットの初期化の後にm.endComputationを呼び出すだけです。

m.startComputation()

m.mount(document.getElementById("widget1-container"), Widget1)

m.mount(document.getElementById("widget2-container"), Widget2)

m.endComputation()