Improve this

Onsen UIの拡張

この章では、Onsen UIを拡張したりカスタマイズしたりする方法について説明します。

Custom Elementsのオーバーライド

もしOnsen UIの各コンポーネントのTMLコードをカスタマイズしたい場合は、コンポーネントに対応するCustom Elementsの定義にアクセスし、オーバーライドしてください。参考として、各コンポーネントのソースコードはcore/src/elementsディレクトリで閲覧できます。

フレームワークバインディングの作成

Onsen UIはWeb Components(標準)の上に実装されているため、純粋なJavaScriptや任意のJSフレームワークと組み合わせて使うことができます。特に、jQuery のように単純に利便性レイヤーを追加するようなライブラリやフレームワークと組み合わせて使う場合は複雑なことはありません。しかし、DOM自体を管理したりDOMをコンパイルしたりするようなより複雑なフレームワークでは、それらのフレームワークの流儀でどうやって新しい要素を作ってロードすれば良いのかをOnsen UIに伝えるために、いくらかの追加ロジックを実装する必要があります。私たちはこれを「バインディング(binding)」と呼んでいます。

たいていの場合バインディングは、処理のタイミングやDOM操作の問題を修正するだけのシンプルなラッパーを作るだけで済みます。しかし場合によっては、各フレームワーク固有の機能をサポートするためにオプショナルな追加ロジックを実装することもあります。例えばAngularJS 1では、属性を使ってイベントハンドラを書き、コントローラのスコープの中でイベントハンドラのコードを実行するというスタイルが一般的です。そのためOnsenUIのAngularJS 1バインディングでは、そういったスタイルをバインディングでサポートするために、バインディング側で属性(ディレクティブ)を定義し、バインディング側でイベントリスナを追加し、バインディング側でイベントハンドラのコードを実行するようにしています。バインディングの開発では、こういった取り組みによって、フレームワークとの連続性を感じさせるような洗練されたコードスタイルを実現することができます。

Onsen UIは、最も広く普及していてかつ強力なフレームワークであるReactやAngularJS 1、Angular 2+、Vueなどに対して既にバインディングを用意しています。なお、Knockout.jsやjQueryなどのライブラリやフレームワークについては、ラッパーすら不要なほどシンプルなため、バインディングは用意していません。

このガイドでは、まだバインディングが用意されていないフレームワークに対して、Onsen UIをどのように拡張・設定すれば良いかを説明します。

ルーティングコンポーネントとページローダー

ページルーティングは、バインディングを作成する際によく修正することになる重要な要素です。ルーティングコンポーネント(ナビゲーターやタブバー、スプリッター)内で新しいページを作る時、Onsen UIはテンプレートの内容(文字列形式)を読み取ったり、外部ファイルにリクエストを送ってコンテンツ(文字列形式)を取得したりします。その後、その文字列がHTMLに変換され、DOMに追加されます。これらの工程は全てons.pageLoaderを通して行われ、バインディングを作成する対象のフレームワークのニーズに応じてons.pageLoaderを修正することもできます。

いずれのルーティングコンポーネントもpageLoaderプロパティを持っており、これは新しいもので上書きできます。pageLoaderコンストラクタは引数としてloader関数とunloader関数の2つを必要とします:

const loader = ({page, parent, params}, done) => {
  myCustomBindings.createPageElementFrom(page)
    .then((pageElement) => {
      parent.appendChild(pageElement);
      done(pageElement);
    })
};

const unloader = (pageElement) => {
  myCustomBindings.cleanAllRelatedTo(pageElement);
  pageElement.remove();
}

myNavigator.pageLoader = new ons.pageLoader(loader, unloader);

loader関数は2つの引数を受け取ります。第1引数は単なるオブジェクトであり、pageparentparamsの3つのプロパティを持ちます。第2引数はdoneという名前のコールバック関数で、引数にはページロード処理の結果が入ります。parentプロパティにはons-navigatorons-splitter-contentのようなHTMLElement要素が入り、この要素にページのロードが行われます。pageプロパティはバインディングに「ページを表すもの」を伝えるための引数です。例えば、Onsen UIコアのons-navigatorでは、myNavigator.pushPage('page.html', options)のようにpage.htmlという文字列を「ページを表すもの」として受け取りますが、pushPage自身はpage.htmlという文字列の解釈を行わず、loader関数に委譲します。この時、loader関数にpageプロパティの値としてpage.htmlが渡ります。pageプロパティの値の解釈は常にloader関数の中で行われます。Onsen UIコアの場合は、コアはpageプロパティに入っている文字列をテンプレートIDもしくはURLと解釈し、それに基づいてページのHTMLコンテンツを取得します。Angular 2+の場合は、バインディングはpageプロパティの値をコンポーネントクラスと解釈し、バインディングはそれを元にページコンポーネントの作成を行います。最後に、paramsプロパティはルーティングコンポーネントに追加オプションを渡すためのオブジェクトです。

一方でunloader関数はとても単純です。pageElement要素を引数として受け取り、要素に関連する全てのものをクリーン(後片付け)するだけの関数です。なお、もしあなたの作成しているバインディングにおいて、要素の外部に何らかの情報を格納し、その情報にアクセスしたいというような状況が生じている場合は、JavaScriptのWeakMapを利用するのがひとつの方策です。loader関数の中で要素自身をキーとして要素の情報をWeakMapに登録し、unloader関数の中で要素をキーとして情報を取り出すといった実装が可能なためです。

コンテンツ系コンポーネントのラッパー

コンテンツ系コンポーネント(content component)のほとんどはシンプルなHTML+CSSにいくらかの付加的な機能が加わったものです。しかし、一部のフレームワークの癖(peculiarity)や機能によって、処理タイミングの問題や、その他細かな問題が起こることがあります。通常、それらの問題はOnsen UIコアのコンポーネントのラッパーを作り、それを代わりに使うことで解決します。ラッパーを作ると、イベントハンドラを実装したり、コンポーネントを必要に応じて修正したりする処理を加えることができます。例えば、AngularJS 1におけるons-inputは、ng-model属性がきちんと動くようにするために、値をAngularJS 1の$parseでパースする必要があります。

実際の例につきましては、Onsen UIに既に実装されているバインディング群のソースコードをご覧ください。なお、新しいバインディングの作成や既存のバインディングの改善に関するプルリクエストはいつでも歓迎しています。お気軽にどうぞ。

他の面白い例としてはKnockout.js + Onsen UIがあります。この例ではシンプルなイベントリスナーでOnsen UIのページにKnockoutの機能を追加することができています。なお、前述したページローダーの改造でも、この例と同等のことが実現できます。