AngularJSには強力なフォームのバリデーション機能があり、任意のタイミングでエラーメッセージを表示することができます。
このエラーメッセージをバリデーションの状態に連動してUI BootstrapのTooltipに表示したいとき、どうすればよいでしょうか。
まずは思い付く方法で
まずは最初に思い付いた方法で実装してみます。
ツールチップはtooltip-enable
ディレクティブで有効/無効を切り替えられるので、バリデーションに失敗したときだけ有効にして表示するようにしてみました。
index.html
...
<form name="form" style="width:500px;" ng-class="{'has-error': form.value1.$dirty && form.value1.$invalid}">
<table class="table">
<tr>
<th>お名前(20文字まで)</th>
<td>
<input type="text" class="form-control input-sm" name="value1" ng-required="true" ng-model="value1" tooltip="20文字以下で入力して下さい" tooltip-enable="form.value1.$dirty && form.value1.$invalid" ng-maxlength="20">
</td>
</tr>
</table>
</form>
...
app.js
angular.module('app', ['ui.bootstrap']);
結果
バリデーションエラー時にinputにマウスカーソルを乗せることでツールチップが表示されるようになりました。
しかし、これではinputにマウスカーソルを乗せるまでエラーの内容が分かりませんし、マウスカーソルを離すとツールチップが消えてしまうのでイマイチです。
これは、ツールチップの表示を切り替えるデフォルトのトリガーイベントがmouseenter/mouseleave
となっているためです。
...
// Default hide triggers for each show trigger
var triggerMap = {
'mouseenter': 'mouseleave',
'click': 'click',
'focus': 'blur',
'none': ''
};
...
.directive('tooltip', [ '$tooltip', function($tooltip) {
return $tooltip('tooltip', 'tooltip', 'mouseenter');
}])
...
tooltipのカスタムトリガーとカスタムディレクティブで実現する
Plunker
今回はバリデーションの状態変化に応じて自動的にツールチップを表示したいので、自由に使えるカスタムトリガーが必要になります。
まずは、適当なconfig
セクションで$tooltipProvider.setTriggers()
を使ってカスタムトリガーを定義しましょう。$tooltipProvider.setTriggers()
の引数には、表示イベント名をプロパティ名、非表示イベントをそのプロパティの値として持つオブジェクトを指定します。
app.js
var app = angular.module('app', ['ui.bootstrap', 'app.directives']);
// 追加
app.config(['$tooltipProvider', function ($tooltipProvider) {
$tooltipProvider.setTriggers({
'show': 'hide'
});
}]);
これで要素がshow
イベントを受け取った時にツールチップが表示されるようになりました。
次に、このトリガーを発火するための仕組みを作ります。
AngularJSのバリデーション機能は、バリデーション対象として指定したモデルの値の変化に応じてリアルタイムに実行され、実行結果がスコープにセットされるようになっています。上記index.htmlの例だと、form.value1.$invalid
やform.value1.$valid
のboolean値を読むことでバリデーションの状態を取得することができます。
今回はtooltip
ディレクティブを拡張するカスタムディレクティブを用意して、その中でform.value1.$invalid
を監視することで、バリデーションの状態が切り替わった時にshow
イベントまたはhide
イベントを発生させてツールチップの表示を切り替えるようにしました。
directives.js
angular.module('app.directives', [])
// ツールチップ本体に付加されるディレクティブ
// デフォルトのものをそのまま流用
.directive( 'myTooltipPopup', function () {
return {
restrict: 'EA',
replace: true,
scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
templateUrl: 'template/tooltip/tooltip-popup.html'
};
})
// tooltipディレクティブを拡張するカスタムディレクティブ
.directive('myTooltip', ['$tooltip', '$timeout', function ($tooltip, $timeout) {
// tooltipのインスタンスを作成する
var tooltip = $tooltip( 'myTooltip', 'myTooltip', 'show' );
var originalCompile = angular.copy(tooltip.compile);
tooltip.scope = {myTooltipShow : "&"}
// compileを拡張
tooltip.compile = function(element, attrs, scope){
// オリジナルのLink function
var originalLink = originalCompile(element, attrs);
// linkを拡張
var link = function(scope, element, attrs) {
// my-tooltip-show属性の値を監視する
scope.$watch(scope.myTooltipShow, function (val) {
if (val) { // 評価値がtrueならばshowイベントを発生させる
$timeout(function() {
element.triggerHandler('show');
});
}else{ // 評価値がfalseならばhideイベントを発生させる
$timeout(function() {
element.triggerHandler('hide');
});
}
});
// オリジナルのlinkを実行する
originalLink(scope, element, attrs);
}
return link;
}
return tooltip;
}])
index.html
...
<input type="text" class="form-control input-sm" name="value2" ng-required="true" ng-model="value2" my-tooltip-show="form.value2.$dirty && form.value2.$invalid" my-tooltip="20文字以下で入力して下さい" ng-maxlength="20">
...
これで、バリデーションの状態がエラーになった時点でツールチップが表示され、正常になった時点で非表示になるようになりました。
my-tooltip-show
にform.value2.$dirty
を加えているのは初期状態でツールチップが表示されるのを抑制するためです。
今回作成したものはPlunkerにデモを用意していますので、ご利用ください。
http://plnkr.co/edit/ynywEEpNvlx0iSA2bUpI?p=preview