What is AngularJS way to create global keyboard shortcuts?

后端 未结 12 2082
一整个雨季
一整个雨季 2020-11-30 22:04

I suppose that I should use directive, but it seems strange to add directive to body, but listen events on document.

What is a proper way to do this?

UPDATE:

相关标签:
12条回答
  • 2020-11-30 22:56

    I made a service for shortcuts.

    It looks like:

    angular.module('myApp.services.shortcuts', [])
      .factory('Shortcuts', function($rootScope) {
         var service = {};
         service.trigger = function(keycode, items, element) {
           // write the shortcuts logic here...
         }
    
         return service;
    })
    

    And I injected it into a controller:

    angular.module('myApp.controllers.mainCtrl', [])
      .controller('mainCtrl', function($scope, $element, $document, Shortcuts) {
       // whatever blah blah
    
       $document.on('keydown', function(){
         // skip if it focused in input tag  
         if(event.target.tagName !== "INPUT") {
            Shortcuts.trigger(event.which, $scope.items, $element);
         }
       })
    })
    

    It works, but you may notice that I inject $element and $document into the controller.

    It's a bad controller practice and violates the 'Dont EVER access $element in the controller' convention.

    I should put it into directive, then use 'ngKeydown' and $event to trigger the service.

    But I think the service is fine and I will rework the controller sooner.


    updated:

    It seems like 'ng-keydown' only works in input tags.

    So I just write a directive and inject $document:

    angular.module('myApp.controllers.mainCtrl', [])
      .directive('keyboard', function($scope, $document, Shortcuts) {
       // whatever blah blah
       return {
         link: function(scope, element, attrs) {
           scope.items = ....;// something not important
    
           $document.on('keydown', function(){
             // skip if it focused in input tag  
             if(event.target.tagName !== "INPUT") {
               Shortcuts.trigger(event.which, scope.items, element);
             }
           })
         }
       }
      })
    

    It's better.

    0 讨论(0)
  • 2020-11-30 22:56

    i don't know if it's a real angular way, but what i've done

    $(document).on('keydown', function(e) {
        $('.button[data-key=' + String.fromCharCode(e.which) + ']').click();
    });
    
    <div class="button" data-key="1" ng-click="clickHandler($event)">
        ButtonLabel         
    </div>
    
    0 讨论(0)
  • 2020-11-30 23:01

    Use $document.bind:

    function FooCtrl($scope, $document) {
        ...
        $document.bind("keypress", function(event) {
            console.debug(event)
        });
        ...
    }
    
    0 讨论(0)
  • 2020-11-30 23:01

    The slightly shorter answer is just look at solution 3 below. If you would like to know more options, you could read the whole thing.

    I agree with jmagnusson. But I believe there is cleaner solution. Instead of binding the keys with functions in directive, you should be able just bind them in html like defining a config file, and the hot keys should be contextual.

    1. Below is a version that use mouse trap with a custom directive. (I wasn't the author of this fiddle.)

      var app = angular.module('keyExample', []);
      
      app.directive('keybinding', function () {
          return {
              restrict: 'E',
              scope: {
                  invoke: '&'
              },
              link: function (scope, el, attr) {
                  Mousetrap.bind(attr.on, scope.invoke);
              }
          };
      });
      
      app.controller('RootController', function ($scope) {
          $scope.gotoInbox = function () {
              alert('Goto Inbox');
          };
      });
      
      app.controller('ChildController', function ($scope) {
          $scope.gotoLabel = function (label) {
              alert('Goto Label: ' + label);
          };
      });
      

      You will need to include mousetrap.js, and you use it like below:

      <div ng-app="keyExample">
          <div ng-controller="RootController">
              <keybinding on="g i" invoke="gotoInbox()" />
              <div ng-controller="ChildController">
                  <keybinding on="g l" invoke="gotoLabel('Sent')" />
              </div>
          </div>
          <div>Click in here to gain focus and then try the following key strokes</div>
          <ul>
              <li>"g i" to show a "Goto Inbox" alert</li>
              <li>"g l" to show a "Goto Label" alert</li>
          </ul>
      </div>
      

      http://jsfiddle.net/BM2gG/3/

      The solution require you to include mousetrap.js which is library that help you to define hotkeys.

    2. If you want to avoid the trouble to develop your own custom directive, you can check out this lib:

      https://github.com/drahak/angular-hotkeys

      And this

      https://github.com/chieffancypants/angular-hotkeys

      The second one provide a bit more features and flexibility, i.e. automatic generated hot key cheat sheet for your app.

    Update: solution 3 is no longer available from Angular ui.

    1. Apart from the solutions above, there is another implementation done by angularui team. But the downside is the solution depends on JQuery lib which is not the trend in the angular community. (Angular community try to just use the jqLite that comes with angularjs and get away from overkilled dependencies.) Here is the link

      http://angular-ui.github.io/ui-utils/#/keypress

    The usage is like this:

    In your html, use the ui-keydown attribute to bind key and functions.

    <div class="modal-inner" ui-keydown="{
                            esc: 'cancelModal()',
                            tab: 'tabWatch($event)',
                            enter: 'initOrSetModel()'
                        }">
    

    In your directive, add those functions in your scope.

    app.directive('yourDirective', function () {
       return {
         restrict: 'E',
         templateUrl: 'your-html-template-address.html'
         link: function(){
            scope.cancelModal() = function (){
               console.log('cancel modal');
            }; 
            scope.tabWatch() = function (){
               console.log('tabWatch');
            };
            scope.initOrSetModel() = function (){
               console.log('init or set model');
            };
         }
       };
    });
    

    After playing around with all of the solutions, I would recommend the one that is implemented by Angular UI team, solution 3 which avoided many small strange issues I have encountered.

    0 讨论(0)
  • 2020-11-30 23:02

    Here's how I've done this with jQuery - I think there's a better way.

    var app = angular.module('angularjs-starter', []);
    
    app.directive('shortcut', function() {
      return {
        restrict: 'E',
        replace: true,
        scope: true,
        link:    function postLink(scope, iElement, iAttrs){
          jQuery(document).on('keypress', function(e){
             scope.$apply(scope.keyPressed(e));
           });
        }
      };
    });
    
    app.controller('MainCtrl', function($scope) {
      $scope.name = 'World';
      $scope.keyCode = "";
      $scope.keyPressed = function(e) {
        $scope.keyCode = e.which;
      };
    });
    
    <body ng-controller="MainCtrl">
      <shortcut></shortcut>
      <h1>View keys pressed</h1>
      {{keyCode}}
    </body>
    

    Plunker demo

    0 讨论(0)
  • 2020-11-30 23:04

    I would say a more proper way (or "Angular way") would be to add it to a directive. Here's a simple one to get you going (just add keypress-events attribute to <body>):

    angular.module('myDirectives', []).directive('keypressEvents', [
      '$document',
      '$rootScope',
      function($document, $rootScope) {
        return {
          restrict: 'A',
          link: function() {
            $document.bind('keypress', function(e) {
              console.log('Got keypress:', e.which);
              $rootScope.$broadcast('keypress', e);
              $rootScope.$broadcast('keypress:' + e.which, e);
            });
          }
        };
      }
    ]);
    

    In your directive you can then simply do something like this:

    module.directive('myDirective', [
      function() {
        return {
          restrict: 'E',
          link: function(scope, el, attrs) {
            scope.keyPressed = 'no press :(';
            // For listening to a keypress event with a specific code
            scope.$on('keypress:13', function(onEvent, keypressEvent) {
              scope.keyPressed = 'Enter';
            });
            // For listening to all keypress events
            scope.$on('keypress', function(onEvent, keypressEvent) {
              if (keypress.which === 120) {
                scope.keyPressed = 'x';
              }
              else {
                scope.keyPressed = 'Keycode: ' + keypressEvent.which;
              }
            });
          },
          template: '<h1>{{keyPressed}}</h1>'
        };
      }
    ]);
    
    0 讨论(0)
提交回复
热议问题