web-dev-qa-db-fra.com

Gestion de ng-click et ng-dblclick sur le même élément avec AngularJS

Je recherchais la gestion d'événements en un ou deux clics avec AngularJS, car AngularJS ne déclenche toujours que l'événement ng-click, même si une directive ng-dblclick est définie pour notre élément.

Voici un code de travail pour ceux qui cherchent une solution:

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>

Ma question est donc la suivante (depuis que je suis un débutant dans AngularJS): quelqu'un de plus expérimenté pourrait-il écrire une directive de Nice pour gérer ces deux événements?

A mon avis, le moyen idéal serait de changer le comportement de ng-click, ng-dblclick et d'ajouter une directive "ng-sglclick" pour gérer le code en un seul clic. Je ne sais pas si c'est même possible, mais je le trouverais très utile pour tout le monde.

N'hésitez pas à partager vos opinions!

36
stealz

Vous pouvez simplement écrire le vôtre. J'ai jeté un coup d'œil à la manière dont le clic géré a été modifié et modifié avec le code que j'ai trouvé ici: Jquery bind double clic et clic simple séparément

<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
              }
          });
        }
    };
}])

Voici un exemple

Plunker

27
Rob

La réponse de Greg est certainement la plus proche de la réponse la plus nette. Je vais m'appuyer sur sa réponse pour proposer une version dans laquelle aucun nouveau code ne doit être écrit ni aucune nouvelle injection ne doit être utilisée dans votre contrôleur.

La première chose à poser est de savoir pourquoi les délais d'attente sont utilisés pour résoudre ce genre de problèmes. Ils sont essentiellement utilisés pour obliger une fonction à ignorer le reste de la boucle d'événements en cours afin que l'exécution soit propre. En mode angulaire, cependant, vous vous intéressez réellement au fonctionnement de la boucle de digestion. C'est presque la même chose que votre gestionnaire d'événements classique, à l'exception de quelques différences mineures qui le rendent idéal pour l'UX. Certains outils que vous avez sous la main pour modifier l'ordre d'exécution des fonctions sont les suivants: scope.$eval, scope.$evalAsync, scope.$apply et scope.$applyAsync.

Je crois que le $applys lancera une autre boucle de résumé, ce qui laisse le $evals. $eval exécutera tout code que vous incluez immédiatement avec le contexte du $scope en cours et $evalAsync mettra en file d'attente votre fonction à exécuter à la fin de la boucle de résumé. $evalAsync est essentiellement une version plus propre de $timeout avec une différence majeure: la première a un contexte et existe dans la portée!

Cela signifie que vous pouvez réellement manipuler ng-click et ng-dblclick sur le même élément. Notez cependant que cela déclenchera toujours la fonction clic simple avant la fonction double-clic. Cela devrait suffire:

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

Voici un jsfiddle avec la fonctionnalité prévue utilisant Angular 1.6.4 .

24
stites

Je suis tombé sur cela et j'ai pensé que je proposerais une alternative. Ce n’est pas très différent de l’affiche originale, à part deux points essentiels.

1) Il n'y a pas de déclaration de fonction imbriquée.

2) J'utilise $ timeout. J'utilise souvent $ timeout même sans délai ... surtout si je lance des promesses de faire un autre travail. Le délai d'attente $ se déclenche lorsque le cycle de résumé est terminé, ce qui garantit que toutes les modifications de données apportées à la portée sont appliquées. 

Donné

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

Dans votre contrôleur, la fonction singleClick ressemblera à:

$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);
};

Et la fonction doubleClick aura l’air normal:

$scope.doubleClick = function () {

    $timeout(function () {

        //do something with your double click here

    });
};

J'espère que ça aide quelqu'un ...

13
Greg Grater

J'ai couru en essayant de trouver un moyen de gérer le double clic et le clic en même temps. J'ai utilisé les concepts ici pour annuler le clic d'origine. Si un deuxième clic se produit avant le délai, l'action du double clic est effectuée. S'il n'y a pas un deuxième clic, une fois le délai écoulé, l'action ngClick par défaut s'exécute et l'événement d'origine est déclenché sur l'élément (et autorisé à bouillonner comme il l'aurait initialement).

Exemple

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

Code

.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

Joindre les morceaux des réponses ici:

  • en utilisant @GregGrater stratégie pour plus de simplicité
  • créer une directive, comme @Rob (celui accepté comme la meilleure réponse dans ce fil)
  • résoudre le problème de la réponse @Rob en remplaçant la directive de construction ngClick en utilisant @EricChen answer

Voici le Plunker avec l’essence de l’idée (idem snippet dans cette réponse; voir ci-dessous).

Note de fin: idéalement, s'il n'y a pas de ng-dblclick défini pour l'élément, cela ne devrait pas empêcher le simple clic (ici un Plunker fork implémentant cette idée)

(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

cela fonctionne aussi si appeler singleClick sur la doubleClick ne fait aucune erreur 

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