[AngularJS] テキストをダブルクリックして編集させるディレクティブ

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

angularjs125title

車輪開発大好きおたいがです。こんにちは。(挨拶)

AngularJS ディレクティブ開発の練度を高めるため、掲題通りのコンポーネントを作ってみました。名前は 'cmEditableText' とでもしておきます。

たとえば <p> 要素のテキストをマウスでダブルクリックしたときに <input type="text"> 要素に差し替かわり編集可能状態になり、編集を終えてフォーカスを解除したときには入力値が <p> 要素に適用される…という仕掛けになっています。

デモ

完成品はこちらです。

ディレクティブのタイプ、および、ngModel 連携

元々配置されていた <p> 要素や <td> 要素をお手軽に編集可能にするという設計思想に基づいて作ったため「属性 (Attribute) タイプ」に指定しています。

また、モデルとのバインディングを保持するために ngModel を必須属性としており NgModelController の機能を使っています。

angular.module('appName').directive('cmEditableText', function () {
    return {
        restrict : 'A', //属性 (Attribute) タイプのディレクティブであることを示す
        require  : '^ngModel', //ngModel が必須であることを示す
        …
<!-- 使用例 -->
<p cm-editavle-text ng-model="testValue" />

使用している NgModelController プロパティ、メソッドは以下のとおりです。

  • ngModel.$viewValue
    ngModel が保持している値
  • ngModel.$setViewValue()
    ngModel に紐づく DOM(view) の値を更新するメソッド

scope.$apply(fn) で正しくモデルの値を更新

<input type="text"> 要素からフォーカスが外れたとき、<input type="text"> 要素を削除して元の状態に戻しますが、このとき単純に ngModel.$setViewValue() を実行しただけではモデルの値が反映されません。jqLite のイベントハンドリングで DOM を操作しているため AngularJS のデータバインドが実行されないので、$apply(fn) を使用してモデルの値を変更して、値の変更を通知しています。

element.on('dblclick', function() {
    var clickTarget = angular.element(this);
    var EDITING_PROP = 'editing';
    if ( !clickTarget.hasClass(EDITING_PROP) ) {
        clickTarget.addClass(EDITING_PROP);
        clickTarget.html('<input type="text" value="' + ngModel.$viewValue + '" />');
        var inputElement = clickTarget.children();
        inputElement.on('focus', function() {
            inputElement.on('blur', function() {
                var inputValue = inputElement.val() || this.defaultValue;
                clickTarget.removeClass(EDITING_PROP).text(inputValue);
                inputElement.off();
                scope.$apply(function() {
                    ngModel.$setViewValue(inputValue);
                });
            });
        });
        inputElement[0].focus();
    }
});

まとめ

ngModel とディレクティブの関係を理解することによって作れる部品の幅は広がります。Angular の jqLite の制限は厳しいですが、頑張ればそれなりのモノが組めると思うので、興味をお持ちの方はぜひ試してみてください。