Wie kann AngularJS globale Tastaturkürzel erstellen?


77

Ich nehme an, ich sollte die Direktive verwenden, aber es scheint seltsam, dem Body eine Direktive hinzuzufügen, aber Ereignisse im Dokument anzuhören.

Was ist ein richtiger Weg, um dies zu tun?

UPDATE: Gefunden AngularJS UI und sah ihre Realisierung keypress Richtlinie.


1
Ich nehme an, Sie meinen Tastaturkürzel ... Ich war auch neugierig, ich komme zu dem Schluss, dass Winkel nicht das beste Werkzeug für diese Aufgabe ist. Ich habe eine Direktive geschrieben, die dies tut, aber es gibt Probleme - erstens die semantische, auf die Sie auch anspielen. Ich denke auch nicht, dass es eine gute Praxis ist, jquery in eine Direktive zu packen, und es hat zu einigen verwirrenden Situationen geführt, wenn es dort ist sind mehrere Vorlagen, von denen nur einige die Dokumentverknüpfungen benötigen.
Jason

Verknüpfungen müssen mit meinem Controller verbunden sein. Und ich sehe keine Vorteile des externen Abfragemoduls. Ich sehe auch zwei Möglichkeiten: 1) Externes jQuery-Shortcuts-Modul + Pubsub-Kommunikation mit dem Controller. 2) Angular-Direktive, was seltsam ist, aber ich nehme an, es ist in Ordnung, die Link-Funktion mit Verknüpfungen zu versehen.
ValeriiVasin

Ich glaube nicht, dass Sie die AngularJS-UI-Anweisungen zum Dokument hinzufügen könnten, sie sind auf ein Element beschränkt.
Jason

3
$document.bind('keypress')
Benötigen

2
Der Link ist jetzt ein 404. Wenn es einen aktualisierten Speicherort gibt, können Sie ihn bitte aktualisieren.
HockeyJ

Antworten:


10

So habe ich das mit jQuery gemacht - ich denke, es gibt einen besseren Weg.

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


Danke für die Antwort. Ich sehe, dass Sie denken, wir sollten es als Richtlinie tun.
ValeriiVasin

link: Funktion postLink (scope, iElement, iAttrs) {window.addEventListener ('load', Funktion (e) {scope. $ apply (scope.keyPressed (e));}, false); }
Devnill

seltsam, eine Richtlinie dafür zu haben, aber keinen Dienst. Direktive - eine wiederverwendbare UI-Komponente (in den meisten Fällen).
Ses

Jedes Mal, wenn ich von JQuery in Angular höre, bekomme ich Gänsehaut
Walox

69

Ich würde sagen, ein geeigneterer Weg (oder "Winkelweg") wäre, ihn einer Richtlinie hinzuzufügen. Hier ist eine einfache Möglichkeit, um loszulegen (fügen Sie einfach ein keypress-eventsAttribut hinzu <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 Ihrer Direktive können Sie dann einfach so etwas tun:

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

2
Gut gemacht, sehr sauber.
Anthony Perot

2
Es war für das $ -Dokument bindend, nicht für das Element, das für mich funktioniert hat, um wichtige Ereignisse auf einem Div zu erhalten. +1, um zu zeigen, wie auch $ document eingefügt wird.
Prototyp

6
Gemäß dem obigen Code bindet diese Anweisung das Ereignis an das Element window.document ($ document), während es an jedes DOM-Tag angehängt werden kann, nicht nur an <body>, da keine Validierung erfolgt. In diesem Fall kann das Element, an das die Direktive angehängt ist, zerstört werden, der Listener für gebundene Ereignisse bleibt jedoch erhalten. Ich würde empfehlen, entweder eine Validierung vorzunehmen (um das Element auf <body> zu beschränken) oder eine Methode zu implementieren, die den Ereignis-Listener mit $ scope.on ('destroy') aufhebt.
Chmurson

1
Warum erfasst es keinen EscapeSchlüssel?
Saeed Neamati

@ SaeedNeamati Siehe Yehuda Katz Antwort auf Resig zum Thema ejohn.org/blog/keypress-in-safari-31
jmagnusson

27

Verwendung $document.bind:

function FooCtrl($scope, $document) {
    ...
    $document.bind("keypress", function(event) {
        console.debug(event)
    });
    ...
}

Es scheint, dass es zwei Ansätze gibt: Der eine besteht darin, eine Direktive zu erstellen und diese $eventüber eine Funktion an den Controller zu übergeben, der andere darin, das Ereignis direkt im Controller zu binden. Die Controller-Methode scheint weniger Code und das gleiche Ergebnis. Gibt es einen Grund, eine Methode der anderen vorzuziehen?
Nucleon

Dieser Ansatz gab mir bei jedem Tastendruck mehrere Ereignisauslöser. Ich habe Mausefalle anstelle von $ document.bind verwendet und es scheint ziemlich gut zu genügen.
Rwheadon

Dieser Ansatz deaktivierte die automatische Aktualisierung für andere Variablen, {{abc}}
windmaomao

1
Sie müssen die Änderung anwenden var that = this; $document.bind("keydown", function(event) { $scope.$apply(function(){ that.handleKeyDown(event); });
FreshPow

20

Ich kann noch nicht dafür bürgen, aber ich habe angefangen, mir AngularHotkeys.js anzuschauen:

http://chieffancypants.github.io/angular-hotkeys/

Wird mit weiteren Informationen aktualisiert, sobald ich meine Zähne drin habe.

Update 1: Oh, es gibt ein Nuget-Paket: Angular-Hotkeys

Update 2: eigentlich sehr einfach zu bedienen, richten Sie Ihre Bindung entweder in Ihrer Route oder wie ich in Ihrem Controller ein:

hotkeys.add('n', 'Create a new Category', $scope.showCreateView);
hotkeys.add('e', 'Edit the selected Category', $scope.showEditView);
hotkeys.add('d', 'Delete the selected Category', $scope.remove);

10

Hier ist ein Beispiel für einen AngularJS-Dienst für Tastaturkürzel: http://jsfiddle.net/firehist/nzUBg/

Es kann dann folgendermaßen verwendet werden:

function MyController($scope, $timeout, keyboardManager) {
    // Bind ctrl+shift+d
    keyboardManager.bind('ctrl+shift+d', function() {
        console.log('Callback ctrl+shift+d');
    });
}

Update: Ich verwende jetzt stattdessen Winkel-Hotkeys .


Ausgezeichnete Geige, aber können wir alle Verknüpfungen in der Direktive binden, damit ich überall in meiner App anrufen kann?
Priyanka Pawar

7

Als Richtlinie

Dies geschieht im Wesentlichen im Angular-Dokumentationscode, dh Drücken Sie /, um die Suche zu starten.

angular
 .module("app", [])
 .directive("keyboard", keyboard);

function keyboard($document) {

  return {
    link: function(scope, element, attrs) {

      $document.on("keydown", function(event) {

      // if keycode...
      event.stopPropagation();
      event.preventDefault();

      scope.$apply(function() {            
        // update scope...          
      });
    }
  };
}

Plunk mit einer Tastaturanweisung

http://plnkr.co/edit/C61Gnn?p=preview


Als Dienstleistung

Die Umwandlung dieser Richtlinie in einen Dienst ist sehr einfach. Der einzige wirkliche Unterschied besteht darin, dass der Bereich für den Dienst nicht verfügbar ist. Um eine Verdauung auszulösen, können Sie die einbringen $rootScopeoder eine verwenden $timeout.

function Keyboard($document, $timeout, keyCodes) {
  var _this = this;
  this.keyHandlers = {};

  $document.on("keydown", function(event) {        
    var keyDown = _this.keyHandlers[event.keyCode];        
    if (keyDown) {
      event.preventDefault();
      $timeout(function() { 
        keyDown.callback(); 
      });          
    }
  });

  this.on = function(keyName, callback) {
    var keyCode = keyCodes[keyName];
    this.keyHandlers[keyCode] = { callback: callback };
    return this;
  };
}

Mit der keyboard.on()Methode können Sie jetzt Rückrufe in Ihrem Controller registrieren .

function MainController(keyboard) {

  keyboard
    .on("ENTER",  function() { // do something... })
    .on("DELETE", function() { // do something... })
    .on("SHIFT",  function() { // do something... })
    .on("INSERT", function() { // do something... });       
}

Alternative Version von Plunk über einen Dienst

http://plnkr.co/edit/z9edu5?p=preview


4

Die etwas kürzere Antwort ist nur die Lösung 3 unten. Wenn Sie mehr Optionen wissen möchten, können Sie das Ganze lesen.

Ich stimme jmagnusson zu. Aber ich glaube, es gibt eine sauberere Lösung. Anstatt die Schlüssel mit Funktionen in der Direktive zu binden, sollten Sie sie einfach in HTML binden können, wie beim Definieren einer Konfigurationsdatei, und die Hotkeys sollten kontextbezogen sein.

  1. Unten finden Sie eine Version, die Mausefalle mit einer benutzerdefinierten Direktive verwendet. (Ich war nicht der Autor dieser Geige.)

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

    Sie müssen mousetrap.js einschließen und verwenden es wie folgt:

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

    Für die Lösung müssen Sie mousetrap.js einbinden, eine Bibliothek, mit der Sie Hotkeys definieren können.

  2. Wenn Sie die Mühe vermeiden möchten, Ihre eigene benutzerdefinierte Direktive zu entwickeln, können Sie diese Bibliothek lesen:

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

    Und das

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

    Die zweite bietet etwas mehr Funktionen und Flexibilität, dh automatisch generierte Hotkey-Spickzettel für Ihre App.

Update : Lösung 3 ist nicht mehr in Angular UI verfügbar.

  1. Abgesehen von den oben genannten Lösungen gibt es eine weitere Implementierung, die vom Angularui-Team durchgeführt wird. Der Nachteil ist jedoch, dass die Lösung von JQuery lib abhängt, was in der Winkel-Community nicht der Trend ist. (Die Angular-Community versucht, nur das mit AngularJs gelieferte jqLite zu verwenden, um überhöhte Abhängigkeiten zu vermeiden.) Hier ist der Link

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

Die Verwendung ist wie folgt:

Verwenden Sie in Ihrem HTML-Code das Attribut ui-keydown, um Schlüssel und Funktionen zu binden.

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

Fügen Sie in Ihrer Direktive diese Funktionen in Ihren Geltungsbereich ein.

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

Nachdem ich mit allen Lösungen herumgespielt habe, würde ich die empfehlen, die vom Angular UI-Team implementiert wird, Lösung 3, die viele kleine seltsame Probleme vermeidet, auf die ich gestoßen bin.


1
Danke für das Teilen. Ich habe darüber abgestimmt. Eigentlich sieht der eckige Hotkey von chieffancypants fantastisch aus, weiß aber nicht, wie man ihn nur an ein bestimmtes Modell anpasst. Drahaks eckige Hotkeys eignen sich gut für Eins-zu-Eins-Beziehungen!
Learner_Programmer

Ich denke, diese dritte Lösung (ui-utils) wird nicht mehr gepflegt oder der Link ist ungültig. Das Github Repo ist als veraltet markiert
Bernardo Ramos

Danke für den Kommentar Bernardo, ich werde die dritte Lösung entfernen.
Tim Hong

1

Ich habe einen Dienst für Verknüpfungen erstellt.

Es sieht aus wie:

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

     return service;
})

Und ich habe es in einen Controller injiziert:

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

Es funktioniert, aber Sie werden vielleicht bemerken, dass ich $ element und $ document in den Controller einspeise.

Es ist eine schlechte Controller-Praxis und verstößt gegen die Konvention "Nie auf $ zugreifen" in der Controller-Konvention.

Ich sollte es in die Direktive setzen und dann 'ngKeydown' und $ event verwenden, um den Dienst auszulösen.

Aber ich denke, der Service ist in Ordnung und ich werde den Controller früher überarbeiten.


Aktualisiert:

Es scheint, dass 'ng-keydown' nur in Eingabe-Tags funktioniert.

Also schreibe ich einfach eine Direktive und füge $ document ein:

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

Es ist besser.


0

Überprüfen Sie dieses Beispiel von den Jungs behid ng-newsletter.com; Schauen Sie sich das Tutorial zum Erstellen eines 2048-Spiels an. Es enthält einen netten Code, der einen Dienst für Tastaturereignisse verwendet.


0

Im Folgenden können Sie Ihre gesamte Verknüpfungslogik in Ihren Controller schreiben, und die Direktive kümmert sich um alles andere.

Richtlinie

.directive('shortcuts', ['$document', '$rootScope', function($document, $rootScope) {
    $rootScope.shortcuts = [];

    $document.on('keydown', function(e) {
        // Skip if it focused in input tag.
        if (event.target.tagName !== "INPUT") {
            $rootScope.shortcuts.forEach(function(eventHandler) {
                // Skip if it focused in input tag.
                if (event.target.tagName !== 'INPUT' && eventHandler)
                    eventHandler(e.originalEvent, e)
            });
        }
    })

    return {
        restrict: 'A',
        scope: {
            'shortcuts': '&'
        },
        link: function(scope, element, attrs) {
            $rootScope.shortcuts.push(scope.shortcuts());
        }
    };
}])

Regler

    $scope.keyUp = function(key) {
        // H.
        if (72 == key.keyCode)
            $scope.toggleHelp();
    };

Html

<div shortcuts="keyUp">
    <!-- Stuff -->
</div>

0

Sie können diese Bibliothek ausprobieren. Sie hat es sehr einfach gemacht, Hotkeys zu verwalten. Sie bindet und bindet automatisch Schlüssel, wenn Sie durch die App navigieren

eckige Hotkeys


0

Ich weiß nicht, ob es ein wirklich eckiger Weg ist, aber was ich getan habe

$(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>
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.