37

'Click everywhere butthiselement'イベントをどのように実装するのか疑問に思っていました。

ファイルエクスプローラーのファイルのリストと比較できるものがあります。特定の要素を選択できますが、要素コントローラーの外側をクリックすると、すべての選択を解除する必要があります。

ここに画像の説明を入力してください

より明確にするためにスクリーンショットを追加しました。ですから、私がやりたいのは、言語要素以外の場所をクリックすると、イベントが発生するということです。

アップデート

明確にするために、私はjQueryでこれをどのように行うことができるかを尋ねていません。

4

8 に答える 8

68

編集:この古い、古い回答にはいくつかの問題がありました。

*また: コミュニティ Wiki にマークを付けます (私にはポイントがありません)。エラーのためです。

  1. ディレクティブの N 回の使用に対する N 回の呼び出し。これは、一致する式を持つ同じスコープ内での使用にはおそらく望ましくありません。

  2. イベントハンドラを破壊するものは何もありません!!!! 悪い!悪い!悪い!

だから、私はこの答えを更新しています。うまくいけば、それは誰にもあまり迷惑をかけませんでした.

更新された回答

これは、これらの問題が修正された新しいプランカーです...個々のアプリケーション開発者が遭遇する他の問題がある可能性があります。これは、この問題を処理する方法の単なる例です。

app.factory('clickAnywhereButHereService', function($document){
  var tracker = [];
  
  return function($scope, expr) {
    var i, t, len;
    for(i = 0, len = tracker.length; i < len; i++) {
      t = tracker[i];
      if(t.expr === expr && t.scope === $scope) {
        return t;    
      }
    }
    var handler = function() {
      $scope.$apply(expr);
    };
    
    $document.on('click', handler);
    
    // IMPORTANT! Tear down this event handler when the scope is destroyed.
    $scope.$on('$destroy', function(){
      $document.off('click', handler);
    });
    
    t = { scope: $scope, expr: expr };
    tracker.push(t);
    return t;
  };
});

app.directive('clickAnywhereButHere', function($document, clickAnywhereButHereService){
  return {
    restrict: 'A',
    link: function(scope, elem, attr, ctrl) {
      var handler = function(e) {
        e.stopPropagation();
      };
      elem.on('click', handler);
      
      scope.$on('$destroy', function(){
        elem.off('click', handler);
      });
      
      clickAnywhereButHereService(scope, attr.clickAnywhereButHere);
    }
  };
});

元の回答 (イベント ハンドラーのティアダウンの修正を含む)

あなたは見つけた 1 つの答えに近づいていましたが、何が欠けているかを示すために、プランクをまとめました。

app.directive('clickAnywhereButHere', function($document){
  return {
    restrict: 'A',
    link: function(scope, elem, attr, ctrl) {
      var elemClickHandler = function(e) {
        e.stopPropagation();
      };
      
      var docClickHandler = function() {
        scope.$apply(attr.clickAnywhereButHere);
      };
      
      elem.on('click', elemClickHandler);
      $document.on('click', docClickHandler);
      
      // teardown the event handlers when the scope is destroyed.
      scope.$on('$destroy', function() {
        elem.off('click', elemClickHandler);
        $document.off('click', docClickHandler);
      });
    }
  }
})

HTML

<a click-anywhere-but-here="clickedSomewhereElse()" 
  ng-click="clickedHere()">Don't Click Me!</a>
于 2012-10-17T13:58:48.700 に答える
28

現在受け入れられている回答の問題は、ディレクティブを複数回使用すると、ディレクティブが添付されているすべてのDOM要素がバブリングを防止することです(したがって、2つの要素にバブリングがあり、どちらかをクリックすると、両方のコールバックがブロックされています)。

編集 - jQuery の回避、クリーンアップ-- スコープで関数を定義し、このディレクティブに直接 (かっこなしで) 渡すと、呼び出されたときにイベントが渡されます。

app.directive('clickAnywhereButHere', function($document, $parse) {
    return {
        restrict: 'A',
        scope: {
            callback : '=clickAnywhereButHere'
        },
        link: function(scope, element, attr, ctrl) {
            var handler = function(event) {
                if (!element[0].contains(event.target)) {
                    scope.callback(event);
                 }
            };

            $document.on('click', handler);
            scope.$on('$destroy', function() {
                $document.off('click', handler);
            });
        }
    }
});

HTML での使用法

<a click-anywhere-but-here="myFunction"></a>

コントローラでの使用

 $scope.myFunction = function (event) { ... } 

-

scope.callback(event)でラップする必要がある場合があることに注意してくださいscope.$apply()

于 2013-05-24T19:14:26.487 に答える
7

このディレクティブを必要とする要素が多数ある場合は、パフォーマンスが最適化された別のソリューションを次に示します。(100 行以上のリストの例で、それぞれにこのディレクティブがあります)

これには、常に $document リスナーが 1 つだけ含まれます。

angular.module('app').directive('clickElsewhere', ['$document', function ($document) {
return {
  link: function postLink(scope, element, attr) {

    var elsewhere = true;

    element.on('click', function(e) {
      elsewhere = false;
      $document.off('click', clickElsewhere);
      $document.on('click', clickElsewhere);
    });

    var clickElsewhere = function() {
      if (elsewhere) {
        scope.$apply(attr.clickElsewhere);
        $document.off('click', clickElsewhere);
      }
      elsewhere = true;
    };

  }
};
}]);

Max Bates の解決策の問題は、すべてのディレクティブが $document.on('click', function(...)); のリスナーを追加していることです。パフォーマンスの問題を引き起こすイベント。

受け入れられた回答の問題には、Max Bates が既に記載されています。

于 2014-06-18T08:46:04.680 に答える
3

このブログ投稿で回答を見つけました。

指令:

app.directive('documentClick', function ($document, $parse) {

  var linkFunction = function ($scope, $element, $attributes) {

    var scopeExpression = $attributes.documentClick;
    var invoker = $parse(scopeExpression);

    $document.on('click', function (event) {

      $scope.$apply(function () {
        invoker($scope, { $event: event });
      });

    });

  };

  return linkFunction;

});

コントローラ:

app.controller('PageCtrl', function ($scope) {

  $scope.click = function (e) {
    if (!$(e.target).is('.language')) {
      //do stuff
    }
  };

});

意見:

<body ng-controller='PageCtrl' document-click='click($event)'></body>
于 2012-10-17T10:17:18.290 に答える
2

以下は、Max のソリューションに基づくバリアントですが、標準の角度イベント ディレクティブに関してはより自然です。

app.directive('clickOut', function($document) {
    return {
        restrict: 'A',
        scope: {
            clickOut: '&'
        },
        link: function (scope, element) {
            var handler = function(event) {
                if (!element[0].contains(event.target)) {
                    scope.$apply(function () {
                        scope.clickOut({ $event : event }); 
                    });
                }
            };

            $document.on('click', handler);
            scope.$on('$destroy', function() {
                $document.off('click', handler);
            });
        }
    };
});

使用法

<div click-out="myFunction()"></div>

クリックイベントの受け渡し

<div click-out="myFunction($event)"></div>
于 2015-04-08T07:07:42.033 に答える
1

最も簡単な方法は、要素の scope.$id と clickScope.$id (クリック イベント ターゲットの scope.$id) を確認することです。

link: function(scope, element, attrs) { 
    //close element when mouse click outside
    var documentClickHandler = function(event) {
        var clickScope = angular.element(event.target).scope();
        if (clickScope.$id != scope.$id) {
            //clickScope.$parent is for clicking on the directive children scope
            if(clickScope.$parent === null || 
               clickScope.$parent.$id != scope.$id){
               //Click everywhere but on this element
               //Do anything you want here, like close your element;
            }
        }

    };

    $document.on('click', documentClickHandler);

    scope.$on('$destroy', function() {
        $document.off('click', documentClickHandler);
    });
}
于 2016-08-22T10:26:39.647 に答える
0

このソリューションの利点:

  1. へのバインドは 1 つだけ必要です$(document)。その他のイベントの発生は$emit、スコープ イベントによって異なります。
  2. のおかげでclick-elsewhere="show=false"、 との両方の式を使用できます。click-elsewhere="fn()"$parse

コード:

// broadcast click event within AppCtrl
app.controller('AppCtrl', function($rootScope) {
  $(document).on('click', function(e) {
    // use $emit so the event stays inside $rootScope
    $rootScope.$emit('click', {target: e.target});
  };
};

app.directive('clickElsewhere', function($rootScope) {
  return {
    restrict: 'A',
    compile: function($element, attr) {
      // store fn in compile so it only execute once
      var fn = $parse(attr['clickElsewhere']); 

      return function(scope, element) {
        var offEvent = $rootScope.$on('click', function(event, target) {
          if ( (element.find($(target)).length) || element.is($(target)) ) return;

          scope.$apply(function() {
            fn(scope, {$event: event});
          });
        });

        scope.$on('$destroy', offEvent);
      };
    }
  };

HTML での使用法:

  1. click-elsewhere="fn()"
  2. click-elsewhere="show=false"
于 2014-11-10T02:51:26.590 に答える
-1

少し文脈から外れている可能性がありますが、ユーザーが「ここをクリックしないでください」要素をクリックすると、選択されたすべてのアイテムを (たとえば「選択済み」で) 分類できます。現在「選択済み」に分類されているすべてのアイテムを分類解除できます。特定の要素を分類する...

于 2012-10-17T09:41:17.090 に答える