03.コントローラ
概要
コントローラはユーザーとのインタラクション処理の中心となります。
具体的には、
- イベントのハンドリング
- 使用するLogic、View(テンプレート)の宣言
- ライフサイクルイベントの定義
- DOM操作
などを行います。
ここでは、以下の項目建てで、コントローラの記述方法を説明します。
「コントローラ化」とは?
コントローラについて詳細に説明する前に、「コントローラ化」という言葉について説明します。
チュートリアル02.HelloWorldで示したように、hifiveではh5.core.controller()メソッドを呼ぶことで
コントローラ定義オブジェクトを要素にバインドし実際に動作する状態にします。
このとき内部では、コントローラを要素にバインドする前に
- コントローラ定義オブジェクトに基づいてインスタンスを生成(クローン)する
- デフォルトで使える便利な関数をインスタンスに追加する
- イベントハンドラを設定する
などの処理を行っており、これを「コントローラ化」と呼んでいます。
今の段階では、コントローラ化は
「h5.core.controller()メソッドを呼んで動作可能状態にすること」
と覚えておけばよいでしょう。
イベントハンドラの書き方
イベントハンドラとは、何らかのきっかけ(イベント)が起きたときに呼び出される関数のことです。
「イベント」の多くは、マウスのクリック、キーボード入力などのユーザー操作に対応して発生します。
コントローラが行う最も基本的な処理はイベント処理(イベントハンドリング)です。
hifiveでは、コントローラに特定の記法でプロパティを記述するとそれを自動的にイベントハンドラとして認識し、
ボタンがクリックされた、などのイベントが発生したときにその関数を呼び出すことができます。
基本構文
idがbtnである要素が押されたとき(clickイベントが発生する)に処理を行うコントローラを作成してみましょう。
__name: 'SampleController',
'#btn click': function(context, $el) {
alert('ボタンがクリックされました。');
}
};
2行目の__nameプロパティはコントローラの名前を表します(__nameプロパティは必須プロパティで、記述がない場合エラーが発生します)。
ここで指定する値はJavaやC#、Ruby等における「クラス名(パッケージ修飾付きの完全修飾クラス名)」に相当するもので、
hifiveフレームワークは基本的に、この名前が同一のものは同じもの(≒クラス)として扱います。
従って、コントローラごとに必ず異なる名前を付けてください。名前がバッティングした場合、動作が不正になる可能性があります。
*__nameプロパティの他に、ロジック・ビューテンプレート・子コントローラをプロパティとして宣言する事ができます(詳しくは、以降のステップで学びます)。
イベントハンドラ
4行目がイベントハンドラの定義です。イベントハンドラは、
// 処理
}
の構文で記述します。
と考えるとわかりやすいでしょう。
イベント名は、clickやsubmitといった既定のものだけではなく、独自のカスタムイベントも指定することができます。
コントローラ化
コントローラの定義が終わったら、要素にバインドします。バインドするときは、h5.core.controller()メソッドを呼びだします。
selector
- コントローラをバインドする要素を指定します。セレクタ、もしくは要素への参照のどちらかを指定します。
controller
- コントローラ定義オブジェクトを指定します。
param
- コントローラに渡す初期化パラメータを指定します。
Returns
- コントローラオブジェクトを返します。
イベントハンドラの引数
第1引数:イベントコンテキスト
イベントハンドラの第1引数には、イベントコンテキストオブジェクトが渡されます。
仮引数名は任意の名前にすることができますが、特別な理由がない限り「context」で統一しておくのがよいでしょう。
イベントコンテキストオブジェクトのプロパティは以下の通りです。
event
- jQueryEventオブジェクト
controller
- コントローラの参照
evArg
- triggerメソッドを使用して渡したパラメータ
第2引数:イベントターゲット要素
第2引数には、現在イベントを処理している要素が渡されます。
仮引数名は任意の名前にすることができますが、特別な理由がない限り「$el」で統一しておくのがよいでしょう("el"はelementの略)。
この引数は、$(context.event.currentTarget)と同等です(contextはハンドラの第1引数)。
つまり、イベントハンドラのセレクタで指定した要素をjQueryオブジェクト化したもの、が渡されます。
要素に直接イベントハンドラをセットするとそのハンドラが呼ばれたときの"this"は要素自身を指しますが、
これに相当するものです。
例:
<li class="listItem">リストアイテム</li> <!-- この要素をクリック -->
</ul>
__name: 'ListController',
'.listItem click': function(context, $el) {
//$elはli要素を指す
}
}
h5.core.controller('.list', listController);
なお、$elはデフォルトではjQueryオブジェクトが渡されますが、jQuery化せず
ネイティブの要素への参照を渡すようにすることもできます。
ネイティブの要素を渡してほしい場合は、以下のコードをh5.jsの読み込み後に記述してください(参照:listenerElementType)。
注:イベントがバブリングしている場合
上記の通り、$elは「現在イベントを処理している(セレクタで指定された)要素」を指します。
そのため、セレクタの要素が子要素を持ち、その子要素でイベントが発生した場合、
「イベントの発生元要素」と$elは異なります。
例:
<div class="parent">
<a class="target" href="#">LINK</a> <!-- この要素をクリック -->
</div>
</div>
__name: 'SampleController',
'.parent click': function(context, $el) {
//aタグをクリックした場合でも、セレクタで指定しているのは親のdiv要素なので
//$elは(parentクラスを指定した)div要素を指す。
//イベントの発生元要素を知りたい場合は$(context.event.target)と記述する。
},
'.target click': function(context, $el) {
//aタグ(を指定するセレクタ)を指定したハンドラでは、
//もちろん$elはaタグを指す。
}
};
h5.core.controller('#container', sampleController);
イベントハンドラの有効範囲
コントローラで定義したイベントハンドラは、原則として
「そのコントローラがバインドした要素の子要素でイベント発生した場合」にのみ動作します。
これにより、コントローラの責任範囲が明確になると同時に、たとえばセレクタの条件が広い場合に
コントローラの範囲外の要素で発生したイベントで不用意にハンドラが動作することを防ぐことができます。
従って、たとえば
HTML:
<div id="container">
<input type="button" name="click1" value="click1" />
</div>
<div>
<input type="button" name="click2" value="click2" />
</div>
</body>
JavaScript:
__name: 'SelectorController',
'input[type=button] click': function(context, $el) {
alert('clicked!');
}
};
h5.core.controller('#container', selectorController);
という例の場合、selectorControllerは<div id="container">の要素にバインドされているため、
click1ボタンを押下すると(click1は<div id="container">の子要素なので)メッセージが表示されますが
click2ボタンを押下しても何も起きません。
ただし、例外的に、コントローラの外側の要素を対象にしたい場合もあるでしょう。
画面に存在するすべての要素(コントローラの外側、もしくは横の要素を含む)をセレクタの対象としたい場合は
セレクタを中括弧で囲めばOKです。
上の例の場合、コントローラの定義を
__name: 'SelectorController',
'{input[type=button]} click': function(context, $el) { //セレクタを{}で囲んだ
alert('clicked!');
}
};
とすると、どちらのボタンを押下してもメッセージが表示されるようになります。
また、セレクタでは、“コントローラをバインドしている要素自身”を指定することはできません。
バインドしている要素自体で発生するイベントに対してハンドラを設定したい場合は、
セレクタの場所に「{rootElement}」と記述します。
__name: 'SelectorController',
'{rootElement} click': function(context, $el) { //{rootElement}と書くと、コントローラをバインドした要素自身を指す
alert('clicked!');
}
};
コントローラ化のライフサイクル
コントローラが要素にバインドされ動作する状態になるまでには、いくつかの段階(ライフサイクル)があります。
コントローラ化のライフサイクルは以下のようになっています。
特定のライフサイクル段階で処理を行いたい場合、特定のキー名で関数をセットしておくと、そのタイミングに達したときに呼び出されます。
__name: 'LifecycleSampleController',
__construct: function(context) {
alert('construct');
},
__init: function(context) {
alert('init');
},
__ready: function(context) {
alert('ready');
}
};
__construct
- コントローラ化が終わったタイミングで実行されます。リソース(ビューテンプレート)の読み込みは終わっていません。
__init
- コントローラ化と動作に必要なリソースの読み込みとViewへのバインドが終わったタイミングで実行されます。まだイベントハンドラはバインドされていま
せん。
__ready
- コントローラ化と動作に必要なリソースの読み込み、Viewとイベントハンドラのバインドが終わったタイミングで実行されます。
「コントローラが動作可能状態になったら何かしたい」場合、ほとんどのケースでは__readyイベントを使用すればよいでしょう。
コントローラの役割を理解し定義に従って書くことで、
自然とスコープが明確でいつ何が起きるのかわかりやすいコードになっていくはずです。
次は、画面(ビュー)の操作について説明します。
次のステップ ⇒ チュートリアル04.ビュー操作