Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect mouse+keyboard events in Angular for ng-repeat

I have an app in which I generate a set of spans using ng-repeat, and each span has a unique id (let's say span-{{$index}}). Now I want to perform the following:

  • If I click on a span, I want the id of the span to be copied. I managed to do this via the ng-click.
  • I want to extend this functionality further to detect multiple clicks, to get an array of the selected ids, but only if the CTRL key is held down during the clicks.

For eg. if I had the ctrl button clicked when I chose span with id 1,3,5,7, my array should have [1,3,5,7] , but if I didn't have the CTRL key pressed then I should only have [7], as it was the last span selected.

Also, can I bind relative events? E.g. if I select span with an id of 1, if I click CTRL+DOWN_ARROW, id 2 is also selected... and then id 3 and so on until I keep pressing DOWN_ARROW.

I guess the closest I've seen of this type of UX is with selecting contacts in Gmail while composing a new mail. I can select contacts using all sorts of keyboard and mouse combinations. I'm looking for something very similar

gmail screenshot

I'm playing around with different UX techniques but I'm stuck on how exactly I could do this with angular.

like image 564
navinpai Avatar asked Feb 03 '15 09:02

navinpai


2 Answers

For your first question see the plunkr below.

If you pass $event through to your ng-click function you can access the event in your controller. In my example I checked if altKey was true which is checking if the alt key was pressed at the same time as the click. You can also access ctrlKey, shiftKey and which mouse button was pressed. See MouseEvent object here - http://www.w3schools.com/jsref/dom_obj_event.asp

The controller:

angular.module('exampleApp', [])

.controller("ItemCtrl", function($scope){

    $scope.items = [
      {text: "Bob", id: 1},
      {text: "Alice", id: 2},
      {text: "Frank", id: 3},
      {text: "Lisa", id: 4}
    ];
    $scope.itemList = [];

    $scope.addItemIdToList = function(event, item){
        if(event.altKey){
          if(isItemInList(item)){
            removeItemIdFromList(item);
          } else {
            addItemIdToList(item);
          }
        } else {
          addItemIdAsSingleSelection(item);
        }
    };

    var isItemInList = function(item){
      var indexOfItem = $scope.itemList.indexOf(item.id);
      return indexOfItem > -1;
    }

    var removeItemIdFromList = function(item){
      var indexOfItem = $scope.itemList.indexOf(item.id);
      $scope.itemList.splice(indexOfItem, 1);
    };

    var addItemIdToList = function(item){
      $scope.itemList.push(item.id);
    };

    var addItemIdAsSingleSelection = function(item){
      $scope.itemList = [item.id];
    };
})

http://plnkr.co/edit/RAX5oxkTomXxryp0sNNc

When the logic starts to become a bit more complicated it would probably be best to do this sort of thing in a directive.

For the second question the basic parts can be seen in the following example:

angular.module('exampleApp', [])

.directive('keypressEvents', function ($document, $rootScope) {
    return {
        restrict: 'E',
        link: function () {
            console.log('linked');
            $document.on('keypress', function(e) {
                if(e.altKey){
                    var s = 223;
                    var a = 229;
                    if(e.which == s){
                      $rootScope.$broadcast("add_next_id");
                    } else if(e.which == a){
                      $rootScope.$broadcast("remove_last_id");
                    }
                }
            })
        }
    }
})

.controller("ItemCtrl", function($scope, $rootScope){

      $scope.items = [
      {text: "Bob", id: 1},
      {text: "Alice", id: 2},
      {text: "Frank", id: 3},
      {text: "Lisa", id: 4}
    ];

    $scope.itemList = [1];

    $rootScope.$on('add_next_id', function (evt, obj, key) {
        $scope.$apply(function () {
            addNextId();
        });
    });

    $rootScope.$on('remove_last_id', function (evt, obj, key) {
        $scope.$apply(function () {
            removeLastId();
        });
    });

    var addNextId = function(){
        var lastId = $scope.itemList[$scope.itemList.length - 1];
        if(lastId < $scope.items.length){
          $scope.itemList.push(lastId+1); 
        }
    };

    var removeLastId = function(){
        if($scope.itemList.length > 1){
          $scope.itemList.pop();
        }
    };

     $scope.isItemInList = function(item){
      var indexOfItem = $scope.itemList.indexOf(item.id);
      return indexOfItem > -1;
    }
})

http://plnkr.co/edit/PyyjfRMovygeq9qNbzWo

We listen on the document for key presses and check again for altKey. Then if the keyCode is one of our hotkeys we send a message to $rootScope using $rootScope.$broadcast() which the controller is listening to with the $rootScope.$on() method.

In the above example alt+s will add more ids and alt+a will remove them down to the originally selected one.

like image 182
Andrew Robinson Avatar answered Sep 17 '22 11:09

Andrew Robinson


Perhaps an Angular directive would be helpful. Here's an example of how you can capture meta key info using an angular directive and get access to the info inside an Angular Controller (Plnkr):

<!DOCTYPE html>
<html>

  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
  </head>

  <body ng-app="myapp" ng-controller="main">
    <h1>Hello {{person}}</h1>
    <p>Ctrl: |{{info.ctrl}}|</p>
    <keyboard info="info"></keyboard>
    <script>
      var app = angular.module('myapp', []);
      app.controller('main', function($scope){
        $scope.info = { ctrl: false };
        $scope.person = "Me";

      });

      app.directive('keyboard', function($timeout){
        return {
          scope: {
            info: '='
          },
          link: function(scope, element, attr){
            console.dir(scope.info);
            $(document).on('keydown', function(e){
              $timeout(function(){
                scope.info.ctrl = e.ctrlKey;
              });
            });
            $(document).on('keyup', function(e){
              $timeout(function(){
                scope.info.ctrl = e.ctrlKey;
              });
            });
          }
        }
      });
    </script>
  </body>

</html>
like image 29
Trevor Avatar answered Sep 19 '22 11:09

Trevor