入力コンポーネントを作ってみる(2)
ngModelと連携した実装
続いて、ngModelと連動するようそれぞれの中身を実装していきます。buttonGroupディレクティブで使用されるButtonGroupControllerの中身はリスト7のようになります。
function ButtonGroupController($scope){
var items = [];
//(1)選択(クリック)されたボタンのスタイルを変更する
function select(value){
for(var i = 0; i < items.length; i++){
var $_scope = items[i];
if($_scope.value == value){
$_scope.active = true;
}
else{
$_scope.active = false;
}
}
}
//(2)buttonItemの$scopeを追加するメソッド
this.addItem = function($_scope){
items.push($_scope);
};
//(3)外部からの変更を受け付けるメソッド
this.setValue = function(val){
select(val);
};
//(4)コンポーネント内での変更を受け付けるメソッド
this.select = function($_scope){
select($_scope.value);
//(5)変更時にはイベントでディレクティブへ値を通知する
$scope.$emit('change',$_scope.value);
}
}
(1)ではボタンがクリックされた時にアクティブな要素の指定を行う共通関数を定義しています。また、(2)では子のbuttonItemディレクティブ(スコープ)を把握できるようにメソッドを作成します。このメソッドは、buttonItemディレクティブでのlinkメソッドから利用します。
続いて(3)ではコンポーネント外で値が変わったときに、その値を反映させるためのメソッドです。そして、最後にコンポーネントでの値の変更、つまり、ボタンをクリックしたときに実行されるメソッドが(4)のようになります。
(3)と同様にボタンをアクティブな値の設定を行いつつ、(5)のように変更のタイミングでイベントは発火するようにしています。これは、スコープを共有するbuttonGroupに変更を通知するためです。
続いて、buttonItemのlinkメソッドはリスト8のように実装します。
//(1)buttonGroupControllerの利用
var link = function($scope,$element,$attrs,buttonGroupController){
//(2)buttonGroupControllerへ追加
buttonGroupController.addItem($scope);
//(3)要素を選択した時の処理
$element.click(function(){
buttonGroupController.select($scope);
});
};
(1)ではlinkメソッドの第4引数でbuttonGroupControllerのインスタンスを取得しています。そして、(2)では先ほど作成したaddItemメソッドで要素オブジェクトの$elementを登録することと、その要素がクリックされた時に(3)のようにselectメソッドを呼びます。
ここまでできれば、見た目の部分の作成は完成したといえます。
続いて、ngModelとの連携をするために、buttonGroupディレクティブのlinkメソッドをリスト9のように実装します。
var link = function($scope,$element,$attrs,controllers){
//(1)requireで指定した順番でcontrollerが配列で取得可能
var buttonGroupController = controllers[0];
var ngModelController = controllers[1];
//(2)ngModelの値が外部より変更されたときによばれるメソッドの定義
ngModelController.$render = function(){
buttonGroupController.setValue(ngModelController.$viewValue);
};
//(3)値が変わったときにngModelで双方向バインディングされている変数への値の設定
$scope.$on('change',function(evt,val){
ngModelController.$setViewValue(val);
});
};
buttonGroupディレクティブのrequire属性では、配列を用いて依存を指定したため、linkメソッドの第4引数のコントローラも配列でわたってきます。従って、(1)のようにコントローラを取得する際には順番に気をつけて取得する必要があります。
(2)では、外部から値を変更されたときの処理で、NgModelControllerでは値の変更時には$renderメソッドが呼ばれますのでその処理を定義します。また、コンポーネントから値が変更されたときは(3)のように$setViewValueメソッドで値を変更します。その他、ここで使っている関数や変数の意味を知るにはNgModelControllerのリファレンスを参照して下さい。
もっと便利なコンポーネントにするために
ngModelを使えばformディレクティブと連携して機能することができます。ただし、これまでの実装だけではformディレクティブを使う際に有効なバリデーションが機能していません。
サンプルコードでは実際にformディレクティブとバリデーション部分で連携するように追加のコードがあります。
これらのサンプルコードを参考に、より便利なコンポーネントに自分好みのカスタマイズをしていただければと思います。
最後に
実はBootstrapとAngularJSを連携したライブラリはUI Bootstrapというものが既に存在します。従って、早急にBootstrapを組み込みたいならこのライブラリを使えばよいのですが、汎用的に作られているので指定できるオプションも非常に多岐にわたり、Bootstrapを知らないと使いにくいところがあります。実際のプロジェクトなどでは用途も定まっているために、もっと簡略化するためや、ng-includeなどの代わりにカスタムディレクティブを使えば、さらに効率のよい開発ができるのではないかと思います。
