Archives pour la catégorie Developpement Web

ASP.NET MVC : Display et Editor Templates

ASP.NET MVC est un outil puissant pour créer des sites web, basés sur des conventions (Conventions over configurations). Nous allons voir dans cet article les notions de display et editor templates qui permettent la gestion du rendu d’un objet/propriété en mode affichage et édition.

Utilisation

ASP.NET MVC fournit par défaut des méthodes dans le helper Html permettant la gestion de l’affichage (DisplayFor) et l’édition (EditorFor).

@Html.DisplayFor(model => model.Title)

@Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })

Par défaut, le helper générera un input en fonction du type des propriétés de votre objet. Ainsi, dans le cas d’une propriété de type string, un input de type text sera généré et dans le cas d’un bool, une case à cocher. Il est possible d’avoir un contrôle plus fin de la génération des input. Pour cela, le helper HTML met à disposition des méthodes pour chaque type de input :

  • Html.TextBox
  • Html.DropDownList

Cela est très pratique et permet de mieux contrôler le HTML généré mais vous êtes obligé de le faire dans chaque vue. Afin de palier à ce problème, il existe les DisplayTemplates et EditorTemplates. Ils permettent de définir pour chaque type comment il sera rendu en mode affichage (display) ou édition (editor). Pour cela, il faut créer des dossiers DisplayTemplates et EditorTemplates dans le dossier Views/Shared puis placer un fichier par type en le nommant type.cshtml.

Dossiers Display et Editor Template

Dans les exemples suivant, nous allons utiliser une classe Album (les annotations de validations sont volontairement omises) :

public class Album
{
    public int ID { get; set; }

    public string Title { get; set; }

    public DateTime ReleaseDate { get; set; }

    public bool isDoubleDisc { get; set; }
}

Par exemple, pour surcharger le template utilisé pour les types DateTime et bool, on créera respectivement un fichier DateTime.cshtml et Boolean.cshtml.

Nous avons vu que les booléens sont affichés sous la forme de checkbox. Voyons comment rendre votre application un peu plus jolie avec des composants ayant un meilleur rendu.
J’ai parlé dans un article précédent des web components et de polymer.

Polymer fournit une bibliothèque de composant (elements dans le jargon Polymer) prêt à l’emploi, notamment les Paper Elements, composant graphiques Material Design.

Voyons comment utiliser des paper-checkbox (la page de démo) pour les booléens grâce aux display templates.

Afin d’utiliser polymer et les elements, il faut les ajouter à la solution. Voici la documentation de polymer permettant de le faire.

Ajoutons un fichier Boolean.cshtml dans le dossier Views/Shared/DisplayTemplates :

@model System.Boolean
@if(@Model) {
    <paper-checkbox disabled checked></paper-checkbox>
} else {
    <paper-checkbox disabled></paper-checkbox>
}

L’affichage utilise maintenant un élement paper-checkbox pour les booléens.

Display Template sous la forme d'un Checkbox

Vous avez peut être remarqué que Polymer propose également un élément paper-toggle-button qui convient également pour les booléens.
ASP.NET MVC contient un attribut UIHint qui permet de spécifier un template à appliquer pour une propriété.

Ajoutons l’attribut UIHint avec la valeur Toggle sur la propriété IsDoubleDisc.

[UIHint("Toggle")]
public bool IsDoubleDisc { get; set; }

Définissons maintenant un fichier Toggle.cshtml dans le dossier DisplayTemplates avec le code suivant :

@model System.Boolean
@if(@Model) {
    <paper-toggle-button disabled checked></paper-toggle-button>
} else {
    <paper-toggle-button disabled></paper-toggle-button>
}

Automatiquement, le champs IsDoubleDisc sera affiché avec un toggle !

Display Template sous la forme d'un Toggle

Il est également possible de créer des templates pour vos propres modèles.

Voici un exemple avec une classe Address :

@model DemoMVC5.Models.Address
 <dl class="dl-horizontal">     
     <dt>
         @Html.DisplayNameFor(model => model.Street)
      </dt>
      <dd>
         @Html.DisplayFor(model => model.Street)
      </dd>
      <dt>
         @Html.DisplayNameFor(model => model.ZipCode)
      </dt>
      <dd>
         @Html.DisplayFor(model => model.ZipCode)
      </dd>
      <dt>
         @Html.DisplayNameFor(model => model.City)
      </dt>
      <dd>
         @Html.DisplayFor(model => model.City)
      </dd>
      <dt>
         @Html.DisplayNameFor(model => model.Country)
      </dt>
      <dd>
         @Html.DisplayFor(model => model.Country)
      </dd>
  </dl>

Vous pourrez alors utiliser @Html.DisplayForModel et éviter de dupliquer votre code dans plusieurs vues.

@model DemoMVC5.Models.Address
<div>
    <h4>Your Address</h4>
    Html.DisplayForModel()
</div>

Si un de vos objets a une propriété Address, l’affichage se fera via :

@model DemoMVC5.Models.User
<div>
    <h4>Your Information</h4>
    Html.DisplayFor(model => model.Name)

    Html.DisplayFor(model => model.Address)
</div>

Conclusion

Display et Editor templates sont des fonctionnalités méconnues de ASP.NET MVC. Elle permettent de facilement modifier l’affichage d’une classe dans votre site de manière simple et sans duplication de code.

Liens

Outils CSS3 – HTML5 – JavaScript : Episode 9

Au menu, des liens, des outils, des présentations … sur HTML, CSS et JavaScript.

Talks

Voici quelques présentations que j’ai beaucoup appréciées :

HTML

CSS

  • Pure : Un framework CSS (gère le responsive)
  • Responsive Web Design
  • PlentyofFrameworks : trouver le framework CSS qui vous convient
  • Enjoycss : éditeur de composant CSS
  • CSSShake : Une feuille de style pour secouez vos éléments
  • Style Guide guide : site regroupant des générateurs de style guide (guide d’utilisation de vos style (un exemple))
  • CSS Values : permet de rechercher une propriété CSS pour facilement connaître son utilisation et les valeurs possibles
  • Bounce.js : un générateur d’animation CSS 3
  • CSS Stats : Statistique concernant CSS (nb de sélecteurs, couleurs, …) d’un site
  • Critical Path CSS Generator : Générateur de Critical Path. Critical Path est une technique qui consiste à injecter, directement dans votre code HTML(inline, dans le header) le code CSS minimal afin d’accélérer la vitesse de rendu de votre page. Le reste du code CSS est à charger via des fichiers CSS classique en fin de page. Cet outils permet d’extraire le code CSS à injecter en fonction d’une page.
  • Flexplorer : un générateur et outils de test de flexbox
  • une cheatsheet sur la propriété flexbox en css3

JavaScript

Divers

Angular : Test End to End avec Protractor

Je vous ai déjà parlé des tests unitaires sur un projet Angular. Aujourd’hui je vais vous présenter comment faire des tests d’intégration (end to end ou E2E). Le but des tests d’intégration est de s’assurer que tous les composants de votre application fonctionnent correctement ensemble. Pour cela, les tests seront joués dans un navigateur et des actions utilisateurs seront simulées.
Les tests d’intégration sont donc complémentaire des tests unitaires.

Angular a été pensé pour être testable et il y a donc un outil pour les tests E2E : Protractor, développé par la team Angular (Il est possible de l’utiliser pour tester des applications qui ne sont pas basé sur Angular).
Protractor utilise Selenium qui permet de contrôler un navigateur (aller à une page, cliquer sur un bouton, …)

Installation

Protractor est un module node et il faut donc avoir node installé sur son poste.

Je vous conseille de l’installer en global (Préfixer la commande par sudo si vous êtes sous Linux (Ubuntu pour moi).

npm install protractor -g

Il faut ensuite configurer selenium via la commande suivante

webdriver-manager update

Configuration

La configuration de Protractor se fait via un fichier .js.
Voici celui que j’utilise dans mon application de volley :

Ecriture des tests

Il faut ensuite écrire vos tests. Par défaut, protractor utilise Jasmine mais vous pouvez utiliser d’autres framework de test comme mocha par exemple.

Protractor met à votre disposition des variables globales vous permettant d’interagir avec le navigateur comme par exemple :

  • browser: browser.get(‘url’) : permet d’ouvrir la page passé en paramètre
  • element and by: element(by.model(‘yourName’)) : permet de récuperer un élement de la apge
  • protractor: protractor.Key : permet la gestion des touches (ex : protractor.Key.ENTER)

Protractor étant à la base pensé pour angular, il fournit des sélecteurs lié à angular comme

  • by.model(‘first’) : récupère l’input lié à la variable de scope first (ng-model= »first »)
  • by.binding(‘myModel’) : récupere l’élement bindé sur myModel (
  • by.repeater(‘user in users’).row(0).column(‘name’) : récupère le nom d’un 1er element de la liste users

Voici une exemple de test sur mon application de volley qui permet de vérifier qu’on ne peut pas lancer de partie si le nom d’une des équipes n’est pas renseignée :

Voici le lien vers la documentation officielle de l’api de protractor ainsi qu’une CheatSheet et un gist (en coffeescript).

Dans le cas ou vous faites un appel serveur, il faut utiliser la ligne suivante afin de ne pas poursuivre vos tests et notamment vos vérifications tant qu’angular n’a pas traité le retour

browser.waitForAngular();

Lancer les tests

Il faut d’abord lancer le server Selenium (webdriver) via la commande suivante :

webdriver-manager start

On peut alors lancer dans un autre terminal protractor :

protractor test/e2e/config.js // Chemin vers votre fichier de configuration

Une fenêtre de navigateur va alors se lancer et jouer les tests sous vos yeux. Magique !

Le résultat de vos tests sera affiché dans la console :

Protractor : résultats des tests E2E

Comme vous pouvez le voir, il me reste encore du travail pour tout valider 😉 .

Attention, votre site doit être accessible. Dans mon cas, je le lance via la commande

grunt serve

NB : Il existe un plugin pour lancer protractor depuis grunt : grunt-protractor-runner. Je n’ai pas encore eu le temps de le tester …

Ressources

AngularJs : Communication entre une directive et un controller

Un petit article rapide concernant la communication entre une directive et un controller en Angular via une fonction.

Il est possible de définir dans une directive une fonction de callback vers notre controller. Il faut définir une propriété de type fonction via un &.

scope: {
   demoOnClick: '&'
}

Voyons comment utiliser une fonction avec des paramètres.

Appel de fonction avec paramètre

Angular utilise l’injection de dépendance qui permet d’injecter dans les différents composants Angular, les ressources requises par ce dernier.

Cela fonctionne de la même façon lors de l’appel de la fonction.

Lors de l’appel de la fonction coté directive, il faut passer un objet JavaScript avec les paramètres nommés. Dans votre code HTML où vous utilisez la directive, il faut spécifier pour la variable de type fonction, le nom des paramètres que l’on souhaite recevoir. Ainsi Angular va injecter seulement les paramètres dont vous avez besoin. Attention de bien utiliser les noms des propriétés au risque de recevoir des undefined ….

Voici un exemple :

Cela est déconcertant au début et j’avoue avoir perdu un peu de temps. Cela peut s’avérer pratique dans le cas d’une directive qui peut passer plusieurs paramètres et dont votre controller n’a besoin que d’un paramètre spécifique comme dans l’exemple ci dessus. Ainsi, il ne n’est pas nécessaire de définir tout les paramètres et ainsi éviter des erreurs JSHint nous indiquant qu’un paramètre n’est pas utilisé.

Google IO : Material Design & Polymer …

La conférence Google I/O 2014 vient de se terminer et Google a annoncé Material Desgin. Material Design est un « langage visuel » qui a pour but d’uniformiser le look d’une application quelque soit le terminal (Web, Mobile, Tablet, …). Il fournit un ensemble de guidelines en termes d’interfaces (couleurs, animations, ombrages, …) à la sauce Google et un ensemble de composant prêt à l’emploi. Ces composants sont basé sur Polymer, un framework créé par Google afin de créer des composants web (web components), dont j’ai fait la présentation dans un précédent article.

Ces composants sont appelés « paper » et viennent compléter les composants core de Polymer. Ils s’utilisent comme un élément HTML après avoir importé ce dernier.

<!-- import du composant -->
<link href="../paper-button/paper-button.html" rel="import">
<!-- Utilisation du composant -->
<paper-button label="My Button" id="paper_button"></paper-button>

Comme vous pouvez le voir, leur utilisation est très facile et il faut l’avouer le rendu de ces composants est superbe (animation au clics, …). Ces composants peuvent être stylés via CSS.

Afin de tester ces composants, vous pouvez créer votre page via le designer en ligne.

Ces composants étant des web components, ceux-ci sont isolés et peuvent donc être utilisés avec d’autres framework sans problème (jQuery, Angular, Backbone, …)

Je vous conseille les vidéos suivantes de la Google IO :

La première est la présentation de Eric Bidelman concernant Polymer et les web components.

La deuxième est la présentation des composants et l’utilisation de ces derniers par Rob Dodson.

AngularJs : Quelques astuces pour les tests unitaires

Une des grandes forces d’Angular est qu’il a été développé pour être facilement testable. Voici quelques astuces pour mieux tester vos applications angular.

Nommage

Dans un fichier de test, nous allons avoir besoin d’utiliser plusieurs fois des objets (services, controllers, …) dans plusieurs tests. Une bonne pratique pour avoir moins de code est d’injecter les différents objets avant chaque test dans le beforeEach global de notre fichier. Comme angular va se charger d’injecter nos objets via leur nom, il n’est pas possible de nommer ses variables du nom de l’objet. Il existe une astuce qui consiste à entourer le nom du service par des underscore (par exemple _user_)

// Defined out reference variable outside
var myService;

// Wrap the parameter in underscores
beforeEach( inject( function(_myService_){
  myService = _myService_;
}));

// Use myService in a series of tests.
it('makes use of myService', function() {
  myService.doStuff();
});

Le lien vers la documentation d’Angular concernant ce point

Organisation de vos tests

Un fichier de tests avec Jasmine est organisé en blocs (describe) qui contiennent des sous blocs et des tests (it). Un bloc describe peut également contenir une fonction beforeEach qui permet d’initialiser des objets avant un test. N’hésitez pas à faire plusieurs niveaux de describe ayant chacun un beforeEach afin de simplifier votre fichier de tests

describe('Mon fichier à tester', function() {
  // Chargement du module à tester
  beforeEach(module('myWebApp'));

  var monCtrl,
      scope,
      myService;

  // Init global ...
  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope, _myService_) {
    scope = $rootScope.$new();
    myService = _myService_;

    monCtrl = $controller('MonCtrl', {
      $scope: scope
    });
  });

  describe('Mon fonction à tester', function() {
    beforeEach(function() {
      //init lié à ma fonction (création var, spy, ...)
    });
    
    it('test 1', function () {
        ...
    });

    it('test 2', function () {
        ...
    });

  });

  describe('Mon 2eme fonction à tester', function() {
    beforeEach(function() {
      //init lié à ma fonction (création var, spy, ...)
    });

    it('test 3', function () {
        ...
    });

  });

});

Simuler un serveur web

Pour tester vos fonctions qui ont besoins de retour du serveur, angular founit l’objet $httpBackend qui permet de mocker des réponses à des requêtes http.

Voici comment par exemple simuler le retour d’un appel serveur qui renvoie un user

$httpBackend.when('GET', '/user/1').respond({userId: 1, firstname : 'Julien', lastname : 'Roy'});

Le lien vers la documentation de $httpBackend.

Tester des promises

Angular inclut $q, un service de promise, qui permet de facilement gérer enchaînement de méthodes asynchrones.

Voyons comment tester une méthode appelant une méthode retournant une promise.

var deferredSuccess = q.defer();

spyOn(projectFactory, 'getPotentialOwnersForProject')
   .and.returnValue(deferredSuccess.promise);

deferredSuccess.resolve(usersMock);
//Il faut appeler digest pour que le resolve soit pris en compte.
scope.$digest(); //ou scope.$apply();

Filtrer les tests à exécuter

Lors de l’écrire de vos tests, il se peut que vous n’ayez pas envie de jouer tous les tests de votre application pour corriger un test ou une série de tests (bloc describe contenant 1 ou plusieurs test it). Il est possible avec Jasmine de filtrer et d’exclure des tests :

  • Ne jouer qu’un seul test : changer it en iit
  • Ne pas jouer un test : changer it en xit
  • Jouer un bloc describe : changer describe en ddescribe
  • Ne pas jouer un bloc describe :changer describe en xdescribe

Edit 14/06/2015 : à partir la version 2.1 de Jasmine, il faut utiliser respectivement fit et fdescribe au lieu de iit et ddescribe (http://jasmine.github.io/2.1/focused_specs.html).

Ecrire dans la console des tests

Si vous souhaitez écrire dans la console des tests (à des fins de debug par exemple), il faut utiliser la méthode dump(var) au lieu de console.log(var) comme on pourrait le penser.

Directive et isolate scope

Dans le cas de directive avec un isolate scope, le moyen d’accéder au scope de la directive se fait via :

element.isolateScope()

Liens

AngularJS : Migration de ngRoute vers AngularUI Router

Je viens d’effectuer la migration, de ngRoute (le gestionnaire de route par défaut d’angular) vers UI Router, le router fournit par le projet Angular UI (je vous conseille de regarder les différents modules qui peuvent être très pratique).

Le router fournit de base par Angular a quelques limitations et peut être pénalisant pour des applications complexes (Il a d’ailleurs été retiré d’Angular pour en faire un module à part depuis la version 1.2. La version 2.0 d’Angular devrait améliorer ce dernier et combler ses lacunes).

UI Router apporte notamment :

  • la gestion des vues imbriqués
  • Vues Multiples
  • resolve imbriqués
  • des directives
    • ui-sref pour la génération des liens
    • ui-sref-active pour ajouter une classe si un état est actif
  • des callbacks onEnter et onExit

Voici un petit tutoriel/retour d’expérience pour effectuer la migration vers UI Router. Dans mon cas, cela s’est très bien passé 🙂 ).

Migration

Installation

Le plus simple est d’installer angular-ui-router via bower avec la commande suivante :

bower install angular-ui-router --save

Il faut ensuite inclure le script dans votre page (pensez à supprimer l’inclusion du script de angular-route et à le supprimer de bower)

<script src="libs/angular-ui-router/release/angular-ui-router.js"></script>

Il faut également injecter la dépendance ui-router dans votre application (et supprimer celle de ngRoute)

angular.module('volleyApp',['ngRoute','angularCharts','ui.bootstrap'])

devient donc

angular.module('volleyApp',['ui.router','angularCharts','ui.bootstrap'])

ng-view est également à remplacer par ui-view.

<div ng-view></div>

Devient

<div ui-view></div>

Gestion des routes

Dans votre fichier de configuration des routes :

app.config(function ($routeProvider) {
  $routeProvider
    .when('/', {
      templateUrl: 'views/home.html',
      controller: 'HomeCtrl'
    })
    .when('/todo', {
      templateUrl: 'views/todo.html',
      controller: 'TodoCtrl'
    })
    //vos autres routes ...
    .when('/histo/stat/:matchId', {
      templateUrl: 'views/stat.html',
      controller: 'StatCtrl'
    })
    .otherwise({
      redirectTo: '/'
    });
});

devient

app.config(function ($stateProvider, $urlRouterProvider) {
  //Set default route
  $urlRouterProvider.otherwise('/');

  //Declare states
  $stateProvider
    .state('home', {
      url : '/',
      templateUrl: 'views/home.html',
      controller: 'HomeCtrl'
    })
    .state('todo', {
      url : '/todo',
      templateUrl: 'views/todo.html',
      controller: 'TodoCtrl'
    })
    //autres routes ...
    .state('stat', {
      url : '/histo/stat/:matchId',
      templateUrl: 'views/stat.html',
      controller: 'StatCtrl'
    });
});

Comme vous pouvez le voir, il n’y a pas grand à faire ici :

  • injecter les providers $stateProvider et $urlRouterProvider à la place de $routeProvider
  • Définition de la route par défaut via $urlRouterProvider.otherwise
  • remplacer when par state et donner un nom de vue (qui sera utiliser plus tard lors de l’utilisation de ui-sref)
  • rajouter un paramètre url pour chaque state.

Récupération des paramètres des routes

Dans ma vue de consultation des statistiques d’un match, j’utilise un paramètre dans l’url pour récuperer le match à consulter.

angular.module('volleyApp')
   .controller('StatCtrl', ['$scope', '$routeParams']
     function ($scope, $routeParams) {
       $scope.matchId = $routeParams.matchId;

Devient

angular.module('volleyApp')
   .controller('StatCtrl', ['$scope', '$stateParams']
     function ($scope, $stateParams) {
       $scope.matchId = $stateParams.matchId;

Ici non plus rien de très complexe. On injecte $stateParams à la place de $routeParams et on l’utilise.

Utilisation de ui-sref et ui-sref-active

UI Router fournit la directive ui-sref permettant la gestion des url.

<a class="btn btn-primary" href="#/histo/stat/{{$index}}>Stats</a>

Devient

<a class="btn btn-primary" ui-sref="stat({matchId: $index})>Stats</a>

Vous remarquerez que l’on utilise le nom du state vers lequel on souhaite être redirigé et que l’on passe les paramètres de l’URL via un objet.

ui-sref-active permet de d’ajouter des classes si l’état est sélectionné. Mon menu de navigation devient donc :

<nav>
  <ul class="nav nav-pills black">
    <li ui-sref-active="active"><a id="homeMenu" ui-sref="home">Home</a></li>
    <li ui-sref-active="active"><a id="matchMenu" ui-sref="match">Match</a></li>
    <li ui-sref-active="active"><a id="matchMenu" ui-sref="histo">Historique</a></li>
    <li ui-sref-active="active"><a id="todoMenu" ui-sref="todo">Todo</a></li>
  </ul>
</nav>

Karma

Si vous avez généré votre projet avec yeoman et que vous utilisez les tests unitaires avec karma, il faut également effectuer quelques changements :

  • Remplacer l’inclusion du angular-route.js par angular-ui-router.min.js dans la partie files du fichier karma.conf
  • Remplacer dans vos fichiers de tests l’utilisation de $routeParams par $stateParams.

Liens

Introduction aux Web components

Actuellement, quand on parle de widget web, on pense Bootstrap, Jquery UI, plugins JQuery, ExtJs, … Cela implique l’utilisation d’une structure de code HTML adapté (avec des classes, des éléments spécifique, …), du javascript, du CSS. L’intégration de ces widgets/composants n’est pas forcément très aisé. Je vais vous parler un peu aujourd’hui du futur du web : les web components (la spécification n’est encore validée et est en cours d’écriture). Le but des web components est de pouvoir créer des widgets évolués qui prendront la forme de nouveaux élément HTML (Le concept des directives dans AngularJS se rapproche un peu des web components). Cela permet notamment :

  • la réutilisation et le partage de composant
  • une meilleur isolation et donc une intégration plus aisée
  • d’avoir du code HTML plus propre

Les web components comportent 4 principales composantes (avec un lien vers un article sur html5rocks.com) :

  • Template : permet de définit du code html non interprétés mais utilisable par la suite
  • Custom element : permet de créer ses propres balises html
  • Shadow DOM : permet d’encapsuler vos composants (comme une coquille qui permet de « protéger » votre composant par rapport à la page le contenant, notamment au niveau des styles.)
  • Import : permet le chargement de ressources externes.

Ces technologies sur lesquelles reposent les web components ne sont pas encore implémentées (ou en partie) dans les navigateurs (mais cela est en cours). Cependant, il y a 2 librairies (qui utilisent des polyfills) qui permettent de jouer avec dès aujourd’hui :

Personnellement, j’ai plus entendu parlé de Polymer que de X-Tag, je me suis donc plus intéressé à lui.

Polymer

Polymer est plus qu’une collection de polyfills permettant d’utiliser les web components. Il est composé de plusieurs parties :

  • Platform : les polyfills (a priori utilisés par X-Tag aussi)
  • Core : Une librairie qui permet de simplifier la création de web components. Polymer permet notamment d’avoir du data-binding bi-directionnel.
  • Elements : une liste de composants

La philosophie de Polymer est « Tout est un élément ». Ainsi vous avez des éléments non visuel comme un élément ajax ou local storage !!! Un exemple avec un composant Ajax :

<polymer-ajax auto url="http://gdata.youtube.com/feeds/api/videos/"
 params='{"alt":"json", "q":"chrome"}'	 	 
 handleAs="json"
 on-polymer-response="{{handleResponse}}">
<polymer-ajax>

Je vous invite à regarder cette vidéo de présentation par Eric Bidelman, un des membres de l’équipe de Polymer. Voici une démo d’un element polymer que j’ai créé qui affiche les informations d’un compte github. Le code est dispo sur github.

Liens

Angular : Retour d’expérience

Comme je l’ai mentionné dans un article précédent, je m’intéresse de près à Angular JS.

J’ai ainsi réécrit mon application de gestion de score de Volley avec ce framework. Le code est disponible sur github et l’application est disponible à cette adresse.

Voici quelques astuces/retours sur des problèmes que j’ai pu rencontrer.

Retour sur l’utilisation de Angular JS

Angular est un framework MVW (Model View Whatever comme aime le décrire Google). Je suis maintenant fan de ce framework. En voici quelques raisons :

  • Projet architecturé : angular définit un certains nombre de concept/objects (modules, controllers, services, directives, …) qui permettent de très bien structurer son code et de le rendre modulaire
  • Binding bi-directionnel : Un des plus de Angular est la gestion du binding bi-directionnel (la vue se met à jour automatiquement suite à une modification de la donnée sur laquelle est est bindée et inversement). Ce mécanisme permet de développer de manière très rapide.
  • POJO : On utilise des objets JavaScript standards et on n’hérite pas d’objets liés à un framework comme ça peut être le cas avec d’autres comme Backbone, Ember, knockout, …)
  • Injection de dépendances : Angular met en place de l’injection de dépendance ce qui va facilité le découplage de vos composants et les tests
  • Directives : Une des forces de Angular est l’utilisation de directives. Elles permettent de créer de nouveaux élément/attribut HTML que vous allez pouvoir utiliser directement dans votre vue. Cela facilite la réutilisation des composants et permet d’avoir une vue en full html. Cela va dans le sens de l’évolution du HTML avec les web components
  • Testabilité : Angular a été développé pour être facilement testable … et il l’est.

Angular est vraiment un super framework qui permet de développer rapidement et très bien pensé. N’hésitez pas, testez le !!! Vous allez rapidement l’adopter. Il y a un fort engouement autour du projet avec une grosse communauté, un écosystème de module et des outils adaptés.

Utilisation du générateur yeoman

J’ai parlé dans un article précédent de yeoman, un outil qui permet d’améliorer le worklow front end. J’avais déjà commencé à travailler sur mon application avec angular avant de décider d’utiliser yeoman. Pour cela, j’ai créé un nouveau projet avec le générateur angular et je l’ai intégré dans mon projet existant. Avec le recul, j’aurai du faire l’inverse 🙂 .

Les commentaires dans le fichier index.html sont très important, ils permettent de gérer l’inclusion aux bons endroits des fichiers js, la concaténation lors du build, …

Si, comme moi, vous changez le dossier par défaut de le bower_components, il ne faut pas le modifier seulement le fichier .bowerrc mais dans tout le projet (.gitignore, Gruntfile.js, …).

L’utilisation de yeoman s’est révélée très  pratique. A la création d’un fichier (controller, directives, services, ….), un fichier de test est automatiquement généré. Grunt et Karma sont déjà configurés afin de vous facilité la vie (live relead, tests, ….).

Publication

Le générateur inclus un tache de build qui va optimiser votre application (concaténation, minification, …) pour le déploiement.

grunt build

Si vous voulez tester votre site optimisé il faut utiliser la commande

grunt serve:dist

Dans le processus d’optimisation, il y a une tache concernant les images qui sont renommés afin d’éviter d’utiliser une ancienne image du cache du navigateur (ex : volley.svg en 56103f21.volley.svg). La tache va ainsi renommer vos fichiers images et les liens vers celles ci dans vos fichiers HTML et CSS. J’ai rencontré un problème avec une image au format SVG. Il est possible d’inclure une image au format SVG de plusieurs manière dans votre code HTML : embed, object, img ou svg (article sur alsacreations). J’utilisais la balise object et le lien vers l’image n’était pas mis à jour dans le code HTML. L’utilisation d’une balise img a résolu le problème.

Controller et ngRoute

Dans mon application, j’utilise le module ngRoute fourni de base par Angular. J’ai une container ngView qui affiche la vue en fonction de la route courante.

Ce qu’il faut savoir, c’est que à chaque changement de vue, un nouveau controller est instancié. Ainsi si on revient sur une page, ce n’est pas le précédent controller qui nous est injecté mais un nouveay. Les données du scope du controller sont donc perdues. Dans mon cas, si on changait de page en cours d’un match, les données du match en cours étaient perdues et il fallait recommencer de zéro.

La solution à ce problème consiste à utiliser un service car celui ci est un singleton. C’est lui qui va garder l’état de vos données. L’utilisation des services est une des bonnes pratiques d’angular. Le controller doit permettre le lien entre vos données et la vue. Ne mettez pas de code métier dans votre controller. Utiliser des services !!

Voici un exemple de code d’un service (sous la forme d’une factory) qui stocke les données

angular.
module('myApp', []).
factory('myData', function() {    
  return {
    shareData : {
      foo : 'foo',
      bar : 'bar'
    }
  }
});

Le code du controller qui expose les données à la vue

angular.
module('myApp', []).
controller('dataCtrl', ['$scope', 'myData', function($scope, myData) {    
   $scope.data = myData.shareData;
  }
]);

Un exemple complet et fonctionnel sur JsFiddle.

Utilisation de module externes

J’ai utilisé des modules externes (angular-charts et angular-bootstrap) dans mon application. Le plus simple est de les ajouter via bower

bower install angular-bootstrap --save

Il faut ensuite ajouter les liens vers les fichiers des modules dans :

  • index.html

    • JavaScript dans la partie <!– build:js scripts/vendor.js –>
    • CSS dans la partie <!– build:css styles/vendor.css –>

  • karma.conf
    • JavaScript dans le tableau files

Edit du 02/06/2014 :
Les scripts sont intégrés automatiquement par grunt dans le fichier index.html via la tache bower-intall configurée par le générateur yeoman (merci Gilles 🙂 )

Si vous n’incluez pas les fichiers dans le karma.conf, vous aurez des erreurs de ce type lors de vos tests :

Error: [$injector:modulerr] Failed to instantiate module volleyApp due to:
Error: [$injector:modulerr] Failed to instantiate module ui.bootstrap due to:
Error: [$injector:nomod] Module 'ui.bootstrap' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.

Directives

Les directives sont vraiment très puissante. J’ai eu, dans le cadre de mon application, à créer mes propres directives. Je me suis heurté à un problème de rafraîchissement de ma vue dans une de mes directives. Cette directive permet de créer un input select avec des options numérique. La directive prend en paramètres la valeur max des options à créer. Il est possible d’associer une fonction à appeler au changement de valeur. La solution de mon problème est l’utilisation de scope.$apply qui permet de forcer un refresh.

Voici le code de la partie liée à l’appel de fonction au changement de valeur :

element.bind('change', function() {
  scope.$apply(function() {
    scope.onChange();
  });
});

Le code de ma directive sous github

Jasmine Matcher

Comme préciser plus haut, j’utilise le générateur yeoman pour angular et celui-ci utilise Jasmine comme framework de test. Jasmine inclut un petit nombre de matcher par défaut. Jasmine matcher permet d’en rajouter pour faciliter l’écriture de vos tests.

L’installation se fait via la commande suivante :

npm install jasmine-expect --save-dev

Il n’existe pas encore de plugins karma pour l’intégrer. Il faut rajouter l’inclusion du fichier js dans le fichier de configuration de karma (karma.conf.js)

files: [
            'node_modules/jasmine-expect/dist/jasmine-matchers.js',
            'src/main/webapp/js/angular/angular.js',
            'src/main/webapp/js/angular/angular-*.js',
            ... etc ...

Batarang

Batarang est une extension Chrome spécifique à Angular. Cela permet de rajouter un onglet dans la  barre d’outils développeur.  Cela permet de voir le scope, l’héritage de scope, consulter les méthodes les plus coûteuses en temps…

Liens

Présentation de Yeoman

Aprés Grunt et Bower, je vais aujourd’hui vous présenter yeoman.

Yeoman est un « générateur » de projet.

Il est basé sur node.js et npm et utilise git pour la récupération des dépendances. Ces outils doivent donc être installés sur votre poste.

Installation

L’installation se fait via la commande suivante (avec l’option -g pour avoir la commande en global)

npm install -g yo

Bower et Grunt sont automatiquement installés.

Il faut également installer les générateurs que vous voulez utiliser. Vous pouvez trouver la liste des générateurs disponible ici. Pour notre exemple, nous allons installer le générateur pour angular via la commande suivante :

npm install -g generator-angular

Utilisation

Il suffit d’appeler ensuite la commande yo avec le générateur de votre choix :

yo angular

Quelques questions vous seront posées (voulez-vous utiliser sass, twitter bootstrap, ….,). Ces questions détermineront ce qui sera installé.Une fois les questions répondues, yeoman va se charger de télécharger toutes les dépendances du projet.

Création d'un projet angular via Yeoman

Comme je vous le disais, yeoman intègre grunt et bower. Des fichiers Gruntfile.js et bower.js seront donc déjà présent et configurés suivant le générateur utilisé. Pratique !!

Les générateurs peuvent également être utilisé pour créer des nouveaux fichiers comme dans le cas du générateur angular qui permet de créer des services, factory, controller, directives,  ….

Astuces

Pour connaître les générateurs installés, il faut lancer l’aide via la commande suivante :

yo -h

Liste des générateurs yoeman installésVous pouvez voir la liste des générateurs disponibles pour le seul angular. Notez également la présence de karma. En effet, le template de projet d’angular inclut Karma (anciennement Testacular), un lanceur de test multi-navigateur créé par l »équipe d’angular,  et permet donc d’initier un projet Karma .

Liens

Conclusion

Yeoman est très pratique pour démarrer rapidement un projet avec une bonne architecture et les bonnes pratiques d’un framework (tests unitaires, arborescence projet, …). Vous avez bien entendu la possibilité de générer votre propre générateur pour vos projets et les partager.