Dans les derniers articles, j'ai beaucoup parlé de Middleman et donc de générateurs de sites statiques. Par statique, on pourrait comprendre qu'un site conçu de cette façon soit obligatoirement figé, son contenu ne pouvant évoluer que lors des mises à jour. Il est pourtant tout à fait possible de dynamiser le contenu côté client avec du Javascript, ici avec AngularJS.

AngularJS

Le statique n'est pas statique

Par statique, dans notre cas, on parle de pages qui ne sont pas générées côté serveur. Le contenu est donc celui existant dans la page .html envoyée au navigateur. Pour changer ce contenu avec Middleman ou tout autre générateur de site statique, il faut modifier les sources et repasser par les étapes de build et deploy.

Dans ce cas, effectivement, le site est plus ou moins figé, son contenu ne changeant que si l'on édite et modifie les pages.

Middleman et les frameworks Javascript

Si on veut dynamiser le contenu, on peut tout à fait ajouter un framework Javascript comme Angular, Backbone ou Ember et étendre ainsi les possibilités.

Middleman sert alors de conteneur qui englobe l'application Javascript en offrant au développeur à la fois ses propres fonctionnalités plus celles du framework JS.

On peut donc imaginer de multiples usages pour ce duo :

  • Visualisation de données
  • Curation de contenu automatisée
  • Single Page Application
  • Consommation d'API Rest
  • Application mobile
  • etc

Exemple avec AngularJS

Pour illustrer le propos rien de tel qu'un exemple. Commençons doucement avec de la visualisation de données.

Voilà le sujet : on a développé avec Middleman un site web faisant la promotion d'un évènement sportif, une course en 3 étapes. La course étant terminée, on voudrait pouvoir afficher les résultats sur le site et utiliser des fonctions de recherche ou de tri.
Les résultats sont exportés dans un fichier .json (on imagine qu'on utilise une application dédiée à cette tâche).

On va utiliser AngularJS pour récupérer le fichier contenant les résultats et les afficher sur le site.

Ajouter AngularJS à Middleman

Pour l'exemple j'ai généré rapidement un site Middleman avec mon template de site.
Dans le dossier assets/javascripts on crée un dossier app qui va lui même contenir les dossiers controllers et services.

Dans le dossier app on crée le fichier app.js Dans le dossier controllers le fichier maincontroller.js Dans le dossier services le fichier results.js

Les fichiers étant créés, on va les ajouter au layout pour les charger, soit dans layouts/layout.slim :

/ Angular
= javascript_include_tag 'https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js'
= javascript_include_tag 'app/app'
= javascript_include_tag 'app/controllers/maincontroller'
= javascript_include_tag 'app/services/results'

Dans le fichier app.js, on va initialiser notre app :

var app = angular.module('resultsApp', []);

De retour dans layout.slim, on va ajouter la directive à la balise body soit :
body ng-app="resultsApp".

Récupérer un fichier JSON

Les données sont stockées dans un fichier JSON. Pour simplifier, le fichier est stocké sur le même nom de domaine que l'application, ce qui évite les erreurs type "Cross-Origin Request Blocked: The Same Origin Policy" et l'ajout de code supplémentaire.

Pour récupérer le fichier, on va créer un service dont la seule fonction est d'aller le chercher à son emplacement et de retourner son contenu. On se place donc dans services/results/js:

// Retourne le contenu du fichier json
app.factory('results', ['$http', function($http) {
  return $http.get('../json/results.json')
    .success(function(data) {
      return data;
    })
    .error(function(data) {
      return err;
    });
}]);

Afficher le contenu dans la page

Avant d'afficher le contenu, il nous faut un controller qu'on va créer dans le fichier controllers/maincontroller.js et auquel on injectera le service results:

app.controller('MainController', ['$scope','results', function($scope, results) {
  results.success(function(data) {
    $scope.runners = data;
  });
}]);

On a donc notre liste de participants qui est stockée sous forme de tableau dans $scope.runners, on peut donc ajouter le contenu sur la page des résultats créée pour l'occasion.

On veille à bien ajouter le controller ng-controller=MainControllerà la balise section.

.section ng-controller="MainController"
  .wrapper
    h1 Résultats de la course

    table.results__show
      thead
        tr
          th Nom
          th Etape 1
          th Etape 2
          th Etape 3
          th Temps total
      tbody
        tr ng-repeat="runner in runners"
          td {{ runner.name }}
          td {{ runner.run1 }}
          td {{ runner.run2 }}
          td {{ runner.run }}  
          td {{ runner.total }}

Les données s'affichent mais on a un petit problème au niveau des temps. Ceux-ci sont enregistrés en secondes dans le fichier JSON, il serait utilise de pouvoir les transformer dans un format plus lisible.

Pour cela on va créer un filtre dans app.js:

app.filter('secondsToDateTime', function() {
  return function(seconds) {
      var d = new Date(0,0,0,0,0,0,0);
      d.setSeconds(seconds);
      return d;
  };
});

On peut ensuite revenir à notre fichier resultats.html pour ajouter ce filtre sur les temps :

tbody
  tr ng-repeat="runner in runners"
    td {{ runner.name }}
    td {{ runner.run1 | secondsToDateTime | date:'HH:mm:ss' }}
    td {{ runner.run2 | secondsToDateTime | date:'HH:mm:ss' }}
    td {{ runner.run3 | secondsToDateTime | date:'HH:mm:ss' }}  
    td {{ runner.total | secondsToDateTime | date:'HH:mm:ss'}}

Chercher rapidement un participant

Nous n'avons pas un grand nombre de participants sur cette course mais il pourrait y en avoir beaucoup plus sur les prochaines. Il pourrait donc être utile d'avoir une recherche rapide en tapant le nom du participant. Sans quitter le fichier resultats.html on va ajouter ce qui suit juste en dessous du titre h1:

p.results__search Chercher un participant
input.results__searchbox type="text" ng-model="nameFilter" placeholder="Luke Skywalker"

On modifie ensuite notre boucle ng-repeat pour prendre en compte ce nouveau filtre :

tbody
  tr ng-repeat="runner in runners | filter: nameFilter"

on peut désormais retrouver rapidement un participant en tapant son nom dans le champ dédié.

Trier les résultats

Pour le moment les résultats ne sont pas triés, mais simplement affichés par ordre d'apparition dans le fichier JSON. Évidement, dans le but de pouvoir établir un classement, il serait maintenant utile d'ajouter des fonctions de tri.

On va donc modifier deux fichiers, le controlleur pour ordonner les données et la page d'affichage pour déclencher le tri en cliquant sur les intitulés dans le tableau.

Soit pour maincontroller.js:

app.controller('MainController', ['$scope', '$filter', 'results', function($scope, $filter, results) {
  var orderBy = $filter('orderBy');

  results.success(function(data) {
    $scope.runners = data;

    $scope.order = function(predicate, reverse) {
      $scope.runners = orderBy($scope.runners, predicate, reverse);
    };

    $scope.order('+total', false);
  });
}]); 

Par défaut on trie le tableau sur la clé "total" de manière à ce que le meilleur temps total soit en premier.

Le fichier resultats.html complet :

.section ng-controller="MainController"
  .wrapper
    h1 Résultats de la course

    p.results__search Chercher un participant
    input.results__searchbox type="text" ng-model="nameFilter" placeholder="Luke Skywalker"

    table.results__show
      thead
        tr
          th 
            a href="" ng-click="reverse=false;order('name', false)" Nom
          th
            a href="" ng-click="reverse=false;order('run1', false)" Etape 1
          th
            a href="" ng-click="reverse=false;order('run2', false)" Etape 2
          th
            a href="" ng-click="reverse=false;order('run3', false)" Etape 3
          th
            a href="" ng-click="reverse=false;order('total', false)" Temps total
      tbody
        tr ng-repeat="runner in runners | filter: nameFilter"
          td {{ runner.name }}
          td {{ runner.run1 | secondsToDateTime | date:'HH:mm:ss' }}
          td {{ runner.run2 | secondsToDateTime | date:'HH:mm:ss' }}
          td {{ runner.run3 | secondsToDateTime | date:'HH:mm:ss' }}  
          td {{ runner.total | secondsToDateTime | date:'HH:mm:ss' }}  

On a donc notre tableau qui affiche le classement par ordre croissant, le meilleur en premier. On peut tirer les résultats de manière à voir qui est premier sur chaque étape et rechercher rapidement un participant pour voir ses temps.

Voir la démo

On a vu ici un exemple assez simple mais on peut tout à fait imaginer comment enrichir un site se voulant "statique" avec du contenu dynamique. Il existe une infinité d'APIs avec lesquelles notre site statique peut discuter et on peut tout aussi bien créer les nôtres.

Article précédent : CouicStart, un template Middleman pour démarrer rapidement un projet
Article suivant : Gulpfile avec Jade et Sass pour la création rapide de landing pages

Je suis disponible pour des missions longues / CDD / CDI Cliquez pour en savoir plus