Devenez un ninja avec AngularJS : l’ebook à prix libre !

Pour l’acheter et donner pour une bonne cause, c’est par ici !

Et si vous voulez plus d’informations sur sa genèse, c’est sur le blog de Ninja Squad.

angularjs-cover-dark-red

Les nouveautés d’AngularJS 1.3 – Part 3

Nous poursuivons notre exploration des nouvelles features d’AngularJS 1.3!
Pour vous rafraîchir la mémoire, nous avons déjà parlé de ngModelOptions et des nouveaux inputs dans la première partie, ainsi que de ngStrictDI, bindOnce, ngMessages et watchCollection dans la seconde partie.

No more global controllers

Changement particulièrement "BREAKING" ! Par défaut, il n’est désormais plus possible de déclarer un controller comme une fonction globale : tout passera désormais par l’utilisation des modules et de la méthodes d’enregistrement .controller().

Ainsi, ce que vous aviez probablement utilisé dans votre première application de TodoList, ne fonctionnera plus :

    // won't work in 1.3
    function PersonCtrl($scope){
      ...
    }

    // now you must use
    angular.module('controllers')
      .controller('PersonCtrl', function($scope){
        ...
      })

A noter qu’il est possible de réactiver l’ancien comportement en configuration le $controllerProvider avec la méthode .allowGlobals() :

    angular.module('todoApp')
      .config($controllerProvider){
        $controllerProvider.allowGlobals();
      }

    // work again
    function PersonCtrl($scope){
      ...
    }

Getter / Setter

Je vous avais déjà parlé de la directive ngModelOptions. Une autre option est dorénavant disponible pour celle-ci : celle d’activer un mode getter/setter pour votre modèle.

    <input ng-model="todo.name" ng-model-options="{ getterSetter: true }" />

Votre controller devra alors posséder un attribut du scope todo.name qui sera une fonction. Si celle-ci est appelée sans paramètres, elle renverra la valeur de l’attribut, sinon elle modifiera éventuellement la valeur de l’attribut avec la valeur du paramètre, de la façon dont vous le déciderez. Un getter/setter qui fonctionnerait sans modification ressemblerait à celui-ci :

    var _name = 'internal name'
    $scope.todo {
      name: function(newName){
        _name = angular.isDefined(newName) ? newName : _name;
        return _name;
      }
    }

L’intérêt principal réside dans le fait que l’on peut alors avoir une représentation interne différente de la représentation envoyée à l’utilisateur. Cela peut remplacer ce qui aurait été précédemment fait avec un watcher, voir cet exemple avec $location.

Vous pouvez alors accéder à votre attribut par son getter avec todo.name() ou changer sa valeur avec todo.name(‘new name’).

multiElement directives

Si vous utilisez des directives multi élements, par exemple :

    <div></div>
    <div>... some content</div>
    <div></div>

Vous devez maintenant déclarer la directive avec un attribut multiElement :

    angular.module('directives')
      .directive('poney', function() {
        return {
          multiElement: true,
          ...
        };
      });

Directives as Element or Attribute by default

Autre nouveauté dans les directives, elles sont maintenant par défaut à la fois un élément et un attribut (restrict: ‘EA’). Jusqu’ici, si rien n’était précisé, la directive cherchait seulement les attributs (restrict: ‘A’).

ngRepeat alias

Vous savez qu’il est possible de filtrer la collection que vous allez afficher dans un ngRepeat, par exemple :

    <div ng-repeat="poney in poneys | filter:search">{{ poney }}</div>

C’est très pratique, mais si l’on veut utiliser le nombre de résultats affichés, on est obligé de réappliquer le même filtre à la même collection :

    <div ng-repeat="poney in poneys | filter:search">{{ poney }}</div>
    <div ng-if="(poneys | filter:search).length === 0">No results.</div>

La version 1.3 introduit un alias as, qui permet justement de stocker le résultat du filtre dans le ngRepeat et de le réutiliser plus tard.

    <div ng-repeat="poney in poneys | filter:search as poneysFiltered">{{ poney }}</div>
    <div ng-if="poneysFiltered.length === 0">No results.</div>

New promise syntax

Il est maintenant possible de construire une promise directement avec le constructeur $q.

Précédemment, il était nécessaire d’utiliser cette syntaxe, dont je n’ai jamais été très fan :

    // creation
    var d = $q.defer();
    if(success){
      d.resolve('foo');
    }
    var promise = d.promise;

    // use
    promise.then(function(result) {});

Il maintenant possible de faire plus élégant, et beaucoup plus proche de la syntaxe adoptée par ES6 :

    // creation
    var promise = $q(function(resolve) {
      if(success){
        resolve('foo');
      }
    });

    // use
    promise.then(function(result) {});

ngTouched and ngSubmitted

Jusqu’ici les champs d’un formulaire avaient les propriétés (et les règles CSS associées) :

- $pristine si le champ était vierge
$dirty si l’utilisateur avait changé le modèle
$invalid si l’une des règles de validation n’était pas respectée
$valid sinon

Deux nouveaux états sont maintenant disponibles, avec les classes CSS associées :

- $untouched si le champ a le focus
$touched si le champ a perdu le focus (même sans avoir changé le modèle)

Concrètement, cela permet d’afficher les erreurs de validation dès qu’un utilisateur sort du champ, même sans l’avoir modifié, là ou $dirty demande d’avoir au moins une modification pour s’appliquer. Par exemple :

    <form name="userForm">
      <input name="email" type="email" ng-model="email" required>
      <div ng-if="userForm.email.$touched && userForm.email.$invalid">Required email</div>
    </form>

Nous saurons également maintenant si un formulaire a été soumis ou non avec l’attribut $submitted qui sera géré au niveau de celui-ci, et avec la classe ng-submitted qui apparaîtra une fois le formulaire soumis.

Vous pouvez tester l’exemple dans ce Plunker.

Déjà une vingtaine de betas sont sorties pour cette version 1.3.0, on se rapproche de la sortie finale. Mais il reste probablement de quoi faire un dernier article avant celle-ci ;).

Les nouveautés d’AngularJS 1.3 – Part 2

Nous poursuivons notre exploration des nouvelles features d’AngularJS 1.3! Si vous avez raté la première partie, c’est par ici.

Et bien sûr, vous pourrez retrouver tout ça dans notre livre dont la sortie se rapproche!

ngStrictDI

AngularJS offre un système d’injection de dépendances qui permet d’injecter les services voulus dans un composant :

    app.controller('MyCtrl', function($scope, $http){
      // $scope and $http are injected by AngularJS
    });

C’est très pratique, mais cela a un "léger" problème si vous minifiez votre application : $scope va devenir a, $http va devenir b, et là c’est le drame, l’injection ne fonctionne plus car Angular se base sur les noms.

Pour éviter cela, une autre syntaxe est disponible :

    app.controller('MyCtrl', ["$scope", "$http", function($scope, $http){
      // $scope and $http are injected by AngularJS
      // and now handles minification
    }]);

Et là, plus de problème. Il faut donc penser à utiliser cette syntaxe (ou s’appuyer sur un plugin dans votre build qui fasse tout ça automatiquement comme ng-min ou ng-annotate. Une autre syntaxe, basée sur l’attribut $inject est aussi proposée. Mais il arrive qu’un développeur oublie de l’utiliser à un seul endroit et l’application ne démarre pas, ce qui est bien sûr un peu pénible.

C’est là ou la version 1.3 apporte une nouveauté : il est désormais possible de démarrer Angular en mode strict-di (injection de dépendance stricte), et un beau message d’erreur pour indiquer que tel composant n’utilise pas la bonne syntaxe apparaît.

    <body ng-app="app" ng-strict-di>...</body>

    app.controller('MyBadCtrl', function($scope, $http){
      // not allowed with strict DI
    });
    // --&gt; 'MyBadCtrl is not using explicit annotation and cannot be invoked in strict mode'

ngMessages

Un nouveau module est également apparu, c’est le module ngMessages.

Comme je l’expliquais dans l’article précédent, il est possible en AngularJS d’afficher des messages d’erreur sur vos formulaires en fonction de différents critères : si le champ est vierge ou si l’utilisateur l’a touché, si le champ est obligatoire, si le champ viole une contrainte particulière… Bref vous pouvez afficher le message que vous voulez!

Le reproche fait à la version actuelle est que la condition booléenne à écrire pour afficher le message est souvent verbeuse du type :

    <span ng-if="eventForm.eventName.$dirty && eventForm.eventName.$error.required">The event name is required</span>
    <span ng-if="eventForm.eventName.$dirty && eventForm.eventName.$error.minlength">The event name is not long enough</span>
    <span ng-if="eventForm.eventName.$dirty && eventForm.eventName.$error.maxlength">The event name is too long</span>
    <span ng-if="eventForm.eventName.$dirty && eventForm.eventName.$error.pattern">The event name must be in lowercase</span>

Ce n’est pas insurmontable mais on a souvent plusieurs messages par champ, et plusieurs champs par formulaire : on se retrouve avec un formulaire HTML bien rempli ! On peut aussi vouloir n’afficher qu’un seul message à la fois, par ordre de priorité.

C’est ici qu’entre en jeu le nouveau module, l’exemple précédent pourra s’écrire :

    <div ng-if="eventForm.eventName.$dirty" ng-messages="eventForm.eventName.$error">
      <div ng-message="required">The event name is required</div>
      <div ng-message="minlength">The event name is too short</div>
      <div ng-message="maxlength">The event name is too long</div>
      <div ng-message="pattern">The event name should be in lowercase</div>
    </div>

C’est un peu plus lisible et cela gère pour nous le fait de n’afficher qu’un seul message et de les afficher par ordre de priorité. Si vous voulez afficher plusieurs messages à la fois, c’est facile, vous ajoutez la directive ng-messages-multiple.

Il est possible d’écrire tout ça différemment, avec un attribut multiple :

    <ng-messages for="eventForm.eventName.$dirty" multiple>
      <ng-message when="required">...</ng-message>
      <ng-message when="minlength">...</ng-message>
      <ng-message when="maxlength">...</ng-message>
      <ng-message when="pattern">...</ng-message>
    </ng-messages>

Le dernier avantage à utiliser ngMessages réside dans la possibilité d’externaliser les messages d’erreur dans des templates, offrant ainsi la possibilité de les réutiliser par ailleurs.

    <!-- error-messages.html -->
    <ng-message when="required">...</ng-message>
    <ng-message when="minlength">...</ng-message>
    <ng-message when="maxlength">...</ng-message>
    <ng-message when="pattern">...</ng-message>

Puis dans votre formulaire :

    <ng-messages for="eventForm.eventName.$dirty" ng-include-messages="error-messages.html"/>

Et si jamais les messages d’erreur sont trop génériques, vous pouvez les surcharger directement dans votre formulaire :

    <ng-messages for="eventForm.eventName.$dirty" ng-include-messages="error-messages.html">
      <ng-message when="required">An overloaded message</ng-message>
    </ng-messages>

A noter que cela fonctionne également avec vos directives de validation custom!

Là encore, amusez-vous avec le Plunker associé.

Watchgroup

Le $scope est enrichi d’une nouvelle méthode pour observer un ensemble de valeurs : watchGroup. Elle fonctionne sensiblement comme la méthode watch déjà existante, à la nuance près qu’au lieu d’observer une seule valeur, elle en observe une collection :

    $scope.watchGroup([user1, user2, user3], function(newUsers, oldUsers) {
      // the listener is called any time one of the user is updated
      // with newUsers representing the new values and oldUsers the old ones.
    );

One time binding

Il est maintenant possible de demander à Angular de cesser d’observer une expression une fois celle-ci évaluée. Il suffit pour cela de la précéder de :: dans vos templates. Ainsi tout changement ultérieur ne sera pas répercuté à l’affichage.

    $scope.name = 'Cédric';

    <!-- updating the input won't affect the displayed div -->
    <input type="text" ng-model="name">
    <div>Bind once {{ ::name }}</div>

Cela fonctionne aussi avec les collections, par exemple lorsqu’elles sont utilisées dans un ng-repeat :

    $scope.tasks = ["laundry", "running", "shopping"];
    $scope.remove = function(){ $scope.tasks.pop(); };

    <!-- removing tasks will not affect the displayed list -->
    <button ng-click="remove()">remove</button>
    <div ng-repeat="task in ::tasks">
      {{ task }}
    </div>

Bien sûr, c’est plus sympa à essayer dans le Plunker associé.

Il reste encore quelques fonctionnalités sympathiques à couvrir dans cette version 1.3 : stay tuned !

Les nouveautés d’AngularJS 1.3

La team Angular prépare avec amour sa nouvelle version. Ayant abandonné son précédent système de versionning pour adopter le semantic versionning, la 1.3 sera donc la nouvelle version stable (précédemment 1.0.x, puis 1.2.x, la 1.1.x étant la branche de développement, à la Unix Kernel).

Mais que contient cette version fraichement émoulue? Outre les nombreux "fixes", on note l’abandon définitif du support d’IE8 : cette fois vous êtes seuls au monde si votre application doit tourner sur ce bon vieux IE. Pour le reste, nous avons passé au crible pour vous les différents commits (plus de 400!) qu’elle contient !

Disclaimer
Nous sommes dans la phase finale de rédaction d’un livre sur AngularJS en français, nous vous en dirons bientôt plus! Et en attendant, si vous voulez en savoir plus, allez voir notre formation, qui est d’ores et déjà à jour!

ngModelOptions

Jusqu’ici la directive ngModel lançait une mise à jour du model à chaque action utilisateur (donc à chaque frappe clavier par exemple, les validations étaient exécutées, et une boucle $digest se déroulait). Cela fonctionne très bien, mais peut être pénalisant si vous avez une page avec beaucoup de bindings surveillés et à mettre à jour. C’est ici qu’intervient cette nouvelle directive : ngModelOptions.

Il devient ainsi possible de changer ce comportement par défaut et de déclencher ces différents événements selon votre envie, en passant un objet représentant les options à cette directive. La pull-request était ouverte depuis plus d’un an, comme quoi il ne faut jamais désespéré quand on contribue à un projet populaire! Tous les inputs portant un ngModel vont alors chercher ces options soit sur l’élément actuel ou sur ses ancêtres.

Ces options peuvent contenir un champ updateOn qui définit sur quel événement utilisateur la mise à jour du modèle doit être effectuée. Vous pouvez par exemple ne la déclencher que sur la perte de focus du champ :

<input name="guests" type="number" ng-required="true" ng-model="event.guests" max="10" ng-model-options="{ updateOn: 'blur' }">

Il est possible de passer plusieurs événements par exemple blur pour la perte de focus et paste pour déclencher la mise à jour si l’utilisateur colle une valeur dans le champs.

<input name="guests" type="number" ng-required="true" ng-model="event.guests" max="10" ng-model-options="{ updateOn: 'blur paste' }">

Pour conserver le comportement normal, utilisez l’événement default : utile si vous voulez simplement ajouter de nouveaux événements.

Les options peuvent également contenir un champ debounce qui spécifie un temps d’attente depuis le dernier événement avant de lancer la mise à jour. Par exemple, seulement une seconde après la dernière action utilisateur :

<input name="guests" type="number" ng-required="true" ng-model="event.guests" max="10" ng-model-options="{ debounce: 1000 }">

La valeur peut être un nombre si le même temps doit être attendu pour tous les événements, ou un objet avec comme attribut chacun des événements pour lequel vous voulez spécifier un debounce et la valeur de celui-ci.

<input name="guests" type="number" ng-required="true" ng-model="event.guests" max="10" ng-model-options="{ debounce: { default: 0, paste: 500 }}">

On peut donc également définir cette option pour toute une page ou tout un formulaire, puisque chaque ngModel recherchera les options également sur ses ancêtres :

<form name="eventForm" ng-model-options="{ debounce: 1000 }">
  <input name="week" type="date" ng-required="true" ng-model="event.weekNumber">
  <input name="guests" type="number" ng-required="true" ng-model="event.guests" max="10">
</form>

Cette nouvelle option est assez pratique, notamment pour un cas très précis qui arrive parfois. Imaginez que vous ayez un formulaire d’inscription et que le champ login vérifie si la valeur entrée par l’utilisateur est disponible côté serveur : on voit ici l’intérêt du debounce pour attendre que l’utilisateur termine son entrée de valeur, ou d’attendre la perte de focus pour faire la vérification, plutôt que de lancer une requête HTTP à chaque frappe clavier!

<!-- poney-unique-name is a custom validator, implemented as a directive, checking with the server if the value is not already taken by another poney -->
<input name="name" type="text" ng-required="true" poney-unique-name ng-model="poney.name" ng-model-options="{updateOn: 'blur'}">

Un problème peut cependant apparaître avec cette approche. Imaginez un bouton ‘Clear’ sur votre formulaire qui vide les champs. Si jamais l’utilisateur remplit un champ avec un debounce, puis clique sur ‘Clear’, les champs vont se vider, puis le debounce s’éxecuter et remplir à nouveau le champ! Il faut donc penser à supprimer tous les debounces en attente dans le code de la méthode clear() appelée par le clic sur le bouton, en utilisant une nouvelle méthode exposée par le controller du champ, appelée $rollbackViewValue.

Vous pouvez jouer avec cette nouvelle fonctionnalité, et ses limites, avec ce Plunker.

Input date

Jusqu’à maintenant, les formidables capacités d’AngularJS pour les formulaires permettaient de gérer la validation des types text, number, email, url, en s’appuyant sur les types d’input HTML5 dans les navigateurs récents et en la simulant à l’aide d’un polyfill dans les autres. Plus besoin donc d’écrire vos propres expressions régulières, le travail est fait pour vous, et le framework se charge d’ajouter dynamiquement des classes CSS sur l’élément (par exemple ng-invalid-email) en cas de violation, ainsi que de maintenir une représentation de votre champ (et du formulaire plus globalement) en JS pour permettre de faire :

<form name="userForm">
  <input name="email" type="email" ng-required="true" ng-model="user.email">
  <span ng-show="userForm.email.$error.email">Email is incorrect</span>
</form>

La variable userForm représente le controller du formulaire (le nom vient du champ name du formulaire), son attribut email représente lui le controller du champ email.

Ainsi le message d’alerte indiquant que le champ n’est pas bien rempli ne s’affiche que si l’email est incorrect. Rien d’autre à faire pour nous, Angular s’occupe de tout.

Vous pouvez vous référer à cet article précédent pour plus d’informations sur le sujet.

La version 1.3 apporte maintenant la gestion des type ‘date’, en utilisant là encore le support HTML5 si disponible (et vous aurez alors le plus ou moins beau date-picker de votre navigateur), ou un champ texte sinon, dans lequel il faudra entrer une date au format ISO-8601, par exemple ‘yyyy-MM-dd’. Le modèle lié doit être une Date JS.

<form name="userForm">
  <input name="birthDate" type="date" ng-required="true" ng-model="user.birthDate" min="1900-01-01" max="2014-01-01">
  <span ng-show="userForm.birthDate.$error.date">Date is incorrect</span>
  <span ng-show="userForm.birthDate.$error.min">Date should be after 1900</span>
  <span ng-show="userForm.birthDate.$error.max">You should be born before 2014</span>
</form>

Si il y a les dates, il y a également les heures.
Si ce type n’est pas supporté par le navigateur, un champ texte sera utilisé et le format ISO sera HH:mm :

<form name="eventForm">
  <input name="startTime" type="time" ng-required="true" ng-model="event.startTime" min="06:00" max="18:00">
  <span ng-show="eventForm.startTime.$error.time">Time is incorrect</span>
  <span ng-show="eventForm.startTime.$error.min">Event should be after 6AM</span>
  <span ng-show="eventForm.startTime.$error.max">Event should be before 6PM</span>
</form>

Le modèle lié est une Date JS avec la date du 1 Janvier 1900 et l’heure saisie.

La version 1.3 supporte également les champs ‘dateTimeLocal’, qui sont donc une date et une heure. Si ce type n’est pas supporté par le navigateur, un champ texte sera utilisé et le format ISO sera yyyy-MM-ddTHH:mm :

<form name="eventForm">
  <input name="startDate" type="dateTimeLocal" ng-required="true" ng-model="event.startDate" min="2014-06-01T00:00" max="2014-06-30T23:59">
  <span ng-show="eventForm.startDate.$error.dateTimeLocal">Date is incorrect</span>
  <span ng-show="eventForm.startDate.$error.min">Event should be after May</span>
  <span ng-show="eventForm.startDate.$error.max">Event should be before July</span>
</form>

Enfin, il est possible d’utiliser les champs ‘month’ ou ‘week’, stocké également dans un modèle de type Date,
qui permettent évidemment de choisir un mois ou une semaine.
Si ce type n’est pas supporté par le navigateur, un champ texte sera utilisé et le format ISO à utiliser sera yyyy-## ou yyyy-W## pour les semaines :

<form name="eventForm">
  <input name="week" type="week" ng-required="true" ng-model="event.weekNumber" min="2014-W01" max="2014-W52">
  <span ng-show="eventForm.week.$error.week">Week is incorrect</span>
  <span ng-show="eventForm.week.$error.min">Event should be after 2013</span>
  <span ng-show="eventForm.week.$error.max">Event should be before 2015</span>
</form>

Ces deux fonctionnalités ne sont qu’une partie de celles offertes par la nouvelle version : on en garde quelques unes pour un prochain post. Stay tuned!

Développer pour Google Glass

Vous n’êtes probablement pas passés à côté de l’existence des Google Glass et vous êtes même probablement intrigués et impatients d’en essayer. Peut être aussi un peu effrayés par ces nouvelles caméras qui vont nous observer dans la vie de tous les jours. Moi aussi. Mais on imagine aussi les possibilités que peuvent offrir un tel device : cela fait donc un moment déjà que je voulais voir comment développer pour Google Glass.

Il existe deux façons de procéder :

- utiliser une API REST (nommée Mirror API) pour envoyer des "cartes" (ce qui s’affichera en haut à droite de l’oeil de votre utilisateur) aux Glasses, pour les insérer dans une Timeline.
– développer une application embarquée sur le device, grâce au Glass Development Toolkit. Cette option a l’avantage de vous donner accès au hardware et de faire du offline. Le Development Kit est assez proche de celui qui permet de faire des applications Android.

L’idée

Mon idée était relativement simple : avec Mix-IT qui approche (disclaimer : je fais parti de l’orga), pourquoi ne pas faire le programme de la conf sur Glass ? Le site de Mix-IT expose déjà une API pour récupérer les différentes sessions, il suffisait donc de trouver le moyen d’envoyer une notification à l’utilisateur avant chaque session. Cela ressemblait bien à ce que proposait la Mirror API !

L’API propose même d’envoyer des "bundles" de cartes : c’est à dire un groupe de cartes, avec une carte particulière pour la couverture. L’idée était donc d’envoyer un bundle de carte quelques minutes avant le début d’un créneau horaire, avec :

- une carte de couverture qui résume le nombre de talks et de workshops à venir, ainsi que l’heure.
– une carte dans le bundle pour chaque talk et workshop, avec le nom de la salle, le titre et la photo du speaker.

La stack

Pour la partie technique, ce qui est cool avec la Mirror API, c’est que vous êtes libres de choisir votre langage préféré pour appeler l’API REST. Google fournit même des librairies facilitant l’utilisation de l’API dans différents langages.

Bonne occasion pour faire un peu de Java 8!

Il est également nécessaire de demander l’autorisation à l’utilisateur d’accéder à sa timeline, il faut donc avoir une page Web très simple qui permet à l’utilisateur de déclencher l’autorisation OAuth, puis à notre code de récupérer le token généré.

Un serveur Web très léger en Java 8 ? Bonne occasion pour essayer fluent-http de la team CodeStory !

Enfin, pour le déploiement, mon obsession du moment s’appelle [Docker](http://docker.io). J’espérais donc pouvoir déployer l’application sur Google Compute Engine, qui propose de faire ça depuis peu.

La réalisation

D’abord, commençons par déclarer notre application Glass. Il suffit de se rendre dans la console Google et de créer un projet, en activant l’API Google Glass dans la partie API puis en enregistrant les URLs autorisées qui vous intéressent (serveur de dév et de prod) dans les credentials. Il faut en effet indiquer à Google quelle URL de callback il devra appeler une fois l’authentification réussie (et
c’est ainsi que l’on récuperera notre token).

Première étape de dév, afficher une petite page statique grâce à fluent-http. Super simple, ce que vous mettez dans le dossier `app` de votre projet est automatiquement servi! Le serveur supporte même différents formats (markdown, yml…).

Application.java

    new WebServer(routes -&gt; {
      routes.get("/", Model.of("oauth", oAuthUrl)
    }).start(8081);

index.html

    <h1>Mix-IT, now on Google Glass</h1>
    <a href='[[oauth]]'>Try it!</a>

Le serveur va répondre le fichier `index.html` et remplacer la variable `oauth` par celle définie dans le code, qui contient l’url d’authentification à appeler.

Ensuite, il faut récupérer le token renvoyé par OAuth. Google met à disposition une librairie utilitaire pour faire la majeure partie du travail, mais, soyons honnête, elle est assez désagréable à utiliser (mal pensée, mal codée, mal documentée). Elle permet quand même de faire l’essentiel du travail sans trop se fatiguer.

On définit donc une nouvelle route dans notre serveur pour récupérer le token renvoyé sur l’url de callback `/subscribe?code=monToken`.

    routes
    .get("/", Model.of("oauth", oAuthUrl)
    .get("/subscribe", context -> {
      storeCredentials(context.get("code"));
      return ModelAndView.of("success");
    })

Le token est stocké, et les prochaines notifications peuvent donc être envoyées à l’utilisateur.

Il ne reste plus qu’à récupérer le programme et envoyer au bon moment les cartes aux utilisateurs inscrits. Pour cela, on récupère le JSON du programme exposé par le site et on le parse. Quitte à être en Java 8, autant utiliser les nouveaux types Date du jdk8 : pour cela Jackson propose un module de sérialisation/déserialisation tout prêt.

C’est plaisant de faire du Java 8, par exemple regrouper les talks par leur heure de début se fait en une ligne :

    talks.stream().collect(groupingBy(Talk::getStart));

`groupingBy` est une méthode offerte par la classe Collectors, et fait le travail pour nous.

ou encore les ordonner, et récupérer le prochain talk :

    talks.keySet()
      .stream()
      .min(Comparator.naturalOrder())
      .get();

C’est simple, ca se lit bien, c’est efficace.

Il n’y a plus qu’à construire le bundle de cartes. Construire une carte est très facile, la librairie Google possède une classe `TimelineItem` avec un attribut `html` dans lequel vous mettez ce que vous voulez afficher à votre utilisateur.

Pour simplifier, voilà comment est construite une carte :

    <article>
      <section>
        <div class="layout-figure">
          <div class="align-center">
            <p class="text-x-large">talk.getStart()</p>
            <p class="text-normal">talk.getRoom()&lt;/p
          </div>
          <div class="text-large" style="background-image:url('talk.getSpeakers().get(0).getUrlimage()');">
            <p>talk.getTitle()</p>
          </div>
        </div>
      </section>
    </article>

Vous pouvez constater qu’il y a quelques classes CSS disponibles pour la taille des textes,
ou leur couleur par exemple.

Une fois les cartes construites, on les envoie aux utilisateurs :

    credentials.forEach(credential -> getMirror(credential).timeline().insert(item).execute());

Yay! \o/

Le déploiement

Donc en ce moment j’aime bien Docker. Et les articles de David Gageot m’avaient donné envie de tester Google Compute Engine. C’est parti pour faire l’image Docker de l’application!
On prépare un Dockerfile qui installe Java 8, on lui ajoute les sources du projet, on expose le port de la webapp et on la démarre. Ca a l’air facile? Ca l’est!

Dockerfile

    from base
    maintainer Cédric Exbrayat

    # Install prerequisites
    run apt-get update
    run apt-get install -y software-properties-common

    # Install java8
    run add-apt-repository -y ppa:webupd8team/java
    run apt-get update
    run echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | sudo /usr/bin/debconf-set-selections
    run apt-get install -y --force-yes oracle-java8-installer

    # Install tools
    run apt-get install -y maven

    # Add sources
    add ./ ./glass
    workdir ./glass

    # Build project
    run mvn install -DskipTests

    # Expose the http port
    expose 8081

    # Launch server on start
    cmd mvn exec:java

Y’a plus qu’à déployer! Mais en fait Google Compute Engine est payant, même pas un petit mode gratuit comme App Engine. Pas grave, on a déjà un petit serveur avec Docker installé, ca fonctionne pareil! Je rajoute un petit script chargé de tuer le container démarré, et de lancer la nouvelle version :

run.sh

    # Kill old container
    echo "kill old container"
    alias docker="docker -H 0.0.0.0:4243"
    docker kill $(cat docker.pid)

    # Build new image
    echo "build image"
    docker build -t ninjasquad/mix-it-on-glass .

    # Run image
    echo "run image"
    DOCKER_CONTAINER=$(docker run -p 8081:8081 -d ninjasquad/mix-it-on-glass)

    # Save container id
    rm docker.pid
    echo "$DOCKER_CONTAINER" >> docker.pid

Procédure de déploiement : `git pull & ./run.sh`.
Temps de déploiement : 15 secondes (quasi entièrement dévolue au build Maven pendant la construction de l’image).

Le test

Vous allez me dire : "Mais t’as des Google Glass toi ?". Non, j’en ai pas. Alors, comment je fais pour tester ? Google propose un Playground pour visualiser sa timeline Glass. Bizarrement assez buggué (les cartes n’apparaissent pas parfois), cela permet quand même de voir si les cartes
sont correctement insérées dans la timeline (même si il faut rafraîchir manuellement, alors que l’on pourrait s’attendre à quelque chose de plus… temps réel!) et d’éditer leur contenu pour jouer avec.

Vous pouvez essayer vous même! C’est par ici : mix-it-on-glass.ninja-squad.com.

TL;DR;

Le développement pour Glass est assez simple, surtout du HTML pour faire de belles cartes, et le langage que vous préférez pour insérer ces cartes dans la timeline. La partie OAuth est légérement simplifiée en utilisant une librairie proposée par Google, même si celle-ci n’est pas fantastique en Java…

Bon, maintenant j’attends de voir des Glass pour faire un test de mon programme. Si vous en avez, tenez moi au courant !

JHipster – boost your Spring and AngularJS app

Mon blog s’appelant Hype Driven Development, ça me semblait difficile
de ne pas faire un article sur JHipster, le générateur d’application full stack pour
les hipsters du Java (avouez, c’est difficile de résister).

Un générateur? Oui, un générateur Yeoman. Vous avez peut être entendu parler
de cet outil, fort pratique pour générer des squelettes de projets,
par exemple pour commencer votre prochain projet Angular (c’est d’ailleur le générateur le plus connu).
Yeoman fournit le moteur de base, et vous pouvez installer les générateurs qui vous intéressent.
Il y en a plein et JHipster commence à être fort populaire.

JHipster est un générateur d’application Spring/AngularJS, et donne un projet tout bien
configuré et prêt à démarrer en moins de 10 minutes,
qui se répartissent entre taper 2 commandes et répondre à quelques questions (2 minutes),
regarder NPM télécharger la partie paire de l’internet (3 minutes) et Maven télécharger la partie impaire (5 minutes).
Oui c’était jour de circulation alternée.
Les seuls pré-requis sont donc NodeJS et NPM, un JDK 1.7 et Maven 3.

Stack serveur

Alors qu’est-ce que ça nous a installé ?
Côté serveur, cela s’appuie sur Spring Boot, un projet assez récent
qui permet de créer rapidement une application fonctionnelle avec Spring 4.
C’est clairement un projet que les équipes Spring veulent mettre en avant, pour montrer
que la création d’une application Spring peut être très simple. Spring Boot va créer une application toute prête
(une CLI est disponible, ou un plugin pour votre outil de build préféré), un pom.xml configuré, un serveur Tomcat ou Jetty embarqué,
pas de XML ou de code généré. Vous avez une classe de démarrage de votre application
avec quelques annotations dessus, et ça roule. Spring Boot ramène des copains à la fête :
Spring MVC pour les controllers REST.
Jackson pour la sérialisation JSON.
Spring JPA et Hibernate pour la partie accès aux données.
HikariCP pour le pool de connexion JDBC.
Spring Security pour la gestion des utilisateurs et de l’authentification.
Joda Time pour la gestion des dates (ca devrait être inutile en Java 8).
Metrics un framework de monitoring, développé par Yammer.
Thymeleaf si vous voulez faire des templates côté serveur.
– les drivers de la base de données choisie (H2, PG ou MySQL).
Liquibase pour gérer les changements de schéma (très pratique comme outil).
JavaMail pour les… mails.
EhCache ou Hazelcast selon le cache choisi.
Logback pour la gestion des logs.
JUnit, Mockito et
Awaitility pour les tests.

Pas mal de code est déjà généré également, notamment pour la partie sécurité,
avec une classe utilisateur, une pour les rôles, une autre pour les audits…
Et bien sûr les repositories, services et controllers associés.
Un service d’authentification est déjà prévu avec une configuration très solide.
L’organisation des sources est claire et très classique, on s’y retrouve facilement.
Les ressources contiennent le nécessaire pour la configuration Liquibase, les fichiers
YAML de paramétrage de Spring selon le profil (deux profils, prod et dev sont fournis).
C’est dans ces fichiers que vous pourriez configurer votre BDD ou votre serveur mail par exemple.
On trouve également les fichiers de messages pour l’i18n, dans plusieurs langues.
Enfin, figurent aussi les fichiers de config du cache et du logger.

Dans les tests, on trouve des exemples de tests unitaires et de tests de controllers REST.

Stack client

Passons côté client. On a bien sûr du AngularJS (what else?).
Grunt est l’outil de build choisi (pas encore de Gulp? c’est pourtant plus hype…), il est déjà bien configuré
pour surveiller les fichiers et les recharger à chaud au moindre changement sauvegardé, passer JsHint pour vérifier,
tout minifier et concaténer. Bower est là pour la gestion de dépendances, rien d’exotique dans ce qui est récupéré :
Angular et ses différents modules indispensables (routes, cookies, translate…). On retrouve bien sûr Twitter Boostrap
pour faire joli et responsive. Les tests unitaires (avec quelques exemples fournis) sont lancés grâce à Karma,
pas de tests end-to-end en revanche.

Le fichier `index.html` est basé sur HTML5 Boilerplate, les fichiers CSS et i18n sont là.
Les scripts JS sont regroupés par type (`controllers.js`, `services.js` …).
Les déclarations d’injection Angular sont faites en mode tableau de chaîne de caractères pour éviter les problèmes de minification.
C’est un bon point, le générateur Yeoman pour Angular propose d’utiliser le plugin ngmin, mais celui-ci a quelques limites.
La sécurité est aussi gérée côté client. Enfin un certain nombre de vues sont disponibles d’origine pour le login et des vues
d’administration : gestion des logs, des perfs.

Run

Lançons tout ça!
Le projet démarre sur le port 8080 avec un simple `mvn spring-boot:run`, sur un écran d’accueil. On peut se connecter en tant qu’admin
et voir des écrans de gestion de profil (mot de passe, settings, sessions), l’écran d’audit (avec la liste des événements), l’écran de configuration des (977!!) loggers par défaut,
dont on peut changer le niveau à chaud, et enfin l’écran exposé par Metrics, avec les différentes… métriques donc!
On peut y voir des ‘Health checks’ (est-ce que la base de données est up? le serveur de mail?), les stats de la JVM
(mémoire, threads, GC), les requêtes HTTP, les statistiques des différents services (nombres d’appel, temps moyen, min, max et différents percentiles)
et enfin les statistiques du cache. J’aime beaucoup cette intégration d’un outil de monitoring simple, c’est certainement
quelque chose que j’utiliserai sur mes projets.
Il est possible de changer dynamiquement le langage de l’application (merci angular-translate).

Développement

JHipster permet aussi de générer des entités pour nous,
depuis la base de données jusqu’à la vue Angular, en passant par le repository et le controller Spring,
la route, le service et le controller Angular. C’est un peu anecdotique (la génération de code, à force…), mais ça marche, et ça
donne un CRUD tout à fait utilisable pour des écrans d’admin. Le service Spring doit lui être généré à part, pour
ne pas encourager la production de code inutile (ce qui est un peu paradoxal pour un générateur de code, vous en conviendrez, mais
je suis sensible au geste).

Ce qui est plus impressionnant, c’est que le projet utilise Spring Loaded, que j’ai découvert très récemment
(merci Brian de l’équipe Spring au détour d’un café à la Cordée!), et que cet agent permet le rechargement de code à chaud, même
lors d’un ajout de bean Spring! Couplez ça à Grunt et son LiveReload, et chaque modification est prise en compte
instantanément. Très très cool! Je pense que Spring Loaded va se retrouver sur les projets Ninja Squad d’ici peu.

Il faut quand même reconnaître qu’entrer la commande

yo jhipster:entity campaign

Puis se rendre directement dans son navigateur à l’url ‘/campaign’ et avoir le CRUD fonctionnel devant les yeux
en moins d’une seconde alors que cela a créé des tables en base, des beans Springs, du JS, qu’on a rien relancé,
c’est quand même pas mal la classe internationale.

TL, DR;

La stack complète est au final extrêmement proche de celle que nous utilisons
sur certains projets chez Ninja Squad : Spring MVC, Spring JPA, Spring Data,
HikariCP, Liquibase, Bower, Grunt, NPM, AngularJS, à l’exception donc
de Spring Boot, Spring Loaded, Metrics, Spring Security et nous préférons LESS à Compass et FestAssert pour les tests.
Puis on fait du Gradle, parce que certains n’aiment pas Maven (je ne donnerai pas de noms).

Mais je crois que Spring Loaded et Metrics vont bientôt rejoindre mes outils favoris.

JHipster propose aussi de faire des WebSockets avec Atmosphere ou
de gérer le clustering des sessions HTTP avec Hazelcast.

Bref, plein de bonnes idées, même si vous connaissez bien toutes les technos, et de bonnes pistes de configuration
si vous les découvrez.

Si vous ne saviez pas comment démarrer votre prochain projet Web Java, vous savez maintenant par quoi commencer!
En plus JHipster propose un container Docker pour tester. Si ça c’est pas le top
de la hype 2014, je sais plus quoi faire…

Workflow, Pull Requests & Code Reviews

Si vous suivez le blog de Ninja Squad, vous savez que nous avons déjà parlé du workflow Git que nous utilisons en local. Mais on ne vous a jamais expliqué les différentes façons de collaborer à plusieurs sur un projet.

Il y a deux grandes façons de travailler à plusieurs :
– un repo partagé par toute l’équipe
– un repo pour chaque développeur

Workflow & Pull Requests

Open Source style

Cette dernière façon est la plus répandue dans le monde open source. Admettons que vous vouliez contribuer au projet AngularJS. Vous n’avez pas les droits d’écriture sur le repository Github utilisé par le projet tant que quelqu’un de l’équipe ne vous les donne pas. Ce qui semble assez logique vu qu’ils ne vous connaissent pas :)

Donc, si vous voulez contribuer au projet, il faut commencer par faire un fork. Il y a un gros bouton sur Github qui permet de faire ça et qui va copier le projet AngularJS dans votre espace utilisateur (par exemple `cexbrayat/angular.js`). Sur ce nouveau repository, j’ai les droits d’écriture. Je peux donc le cloner sur mon laptop :

git clone https://github.com/cexbrayat/angular.js

Une fois cloné, le repository Github est référencé comme le repo ‘origin’ dans nos remotes. Je peux faire des modifications dans une branche :

cd angular.js
git branch awesome-feature
git checkout awesome-feature
... commits

et pousser mon code sur mon repository :

git push origin awesome-feature

Il faut noter que ce repository n’est pas synchronisé automatiquement avec le repository forké : si les développeurs de la team Google ajoutent de nouveaux commits au repository `angular/angular.js`, nous ne les verrons pas dans notre repository. Si ces commits nous intéressent, il est possible d’ajouter ce repo come un autre de nos remotes. Par convention, ce repository est généralement appelé ‘upstream’.

git remote add upstream https://github.com/angular/angularjs

On peu coder notre fonctionnalité comme précédemment :

git branch awesome-feature
git checkout awesome-feature
... commits

Et l’on peut maintenant récupérer les modifications de la team Google et mettre à jour notre branche locale avec celles-ci :

# on récupère les derniers commits de upstream
git fetch upstream
git rebase upstream/master
# on pousse notre branche à jour
git push origin awesome-feature

Une nouvelle branche `updated-with-upstream` sera alors ajoutée sur notre repository Github, à jour avec les dernières modifications faites par l’équipe de Google.

Si cette branche contient une fonctionnalité que vous voulez contribuer, vous pouvez alors créer une pull request. En vous plaçant sur votre branche dans l’interface Github, vous pouvez choisir de créer une pull request vers le repository que vous avez forké, par défaut sur la branche `master`, mais rien ne vous empêche de changer de branche. Vous entrez alors un titre, une description du but de cette PR. Lorsque vous validez, les propriétaires du repository sur lequel vous avez fait la PR vont recevoir un mail leur indiquant l’arrivée d’une nouvelle requête.

Dans l’interface Github, ils pourront faire une revue de code, mettre des commentaires sur certaines lignes (vous ne vous attendiez pas à réussir du premier coup si?), puis enfin choisir de refuser la PR (dommage) ou de la merger (\o/). Dans ce dernier cas, votre code sera ajouté à la branche en question, bravo!

Github fournit maintenant un ensemble de guides fort pratiques pour débuter : vous pouvez regarder celui sur le workflow des PRs.

Enterprise style

En entreprise, le workflow est généralement différent. La plupart du temps, l’équipe collabore sur un seul repository sur lequel tout le monde possède les droits d’écriture. Il y a donc deux façons de procéder.

La première consiste à pousser ses modifications directement sur le `master` (si c’est votre branche de développement actif). On laisse alors le soin à l’intégration continue de repérer tout problème (il n’y a plus qu’à espérer avoir suffisamment de tests…).

git branch feature-one
git checkout feature-one
... commits
# on récupère les derniers commits de origin
git fetch origin
git rebase origin/master
# on pousse notre branche à jour sur le master
git push origin feature-one:master

La seconde consiste à pousser ses modifications sur une branche partagée, puis de créer une pull request entre cette branche et le master! L’avantage est de pouvoir faire une revue de code sur cette pull request, d’en discuter puis de l’intégrer.

git branch feature-one
git checkout feature-one
... commits
# on récupère les derniers commits de origin
git fetch origin
git rebase origin/master
# on pousse notre branche à jour sur une branche partagée pour faire une PR
git push origin feature-one

Code review, sweet code review

Le mécanisme de code review est vraiment intéressant à systématiser. Bien sûr, cela introduit une tâche supplémentaire, que l’on peut voir comme une perte de temps. Mais il est très difficile de coder juste tout le temps du premier coup (en tout cas moi je sais pas faire), et un oeil externe peut souvent voir certains problèmes qui nous ont échappé au moment de la réalisation.

Au-delà de ça, le niveau d’une équipe n’est jamais parfaitement homogène : certains sont d’excellents développeurs front-end, d’autres seront plus à l’aise sur le back-end, ou l’automatisation du projet. La code review est alors un excellent moyen de transmettre des compétences! Si la personne qui relit le code est plus expérimentée, elle sera à même de donner des conseils, des solutions plus élégantes et de repérer des problèmes. Si elle est moins expérimentée que celle qui a développé, elle pourra poser des questions, comprendre de nouvelles choses, s’inspirer. Et elle trouvera des erreurs aussi, c’est garanti!

C’est une procédure délicate, car il faut être capable de dire les choses sans blesser les autres (on est tous un peu sensible sur notre code), ou être capable d’admettre que l’on ne sait pas ce que fait cette méthode et de poser la question.

La code review permet aussi d’homogénéiser les développements, parce que cela force à relire le code des autres, ce que l’on ne fait que rarement si on est la tête dans le guidon. Et surtout, surtout, cela partage les connaissances fonctionnelles. Si vous avez connu le phénomène de "j’ai pas codé cette partie, je sais pas comment ça marche", vous savez de quoi je parle. Faire une revue de code force à comprendre les nouvelles fonctionnalités de l’application même si l’on a pas codé directement dessus. Cela limite ensuite le ‘Bus Effect’ (autrement appelé ‘Scooter Effect’ chez Ninja Squad, grâce aux aventures de Cyril).

Selon les projets et les clients, nous utilisons actuellement ces 3 façons de travailler. La dernière (un seul repo, avec une branche partagée par fonctionnalité, puis pull request pour intégration sur master) est vraiment agréable à utiliser.

Nous avons récemment commencé un projet avec ce workflow et notre intégration continue (Jenkins) se charge de construire chaque pull request indépendamment et d’inscrire sur Github un commentaire avec ‘Tests pass’ ou ‘Tests fail’. De plus nous pouvons déployer un container Docker pour tester une Pull Request de façon isolée. Je trouve ça génial : en 35 secondes l’application est buildée complètement, un container Linux isolé est démarré avec le nécessaire (serveur, bases et données), et l’application est exposée sur un port aléatoire. On se rend alors sur cette adresse pour tester la fonctionnalité et éventuellement remonter des bugs à l’auteur. Sans ouvrir un IDE, sans taper une seule ligne de commande, il est possible de lire le code, le commenter, voir si des tests échouent, et tester la fonctionnalité dans un environnement similaire à la prod. Magique.

Vous l’aurez compris, faire une pull request n’est pas très compliqué. Si vous démarrez un projet et hésitez sur la façon de travailler, pensez à considérer un workflow basé sur des pull requests et des revues de code : votre équipe finira par ne plus pouvoir s’en passer!

Angular, Express et Socket.io

Vous savez, si vous lisez le blog de Ninja Squad régulièrement, que nous donnons des cours dans différents établissements, de la fac aux écoles d’ingé, en passant par l’IUT. Avec cette nouvelle année scolaire, nous voici repartis!

Je donne depuis quelques années un cours sur les web services à l’INSA, à l’invitation du Docteur Ponge : c’est l’occasion de parler SOAP (beurk), REST (yay!) et Websocket (re-yay!).

Cette année, je veux faire une démo des websockets pour que cela soit un peu moins mystérieux. Faire un peu de code live est toujours un exercice périlleux, mais c’est un bon moyen pour échanger avec les étudiants et leur présenter un certain nombre de technologies, qu’ils connaissent parfois de nom ou pas du tout.

Les websockets sont l’occasion idéale de faire une application web basique, mais permettant d’introduire quelques concepts du Web parfois mal connus (HTML, CSS, Javascript) et un peu de code plus innovant (Node.js, Express, Socket.io, AngularJS, Bootstrap), de discuter sur l’histoire du Web, le fonctionnement des navigateurs (Chrome, Firefox, V8…) et quelques outils pratiques (npm, Git). Bref, d’apporter un peu de culture générale de notre métier, chose que j’appréciais beaucoup lors des interventions de professionnels lorsque j’étais étudiant.

Après cette remise en contexte, passons aux choses sérieuses!

Il me fallait donc un exemple d’application, qui me prenne une trentaine de minutes à réaliser au maximum. J’ai choisi de faire simple : une application de vote qui permet de choisir votre framework Javascript préféré, et de voir les résultats en temps réel. Nous allons voir les différentes étapes pour y parvenir.

Vous trouverez le nécessaire pour utiliser l’application sur le repo Github de Ninja Squad.

Express/Angular

La première branche Git, nommée `express` met en place une application Node.js/Express minimale.

    var express = require('express')
      , app = express()
      , server = require('http').createServer(app);
    
    app.use(express.static(__dirname + '/'));
    
    server.listen(9003);

L’application Node.js va servir les ressources statiques sur le port 9003. On peut alors ajouter le fichier HTML de notre application, qui contient basiquement :

    <div class="row">
      <div class="col-xs-4 vote"> {{ vote.choice }} </div>
      <div class="col-xs-4 vote"> {{ vote.votes }} </div> 
      <div class="btn btn-primary col-xs-4">+1</div>
    </div>

Pour chaque vote de la collection `votes`, le choix (VanillaJS, AngularJS, BackboneJS ou EmberJS), le nombre de vote pour ce choix et un bouton pour ajouter un vote seront affichés. Cela est réalisé en utilisant la directive `ng-repeat` d’Angular.

Le bouton de vote comporte l’attribut `ng-click` qui permet de lui lier une fonction à exécuter. Cette fonction `voteFor` est définie dans le controller :

	function VoteCtrl($scope){
      $scope.votes = [ { choice: 1, label: 'VanillaJS', votes: 0 }, { choice: 2, label: 'AngularJS', votes: 0 }, { choice: 3, label: 'BackboneJS', votes: 0 }, { choice: 4, label: 'EmberJS', votes: 0 }];

      $scope.voteFor = function(choice){ $scope.votes[choice-1].votes++; }
    }

Le controller Angular initialise les votes et définit la fonction de vote, qui ajoute simplement 1 aux votes du choix cliqué.

Etape 1 terminée! Passons maintenant à l’intégration de Socket.io.

Socket.io

Socket.io est l’une des librairies les plus utilisées pour les websockets dans Node (même si elle est maintenant concurrencée par d’autres comme SockJS). Elle a l’avantage de gérer le fallback si les websockets ne sont pas disponibles, et d’être très simple à utiliser à la fois côté client et côté serveur.

La branche `websocket` contient le code correspondant. Outre l’installation de socket.io (`npm install` is your best friend), il nous faut modifier un peu le serveur :

    io.sockets.on('connection', function (socket) {
      socket.emit('votes', { votes: votes });
      socket.on('vote', function(msg){
      	votes[msg.vote-1].votes++;
      	io.sockets.emit('votes', { votes: votes });
      })
    });

L’implémentation est naïve mais suffit à la démonstration : à la connexion d’un nouveau client (`socket.on(‘connection’, …)`), on envoie les votes dans l’état du moment (`socket.emit`). Puis, lorsque l’on recevra un vote (`socket.on(‘vote’, …)`), on incrémente les votes du choix correspondant et on informe tous les participants avec les nouvelles valeurs (`io.sockets.emit`).

Reste à mettre à jour le client pour communiquer avec les sockets :

    var socket = io.connect('http://localhost:9003');

    $scope.voteFor = function(choice){
      socket.emit('vote', {vote : choice })
    }

    socket.on('votes', function(msg){
      $scope.votes = msg.votes;
      $scope.$apply();
    });

On commence par se connecter aux websockets (`io.connect`). La fonction `voteFor` est modifiée pour maintenant envoyer un évenement de vote au serveur (`socket.emit`). Enfin, à chaque fois que les votes sont reçus, les votes du `$scope` Angular sont mis à jour. Petite subtilité : comme cette mise à jour intervient en dehors de la boucle d’exécution d’Angular, il nous faut appeler la fonction `$apply()` pour que le framework prenne les nouvelles valeurs en compte et rafraîchisse les vues.

Nous sommes prêts : si vous ouvrez cette application dans deux onglets, vous pourrez la voir se mettre à jour en temps réel!

A noter que le brillant Brian Ford de l’équipe Angular propose un service Angular pour Socket.io, afin de simplifier son utilisation et notamment les appels à `$apply()`.

Vous pouvez voir une démo en ligne, déployé sur Heroku (le code nécessaire pour cette partie est également sur le repo Github).

Espérons que ce petit essai vous plaise et plaise à nos étudiants!

Bitbucket vs Github

Vous savez peut-être que Bitbucket vous permet depuis fin 2011 d’héberger vos projets Git, comme Github, après avoir été un temple de Mercurial. Est-ce pour autant une bonne alternative ? Passons en revue leurs particularités !

Repository

Les interfaces du site sont extrêmement similaires, même si les designers de Github sont probablement légèrement meilleurs.

Chacun permet de créer des repositories publics et privés (mais pratique des prix différents, voir plus bas). Une fois le repository créé, on obtient une URL de remote, sur laquelle il est possible de pousser du code.

La navigation dans les sources se fait de façon identique sur le site, en parcourant les répertoires ou en utilisant les raccourcis claviers (par exemple `t` pour rechercher un fichier dans Github et `f` dans Bitbucket). Une fois le fichier trouvé, il est possible de le visionner, de faire un `blame`, de voir son historique ou de le modifier depuis le navigateur dans les deux cas.

Il est également possible de voir l’activité du projet, et les deux concurrents utilisent le fichier README de façon similaire pour décrire le projet.

L’historique des commits est un peu plus fonctionnel dans Bitbucket avec l’affichage possible de toutes les branches. La visualisation des branches est aussi intéressante, plus graphique que celle proposée par Github.

Fork/Pull Request/Code review

On trouve également le même mécanisme de fork (copie d’un repository dans votre espace utilisateur sur lequel vous avez tous les droits), de pull requests (demande d’intégration d’une fonctionnalité que vous avez codé dans un repo qui ne vous appartient pas) et de code reviews (possibilité de voir les différences introduites et de commenter le code).

Bitbucket ajoute quelques features "nice to have" : il est possible d’afficher un diff avec le fichier dans son ancienne version et dans sa nouvelle version côte à côte, d’affecter les code reviews à certains collaborateurs pour approbation et de ne pouvoir merger que si la pull request a été approuvée un certain nombre de fois. Ce petit workflow d’intégration n’est pas sans intérêt, même s’il est souvent pratiqué informellement sur Github. Autre petit avantage : lorsqu’une pull request ne peut être mergée pour cause de conflit, Bitbucket affiche clairement quels sont les fichiers et lignes en cause.

Administration

Peu de différence là encore : possibilité de créer des équipes et de leur affecter certains droits sur un repository. Bitbucket innove avec la possibilité de donner certains droits sur une branche spécifique.

Bug tracking

Chacun des concurrents propose un bug tracker intégré. Les fonctionnalités sont à peu près identiques :
– création d’anomalie avec assignation possible et version cible de correction.
– description au format markdown, avec images jointes.
– recherche des anomalies.
– lien possible entre anomalies (mais pas d’autocomplétion sur Bitbucket…).
– surveiller les anomalies.
– résolution automatique par commit.

Bitbucket propose en plus une criticité, une priorité et une gestion du workflow intégrée alors que Github compense en permettant la création dynamique de labels comme vous l’entendez. Le système de Github est flexible mais demande un peu plus de travail.

Une autre fonctionnalité intéressante réside dans le vote sur les issues. Là où Github ne permet toujours pas de voter, et où les commentaires ‘+1′ sont le seul moyen de manifester son intérêt, Bitbucket intègre directement le vote sur les issues, ce qui permet de jauger l’intérêt de la communauté pour une feature particulière.

La synchronisation avec d’autres bug trackers est généralement possible. L’intégration de Bitbucket avec Jira est mise en avant mais le même connecteur est utilisé pour Github et Bitbucket dans JIRA, les fonctionnalités sont donc possiblement équivalentes (mais je n’ai pas testé).

Wiki

Un wiki minimaliste est disponible pour les deux sites, avec syntaxe markdown, code highlighting et téléchargement possible (avec Git bien sûr) pour consultation offline.

Money, Money

Bitbucket mise sur un bon argument pour attirer les développeurs : les repositories privés. Alors que sur Github, le plan gratuit ne vous donne accès qu’à des repositories publics, Bitbucket autorise la création gratuite et illimitée de repositories privés. La restriction, car il faut bien une incitation à passer à la version payante, concerne le nombre maximum d’utilisateurs d’un repository privé : 5. Vous ne pouvez donc donner les droits d’accès à ces repo privés qu’à 5 de vos collègues : au-delà, il faudra mettre la main au portefeuille.

Les stratégies sont donc différentes en terme de marketing :
Github limite le nombre de repositories privés en fonction du prix (0 en gratuit, puis 5 pour 7$, 10 pour 12$…), le nombre de collaborateurs étant illimité.
Bitbucket permet de créer un nombre illimité de repositories privés mais limite le nombre de collaborateurs (5 en gratuit, puis 10 pour 10$, 25 pour 25$…).

Bitbucket a donc un argument intéressant pour une petite équipe créant un projet privé. A noter également la possibilité d’héberger vous même un Github Enterprise ou la suite professionelle de Bitbucket, nommée Stash, si la perspective d’avoir vos sources sur des serveurs américains vous trouble (mais franchement on ne voit pas pourquoi…). Ces outils vous donnent toutes les fonctionnalités de base plus la possibilité de s’intégrer avec votre système d’authentification interne.

Les prix sont tout de suite plus … entreprise! Github Enterprise démarre à 5000$ par an pour 20 utilisateurs, et est à peu près linéaire avec 250$ par utilisateur (100 utilisateurs donnent donc 25000$ par an, aïe). Bitbucket utilise là aussi une stratégie incitative avec une offre à seulement 10$ par mois pour 10 utilisateurs. La pente est ensuite plus raide mais les prix restent beaucoup plus abordables que ceux de Github avec 100 utilisateurs à 6000$ par an. A noter que Stash offre quelques fonctionnalités intéressantes comme une intégration poussée avec Jira (le bugtracker de la même société), ou les merges automatiques en cascade (un bugfix sur une ancienne release peut être automatiquement mergé sur les releases plus récentes).

Extras

Tous deux proposent une très bonne API REST, et des "hooks" qui permettent de s’intégrer avec tout ce que votre écosystème comporte d’important (les intégrations continues, dashboards, issue trackers…).
Bitbucket ne pose aucune limite sur la taille des fichiers, là où Github restreint à 100Mb par fichier.
Dans les petits bonus de Github, il ne faut pas oublier Github Pages, un support de nouveaux formats (fichier STL 3D, fichier GeoJSON) et une application mobile (même si c’est un peu anecdotique).

Communauté

Difficile de concurrencer Github, leader historique, dans le domaine. Avec près de 5 millions d’utilisateurs contre 1.5 million pour Bitbucket, la marge est encore grande. Les projets OSS phares hébergés sur Github sont très connus : Twitter Bootstrap, Node.js, Rails, JQuery, Angular.js, MongoDB, Linux Kernel. Bitbucket de son coté héberge les projets Atlassian, quelques projets de l’écosystème Python/Django et… pas grand chose d’autre de renommé. Mais surtout très difficile de trouver l’information, qui n’est pas mise en avant. A croire donc que les projets open source boudent le produit.

Les deux sites ont un petit aspect social, avec la possibilité de suivre des utilisateurs, de voir leur flux d’activité public, de mettre en favoris certains projets…

TL; DR;

Bitbucket a bien rattrapé son retard et ne souffre d’aucune lacune flagrante, au-delà de sa communauté moins nombreuse. Il possède même quelques fonctionnalités que l’on retrouverait avec plaisir sur Github.

Pour résumer :
– vous avez un projet open source ? Github sans réfléchir. L’exposition sera un ordre de magnitude supérieure.
– vous avez beaucoup de repositories privés, une petite équipe et peu d’argent ? Bitbucket est la solution économique. Vous pouvez même envisager Stash, leur solution pro.
– vous avez peu de repo privés et/ou de grandes équipes ? Github a un pricing plus intéressant.
– vous voulez héberger la solution chez vous ? Stash est beaucoup moins cher et ajoute quelques fonctionnalités intéressantes. Mais vous pouvez également regarder du côté des projets open source gratuits comme Gitlab par exemple.

Forms in AngularJS

Si vous lisez ceci, vous savez probablement créer un formulaire en HTML. Toute application web en contient son lot. Un formulaire contient un ensemble de champs, chaque champ étant une manière pour l’utilisateur de saisir des informations. Ces champs groupés ensemble dans un formulaire ont un sens, que ce soit une page d’enregistrement pour vos utilisateurs, ou une formulaire de login.

Les formulaires en Angular étendent les formulaires HTML en leur ajoutant un certain nombre d’états et en donnant aux développeurs de nouvelles façon d’agir.

Les formulaires, comme beaucoup de composants en Angular, sont des directives. Chaque fois que vous utilisez un formulaire en Angular, la directive ‘form’ va instancier un controller nommé ‘FormController’. Il est possible d’accéder au controller dans votre code en utilisant comme nom l’attribut ‘name’ de votre formulaire.

Ce controller expose un ensemble de méthode et de propriétés :
– ‘$pristine’ : permet de savoir si l’utilisateur a déjà touché au formulaire. On pourrait traduire en français par vierge. Ce booléen sera donc vrai tant qu’il n’y aura pas eu d’interaction, puis faux ensuite.
– ‘$dirty’ : à l’inverse de ‘$pristine’, ‘$dirty’ sera vrai si l’utilisateur a commencé à interagir avec le formulaire. Pour résumer : $dirty == !$pristine.
– ‘$valid’ : sera vrai si l’ensemble des champs (et éventuellement des formulaires imbriqués) sont valides.
– ‘$invalid’ : sera vrai si au moins un champ (ou formulaire imbriqué) est invalide.
– ‘$error’ : représente un dictionnaire des erreurs, avec comme clé le nom de l’erreur, par exemple ‘required’, et comme valeur la liste des champs avec cette erreur, par exemple login et password.

Ces états sont disponibles sur le formulaire global mais également sur chaque champ du formulaire. Si votre formulaire se nomme ‘loginForm’, et contient un champ ‘password’ alors vous pouvez voir si ce champ est valide grâce au code suivant :

    loginForm.password.$valid

Vous vous voyez déjà ajouter du code JS pour surveiller ces états et changer le CSS en cas d’erreur. Pas besoin! Angular ajoute ces états comme classe CSS directement sur chaque champ et le formulaire. Ainsi lorsque le formulaire encore vierge s’affiche, il contient déjà la classe CSS ‘ng-pristine’ :

    <form name='loginForm' class='ng-pristine'>
      <input name='login' ng-model='user.login' class='ng-pristine'/>
      <input name='password' ng-model='user.password' class='ng-pristine'/>
    </form>

Dès la saisie du premier caractère dans le champ ‘input’, le formulaire devient dirty :

    <form name='loginForm' class='ng-dirty'>
      <input name='login' ng-model='user.login' class='ng-dirty'/>
      <input name='password' ng-model='user.password' class='ng-pristine'/>
    </form>

A noter que le formulaire, ainsi que l’input, reste dirty une fois modifié, même si la modification est annulée.

Si l’un des champs présente une contrainte (comme par exemple ‘required’ ou ‘url’), alors le formulaire sera invalide tant que cette contrainte ne sera pas satisfaite. Le champ comme le formulaire aura donc la classe ‘ng-invalid’ ainsi que le champ en question. Une fois la condition satisfaite, la classe ‘ng-valid’ remplace la classe ‘ng-invalid’.

Vous pouvez donc très facilement customiser votre CSS pour ajouter un style selon l’état de votre formulaire. Cela peut être un bord rouge en cas de champ invalide :

    input.ng-dirty.ng-invalid {
      border: 1px solid red;
    }

Ou encore afficher un message d’erreur dans votre HTML :

    <form name='loginForm'>
      <input name='login' type='email' ng-model='user.login'/>
      <span ng-show='loginForm.login.$invalid'>Your login should be a valid email</span>
      <input name='password' ng-model='user.password'/>
    </form>

Ajouter un type ‘email’ sur un champ place une contrainte sur celui-ci, obligeant l’utilisateur à entrer un login sous la forme d’une adresse email valide. Dès que l’utilisateur commencera sa saisie de login, le champ deviendra invalide, rendant l’expression ‘loginForm.login.$invalid’ vraie. La directive ‘ng-show’ activera alors l’affichage du message d’avertissement. Dès que le login saisi sera un email valide, l’expression deviendra fausse et l’avertissement sera caché. Plutôt pas mal pour une ligne de HTML non?

Vous pouvez bien sûr cumuler les conditions d’affichage de l’avertissement, ou faire un avertissement par type d’erreur :

    <form name='loginForm'>
      <input name='login' required type='email' ng-model='user.login'/>
      <span ng-show='loginForm.login.$dirty && loginForm.login.$error.required'>A login is required</span>
      <span ng-show='loginForm.login.$error.email'>Your login should be a valid email</span>
      <input name='password' ng-model='user.password'/>
    </form>

Ainsi si l’utilisateur, après avoir entré un login (rendant ainsi le champ "dirty"), l’efface, un message d’avertissement apparaîtra pour indiquer que le login est nécessaire. Les combinaisons ne sont limitées que par votre imagination!

Plusieurs méthodes sont disponibles sur le controller :
– ‘addControl()’, ‘removeControl()’ permettent d’ajouter ou de supprimer des champs du formulaire. Par défaut tous les inputs "classiques" (input, select, textarea) que vous utilisez avec un ‘ng-model’ sont ajoutés au formulaire pour vous. Ils ont tous un controller nommé ngModelController, qui gère justement les états ($pristine, $valid, etc…), la validation, l’ajout au formulaire pour vous ainsi que le binding des vues au modèle. Les méthodes ‘addControl()’ et ‘removeControl()’ peuvent être intéressantes si vous voulez ajouter un autre type de champ à votre formulaire.
– ‘setDirty()’, ‘setPristine()’ vont respectivement mettre le formulaire dans un état ‘dirty’ou ‘pristine’ avec les classes CSS associées positionnées. A noter que les méthodes se propagent vers les formulaires parents si ils existent. En revanche, seule la méthode ‘setPristine()’ s’applique sur chaque champ du formulaire. Vous pouvez donc faire un ‘reset’ du formulaire et de ses champs grâce à ‘$setPristine()’.
– ‘setValidity(errorType, isValid, control)’ va, comme son nom l’indique, passer un champ de votre formulaire comme $valid ou $invalid pour le type d’erreur précisé, affectant par le même coup la validité du formulaire ainsi que celles des éventuels formulaires parents.

La dernière chose à retenir sur les formulaires concerne la soumission. Angular étant conçu pour les "single page applications", on cherche à éviter les rechargements non nécessaires. Ainsi l’action par défaut du formulaire sera désactivée, à moins que vous le vouliez explicitement en précisant une ‘action’ dans la balise ‘form’. Angular cherchera plutôt à vous faire gérer vous même la soumission du formulaire, soit en utilisant la directive ‘ngSubmit’ dans la balise ‘form’ soit en utilisant la directive ‘ngClick’ sur un ‘input’ de type ‘submit’ (mais pas les deux à la fois, sinon vous avez une double soumission!).

Vous avez maintenant toutes les clés pour faire une bonne validation côté client de vos formulaires en Angular. Mais que cela ne vous empêche de vérifier côté serveur! ;-)

Envie d’en savoir plus? Je donne une formation sur AngularJs!

Suivre

Recevez les nouvelles publications par mail.

Rejoignez 992 autres abonnés