Обработка ng-click и ng-dblclick на одном элементе с AngularJS

Я искал как одиночную, так и двойную обработку событий с помощью AngularJS, поскольку AngularJS всегда запускает только событие ng-click, даже если для нашего элемента установлена директива ng-dblclick.

вот рабочий код для тех, кто ищет решение:

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" для обработки кода только с одним щелчком мыши. Не знаю, возможно ли это, но я считаю, что это очень полезно для всех.

Не стесняйтесь делиться своим мнением!

6 ответов


вы могли бы просто написать свой собственный. Я посмотрел, как угловой обработанный клик и изменил его с помощью кода, который я нашел здесь:jQuery bind двойной щелчок и один щелчок отдельно

<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


ответ Грега определенно ближе всего к самому чистому ответу. Я собираюсь основываться на его ответе, чтобы придумать версию, в которой не нужно писать новый код, и не нужно использовать новые инъекции в вашем контроллере.

первое, что нужно спросить, Почему тайм-ауты используются для взлома таких проблем. По сути, они используются, чтобы заставить функцию пропустить остальную часть текущего цикла событий, чтобы выполнение было чистым. Угловой, однако, вы на самом деле интересует, как работает цикл digest. Это почти то же самое, что и ваш классический обработчик событий, за исключением некоторых незначительных различий, которые делают его отличным для UX. Некоторые инструменты, которые у вас есть, чтобы возиться с порядком выполнения функций, включают scope.$eval, scope.$evalAsync,scope.$apply и scope.$applyAsync.

я считаю $applys запустит еще один цикл дайджеста, который оставляет $evals. $eval будет выполняться любой код, который вы включаете немедленно с контекстом текущего $scope и $evalAsync будет очередь вашей функции для запуска в конце цикла дайджеста. По существу,$evalAsync является более чистой версией $timeout С одной большой разницей-первый имеет контекст и существует в области!

это означает, что вы можете, на самом деле, ручка ng-click и ng-dblclick на том же элементе. обратите внимание, однако, что это все равно вызовет функцию с одним щелчком мыши перед функцией двойного щелчка. Это должно быть достаточно:

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

вот jsfiddle с предполагаемой функциональностью, используя Angular 1.6.4.


наткнулся на это и подумал, что я выброшу альтернативу. Это не слишком отличается от оригинального плаката, кроме двух ключевых моментов.

1) нет вложенных объявлений функций.

2) я использую $ 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

    });
};

надеюсь, это кому-то поможет...


Я побежал, пытаясь найти способ справиться с двойным щелчком и одновременно щелкнуть. Я использовал концепции здесь, чтобы отменить исходный щелчок. Если перед задержкой происходит второй щелчок, выполняется действие двойного щелчка. Если нет второго щелчка, как только задержка закончится, выполняется действие 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)
      })

    }
  }

})

объединение частей ответов здесь:

  • используя @GregGrater стратегия для простоты
  • создание directive, как @Rob (тот, который принят как лучший ответ в этой теме)
  • решение проблемы ответа @Rob, заменив ngClick build-in директива с помощью @EricChen ответ
на Plunker с сутью идеи (такой же, как фрагмент в этом ответе; см. ниже).

в сторону Примечание: в идеале, если нет ng-dblclick определенный для элемента, он не должен препятствовать одиночному щелчку (здесь Plunker вилка реализации этой идеи)

(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>

это тоже работает, если вызов singleClick на doubleClick не делает никакой ошибки

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