web-dev-qa-db-ja.com

AngularJSを使用して同じ要素でng-clickとng-dblclickを処理する

要素にng-dblclickディレクティブが設定されていても、AngularJSは常にng-clickイベントのみを起動するため、AngularJSでのシングルクリックとダブルクリックの両方のイベント処理を探していました。

以下は、解決策を模索している人向けの実用的なコードです。

JS:

function MyController($scope) {

    var waitingForSecondClick = false;
    $scope.singleClickAction = function() {

        executingDoubleClick = false;
        if (waitingForSecondClick) {
            waitingForSecondClick = false;
            executingDoubleClick = true;
            return $scope.doubleClickAction();
        }
        waitingForSecondClick = true;

        setTimeout(function() {
            waitingForSecondClick = false;
            return singleClickOnlyAction();
        }, 250); // delay

        /*
         * Code executed with single AND double-click goes here.
         * ...
         */

        var singleClickOnlyAction = function() {
            if (executingDoubleClick) return;

            /*
             * Code executed ONLY with single-click goes here.
             * ...
             */

        }

    }

    $scope.doubleClickAction = function() {

        /*
         * Code executed ONLY with double-click goes here.
         * ...
         */

    };

}

HTML:

<div ng-controller="MyController">
    <a href="#" ng-click="singleClickAction()">CLICK</a>
</div>

だから私の質問は(私はAngularJS初心者なので):もっと経験のある人がこれらの両方のイベントを処理するためのニースのディレクティブを書くことができますか?

私の意見では、完璧な方法は、ng-click、ng-dblclickの動作を変更し、シングルクリックのみのコードを処理するための「ng-sglclick」ディレクティブを追加することです。それが可能かどうかはわかりませんが、誰にとっても非常に役立つと思います。

ご意見をお聞かせください!

36
stealz

自分で書くこともできます。 angularクリックを処理し、ここで見つけたコードで修正しました: Jquery bind double click and single click Separate

<div sglclick="singleClick()" ng-dblClick="doubleClick()" style="height:200px;width:200px;background-color:black">


mainMod.controller('AppCntrl', ['$scope', function ($scope) {
    $scope.singleClick = function() {
      alert('Single Click');
    }
    $scope.doubleClick = function() {
      alert('Double Click');
    }
}])


mainMod.directive('sglclick', ['$parse', function($parse) {
    return {
        restrict: 'A',
        link: function(scope, element, attr) {
          var fn = $parse(attr['sglclick']);
          var delay = 300, clicks = 0, timer = null;
          element.on('click', function (event) {
            clicks++;  //count clicks
            if(clicks === 1) {
              timer = setTimeout(function() {
                scope.$apply(function () {
                    fn(scope, { $event: event });
                }); 
                clicks = 0;             //after action performed, reset counter
              }, delay);
              } else {
                clearTimeout(timer);    //prevent single-click action
                clicks = 0;             //after action performed, reset counter
              }
          });
        }
    };
}])

ここに例があります

Plunker

28
Rob

グレッグの答えは間違いなく最もクリーンな答えに最も近い。彼の答えに基づいて、新しいコードを記述する必要がなく、コントローラーで新しいインジェクションを使用する必要のないバージョンを作成します。

最初に疑問に思うのは、なぜこのような問題を回避するためにタイムアウトが使用されるのかということです。基本的に、関数が現在のイベントループの残りをスキップして、実行がクリーンになるようにするために使用されます。ただし、角度では、実際にはダイジェストループの仕組みに興味があります。 UXに最適な小さな違いを除いて、従来のイベントハンドラーとほぼ同じです。関数の実行順序をいじる手元にあるいくつかのツールには、scope.$evalscope.$evalAsyncscope.$apply、およびscope.$applyAsyncがあります。

$applysは別のダイジェストループを開始し、$evalsを残すと信じています。 $evalは、現在の$scopeのコンテキストですぐに含めたコードを実行し、$evalAsyncはダイジェストループの終わりに実行する関数をキューに入れます。基本的に、$evalAsync$timeoutのクリーンバージョンであり、1つの大きな違いがあります—最初のものはコンテキストを持ち、スコープに存在します!

これは、実際には、同じ要素でng-clickng-dblclickを処理できることを意味します。 ただし、ダブルクリック機能の前にシングルクリック機能がトリガーされることに注意してください。これで十分です:

<div ng-controller="MyController">
    <a href="#"
       ng-click="$evalAsync(singleClickAction())"
       ng-dblclick="doubleClickAction()">
       CLICK
    </a>
</div>

Angular 1.6.4 を使用して、意図した機能を備えたjsfiddleを示します。

24
stites

これに出くわして、私は代替案を捨てると思いました。 2つの重要な点を除けば、元のポスターとそれほど違いはありません。

1)ネストされた関数宣言はありません。

2)$ timeoutを使用します。遅滞なく$ timeoutを使用することがよくあります。特に、他の作業を行う約束を開始する場合はそうです。ダイジェストサイクルが終了すると、$ timeoutが発生し、スコープへのデータ変更が適用されます。

与えられた

<img src="myImage.jpg" ng-click="singleClick()" ng-dblclick="doubleClick()">

コントローラーでは、singleClick関数は次のようになります。

$scope.singleClick = function () {
    if ($scope.clicked) {
        $scope.cancelClick = true;
        return;
    }

    $scope.clicked = true;

    $timeout(function () {
        if ($scope.cancelClick) {
            $scope.cancelClick = false;
            $scope.clicked = false;
            return;
        }

        //do something with your single click here

        //clean up
        $scope.cancelClick = false;
        $scope.clicked = false;
    }, 500);
};

また、doubleClick関数は正常に見えます。

$scope.doubleClick = function () {

    $timeout(function () {

        //do something with your double click here

    });
};

これが誰かを助けることを願っています...

13
Greg Grater

ダブルクリックとクリックを同時に処理する方法を見つけようとしているときに出会いました。ここでの概念を使用して、元のクリックをキャンセルしました。遅延の前に2回目のクリックが発生すると、ダブルクリックアクションが実行されます。 2回目のクリックがない場合、遅延が終了すると、デフォルトのngClickアクションが実行され、元のイベントが要素でトリガーされます(最初のようにバブルできます)。

<div ng-click="singleClick()"><span double-click="doubleClick()">double click me</span></div>

コード

.directive('doubleClick', function($timeout, _) {

  var CLICK_DELAY = 300
  var $ = angular.element

  return {
    priority: 1, // run before event directives
    restrict: 'A',
    link: function(scope, element, attrs) {
      var clickCount = 0
      var clickTimeout

      function doubleClick(e) {
        e.preventDefault()
        e.stopImmediatePropagation()
        $timeout.cancel(clickTimeout)
        clickCount = 0
        scope.$apply(function() { scope.$eval(attrs.doubleClick) })
      }

      function singleClick(clonedEvent) {
        clickCount = 0
        if (attrs.ngClick) scope.$apply(function() { scope.$eval(attrs.ngClick) })
        if (clonedEvent) element.trigger(clonedEvent)
      }

      function delaySingleClick(e) {
        var clonedEvent = $.Event('click', e)
        clonedEvent._delayedSingleClick = true
        e.preventDefault()
        e.stopImmediatePropagation()
        clickTimeout = $timeout(singleClick.bind(null, clonedEvent), CLICK_DELAY)
      }

      element.bind('click', function(e) {
        if (e._delayedSingleClick) return
        if (clickCount++) doubleClick(e)
        else delaySingleClick(e)
      })

    }
  }

})
2
Jesse Houchins

ここで回答の一部に参加します。

  • @ GregGrater strategy を使用して簡単にする
  • @Robとしてdirectiveを作成します(このスレッドでベストアンサーとして受け入れられたもの)
  • ngClickビルドインディレクティブを @ EricChen answer を使用して置き換えることにより、@ Robの回答の問題を解決します。

ここで、Plunkerとアイデアの本質(この回答のスニペットと同じ。以下を参照)。

ノートは別として:理想的には、要素にng-dblclickが定義されていない場合、シングルクリックを妨げないはずです(ここではPlunker forkこのアイデアの実装)

(function(angular) {
  'use strict';
var myApp = angular.module('myApp', []);

myApp.controller('myCtrl', ['$scope', function($scope) {
  $scope.click = false;
  $scope.singleClick = function() {
    $scope.click = 'single';
  };
  $scope.doubleClick = function() {
    $scope.click = 'double';
 };
}]);

// remove the buildin ng-Click, to solve issue with https://stackoverflow.com/a/20445344/4352306
myApp.config(function($provide) { // Source: https://stackoverflow.com/a/23209542/4352306
  $provide.decorator('ngClickDirective', ['$delegate', function ($delegate) {
   //$delegate is array of all ng-click directive, in this case 
   // first one is angular buildin ng-click, so we remove it.
   $delegate.shift();
   return $delegate;
   }]);
});

// add custom single click directive: make ngClick to only trigger if not double click
myApp.directive('ngClick', ['$parse', '$timeout', dirSingleClickExclusive]); 

function dirSingleClickExclusive($parse, $timeout) {
  return {
    restrict: 'A',
    replace : false,
    priority: 99, // after all build-in directive are compiled
    link: link
  }
        
  function link ($scope, element, attrs) {
    const delay = 400;
    var clicked = false, cancelClick = false;
    var user_function = $parse(attrs['ngClick']); //(scope);
        
    element.on('click', function (e) {
      // Adapted from: https://stackoverflow.com/a/29073481/4352306
      if (clicked) cancelClick = true; // it is not a single click
      clicked = true;
      
      if (!cancelClick) { // prevent a second timeout
        $timeout(function () { // for time window between clicks (delay)
          if (cancelClick) {
            clicked = false; cancelClick = false;
            return;
          }
          $scope.$apply(function () {
            user_function($scope, {$event : e});
          });
                                
          // reset click status
          clicked = false; cancelClick = false;
        }, delay);
      }
    });
  }
}
})(window.angular);
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Example - custom single click</title>
  
  <script src="//code.angularjs.org/snapshot/angular.min.js"></script>
  <script src="app.js"></script>
  
</head>
<body ng-app="myApp">
  <div ng-controller="myCtrl">
   <button ng-click="singleClick()" ng-dblclick="doubleClick()">Click me!</button>
   <p ng-if="click">This was a {{click}} click.</p>
  </div>
</body>
</html>
1
rellampec

singleClickdoubleClickを呼び出しても間違いがない場合は機能します

<div 
    onclick="var scope = angular.element(this).scope(); scope.singleClick();"
    ng-click="null" 
    ng-dblclick="doubleClick()"
></div>
0
Sajjad Shirazy