mercredi 23 octobre 2013

Service AngularJS de notification

Un autre exemple de service qui peut être très utile : il s'agit d'afficher des notifications pendant quelques secondes, comme le font les applications de Google, avec un lien dans le message permettant d'annuler l'action.

C'est typiquement le genre de truc qu'on imagine compliqué, mais si on le prend bien, avec AngularJS ça s'écrit en quelques lignes - enfin sans compter le CSS qui finalement est le plus gros du boulot.

L'exemple se trouve ici sur GitHub, et il y a un lien vers la démo (c'est le même que pour l'article "Conserver les valeurs des critères de recherche"). Ajoutez des articles au panier, et quand vous en supprimez du panier, vous avez une notification avec un lien pour annuler l'action.


Cette fonctionnalité de notifications est dans deux fichiers : un template à inclure et un module à ajouter comme dépendance.


Un template


Le template partials/notifications.html, à inclure dans la page index.html de l'application, n'a rien de compliqué : un <div> avec un contrôleur, un <ul> avec un <li> répété sur le contenu de la liste des notifications - il peut y en avoir plusieurs en même temps.


<div id="notifications" ng-controller="NotificationsCtrl">
    <ul>
        <li ng-repeat="notif in notifications">
            <span ng-bind="notif.text"></span>
            <a ng-if="notif.canUndo()" 
                  ng-click="notif.undo()" href="">Annuler</a>
        </li>
    </ul>
</div>

Chaque notification affiche son texte, et si elle le undo est possible, un lien "Annuler" qui appelle la fonction undo() quand on clique dessus.

Le module se trouve lui dans le fichier js/notif.js. Il contient le contrôleur, qui se contente de publier la liste des notifications dans le scope :

.controller('NotificationsCtrl', function ($scope, notification) {
    $scope.notifications = notification.list;
})


Un service


Tout le code - pas si long que ça - est en fait dans le service notification, dans le même module :

.factory('notification', function ($timeout) {
    var service = {
        list: {},
        add: function (text, undo, delay) {
            var timestamp = (new Date()).getTime();
            service.list[timestamp] = {
                text: text,
                canUndo: function () {
                    return angular.isFunction(undo);
                },
                undo: function () {
                    if (angular.isFunction(undo)) {
                        delete service.list[timestamp];
                        undo();
                    }
                }
            };
            $timeout(function () {
                delete service.list[timestamp];
            }, (delay || 5) * 1000);
        }
    };
    return service;
});

Le service conserve une liste des notifications, nommée avec beaucoup d'originalité list.
Et une fonction d'ajout d'une notification, qui prend en paramètres le texte de la notification, la fonction d'annulation, et une durée d'affichage en seconde.

Cette fonction d'ajout utilise un timestamp en millisecondes comme identifiant de la notification - c'était suffisant pour la démo, où il faudrait que l'utilisateur soit particulièrement rapide pour qu'il arrive à faire plusieurs opérations pendant la même milliseconde. Mais pour que ce service soit plus général, et que des notifications puissent éventuellement être créées par d'autres services sans risque de collision, il faudrait utiliser un identifiant plus unique que ça.

Donc la fonction ajoute à la liste un objet contenant le texte de la notification, une fonction canUndo() qui indique si l'on a fourni une fonction d'annulation, et une fonction undo() à appeler pour annuler l'action et qui retire aussi la notification de la liste pour qu'elle disparaisse après annulation.

Après l'ajout dans la liste, elle programme un $timeout pour retirer la notification au bout de la durée d'affichage indiquée, ou 5 secondes par défaut.


Utilisation du service


Alors comment s'utilise ce service ? C'est dans le fichier js/controllers.js que ça se passe. La fonction de suppression d'un article du panier est celle-ci :

        remove: function (row) {
            var self = this;
            delete self.rows[row.game.ref];
            notification.add("Article supprimé : " + row.game.name + ". ", 
                             function () {
                self.rows[row.game.ref] = row;
            }, 6);
        }

Après le delete qui supprime la ligne du panier de l'objet indexé rows, elle appelle l'ajout d'une notification, en passant le message, une fonction d'annulation qui se contente de remettre la ligne supprimée à sa place antérieure, et une durée.

Et le tour est joué !




6 commentaires:

  1. Idée fort intéressante, merci du partage :)

    RépondreSupprimer
  2. Merci pour cet article !
    Quel est la licence pour le code ci-dessus ou la démo ?

    RépondreSupprimer
  3. Je n'ai même pas pensé à indiquer de licence parce que ce n'est qu'un exemple fait pour être reproduit et trituré, pas un vrai projet utilisable tel quel.

    RépondreSupprimer
    Réponses
    1. merci ! mais le code est automatiquement sous copyright à instant où il est écrit, et la licence implicite n’existe pas. Sans licence explicite, impossible justement de reproduire / modifier / etc le code :-(
      btw, je ne dis pas cela pour embêter, merci pour ces articles !

      Supprimer
    2. Ok, j'ai mis une licence WTFPL pour que ça n'impose strictement rien, même pas de joindre la licence. Comme ça c'est officiellement un exemple dont on peut faire ce qu'on veut.

      Supprimer