作成者別アーカイブ: y_momose

AngularJS 自作ディレクティブ集

| コメントをどうぞ

シンプルなものがほとんどですが、今後も使えそうなものもあるので備忘録として載せておきます。

imgタグをsvgに置き換える

svgクラスが付加されたimgタグをsrcに指定されたsvgに置き換えます。CSSの疑似クラスを使ってsvg内の要素に効果を付けたい時などに使えます。(注:jQueryのロードが必要です)


.directive('img2svg', function($http){
  return {
    restrict: 'EA',
    link: function(scope, elm, attrs){
      $this = $(elm);
      $this.find('img.svg').each(function(){
        var $img = $(this);
        var imgURL = $img.attr('src');

        $http.get(imgURL, {cache:true}).success(function(data) {
          var $svg = $(data)[4];
          $img.replaceWith($svg);
        }, 'xml');
      });
    }
  }
})

サンプル:

<div img2svg>
  <img class="svg" src="hoge.svg">
  <img class="svg" src="piyo.svg">
</div>

特定のイベントを受け取った時に要素にフォーカスを移す

属性値に指定したイベントを要素が受け取った時にフォーカスを移します。


.directive('focusOnEvent', ['$timeout', function($timeout) {
    return {
      restrict:'A',
      link: function(scope, element, attrs) {
        scope.$on(attrs.ngFocusOnEvent, function(){
          $timeout(function() { 
            element[0].focus();
          });
        });
      }
    };
}])

サンプル:
HTML

<body ng-app="sample" ng-controller="SampleCtrl as sample">
  <button ng-click="sample.trigger('btnClicked')">フォーカスを移す</button>
  <input type="text" ng-focus-on-event="btnClicked">
</body>

Controller


.controller('SampleCtrl', ['$scope', function($scope){
  this.trigger = function(event){
    $scope.$broadcast(event);
  }
}]);

指定した値(モデル)と同じ値を持たないかチェック

バリデーション用のディレクティブです。ディレクティブを付与した要素のモデルの値とディレクティブで指定したモデルの値が一致しないかチェックします。


.directive('notSame', function(){
  return {
    require: 'ngModel',
    link: function(scope, $element, attrs, ctrl){
      var validator = function(viewValue) {
        var targetVal = scope.$eval(attrs.notSame);
        if(!viewValue) return viewValue;
        if(viewValue == targetVal){
          ctrl.$setValidity('notSame', false);
          return viewValue;
        }else{
          ctrl.$setValidity('notSame', true);
          return viewValue;
        }
      }
      ctrl.$parsers.unshift(validator);
      ctrl.$formatters.unshift(validator);
    }
  }
})

サンプル:

<div ng-form="form" ng-init="text1='hoge'; text2='hoge'">
  <input type="text" ng-model="text2" not-same="text1" name="textInput2">
  <span ng-show="form.textInput2.$error.notSame">エラー</span>
  <input type="text" ng-model="text3" not-same="'piyo'" name="textInput3">
  <span ng-show="form.textInput3.$error.notSame">エラー</span>
</div>

指定した値(モデル)以上かチェック

バリデーション用のディレクティブです。ディレクティブを付与した要素のモデルの値が指定した値以上かチェックします。不等号を変えれば以下や未満などもできると思います。


.directive('greaterEqual', function(){
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, $element, attrs, ctrl){
      var validate = function(viewValue) {
        var targetVal = scope.$eval(attrs.greaterEqual);
        if(viewValue === undefined || viewValue.length == 0) {
          ctrl.$setValidity('greaterEqual', true);
          return viewValue;
        }
        if(viewValue < targetVal || !isFinite(viewValue)){
          ctrl.$setValidity('greaterEqual', false);
          return viewValue;
        }else{
          ctrl.$setValidity('greaterEqual', true);
          return viewValue;
        }
      };
      ctrl.$parsers.unshift(validate);
      ctrl.$formatters.unshift(validate);
    }
  }
})

ブラウザのリサイズ時に何か処理を行う

サイズ変更時にブラウザが発生させるイベントを拾って属性値に指定したイベントハンドラを実行します。また、スコープにイベントを通知します。


.directive('resize', ['$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout){
  return {
    restrict: 'EA',
    scope: {
      resize: '&'
    },
    link: function(scope){
      var timer = false;
      angular.element($window).on('load resize', function(e){
        if(timer) $timeout.cancel(timer);

        timer = $timeout(function(){
          scope.resize();
        }, 200);
        
      });
    }
  }
}]);

サンプル:
HTML

<body ng-app="sample" ng-controller="SampleCtrl as sample">
  <div resize="sample.resizeHandler()"></div>
</body>

Controller


.controller('SampleCtrl', ['$scope', function($scope){
  this.trigger = function(event){
    $scope.$broadcast(event);
  }

  this.resizeHandler = function(){
    alert('リサイズされました');
  }
}]);

バリデーションエラーをUI Bootstrapのtooltipに表示させる

以前の記事をご覧ください。

AngularJSでUI BootstrapのTooltipをValidationに連動させて表示させる

| コメントをどうぞ

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にマウスカーソルを乗せることでツールチップが表示されるようになりました。

tooltip1

しかし、これではinputにマウスカーソルを乗せるまでエラーの内容が分かりませんし、マウスカーソルを離すとツールチップが消えてしまうのでイマイチです。
これは、ツールチップの表示を切り替えるデフォルトのトリガーイベントがmouseenter/mouseleaveとなっているためです。

tooltip.js

...

  // 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.$invalidform.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-showform.value2.$dirtyを加えているのは初期状態でツールチップが表示されるのを抑制するためです。

今回作成したものはPlunkerにデモを用意していますので、ご利用ください。
http://plnkr.co/edit/ynywEEpNvlx0iSA2bUpI?p=preview