mardi 15 janvier 2013

AngularJS: Service de gestion de raccourcis clavier

A l'image des applications Google, Twitter et autres grosses applications Web du moment, il est très "user-friendly" (convivial pour les franglophobes !) de mettre en place une série de raccourcis clavier pour utiliser de manière optimisée l'application.

Les raccourcis clavier sont une touche ou une combinaison de deux ou plusieurs touches à presser simultanément afin d'éviter une action de pointage par la souris. Le fait de ne pas déplacer en permanence la main entre le clavier et la souris fait déjà gagner un temps précieux pour les gens qui travaillent toute la journée sur une application. De plus, à chaque action de la souris, il faut un temps de pointage pour déplacer le curseur sur l'endroit voulu.

Comment centraliser la gestion de ces raccourcis dans une application AngularJS ?

Nous allons voir comment utiliser un service AngularJS pour rendre cette gestion de raccourcis clavier générale.

Les services dans AngularJS


Je vous invite à prendre connaissance de cet article concernant la création et la déclaration des services dans AngularJS: http://www.frangular.com/2012/12/differentes-facons-de-creer-un-service-angularjs.html

Cet article vous décrira ce qu'est un service pour AngularJS, et vous expliquera comment les créer, quel pattern utiliser, etc... Bonne lecture!

Création du service


Pour créer ce service, je me suis inspiré très fortement d'une librairie disponible sous licence BSD (cf. définition) disponible à cette adresse: http://www.openjs.com/scripts/events/keyboard_shortcuts/

J'ai effectué la conversion de la librairie pour qu'elle s'intègre correctement avec l'environnement que nous met à disposition AngularJS.

Vous trouverez ci-dessous un code de démonstration sous jsFiddle.



Voilà un cas simple d'utilisation des services avec AngularJS. La force de ces singletons, c'est qu'ils assurent une unicité très forte dans toute notre application. Cette assurance nous permet de gérer les raccourcis facilement de n'importe quel contrôleur ou service de notre application, à la seule condition d'avoir injecté correctement ce service.

Intégrer les modifications dans le cycle de vie d'AngularJS

Lors de l'utilisation d'un raccourci, le callback est exécuté en utilisant le service $timeout fourni par AngularJS, ce qui permet de placer son exécution au sein du cycle de vie d'AngularJS.
Il est également possible d'utiliser directement la méthode $apply ou $digest sur le $rootScope (cf article sur les Scopes et évènements), mais ayant eu quelques fois l'erreur suivante : Error: $digest already in progress, j'ai décidé d'opter pour le $timeout qui s'occupe seul de synchroniser les données tout en évitant les erreurs.
PS: Il existe un moyen de prévenir cette erreur à cette adresse https://coderwall.com/p/ngisma, mais je trouve personnellement cette implémentation plus lourde qu'un bon $timeout!
J'attends vos avis sur la question.

Aller plus loin


Il est bien évidemment possible d'améliorer cette librairie en permettant d'effectuer une suite de combinaisons de touches (à la façon de la librairie jQuery MouseTrap).

On peut également penser à lever des évènements afin de rendre le service totalement autonome. Je vous conseille de lire un article de FrAngular qui vous aidera à bien appréhender le fonctionnement des évènements dans AngularJS.

L'idée de cette amélioration est de rendre indépendant ce service. Toutes les combinaisons (ou suites de combinaisons si vous en avez le temps :D) seraient alors initialisées dans une méthode init() de notre service.

Chaque raccourci clavier initialisé se verrait attribuer une étiquette unique qui sera diffusée à travers tous les scopes de notre application grâce à la méthode $broadcast sur le service AngularJS $rootScope (cf article sur les évènements).

On est maintenant libre de prendre en compte ou non ces raccourcis dans le contrôleur, et d'effectuer les actions appropriées au contrôleur courant.

Merci de votre lecture. N'hésitez pas à nous faire part de vos remarques ou interrogations si vous en avez... :-)

Bon dév'

3 commentaires:

  1. J'ai implémenté quelque chose de similaire mais avec une directive qui peut enregistrer les raccourcis.

    L'avantage est qu'on peut facilement associer un raccourci à un élément html (celui de la directive) et lorsque la directive est supprimée (quand l'élément est supprimé), les raccourcis liés sont automatiquement déliés (unbinded) via l'évènement destroy. De cette manière on doit moins se soucier de délier les évènements.

    RépondreSupprimer
  2. Salut Vincent,
    Merci pour ton retour. J'ai effectivement pensé à ce style d'intégration, mais dans le cadre de mes applications, j'ai plus souvent été amené à créer des raccourcis globaux.
    A l'occasion je ferai une nouvelle version de ce post sous forme de directive.
    Bonne journée!

    RépondreSupprimer
  3. Intéressant surtout la partie sur le $digest ... Ce qui est fou c'est que le cycle de vie ne soit pas géré par le framework et qu'un simple $apply sur le $rootScope peut très vite venir mettre la pagaille...

    $timeout est nettement mieux car il garantit de ne pas s’exécuter dans la boucle $digest en cours

    RépondreSupprimer