Archives du blog

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!

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…

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!

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!

Angular filter – Part 1

L’une des fonctionalités les plus appréciables et méconnues d’Angular réside dans les filtres disponibles. Dans toutes applications web, il est courant de devoir filtrer ou réordonner une collection pour l’afficher. Les filtres Angular sont applicables à la fois en HTML ou en Javascript, à un seul élément ou un tableau. En HTML, la syntaxe se rapproche du style Unix, où l’on peut chaîner les commandes à l’aide du pipe. Par exemple, on peut appliquer un filtre nommé ‘uppercase’ sur une expression de la façon suivante :

   {{ expression | uppercase }}

On peut également chaîner plusieurs appels de filtre :

    {{ expression | uppercase | trim }}

Certains filtres prennent des paramètres : c’est le cas du filtre ‘number’, qui nécessite de préciser le nombre de chiffres suivant la virgule. Pour passer les paramètres au filtre, il suffir de procéder comme suit :

    {{ expression | number:2 | currency:'$'}}

Il est également possible d’invoquer les filtres depuis le code Javascript :

    $filter('uppercase')

Angular va alors retrouver la fonction de filtre correspondant à la chaîne de caractères passée en paramètre.

Angular propose par défaut certains filtres communs :

- number : permet de préciser le nombre de chiffres après la virgule à afficher (arrondi au plus proche).

    {{ 87.67 | number:1 }} // 87.7 
    {{ 87.67 | number:3 }} // 87.670

- currency : permet de préciser la monnaie.

    {{ 87.67 | currency:'$' }} // $87.67

- date : permet de formatter l’affichage des dates, en précisant un pattern. On retrouve l’écriture classique de pattern :

    {{ today | date:'yyyy-MM-dd' }} // 2013-06-25

Un certain nombre de patterns sont disponibles (avec un rendu différent selon la locale) :

    {{ today | date:'longDate' }} // June 25, 2013

- lowercase/uppercase : de façon assez évidente, ces filtres vont convertir l’expression en majuscules ou minuscules.

    {{ "Cedric" | uppercase }} // CEDRIC   
    {{ "Cedric" | lowercase }} // cedric

- json : moins connu, ce filtre permet d’afficher l’objet au format JSON. Il est également moins utile, car, par défaut, afficher un object avec la notation ‘{{ }}’ convertit l’objet en JSON.

    {{ person | json }} // { name: 'Cedric', company: 'Ninja Squad'} 
    {{ person }} // { name: 'Cedric', company: 'Ninja Squad'} 

- limitTo : ce filtre s’applique quant à lui à un tableau, en créant un nouveau tableau ne contenant que le nombre d’éléments passés en paramètre. Selon le signe de l’argument, les éléments sont retenus depuis le début ou la fin du tableau.

    {{ ['a','b','c'] | limitTo:2 }} // ['a','b'] 
    {{ ['a','b','c'] | limitTo:-2 }} // ['b','c'] 

- orderBy : là encore un filtre s’appliquant à un tableau. Celui-ci va trier le tableau selon le prédicat passé en paramètre. Le prédicat peut être une chaîne de caractères représentant une propriété des objets à trier ou une fonction. Le prédicat sera appliqué sur chaque élément du tableau pour donner un résultat, puis le tableau de ces résultats sera trié selon les opérateur ‘. Une propriété peut être précédée du signe ‘-‘ pour indiquer que le tri doit être inversé. A la place d’un simple prédicat, il est possible de passer un tableau de propriétés ou de fonctions (chaque propriété ou fonction supplémentaire servant à affiner le tri primaire). Un second paramètre, booléen, permet quant à lui d’indiquer si le tri doit être inversé.

    var jb = {name: 'JB', gender: 'male'};    
    var cyril = {name: 'Cyril', gender: 'male'};     
    var agnes = {name: 'Agnes', gender: 'female'};     
    var cedric = {name: 'cedric', gender: 'male'};     
    $scope.ninjas = [jb, cyril, agnes, cedric];  
    
    // order by the property 'gender' 
    {{ ninjas | orderBy:'gender'}} // Agnes,JB,Cyril,Cédric 

    // order by a function (lowercase last) 
    $scope.lowercaseLast = function(elem){ 
      return elem.name === elem.name.toLowerCase() 
    }; 
    {{ ninjas | orderBy:lowercaseLast }} // Agnes,JB,Cyril,cedric 

    // order by an array of properties or functions 
    {{ ninjas | orderBy:['-gender','name'] }} // cedric,Cyril,JB,Agnes 

Dans le prochain billet, nous verrons comment créer nos propres filtres.

MongoDB Aggregation Framework

Vous avez probablement entendu parlé de MongoDb, une solution NoSQL orientée document développée par 10Gen. Les documents sont stockés en JSON, et bien que vous ayez un driver disponible pour chaque language, on se retrouve souvent à coder les requêtes en javascript dans le shell mongo fourni. Je vais vous parler de la version 2.2 qui est la dernière version stable et contient le framework d’aggregation, grande nouveauté attendue par les développeurs. Pour votre information, les numéros de version de Mongo suivent le vieux modèle du kernel Linux : les numéros pairs sont stables (2.2) alors que les versions de développement sont instables (2.1). Node.js suit le même modèle par exemple.

L’aggrégation donc, qu’est ce que c’est? Pour vous faire comprendre l’intérêt nous allons prendre un petit exemple (version simplifée d’un vrai projet). Admettons que vous stockiez les connexions à votre application toute les minutes, par exemple avec un document qui ressemblerait à

{"timestamp": 1358608980 , "connections": 150}

C’est à dire un timestamp qui correspond à la minute concernée et un nombre de connexions total.

Disons que vous vouliez récupérer les statistiques sur une plage de temps, par exemple sur une heure : il faudrait alors aggréger ces données pour obtenir le nombre total de connexion et le nombre moyen par minute. Seulement voilà, MongoDb ne propose pas de “group by”, de “sum” ou de “avg” comme l’on pourrait avoir en SQL. Ce genre d’opération est même déconseillé, car fait en javascript cela prend un plus de temps que dans une base classique. C’est en tout cas à éviter pour répondre à des requêtes en temps réel. Mais bon des fois, on est obligé…

 The old way : Map/Reduce
Jusqu’à la version 2.2 donc, on utilisait un algo map/reduce pour arriver à nos fins. Si vous ne connaissez pas, je vous invite à lire cet article de votre serviteur expliquant le fonctionnement. Dans un algo map/reduce, Il faut écrire une fonction map et une fonction reduce, qui vont s’appliquer sur les données selectionnées par une requête (un sous ensemble de votre collection MongoDb).

La requête qui permet de selectionner ce sous ensemble serait par exemple :

// stats comprises entre 15:00 et 16:00

var query = { timestamp : { $gte: 1358607600, $lte: 1358611200 }}

La fonction map va renvoyer les informations qui vous intéressent pour une clé. Ici nous voulons les connexions pour l’heure qui nous intéresse, donc nous aurons une fonction comme suit :

// on renvoie les infos pour la clé 15:00

var map = function(){ emit(1358607600, { connections : this.connections}) }

La fonction reduce va ensuite aggréger les informations, en ajoutant les connexions totales pour la clé 15:00 et calculer la moyenne associée.

// calculer la somme de toutes les connexions et la moyenne

var reduce = function(key, values){
 var connections = Array.sum(values.connections);
 var avg = connections/values.length;
 return { connections: connections, avg: avg}
 }

Maintenant que nous avons nos fonctions map et reduce, ainsi que la requête pour remonter les données qui nous intéressent, on peut lancer le map reduce.

// dans le shell mongo

db.statistics.mapReduce(map, reduce, { query: query, out: { inline: 1 }})

Le out inline permet d’écrire la réponse dans le shell directement (sinon il faut préciser une collection qui acceuillera le résultat). On obtient une réponse du style :

{connections: 180000, avg: 3000}

en 4,5 secondes environ sur ma collection de plusieurs millions de document légèrement plus complexes que l’exemple.

The new way : Aggregation Framework
Maintenant voyons la nouvelle façon de faire avec le framework d’aggrégation. Une nouvelle opération apparaît : aggregate. Celle-ci remplace mapReduce et fonctionne comme le pipe sous Linux : de nouveaux opérateurs sont disponibles et on peut les enchaîner. Par exemple, le “group by” est simplifié avec un nouvel attribut $group. La requête qui permet de filtrer un sous ensemble de la collection est écrite avec un opérateur $match. Enfin de nouveaux opérateurs viennent nous simplifier la vie : $sum, $avg, $min, $max… J’imagine que vous avez saisi l’idée.

Ici on veut un élément match qui limite l’opération aux données de l’heure concernée, on peut réutiliser la même query que tout à l’heure. On groupe ensuite les documents avec une seule clé : celle de l’heure qui nous intéresse, puis l’on demande le calcul de deux valeurs, le nombre total de connexions (une somme) et la moyenne des connections (une moyenne donc).

db.statistics.aggregate(
 { $match: query},
 { $group: { _id: 1358607600, totalCompleted: {$sum: "$connections"}, totalAvg: {$avg: "$connections"}
 }})

Le résultat est le suivant (en 4,2 secondes, soit un temps légérement inférieur au précédent) :

{ result: [{
 "_id": 1358607600,
 "totalCompleted": 180000,
 "totalAvg": 3000
 }], ok: 1}

L’avantage principal du framework d’aggrégation réside dans sa plus grande simplicité d’écriture et de lecture : plus besoin d’écrire des fonctions js soi-même pour des opérations somme toute assez courantes. Spring Data Mongo par exemple, le très bon projet de SpringSource pour vous simplifier la vie, demande d’écrire des fonctions en js pour faire du map/reduce. Vous avez donc un projet Java, qui contient quand même quelques fichiers js au milieu pour faire certaines opérations. Beaucoup attendent donc avec impatience l’arrivée du support du framework d’aggrégation dans Spring Data. Espérons qu’il ne tarde pas trop! En attendant d’autres frameworks comme Jongo l’ont déjà intégré. Il y a toutefois quelques limites comme le résultat de l’aggregate qui doit faire moins de 16Mo. Bref tout n’est pas idéal, mais ce très bon produit s’améliore à chaque version!

Meetup W3C de Lyon

Lundi 28 Octobre avait lieu à Lyon le W3C meetup. Le principe est simple, le w3c prend l’avion avec quelques uns de ses développeurs et speakers et débarque pour faire une série de mini talks sur le web. Organisé en partenariat avec le Grand Lyon, des startups du coin étaient aussi conviées à présenter leurs produits en essayant de les lier, au moins vaguement au web.

Premier bon point pour les locaux, dans l’hôtel de ville, ce qui a quand même autrement plus la classe que le bar ou l’école du coin! Par contre, pas de chaises prévues, tout le monde s’assoit par terre, pendant que les speakers se préparent pour leurs 5 à 10 minutes de talk.

On commence avec ohmytoast, une startup lyonnaise, qui propose une solution pour enrichir vos photos avec des métadonnées, du web semantique, pour créer des albums avec un sens. Le concept a l’air sympa, niveau techno, l’équipe se repose sur html5, css3 et Apache Camel pour la partie serveur.

Le deuxième talk, extrêmement court, était pour moi le plus intéressant de la soirée. En 3 minutes, Rudy Rigot nous montre l’histoire du webdesign et l’intérêt du responsive design. Prenez deux minutes pour voir ses slides, et jouer avec le resize, ca vaut le détour.

Vient ensuite, Eric Daspet qui présente Tea, une solution de lecture d’ebook dans le navigateur, qui repose donc uniquement sur des technos web. Le choix full web du reader n’a pas été simple, mais Eric voit de nombreux avantages (navigateurs présents sur tous les devices, pas de frais liés à Apple ou Google) malgré quelques inconvénients (moins de performances, pas de présence sur les marketplaces). Tea est encore en phase de développement, à revoir dans quelques mois donc.

Viennent ensuite des speakers W3C qui présentent les nouveautés HTML5. Mais bon, ça commence à sentir le réchauffé avec les différentes balises video, audio… Rien d’exceptionnel. Deux développeurs de Mozilla font une démo d’un jeu 3D qui tourne dans Firefox 16 : ils ont développé le jeu en interne (un quake-like) en C++ et un compilateur transpile le code en JS. Ils prédisent donc que de vrais jeux vont voir le jour dans le navigateur dans les prochaines années. Ils montrent également une fonction de login dans le navigateur qui permet de partager facilement des pages avec ses amis par tchat.

Les mêmes développeurs enchaînent ensuite sur une démo de Firefox OS, un OS full html, qui vise les plateformes mobiles. C’est en version très très alpha, avec peu de téléphones qui fonctionnent. Un market sera disponible, les applications étant des pur webapps, avec un packaging léger (un peu comme les webapps Chrome, un fichier json servant à la description de l’application). Des apis supplémentaires ont bien sûr été développées pour accéder à l’appareil photo, passer des appels, etc… Ces apis ont été soumises au W3C pour devenir un jour des standards accessibles dans le navigateur.

Un speaker vient ensuite parler des hyperdevices, autrement dit connecter plusieurs devices avec plusieurs technologies (websockets, réalité augmentée, web sémantique…) et propose quelques démos sympathiques mais souvent déjà vues (dessiner sur un téléphone et afficher le résultat en temps réel sur son écran de laptop…).

Dernière partie, sur les nouveautés du W3C. La seule notable est présentée par Lea Verrou, nouvelle arrivée au W3C, qui nous parle du sitee WebPlatform.org sorti ce mois-ci. L’ambition du site est de devenir la référence en matière de documentation sur les technologies web. Il devrait notamment être enrichi et traduit dans les prochaines semaines et, à terme, une API sera disponible pour permettre aux outils comme les IDEs de récupérer cette documentation.

La soirée se termine avec un buffet excellent qui permet de discuter avec les nombreux participants et quelques speakers. Pour un développeur web qui se tient un peu au courant de l’actualité, ce n’était pas une soirée extrêmement palpitante, mais elle a le mérite d’exister et d’amener quelques noms connus du W3C en France. Et puis le buffet était vraiment bien!

Suivre

Recevez les nouvelles publications par mail.

Rejoignez 994 autres abonnés