独自コンポーネントでng-modelを使い、値を変更・参照する方法。
AngularJS1を使ったウェブアプリで要素を選択させる際に通常のselect〜optionを使っていたのだけど、環境によって見た目が異なる、CSS Transformでscaleを使っていても無視して元のフォントサイズで描画される、スタイルを上手く指定できない、という問題があったため別の手段を使う必要があった。
Bootstrapのドロップダウンを使おうともしたんだけど、選択ボックスに選択された候補のテキストを当て込むようにしたい、そのため横幅を選択させる候補中の最大のものにしたい、それとBootstrapのドロップダウンは普通のインライン要素にできない?ので独自で実装することにした。
デモ
使用側
<my-select items="['apple', 'banana', 'carrot']" |
仮にコンポーネントのタグをmy-selectとして、itemsに配列を渡すとその中から1つをユーザが選択できる。
選択された項目はng-modelで指定した変数に格納される。
変更はng-changeで通知される。
- itemsの各要素は固定で、状態によって変わらないという前提
- 選択された項目はそのインデクスが
ng-modelで指定した変数に格納されるものとする- 未選択の場合に
-1を指定しておくことで、表示の位置調整を簡潔にする
- 未選択の場合に
- 変更があったら
ng-changeが実行される
コンポーネントの実装
クラス定義:
class MySelect { |
$element.controller('ngModel')でタグの属性として指定されたngModelのコントローラを取得できる- 値の変更を通知するときに使用するため、保持しておく
$onInit() { |
- 初期化で
itemsの各要素の幅を調べ、一番広いものを選択ボックスの幅とする - 要素の高さは選択ボックスの高さから求める(ボーダーが1pxという前提…)
- なぜ
$timeoutで待たなければいけないのかよくわからない…
テキストの幅計算:
static calcTextWidth(parent, text) { |
- 埋め込み先の要素にテキストを生成し、幅を取得する
white-space:nowrapとした上でテキストを設定し、offsetWidthで取得
選択ボックスがクリックされたとき:
onClick($event) { |
- ポップアップを開くためにスタイルを変更
- その際、フェードインさせるため
opacityを操作(CSSでtransition-durationを指定しておく) - フェードインが効かないことがある…なぜだ
- その際、フェードインさせるため
- 現在選択されている項目がポップアップ内の項目と合うように、現在の値(
this.ngModel.$viewValue)から位置を計算 - ポップアップやその他どこかをクリックされた時に閉じるために、
documentにクリックハンドラを設定する
項目をクリックされたとき:
onClickItem($event, index) { |
ngModelの$setViewValue()で、ng-modelで指定された変数に設定できる- イベントの伝播を止めてやらないと、クリックイベントが選択ボックスに渡って再度開いてしまう
ポップアップを閉じる
closePopup() { |
- フェードアウトさせてから非表示にする
コンポーネント定義:
angular.module('mySelect', []) |
require: 'ngModel'で必須にしてやるngModelを扱うにはdirectiveを使い、linkでなんか処理する必要があるのかと思っていたが、componentだけでできたngModelを使用すれば自動的にngChangeが呼び出される?ようで、明示的に扱う必要はなかった
HTML:
<span class="select" |
- 未指定の場合(テキストが空の場合)に見た目を変えられるように、
emptyクラスを追加してやる - ポップアップ中の選択されている要素には
selectedクラスを追加する
SCSS:
my-select { |
- 選択ボックス、ポップアップなどは
display: inline-blockを指定 - 選択ボックスを
position:relativeにしてポップアップをposition:absoluteにすることで、ポップアップを選択ボックスからの相対位置で指定できるようにする - ポップアップ要素のhtmlは選択ボックス内の後の方に記述することで、
z-indexを指定しなくても他の要素より手前に描画されている(かぶる位置にz-indexを指定している要素がない前提)