seedstack.org : la solution open source de PSA

seedstack_image


Un besoin permanent de productivité et de cadrage

Bien avant l’apparition du langage Java, PSA construisait déjà des frameworks permettant d’accélérer le développement logiciel et de répondre au mieux à ses besoins métiers. Lorsque Java est arrivé avec son lot de frameworks et d’outils, les frameworks PSA ont évolués pour devenir des couches d’intégration autour de frameworks existants et ainsi fournir des outils communs à l’ensemble de ses développeurs. Cependant, ce travail d’intégration d’outils qui n’ont pas été prévus pour fonctionner ensemble et difficiles à configurer a fini par montrer ses limites. De nombreux projets restaient bloqués sur des frameworks obsolètes parce que « montée de version » voulait dire réécriture complète du projet. Le manque de modularité se faisait cruellement sentir. De plus, même si les outils étaient communs (Spring, Struts, etc.), ils leur manquaient de l’opinion et des bonnes pratiques. C’est à dire qu’à chaque fois qu’un développeur changeait de projet il avait tout à réapprendre: organisation, conventions, etc…


Un programme d’entreprise

Début 2013, PSA a donc décidé de rassembler une équipe pour faire face à ce problème. Composée de deux architectes et de deux à huit développeurs selon le plan de charge (moitié PSA, moitié collaborateurs Infotel en mission pour PSA), l’équipe s’est donc attelé à la tâche : réécrire une solution complète nommée SeedStack. Dès l’origine du projet, des orientations structurantes ont été faites.


Des choix novateurs

Premièrement, la stack n’intègrerait pas Spring, comme les précedants frameworks. Elle aurait un noyau plus léger et plus modulaire pour éliminer le couplage. Le micro framework Nuun.io a été choisi pour d’adresser ces éléments grace à son architecture kernel/plugin. Il confère une grande agilité à SeedStack qui a pu grandir et se réorganiser depuis deux ans sans rencontrer aucun obstacle. Chaque plugin expose ses API (Application Programming Interface) qui servent de contrats avec les développeurs et facilitent ainsi les montées de version. Certains exposent également des SPI (Service Provider Interface) permettant aux développeurs d’étendre les fonctionnalités du framework et donc de répondre à tous leurs besoins.


Mettre le métier au cœur du développement et le cadrer

Deuxièmement, il a été décidé de promouvoir des styles d’architectures de type REST (Representational State Transfer) et DDD (Domain Driven Design). Le REST via ses API hypermedia permet aux applications d’exposer des ressources Web simples à utiliser, qui garantissent une compatibilité à long terme tout en permettant une grande évolutivité. Son support a été développé en se basant sur les spécifications JAX-RS (via Jersey) et HAL. Le DDD quant à lui permet de se recentrer sur le domaine métier. Il remet la conception au coeur du travail de développeur, alors qu’elle est trop souvent délaissée au profit d’architecture de type CRUD : correspondance 1-1 entre de la base de données et l’IHM. Aucune solution satisfaisante n’étant disponible pour son implémentation, il a été décidé de développer un nouveau framework adressant la partie tactique du DDD.

Enfin, pour la partie Web, un framework a été développé pour combiner le meilleur d’AngularJs, RequireJs, et Bootstrap. L’utilisation du REST côté serveur et d’une SPA (Single Page Application) côté UI permet ainsi, le découplage du front et du back qui peuvent maintenant avoir des cycles de vie séparés. Par ailleurs, ce framework Web n’est pas en reste côté modularité car il permet via un système de fragments de composer son application à partir de modules indépendants.


Une suite Open Source

Suite à ces choix SeedStack a connu un départ rapide et les gains en productivité ne se sont pas laissés attendre. La première version a été finalisée après seulement 6 mois de développement. Dès lors, elle a été intégrée par des projets pilotes, qui ont permis à la solution de se développer et de s’affiner. Avec un rythme de 3 versions par an, SeedStack a connu des évolutions fréquentes. Début 2015, une quarantaine de projets (dont la moitié en production) utilisaient déjà cette stack. Conscient de l’intérêt de sa solution, PSA a alors décidé de publier SeedStack sous une license libre : la MPL 2.0 (Mozilla Public License 2.0). Après un audit juridique et technique ainsi qu’un peu de refactoring, la stack a été publié sur GitHub en version béta en mai 2015. Puis, après quelques mois de travail une première version open source a été publiée en Juillet. Loin d’achever cette « success story », la mise en open source lance de nouveaux défis : tout d’abord continuer d’améliorer la stack, mais aussi communiquer et trouver de nouveaux partenaires.

Des premières contributions extérieures ont été enregistrées et je vous encourage tous à venir participer. Il n’y a pas de petites contributions ! Que ce soit de la documentation, des rapports de bug ou du code, toutes les contributions sont les bienvenues.



BackboneJS et développement mobile : trucs et astuces (…fin)

Voici la suite (et fin) tant attendue de notre série sur le développement avec BackboneJS, framework JavaScript que nous utilisons pour nos créations de sites web / mobile HTML5, quand nous n’optons pas pour AngularJS.

Certaines bonnes pratiques sont communes à tout projet HTML5, notamment les techniques de responsive design, gestion des dépendances JavaScript et outillage des tests, ce qui constitue notre menu du jour :

Organisation des librairies

Require

Il s’agit de l’implémentation de l’API AMD la plus répandue. Elle permet de gérer les dépendances et chargement dynamique des scripts.
Pour démarrer, voici un lien très utile :
http://www.ringabell.org/un-simple-guide-pour-debuter-avec-requirejs/

Points d’attention

Nous avons fait face un certain nombre de fois des comportements étranges dans notre application qui étaient en fait du à des erreurs bêtes liées à l’utilisation de Require. Un bon réflexe quand vous ne comprenez absolument pas pourquoi votre code ne fait pas du tout ce qui est attendu est de vérifier les points suivants :

  • ne pas oublier le return avec le nom du module à la fin de la déclaration du dit module
  • l’ordre de déclaration des dépendances vers les modules qui doit correspondre exactement aux paramètres de la fonction (attention aux décalages par inadvertance!)

Optimisation

Bien que nous ne l’ayons pas utilisé dans notre projet, il peut être intéressant de mentionner que Require JS propose également un outil pour optimiser le chargement des scripts en concaténant et minifiant tous les fichiers JS nécessaires au démarrage de l’application en un seul fichier : http://requirejs.org/docs/optimization.html
Et pour un tutoriel en français sur le sujet : http://www.ringabell.org/requirejs-une-utilisation-plus-poussee/

Optimisation des performances

Dans le cas où nous travaillons avec une seule page HTML, on peut généralement la découper en différentes portions qui correspondront chacun à des vues Backbone à l’intérieur d’une vue globale (la page).
Il paraît alors préférable, dans la vue globale, de garder des références vers les sous-vues et d’éviter, quand c’est possible d’instancier de nouvelles vues à chaque changement dans un modèle sous-jacent. Il est possible d’associer un nouveau modèle (ou un modèle modifié) à une vue avant de la rafraîchir (via #render).

Var MyView = Backbone.View.extend({
 
 
    initialize: function() {
	    ...	
	    this.subView1 = new SubView1 ({model : modelA}) ;
 	   ...
    }
 
       refresh : function() {
	...	
      	this.subview1.model = myNewModel ;
	this.subview1.render() ;
       }
});

A lire également, pour éviter quelques erreurs qui peuvent être à l’origine de fuite de mémoire si on ne fait pas attention. Y sont notamment abordés le binding d’événements dans une vue et le rendering d’une collections de vues :
http://ozkatz.github.io/avoiding-common-backbonejs-pitfalls.html

Responsive Design

Les techniques de Responsive Design permettent d’assurer que l’affichage d’un site s’adapte correctement aux dimensions et résolution du support, que ce soit un ordinateur, une tablette ou un téléphone mobile.

Utilisation des media queries

Les Media Queries CSS3 sont une spécification du W3C qui permettant de définir l’application de feuilles de styles en fonction des périphériques (média). Mais elles vont bien au-delà de ce qu’il était possible de faire avec CSS2 où on pouvait définir une feuille de style différente pour l’impression par exemple puisqu’on peut créer des règles très précises qui prennent en compte, en plus du type de média, la largeur ou la hauteur de l’écran, l’orientation (portrait ou paysage), le support de la couleur etc. (voir : http://reference.sitepoint.com/css/mediaqueries#mediaqueries__tbl_media-queries_media-features)

Comme l’écriture d’une Media Query correspond à une expression logique, évaluée à vrai ou fausse, on peut tout à fait combiner ces différents paramètres, ce qui s’avère très pratique pour le design de sites mobiles.
Par exemple, pour un style spécifique à l’iPad :

/* iPads (portrait and landscape) ----------- */
@media only screen
and (min-width : 768px)
and (max-width : 1024px) {
/* Styles */
}

On trouvera un squelette de CSS avec Media Queries pour débuter avec le Responsive Design pour mobiles à l’adresse suivante : http://www.paulund.co.uk/boilerplate-css-media-queries
Les Media Queries sont supportées par la plupart des navigateurs récents (voir http://caniuse.com/css-mediaqueries pour un tableau récapitulatif des compatibilités)

Meta-balise « viewport »

Le « viewport » désigne normalement la surface d’affichage de la fenêtre du navigateur.

Sur un navigateur de bureau, un pixel écran correspond à un pixel CSS.

Mais comme expliqué dans l’excellent article, http://www.alsacreations.com/article/lire/1490-comprendre-le-viewport-dans-le-web-mobile.html, la plupart des terminaux mobiles ont une surface « réelle » qui correspond aux nombre de pixels physiques composant la matrice de l’écran et une surface « virtuelle » en pixels CSS, plus connue sous le terme de device-width/device-height et sur lequel se base en fait le terminal pour afficher les pages.

Et comme on peut le voir sur le site http://screensiz.es/phone , surface réelle et virtuelles ne correspondent pas forcément !

Pour couronner le tout, sur les terminaux mobiles, la valeur par défaut du « viewport » peut ne correspondre ni à l’un ni à l’autre… et dépend en fait du navigateur.
C’est ce qui explique les différences de zoom initial à l’ouverture d’une même page.
Pour s’assurer du bon affichage de son site quel que soit le navigateur, on peut recourir à la meta balise « viewport ».
Son attribut content offre la possibilité de fixer la largeur de viewport à la valeur souhaitée

<meta name="viewport" content="width=320">

Ou de l’adapter automatiquement à la valeur de device-width du terminal.

<meta name="viewport" content="width=device-width">

A noter enfin que cette balise ne fait partie d’aucune spécification, car elle a été introduite par Apple. Mais elle est généralement prise en compte par les autres navigateurs.
Il existe en fait un équivalent « officiel » du W3C sous forme de règle CSS @viewport, mais qui n’est malheureusement pas encore supporté par tous.

Quelques liens complémentaires forts intéressants pour démystifier le concept de viewport :

Tests

Emulation mobile sous Chrome

Les outils de développement Chrome permettent de simuler les événements tactiles (touch events) propres aux terminaux mobiles et qui n’existent normalement pas sur un navigateur de bureau.
Plus d’informations sur la page : https://developers.google.com/chrome-developer-tools/docs/mobile-emulation?hl=fr
On voit qu’en plus du support des touch events, les Chrome Dev Tools permettent également de simuler des tailles de viewports, l’orientation, différentes vitesses de connexion réseau, qui sont des spécificités propres aux mobiles qu’il faut prendre en compte.

Débugger une page dans Chrome pour Android depuis l’ordinateur

Il est possible de débugger les pages affichées dans le navigateur Chrome d’un téléphone Android connecté via un câble USB directement depuis un ordinateur. Pour cela, il faut :

  • Chrome 28+ sur l’ordinateur (tout OS) avec l’extension ADB installée
  • Chrome 28+ pour Android
  • Activer le débogage USB sur le téléphone

La procédure d’installation détaillée officielle est expliquée sur cette page : https://developers.google.com/chrome-developer-tools/docs/remote-debugging#remote-debugging
A noter que sur Windows uniquement, il faut impérativement installer le driver USB correspond au modèle du téléphone.
Plus d’informations sur ce lien : http://developer.android.com/tools/extras/oem-usb.html

Quant aux drivers pour les terminaux Google (Nexus…), ils sont téléchargeables sur la page suivante : http://developer.android.com/sdk/win-usb.html
Même en suivant point par point ces instructions, il se peut qu’un téléphone connecté ne soit pas reconnu directement par le plugin de Chrome. Ce fut le cas pour nous (Chrome 30 sur Windows 7 et Android + téléphone Nexus 4), il est alors nécessaire d’installer quand même ADB sur l’ordinateur.

  • Télécharger le SDK Android : http://developer.android.com/sdk/index.html
  • Décompresser l’archive dans le répertoire de votre choix
  • Ouvrir une invite de commande dans /platform-tools : adb devices
  • et vérifier que le téléphone est bien listé parmi les terminaux connectés

A ce stade, le plugin ADB de Chrome de l’ordinateur devrait proposer le téléphone dans : chrome://inspect/
et il est alors possible d’inspecter toute page ouverte dans le navigateur du téléphone, avec les mêmes outils de développement que pour la version web.

Débugger une page dans Firefox Mobile pour Android depuis l’ordinateur

A l’instar de Chrome, il est possible de débuguer une page ouverte dans Firefox Mobile à partir de la version Desktop du navigateur, comme expliqué sur la page « Remote Debugging » de Mozilla :
https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging
Les prérequis sont :

  • Avoir activé le débogage USB sur le téléphone Android (et éventuellement le driver USB pour Windows, voir le paragraphe sur Chrome)
  • Avoir activé le débogage distant dans le navigateur Firefox Mobile du téléphone (Paramètres > Outils de développement > Débogage distant)
  • Pouvoir utiliser ADB sur son ordinateur (voir la procédure d’installation résumée plus haut pour Chrome)

Pour se connecter depuis l’ordinateur au téléphone mobile, il faut rentrer la commande ADB suivante :

adb forward tcp:6000 tcp:6000

puis autoriser la connexion entrante sur le téléphone Android.
Malheureusement, il n’est pas possible pour l’instant, via le débogage distant d’utiliser les outils habituels du panneau « Inspecteur » pour inspecter les éléments, éditer du code HTML et des styles CSS à la volée. D’après la documentation, cette fonctionnalité sera rendu accessible avec Firefox 26.



BackboneJS : trucs, astuces & bonnes pratiques (part 2)

Vues

Passer des arguments à la construction d’une vue

Nous pouvons avoir besoin de passer des arguments, en plus du modèle, lors de la construction/initialisation d’une vue Backbone. Par exemple, une référence sur la vue parente.
On peut alors être tenté de faire quelque chose comme

new ServiceView ({parentView: this.myParentView, model: this.myModel })

mais on se rend vite compte avec un this.myParentView dans le code de la vue ne nous donne rien. parentView est en fait attaché à l’objet options de la vue lors de la construction de cette dernière.
En effet, d’après la documentation, seuls certains paramètres comme par exemple model et collection sont accessibles directement.
There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, tagName, attributes and events.

Pour tous les autres, il faudra donc écrire :

// Instanciation de la vue
 
new ServiceView({
    model: serviceModel,
    parentView: this.parentView
});
 
// A l'initialisation, dans ServiceView
initialize: function() {
    this.parentView = this.options.parentView;
    // par la suite, on pourra récupérer la référence avec this.mainView;
}

Collection de modèles et listes de vues

On trouvera à travers les exemples sur le net plusieurs façons de créer un ensemble de vues à partir d’une collection Backbone.
Nous avons adopté l’approche suivante, qui nous paraît propre et facile à comprendre :

// Modèle
 var ProduitModel = Backbone.Model.extend({ }) ;
 
// Collection 
var ProduitsCollection = Backbone.Collection.extend({
 
        model: ProduitModel,
 
        }
});
 
// vue Liste
var ListeProduitsView = Backbone.View.extend({
 
        el: '#listeProduits',
 
        initialize: function() {
 
            this.collection.bind('reset', this.render, this);
 
            this.collection.bind('add', this.addItem, this);
 
        },
 
        render: function() {
 
            this.$el.empty();
 
            this.collection.each(this.addItem, this);
 
            return this.$el;
 
        },
 
 
        addItem: function(item) {
 
            this.$el.append(new ProduitView({
 
                model: item,
 
            }).render());
 
        }
 
 });
 
 
// Vue produit
var ProduitView = Backbone.View.extend({
 
      initialize: function() {            
 
            this.template = _.template(tmpl);
 
            _.bindAll(this, 'render');
 
            this.model.bind('change', this.render);
 
            this.render();
 
      }, 
 
render : function() {
 
            this.$el.html(this.template(
 
                this.model.toJSON()
 
            ));
 
            return this.$el;
 
    	}
 
});
 
//Nous pouvons directement créer une collection de produits correspondant au JSON suivant
{
  "ListeProduits": {
    "Produit": [
      {
        "CodeProduit": "P1",
        "Prix": "345"
      },
      {
        "CodeProduit": "P2",
        "Prix": "637"
      },
      {
        "CodeProduit": "P3",
        "Prix": "890"
      }
    ]
  }
}
 
// On remarquera que pour que le mapping collection / modèles corresponde exactement au JSON, il faut se placer au niveau de l'élément « enfant » qui correspond au modèle (ici 'Produit' et non pas 'ListeProduits')
var produitsCollection = new ProduitsCollection(data.ListeProduits.Produit);
 
 
// création de la liste de vues à partir d'une collection 
var listeProduitsView = new ListeProduitsView({collection: produitsCollection});
 
listeProduitsView.render();

Ecoute des événements JQuery

Tout comme une vue Backbone peut réagir aux événements survenant sur le modèle associé, elle peut évidemment réagir aux interactions utilisateurs et aux événements JavaScript/JQuery comme un clic, un cochage de checkbox…

Les associations avec des fonctions de callback se font dans ‘events’

MyView = Backbone.View.extend({
    events: {
        'click .item': 'handleClick'   // name of the event : name of the callback function
    },
 
    handleClick: function(e) {
        this; // The view instance
        e.target; // The element that was clicked 
        e.currentTarget; // The element that was bound by the click event
	    this.model.set({someValue : jQuery(e.currentTarget).val())}); 
    }
});

Templates

Associer un template à une vue/ un modèle à l’aide du plugin text.js de Require

Require contient un plugin « text.js » qui nous permet d’associer des templates soit aux vues soit aux modèles (exemple : modèle des requêtes soap). Pour pouvoir les charger, il faut procéder aux étapes suivantes :
– Editer la configuration de requireJS dans le main.js avec la ligne suivante :

 require.config({
   paths : {,
  "text" : "vendor/require/plugins/text" 
 
   }
});

– Exemple d’utilisation de require et plus particulièrement de la définition du paramètre qui permettra de récupérer le template

define([
    "jquery",
    "underscore",
 	    "backbone",
    "text!templates/html/templatePack.tpl"
 //Attention :  il faut préciser l’extension du fichier avec le plugin !  
], function($, _, Backbone, tmpl) {});
-	Et enfin, association de la vue ou du modèle avec le template
this.template = _.template(tmpl);

Ces fichiers sont chargés via des appels asynchrones XMLHttpRequest (XHR). Internet Explorer 9 et versions inférieures ne supportent pas cette technologie. Ils implémentent la technologie XDomainRequest. Par conséquent, pour résoudre ce problème, il a fallu que nous téléchargions un plugin javascript jquery.xdomainrequest.min et qui est chargé via RequireJS.

Variables indéfinies dans le modèle

Si on cherche à afficher dans un template un attribut qui n’est pas défini dans le modèle cela provoque une erreur.
Il faut donc soit faire une vérification préalable de type :

if (typeof myVar !== "undefined") {
    <div><%= myVar %>"</div>
}

Soit initialiser les attributs du modèle qu’on souhaite afficher avec des valeurs par défaut pour être plus prudent :

var MyModel = Backbone.Model.extend({
   defaults : {
    myVar : ‘’
    anotherVar : ‘’   
  }, 
});

Itération dans un template

Il est tout à fait possible d’utiliser des fonctions utilitaires d’Underscore à l’intérieur des templates. Par exemple pour itérer sur un tableau à l’intérieur dans un JSON :

{
  "ListEntryVarValues": {
    "EntryVarValue": [
      "12",
      "24",
      "36",
      "48",
      "60",
      "72"
    ]
  }
}
 
<% _.each(ListEntryVarValues.EntryVarValue, function(variable){ %>
 
    <input type="radio"
<% if (variable === Value)  { %> checked="checked" <% } %> name="radioGroup-<%=ShortDescriptionVar%>" value="<%= variable%>"/><label for="<%=ShortDescriptionVar%><%= variable %>">
<%= variable %> </label>
 
<% }); %>

Utilisation de fonctions

Dans un template, « this » fait référence à la Vue Backbone associée.
Ainsi, on peut donc tout à fait définir des fonctions dans la Vue Backbone et les utiliser ensuite à l’intérieur du template.

var MyView = Backbone.View.extend({ 
    dummyConcatFunction :function(name1, name2 ){
 
        return name1 + '':'' + 'name2 ; 
    }
});

// Appel dans le Template correspondant :

<div><%= this.dummyConcatFunction (nom1, nom2) %> </div>

Attention ! A l’intérieur d’une boucle ‘each’, ‘this’ ne fait plus référence à la vue Backbone. Une solution qui peut être adoptée :

<% var concatFunction = this.dummyConcatFunction ;
        _.each( variable, function(aVar){ %>                               
        <tr>
            <td> concatFunction(aVar.Value) %> </td>           
        </tr>

A suivre …



BackboneJS : trucs, astuces & bonnes pratiques (part 1)

Nous entamons ici une petite série d’articles techniques en retour des développements de site Web / Mobile responsive.

Pour commence : Quelques bonnes raisons de choisir Backbone

Structuration du code et découplage

Backbone est un framework JavaScript MVC qui permet de mettre en place rapidement une structure d’application où modèles et vues sont bien découplés et ainsi d’éviter le piège du code spaghetti d’une page web avec AJAX rafraîchie à grand coups de callback Jquery dans tous les sens.
Avec cette approche, il devient plus facile de maintenir synchronisées les données et leurs représentations, en réponse aux interactions de l’utilisateur.

Une forte communauté

Le framework jouit également d’une forte popularité ces derniers mois, soutenue par une grande communauté de développeurs. A la moindre difficulté, il sera facile de trouver de l’aide sur Stackoverflow.com ou autres.

Un moteur de templates intégré

De plus, il intègre nativement un moteur de templating, Underscore (qui est en fait sa seule dépendance forte) et se veut facile à intégrer avec d’autres bibliothèques JavaScript (comme JQuery)

Une prise en main rapide

Il est relativement aisé de démarrer avec ce framework étant donné qu’il y a peu de concepts-clés / composants à connaître : Modèles et Collections, Vues et Routeur.

….mais aussi quelques inconvénients

Complétude de Documentation

Cependant, nous avons trouvé que la documentation officielle de l’API n’était pas forcément des plus claires et manquait parfois d’exemples concrets. On voit ainsi souvent le paramètres « options » dans l’appel de fonctions, sans savoir exactement à quoi peuvent bien correspondre ces fameuses options.

Bonnes pratiques ?

Backbone revendique haut et fort sa flexibilité et la liberté laissée par le framework pour les développements, « There’s more Than One Way to Do it »… mais justement, n’y a-t-il pas trop de façons de faire ? En parcourant les tutoriels sur internet, on se rend en effet vite compte que chacun fait un peu à sa sauce, par exemple dans l’association (binding) entre vue et modèles ou l’orchestration des vues et des événements.
On en vient donc à se demander s’il existe vraiment de « bonnes pratiques » de développement avec Backbone, si l’utilisation qu’on fait du framework permet vraiment d’en tirer parti ou si on passe complètement à côté des possibilités qu’il offre.
En se voulant minimaliste et non-contraignant, Backbone laisse en réalité beaucoup de réflexions sur des points d’architectures à la charge du développeur… ce qui peut être vu par certains comme une force alors que d’autres y verront une limitation par rapport à d’autres fraweworks JavaScript qui cadrent davantage le développement.
En ce sens, il faudra explorer des extensions à Backbone tels que Chaplin (http://chaplinjs.org/) ou encore Marionette (http://marionettejs.com/) qui permettent de répondre à des problématiques plus complexes ou récurrentes (par exemple, gestion de la mémoire, gestions des événements) et de façon bien plus structurée.

Trucs & astuces concernant les ‘Models’

Getters: A.get(‘attribute’) VS A.attribute

Une étourderie qui nous coûte souvent une erreur de type « variable is undefined » quand on travaille à la fois avec des modèles Backbone et de simples objets JavaScript est d’oublier que pour accéder aux propriétés d’un modèle Backbone, il faut utiliser model.get(‘attribut’) et non pas model.attribut.

Il en va de même pour les setters.

Les différences entre le modèle et un objet JSON est par ailleurs très bien résumée dans le schéma de la page : http://www.atinux.fr/2012/04/15/backbone-js-le-fonctionnement-des-modeles/

On remarquera toutefois qu’il est tout à fait possible qu’un attribut d’un modèle Backbone corresponde en fait lui-même à un objet JSON, pour lequel la notation avec un point peut s’appliquer.
Par exemple, pour le modèle qui correspond au JSON suivant

{
  "Site": {
    "Login": "TOTO",
    "Password": "superpasswordsecure"
  }
}

Si on veut récupérer la valeur de Password, il faudra écrire : model.get(‘Site’).Password

Itération sur une collection Backbone

Nous pouvons utiliser la fonction each d’Underscore pour parcourir une collection :

produitsCollection.each(function(produit) {
    console.log(produit.get('name')) ;
});

Modifier un attribut de modèle sans déclencher l’événement « change » sur ce dernier

Dans quelques (rares) cas, il peut être utile de modifier un des attributs du modèle sans pour autant propager l’information que le modèle a changé (« change » event). Par exemple si on ne souhaite pas mettre à jour immédiatement la vue associée.
Pour cela, il faut mettre l’option silent à true :

this.model.set({headerValue: "lalala"}, {silent: true})

Redéfinition de fetch et parse

Il peut être utile de redéfinir la méthode fetch d’un modèle Backbone, par exemple pour utiliser les toujours les mêmes options lors d’un appel serveur.
Les options possibles sont celles de jQuery Ajax – http://api.jquery.com/jQuery.ajax/

var ServiceModel = Backbone.Model.extend({
        fetch: function(options) {
            options = options || {};
            options.method = "POST"; 
            options.data = 
            options.headers = {  "Content-Type" : "text/xml"  };
            };
 
            return Backbone.Model.prototype.fetch.call(this, options);
 
        } 
});

Rappelons que fetch s’exécute par défaut de façon asynchrone. De ce fait, pour pouvoir créer ou mettre à jours des vues avec les données récupérées du serveur, il faut le faire dans la méthode de callback success (il existe par ailleurs son pendant error pour la gestion d’erreur)

var MaView = Backbone.View.extend({
var self = this;
serviceModel.fetch({
       success: function() {
self.subview.render();
       },
       error: function(xhr, textStatus, options) {
            self.showError();
      console.log("Erreur sur serviceModel :" +      JSON.stringify(options.xhr));
       }
});
 
});

On remarquera qu’à l’intérieur de la function success, « this » ne fait pas référence au modèle. Une technique communément admise est de conserver la référence sur dans une variable extérieure par exemple var self = this.

Parse est appelé systématiquement après chaque fetch et save. L’implémentation par défaut se contente de retourner la réponse brute telle que reçue du serveur, mais il peut être utile de redéfinir la fonction pour appliquer un traitement sur ces données avant de les utiliser dans le modèle. Par exemple, si on reçoit un flux xml, le convertir en JSON.

parse: function(data) {
// data est la réponse telle que renvoyée par le serveur           
      var x2js = new X2JS();
      var result;
      result = x2js.xml_str2json(data);
      return result;
},

La méthode doit retourner un objet JSON qui décrit les attributs à setter sur le modèle.



à suivre…



AngularJS, premiers pas vers des applications HTML riches

On a entrevu ici les raisons pour lesquelles le JavaScript devient une technologie incontournable et que les besoins de structuration du code expliquent en grande partie l’engouement pour BackboneJS, EmberJS ou AngularJS.

Infotel a réalisé à ce jour plusieurs projets AngularJS qui nous donnent l’occasion de partager quelques bonnes pratiques et retour d’expérience glanés. Ce premier article présente les concepts généraux ainsi que les subtilités des directives pour maîtriser parfaitement l’affichage d’informations et les animations avancées.

présentation graphique et fluide en AngularJS

CRUD - Gestion Back Office en AngularJS

Pourquoi utiliser AngularJS ?

  • AngularJS fonctionne sur le principe MVC (Modèle-Vue-Contrôleur) et apporte aux applications web côté client les services traditionnellement apportés côté serveur, comme les contrôleurs de vues. En conséquence, une bonne partie du fardeau supporté par le back-end est supprimée, ce qui conduit à des applications web plus légères.
  • Le « Data binding » ou couplage des données est un des points fort d’AngularJS. Il permet de relier la vue aux modèles de données et de permettre une mise à jour automatique des changements dans les deux sens : des changements de la vue mettent à jour le modèle et inversement. Ainsi les manipulations du DOM deviennent (quasi) transparentes pour le développeur

Exemple :

<input type="text" ng-model="yourName" placeholder="Enter a name here">
<h1>Hello {{yourName}}!</h1>

Le modèle « yourName » contiendra la valeur entrée dans le champ de texte input. Le « binding » ou couplage au niveau de l’affichage s’effectue via l’opérateur {{ }} qui affiche le texte entré à la volée. Si l’on modifie le champ le modèle se met à jour automatiquement ainsi que la vue.

• Les Contrôleurs peuvent être déclarés sur n’importe quel élément du DOM et permettent de s’occuper de la logique de traitement (réception ou envoi de données vers le serveur par exemple, remplissage d’une liste ou d’un tableau…). L’avantage est que ces manipulations sont découplées de la vue et s’effectuent dans des fichiers séparés permettant une meilleure lisibilité entre la logique métier et la vue.

• Le routage entre différentes vues de l’application est assuré et géré par AngularJS ce qui économise ce travail sur le serveur. Cela permet la réalisation de SPA (Single Page Application) avec plusieurs vues sans harcelé le serveur pour chaque vues.

• La communication avec le serveur est grandement facilitée par les Services. AngularJS propose directement des méthodes pour le CRUD (Create-Read-Update-Delete) sous forme de requêtes XHR(XMLHttpRequest) pour l’insertion, suppression et modification avec une gestion des exceptions en fonction de la réponse du serveur, le tout de façon asynchrone.

• Les directives sont une fonctionnalité unique d’AngularJS et un gros point fort dans la création de composants à insérer dans le DOM. Par exemple il est possible de créer une directive qui s’occupe d’afficher un slider d’images :
HTML : Plus lisible, la directive semble étendre les propriétés de base de l’HTML

La directive n’est pas montrée intégralement ici car nous verrons plus en détails comment les utiliser. L’avantage de ce type de déclaration est double : meilleure lisibilité du DOM et composant réutilisable.
RQ : La directive s’appelle imageSlider mais dans le HTML il faut remplacer les majuscules et elle devient (Autre exemple : LeNomDuneDirective devient le-nom-dune-directive quand elle est utilisé dans le DOM).
• Injection de dépendances : AngularJS permet l’injection de services ou d’objets directement dans le contrôleur ou les directives ce qui réduit également la gestion de cet aspect par le programmeur.
Résumons les principales fonctionnalités d’AngularJS par un schéma :

Rôle des composants Angular

On abordera une prochaine fois la structuration du projet, les rôles des contrôleurs et service, mais nous allons détailler ici le rôle et le fonctionnement des directives (et cela peut-être subtile) :

Les directives
Les directives sont un des éléments phare d’AngularJS car elles distinguent le framework des autres. Nous allons voir comment écrire une directive dans AngularJS.
Pour écrire des directives on déclare un module ‘infotelDirectives’ dans directives.js (qu’on a déjà configuré dans app.js)

angular.module('infotelDirectives', [])
.directive(....) // 1ère  directive
.directive(....) // 2nde directive

Voici un des plus simples exemples de directives: On déclare un nom pour la directive (« blink ») et on lui demande de retourner un template HTML. En l’occurrence on retourne une balise <marquee> qui permet de faire défiler du texte, avec un scrollamount de 100% qui donne l’impression que le texte clignote.

.directive('blink', function() {
  return {
    template: '<marquee scrollamount="100%">Blink!</marquee>'
  };
});

On peut alors utiliser la directive où bon nous semble :

<div blink></div>

Qui devient :

<marquee scrollamount="100%">Blink!</marquee>
Blink!

Resctrict :
Si l’on souhaite utiliser la directive autrement que comme un attribut on doit le signaler avec restrict :

E : element : <blink></blink>
A (par défaut) : attribut : <div blink></div>
C : class : <div class="blink"></div>
M: commentaire : <!– directive: blink –>

.directive('blink', function() {
  return {
    restrict: ‘E,A’, // Ici on ne peut l’utiliser que comme elem ou attr
    template: '<marquee scrollamount="100%">Blink!</marquee>'
  };
});

Transclude :

Si l’on souhaite placer le texte (« Blink ! ») dans le DOM et que le template l’entoure on doit utiliser la transclusion :

.directive('blink', function() {
  return {
    restrict: ‘E,A’, // Ici on ne peut l’utiliser que comme elem ou attr
    template: '<marquee scrollamount="100%" ng-transclude></marquee>',
  };
});

HTML :

Texte placé dans le DOM et non plus dans le template de la directive

Qui devient :

<marquee scrollamount="100%"> Texte placé dans le DOM et non plus dans le template de la directive </marquee>
Texte placé dans le DOM et non plus dans le template de la directive

NgTransclude sert à placer les éléments qui se trouve entre <blink> et </blink> dans le DOM à l’emplacement précisé par ng-transclude.

Declarer le template dans un fichier séparé

Il est également possible de remplacer la déclaration du template par un fichier qui contiendra le bout de HTML a cloné dans le DOM. Dans ce cas il faut utiliser templateUrl.

temp.html :

<marquee scrollamount="100%" ng-transclude></marquee>

Directive :

.directive('blink', function() {
  return {
    restrict: ‘E,A’, // Ici on ne peut l’utiliser que comme elem ou attr
    templateUrl: chemin/vers/le/fichier/temp.html
 
  };
});

Fonction link :

Une fois que le template a été cloné dans le DOM, la fonction link est exécutée. C’est dans cette fonction que la plupart de la logique de la directive se trouve.
Il y a trois éléments à passer à la fonction link :

<div blink attr1=’attribut1’ attr2=’attr2’></div>

• scope : un objet scope pour manipuler des modèles de la directive
• element : l’élément sur lequel la directive est appliquée (le div)
• attributes : les attributs de l’élément accessible comme un objet (on accède à attr1 via attributes.attr1 etc.)

Par exemple si l’on désire effectuer un ng-click sur le texte clignotant et obtenir un champ de texte à la place on peut masquer le texte et montrer le champ input. Cela se réalise avec les directives NgShow et NgHide.

.directive('blink', function() {
  return {
    restrict: ‘E,A’,
    template: '
<div><marquee ng-click="edit()" ng-hide="editMode" ' +
              'scrollamount="100%" ng-transclude></marquee> ' +
              '
<input type="text" ng-show="editMode" ></div>
 
',
  link: function(scope, element, attrs) {
      // le booléen editMode contrôle la visibilité de blink et de input
      scope.editMode = false; // initialement
      // appellé quand la balise marquee est cliquée
      scope.edit = function() {
          scope.editMode = true; // toggle le texte ou le champ
      };
    }
 
 };
});

ng-hide=true : masqué, ng-show=true : visible | ng-hide=false : visible, ng-show=false : masqué
Afin de permettre la modification du texte affiché par le champ input on attache un événement « blur » (le contraire de focus) pour signaler au marquee de modifier son texte quand on quitte le champ input.

Il faut appeler $apply pour signaler à la vue de se mettre à jour :

.directive('blink', function() {
  return {
    restrict: E,A,
    template: '
<div><marquee ng-click="edit()" ng-hide="editMode" ' +
              'scrollamount="100%" ng-transclude></marquee> ' +
              '
<input type="text" ng-show="editMode" ></div>
 
',
  link: function(scope, element, attrs) {
 
// sélectionner les éléments du template : angular.js embarque jqlite, une version minimale de jquery qui permet d’utiliser les sélecteurs usuelles de Jquery
var marquee = element.find("marquee");
var input = element.find("input");
 
// attacher un évenement blur (perte de focus) au input
      input.bind('blur', function() {
// Si on n’utilise pas $apply, la vue ne sera pas au courant de la transformation
        scope.$apply(function() {
          // masquer le champ input et montrer le texte du marquee
          scope.editMode = false;
          // mettre à jour le champ du marquee
          marquee.text(input.val());
        });
      });
      // le booléen editMode contrôle la visibilité du texte blink et du input
      scope.editMode = false; // initialement
      // appellé quand la balise marquee est cliqué
      scope.edit = function() {
          scope.editMode = true; // toggle le texte ou le champ
      };
    }
 
 };
});

Directives ImageSlider: le casse-tête final !
Voyons un autre exemple faisant intervenir un composant écrit pour Jquery. Il s’agit de bxslider.js, un slider d’images. En réalité, on a utilisé deux directives pour que le composant s’initialise correctement :
La première directive s’appelle « imageSlider » :
On crée un template pour notre slider. Il doit déclarer la classe « bxslider » sur un <ul> et les images doivent être associées dans les

  • suivant.

       .directive("imageSlider", function() {
        return {
            restrict: 'E',
            replace: true,// signifie que l’on remplace la balise par le template
             scope: {
                image: '=' // déclare un attribut image, dans le dom on passera à cet                          //attribut la source de données contenant les chemins d’images :
        //<image-slide image=“projet.imagesSet”></image-slide>
            },
            template: '
    <ul class="bxslider">'+
                      '
    <li check-last ng-repeat="slide in image">'+
                      '<a ng-click="open(slide.chemin)" href="">'+
                      '<img ng-src="image/{{slide.chemin}}" alt="image non disponible : {{slide.chemin}}" title="{{slide.chemin}}" class="imageSlide">'+
                      '</a>'+
                      '</li>
     
    '+
                      '</ul>
     
    '
        };
    })

    On utilise ng-repeat pour ajouter des diapositives dans chaque li. Chaque « slide » provient de projet.imagesSet , qui correspond au modèle contenant les chemins d’images. On obtient ce modèle via la déclaration scope :{image : ‘=’} qui spécifie que la valeur de scope.image sera égale à la valeur du modèle que l’on passe à l’attribut image dans la directive <image-slider image="modele"> (en l’occurrence « modele » sera projet.imagesSet).

    On veut également mettre en place une fonction qui permet d’afficher l’image dans une fenêtre modale en cliquant dessus (« open » qui est définit dans le contrôleur). Mais l’utilisation d’un ng-repeat dans le template est problématique car si l’on essaye d’initialiser le plugin directement dans la fonction link de cette directive cela ne fonctionne pas. Il semble que l’exécution du NgRepeat ne soit pas terminée au moment où l’on appelle la fonction link.
    L’idée est donc d’appeler une autre directive « checkLast » sur chaque <li> qui vérifie s’il s’agit du dernier élément de la boucle et si c’est le cas d’initialiser le composant.

            .directive('checkLast', function() {
        return function(scope, element, attrs) {
            if (scope.$last === true) { // s’il s’agit du dernier
    <li> du ng-repeat
                element.ready(function() { // on initialise le composant
                    var bx = $('.bxslider').bxSlider({
                        mode: 'fade', // les options du composant Jquery
                        pager: true
                    });
                })
            }
        }
    })
            }
        }
    });

    Au niveau du DOM on peut alors déclarer le composant et choisir sa source d’images. De plus il est réutilisable :
    <image-slide image="projet.imagesSet"></image-slide>

    Nous aborderons en détail l’appel au services REST, et comment on intègre la gestion du LocalStorage pour booster les performances et fonctionner en mode déconnecté !
    A suivre…