Une introduction en profondeur à Ember.js

Source: An In-Depth Introduction To Ember.js de Julien Knebel pour Smashing

Maintenant que Ember.js 1.0 est sorti, il est temps d’y jeter un coup d’oeil. Cet article s’adresse aux débutants qui souhaitent comprendre ce framework.

Il est fréquent d’entendre les utilisateurs dire que la courbe d’apprentissage est raide mais qu’une fois les difficultés surmontées, Ember.js est tout simplement phénoménal. Ça a été également le cas pour moi. Bien que les guides officiels soient extrêment précis et parfaitement à jour (vraiment !), cet article a pour but de rendre les choses encore plus aisées pour les débutants.

Tout d’abord, nous allons éclaircir les principaux concepts du framework. Nous verrons ensuite, étape par étape, comment construire une application avec Ember.js et Ember-Data, la couche de stockage de données d’Ember. Nous verrons ensuite comment les views et les components aident à gérer les interactions utilisateurs.

Une introduction en profondeur à Ember.js
La fameuse mascotte d’Ember, Tomster. (Crédits)

La démo non stylisée ci-dessous, vous aidera à suivre chaque étape de ce tutoriel. La démo stylisée est essentiellement la même mais avec bien plus de CSS, d’animations et de réactivité sur petits écrans.

Démo non stylisée Code source Démo stylisée

Sommaire

Principaux concepts

Le schéma ci-dessous montre comment les routes, les contrôleurs, les vues, les templates et les modèles interagissent les uns avec les autres.

ember-sketch

Voici une description de chacun des concepts. Pour en apprendre plus, référez-vous à la section correspondante dans les guides officiels :

Modèles

Mettons que notre application gère une liste d’utilisateurs. Ces utilisateurs et leurs informations seraient le modèle. Vous pouvez voir cela comme les données stockées en base de données. Les modèles peuvent être récupérés et mis à jour en implémentant des callbacks AJAX dans vos routes ou vous pouvez utiliser Ember-Data (une couche d’abstraction de stockage de donnés) pour simplifier la récupération, la mise à jour et la persistence des modèles au travers d’une API REST.

Le Routeur

Il y a le Router et, ensuite, les routes. Le Router est juste un synopsis de toutes vos routes. Les routes sont la version URL des objects de votre application (par exemple, une route posts correspond à un listing d’utilisateurs). Le but des routes est d’appeler le modèle, via leur hook model, pour qu’il soit accessible dans les contrôleurs et templates. Les routes peuvent également servir à valuer les propriétés d’un contrôleur, à exécuter des événements ou des actions, ou encore connecter un template à un contrôleur spécifique. De plus, le hook model peut retourner une promise ce qui permet d’implémenter une LoadingRoute qui attend que le modèle soit récupéré de façon asynchrone.

Contrôleurs

Un controller commence par récupérer le modèle d’une route. Il fait ensuite le pont entre le modèle et la vue ou le template. Mettons que vous ayez besoin d’une fonction pour alterner entre le mode édition et le mode normal. Des méthodes comme goIntoEditMode() et closeEditMode() seraient parfaites et c’est exactement ce à quoi servent les contrôleurs.

Ember.js génère automatiquement les contrôleurs si vous ne les déclarez pas. Vous pouvez par exemple créer un template user et une UserRoute sans créer de UserController (parce que vous n’en avez pas besoin), Ember.js le créera pour vous en interne (en mémoire). L’extension Chrome appelée Ember Inspector peut vous aider à trouver ces contrôleurs magiques.

Vues

Les vues représentent les différentes parties de votre application (les parties visibles par l’utilisateur dans le navigateur). Une View est associée à un Controller, un template Handlebars et une Route. La différence entre vue et template est particulière. Vous utiliserez une vue lorsque vous voudrez gérer des événements ou des interactions utilisateurs qui ne peuvent pas être pris en charge par un simple template. Elle ont un hook bien pratique appelé didInsertElement au travers duquel vous pouvez appeler jQuery très facilement et sont également très utiles pour créer des vues réutilisables comme une modal, une popover, un date-picker ou encore un champ auto-complété.

Components

Un Component est une View complètement isolée, qui n’a pas accès au context dans lequel il est appelé. C’est un excellent moyen de créer un composant réutilisable pour votre application. Ce Button Twitter, ce select personnalisé ou encore ces graphiques réutilisables sont de très bons exemples de composants. Ce sont en fait de si bonnes idées que le W3C travaille actuellement avec l’équipe Ember sur la spécification d’éléments personnalisés.

Templates

Pour faire simple, un template est la partie HTML d’une vue. Il permet d’afficher les données du modèle et se met automatiquement à jour lorsque ce dernier change. Ember.js utilise Handlebars, un mécanisme léger de templating également maintenu par l’équipe Ember. Il fournit les outils logiques habituels comme if et else, les boucles et les helpers de formatage, ce genre de choses. Les templates peuvent être précompilés (si vous souhaitez les organiser en fichiers .hbs ou .handlebars séparés) ou tout simplement écrits dans une balise <script type="text/x-handlebars"></script> dans votre page HTML. Pour en savoir plus sur le sujet, vous pouvez vous reporter à la section Précompiler ou non les templates.

Helpers

Les helpers Handlebars sont des fonctions qui modifient les données avant leur affichage (par exemple, pour donner un meilleur format que Mon Jul 29 2013 13:37:39 GMT+0200 (CEST) à une date). Si votre date est écrite sous la forme {{date}} dans votre template et que vous avez un helper formatDate (qui converti une date en quelque chose de plus élégant, comme “Il y a un mois” ou “29 juillet 2013”), vous pouvez vous en servir en utilisant {{formatDate date}}.

Composants ? Helpers ? Vues ? Au secours !

Le forum Ember.js a une réponse, tout comme StackOverflow, qui peuvent vous éviter les maux de crâne.

Créons une application

Dans cette section, nous allons créer une véritable application, une simple interface de gestion d’utilisateurs (une application de CRUD). Voici ce que nous allons faire :

  • un tour de l’architecture que nous souhaitons mettre en place ;
  • voir les dépendances, la structure de fichiers, etc ;
  • mettre en place le modèle avec le FixtureAdapter d’Ember-Data ;
  • voir comment les routes, contrôleurs, vues et templates interagissent ;
  • et enfin, remplacer FixtureAdapter par LSAdapter pour stocker les données dans le local storage du navigateur.

Schéma de notre application

Nous avons besoin d’une vue assez simple qui affiche un groupe d’utilisateurs (voir 1 ci-dessous). Il nous faut également une vue pour voir les informations d’un utilisateur spécifique (2). Nous devons être capables de modifier et supprimer ces informations (3). Nous devons enfin être à même de créer un nouvel utilisateur, pour ce faire nous réutiliserons le formulaire de modification.

app-sketch

Ember.js utilise beaucoup les conventions de nommage. Si vous voulez avoir la page /foo dans votre application, vous aurez ce qui suit :

  • un template foo ;
  • une route FooRoute ;
  • un contrôleur FooController ;
  • une vue FooView.

Pour en savoir plus, référez-vous à la section Naming conventions dans les guides.

Ce qu’il vous faut pour bien commencer

Vous aurez besoin de :

  • jQuery ;
  • Ember.js, bien sûr ;
  • Handlebars le moteur de template d’Ember ;
  • Ember-Data, la couche d’abstraction de stockage d’Ember.
/* /index.html
*/
 <script src="//code.jquery.com/jquery-2.0.3.min.js"></script>
 <script src="//builds.emberjs.com/handlebars-1.0.0.js"></script>
 <script src="//builds.emberjs.com/tags/v1.1.2/ember.js"></script>
 <script src="//builds.emberjs.com/tags/v1.0.0-beta.3/ember-data.js"></script>
 <script>
   // votre code
 </script>
</body>
</html>

Le site d’Ember a un section Builds dans laquelle vous pouvez trouver tous les liens vers Ember.js et Ember-Data. Pour le moment, Handlebars n’est pas présent sur la page, vous le trouverez sur le site officiel de Handlebars.

Une fois les dépendances récupérées, nous pouvons commencer à créer notre application. Nous allons tout d’abord créer un fichier nommé app.js dans lequel nous allons initialiser Ember :

/* /app.js
*/
window.App = Ember.Application.create();

Juste pour vérifier que tout fonctionne, vous devriez voir les logs de debug dans la console du navigateur.

console-log-1

Organisation de nos fichiers

Il n’y a pas vraiment de convention en ce qui concerne l’organisation des fichiers et dossiers. L’App Kit Ember (un environnement de démarrage d’application Ember basé sur Grunt) propose une sorte de standard puisqu’il est maintenu par l’équipe Ember. Pour faire encore plus simple, vous pourriez tout mettre dans le fichier app.js. C’est à vous de voir.

Pour ce tutoriel, nous mettrons les contrôleurs dans un dossier controllers, nos vues dans un dossier views et ainsi de suite.

components/
controllers/
helpers/
models/
routes/
templates/
views/
app.js
router.js
store.js

Précompiler ou non les templates ?

Il y a deux façons de déclarer les templates. La plus simple est d’ajouter une balise script spéciale dans votre fichier index.html.

<script type="text/x-handlebars" id="templatename">
  <div>Je suis un template</div>
</script>

Pour chaque template, il vous faut une balise script. C’est simple et rapide mais ça peut très vite devenir un gros désordre si vous avez beaucoup de templates.

L’alternative est de créer un fichier .hbs (ou .handlebars) pour chaque template. C’est ce qu’on appelle la “précompilation de templates” et une section entière de cet article y est dédiée.

Notre démo non stylisée utilise des balises <script type="text/x-handlebars"> et tous les templates de notre démo améliorée sont stockés dans des fichiers .hbs qui sont précompilés par une tâche Grunt. Vous pouvez ainsi comparer les deux techniques.

Créer notre modèle avec le FixtureAdapter de Ember-Data

Ember-Data est une bibliothèque qui permet de récupérer les données stockées sur le serveur, de les retenir dans un Store, de les mettre à jour dans le navigateur et enfin des les renvoyer au serveur pour sauvegarde. Le Store peut être configuré avec différents adapters (par exemple, le RESTAdapter qui interagit avec une API JSON ou le LSAdapter qui stocke les données dans le local storage du navigateur). Une section entière de cet article est dédiée à Ember-Data.

Nous allons utiliser FixtureAdapter. Nous commençons donc par l’instancier :

/* /store.js
*/
App.ApplicationAdapter = DS.FixtureAdapter;

Dans les versions précédentes d’Ember, il fallait hériter de DS.Store. Ce n’est plus nécessaire pour instancier les adapters.

FixtureAdapter est un très bon moyen de démarrer avec Ember.js et Ember-Data. Il vous permet de travailler avec des données en développement. Nous passerons au LocalStorage adapter (ou LSAdapter) en fin de parcours.

Commençons par définir notre modèle. Un utilisateur aura un nom name, une adresse email, une courte bio, un avatar avatarUrl et une date de création creationDate.

/* /models/user.js
*/
App.User = DS.Model.extend({
  name         : DS.attr(),
  email        : DS.attr(),
  bio          : DS.attr(),
  avatarUrl    : DS.attr(),
  creationDate : DS.attr()
});

Ajoutons ensuite quelques données d’exemple dans notre Store. Vous pouvez ajouter autant d’utilisateurs que vous le souhaitez :

/* /models/user.js
*/
App.User.FIXTURES = [{
  id: 1,
  name: 'Sponge Bob',
  email: 'bob@sponge.com',
  bio: 'Lorem ispum dolor sit amet in voluptate fugiat nulla pariatur.',
  avatarUrl: 'http://jkneb.github.io/ember-crud/assets/images/avatars/sb.jpg',
  creationDate: 'Mon, 26 Aug 2013 20:23:43 GMT'
}, {
  id: 2,
  name: 'John David',
  email: 'john@david.com',
  bio: 'Lorem ispum dolor sit amet in voluptate fugiat nulla pariatur.',
  avatarUrl: 'http://jkneb.github.io/ember-crud/assets/images/avatars/jk.jpg',
  creationDate: 'Fri, 07 Aug 2013 10:10:10 GMT'
}

];

Pour en savoir plus sur les modèles, consultez la documentation.

Instancier le Router

Définissons notre Router avec les routes dont nous avons besoin (basées sur le diagramme vu précédemment).

/* /router.js
*/
App.Router.map(function(){
  this.resource('users', function(){
    this.resource('user', { path:'/:user_id' }, function(){
      this.route('edit');
    });
    this.route('create');
  });
});

Le Router va générer les routes suivantes :

URL Route Name Controller Route Template
N/A N/A ApplicationController ApplicationRoute application
/ index IndexController IndexRoute index
N/A users UsersController UsersRoute users
/users users.index UsersIndexController UsersIndexRoute users/index
N/A user UserController UserRoute user
/users/:user_id user.index UserIndexController UserIndexRoute user/index
/users/:user_id/edit user.edit UserEditController UserEditRoute user/edit
/users/create users.create UsersCreateController UsersCreateRoute users/create

La partie :user_id est appelée segment dynamique. L’ID de l’utilisateur sera injecté dans l’URL à cet emplacement. Cela aura donc la forme /users/3/edit, 3 représentant l’utilisateur d’ID 3.

Vous pouvez définir soit une route, soit une resource. Une resource est un groupe de routes et permet d’imbriquer d’autres routes.

Une resource réinitialise également la convention de nommage de la ressource précédente. Cela signifie qu’au lieu d’avoir UsersUserEditRoute, nous aurons UserEditRoute. En d’autres termes, si vous avez une ressource imbriquée dans une autre ressource, les noms de nos fichiers seraient :

  • UserEditRoute au lieu de UsersUserEditRoute ;
  • UserEditControler au lieu de UsersUserEditController ;
  • UserEditView au lieu de UsersUserEditView ;
  • pour les templates, user/edit au lieu de users/user/edit.

Vous pouvez en apprendre plus sur les routes dans les guides.

Le template de l’application

Chaque application Ember.js nécéssite un template Application contenant une balise {{outlet}} qui permet de contenir tous les autres templates.

/* /templates/application.hbs
*/
<div class="main">
  <h1>Hello World</h1>
  {{outlet}}
</div>

Si vous suivez ce tutorial sans précompiler vos templates, voici ce à quoi devrait ressembler votre index.html :

/* /index.html
*/
  <script type="text/x-handlebars" id="application">
    <div class="main">
      <h1>Hello World</h1>
      {{outlet}}
    </div>
  </script>

  <script src="dependencies.js"></script>
  <script src="your-app.js"></script>
</body>
</html>

La route users

Cette route est liée à notre liste d’utilisateurs. Comme nous l’avons vu précédemment dans les définitions, une route est chargée d’appeler le modèle. Les routes ont un hook model au travers duquel nous pouvons effectuer une requête AJAX (pour récupérer les données lorsque l’on n’utilise pas Ember-Data) ou faire appel au Store (si l’on utilise Ember-Data). Si vous souhaitez savoir comment récupérer les données sans Ember-Data, vous pouvez consulter la section dans laquelle j’explique brièvement comment le faire.

Créons maintenant notre UsersRoute :

/* /routes/usersRoute.js
*/
App.UsersRoute = Ember.Route.extend({
  model: function(){
    return this.store.find('user');
  }
});

Vous pouvez en savoir plus sur comment utiliser le hook model des routes dans les guides.

Si vous visitez votre application à l’URL http://localhost/#/users, rien ne se produit, nous avons d’abord besoin du template users. Le voici :

/* /templates/users.hbs
*/
<ul class="users-listing">
  {{#each user in controller}}
    <li>{{user.name}}</li>
  {{else}}
    <li>pas d'utilisateurs… :-(</li>
  {{/each}}
</ul>

La boucle each parcourt la collection d’utilisateurs, controller vaut ici UsersController. Notez que la boucle {{#each}} contient un {{else}} de façon à ce que, lorsque le modèle est vide, pas d'utilisateurs… :-( soit affiché.

Comme nous suivons la convention de nommage d’Ember, nous pouvons nous passer de déclarer UsersController. Ember devine que l’on gère une collection car nous avons utilisé la forme plurielle de “user”.

ObjectController vs. ArrayController

Un ObjectController est lié à un seul objet et un ArrayController est lié à un groupe d’objets (comme une collection). Comme nous venons de le voir, nous n’avons pas besoin de déclarer de ArrayController. Juste pour cet article, nous allons cependant le déclarer pour lui attribuer quelques propriétés :

/* /controllers/usersController.js
*/
App.UsersController = Ember.ArrayController.extend({
  sortProperties: ['name'],
  sortAscending: true // false = descending
});

Nous avons simplement trié nos utilisateurs par ordre alphabétique. Consultez les guides pour en apprendre plus sur les contrôleurs.

Afficher le nombre d’utilisateurs

Utilisons UsersController pour créer notre première propriété calculée (computed property). Celle-ci affichera le nombre d’utilisateurs pour que nous puissions voir un changement lors de l’ajout ou de la suppression d’un utilisateur.

Dans le template, il nous suffit d’utiliser ceci :

/* /templates/users.hbs
*/
<div>Utilisateurs: {{usersCount}}</div>

Déclarons ensuite la propriété usersCount dans UsersController à la différence que ce n’est pas une propriété classique puisqu’elle dépend de la longueur du modèle.

/* /controllers/usersController.js
*/
App.UsersController = Em.ArrayController.extend({
  
  usersCount: function(){
    return this.get('model.length');
  }.property('@each')
});

Pour faire simple, usersCount utilise la méthode .property('@each') qui indique à Ember.js que cette fonction est une propriété qui observe tout changement sur l’un des modèles de la collection (ici les utilisateurs). Nous verrons ensuite usersCount s’incrémenter ou se décrémenter chaque fois que nous ajouterons ou supprimerons un utilisateur.

Propriétés calculées

Les propriétés calculées sont puissantes. Elles permettent de déclarer des fonctions en tant que propriétés. Voyons comment elles fonctionnent.

App.Person = Ember.Object.extend({
  firstName: null,
  lastName: null,

  fullName: function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  }.property('firstName', 'lastName')
});

var ironMan = App.Person.create({
  firstName: "Tony",
  lastName:  "Stark"
});

ironMan.get('fullName') // "Tony Stark"

Dans l’exemple ci-dessus, l’objet Person a deux propriétés statiques, firsName et lastName. Il a également une propriétés calculées fullName qui concatène le prénom et le nom pour créer un nom complet. Notez que l’appel à .property('firsName', 'lastName') indique que la fonction est de nouveau exécutée lorsque firsName ou lastName change.

Les propriétés (statiques ou calculées) sont récupérables grâce à la méthode .get('property') et peuvent être assignées via .set('property', newValue).

Si vous avez besoin d’assigner plusieurs propriétés, il y a mieux que de le faire une par une. Vous pouvez utiliser .setProperties({}) plutôt que plusieurs appels à .set(). Au lieu de ceci :

this.set('propertyA', 'valueA');
this.set('propertyB', valueB);
this.set('propertyC', 0);
this.set('propertyD', false);

Utilisez plutôt cela :

this.setProperties({
  'propertyA': 'valueA',
  'propertyB': valueB,
  'propertyC': 0,
  'propertyD': false
});

La documentation contient beaucoup d’informations sur comment lier les données grâce aux propriétés calculées, aux observers et aux bindings.

Rediriger depuis la page Index

Si vous visitez la page d’accueil de votre application (http://localhost/), rien ne se passe. Cela est dû au fait que nous consultons la page index et que nous n’avons pas de template index, nous allons donc en ajouter un que nous appellerons index.hbs.

Ember.js sait que vous créez un template index pour la route IndexRoute, vous n’avez donc rien à indiquer dans le Router pour que tout fonctionne. C’est ce que l’on appelle une route initiale. Il en existe trois : ApplicationRoute, IndexRoute et LoadingRoute. Consultez les guides pour en savoir plus.

Ajoutons un lien vers la page des utilisateurs avec le block helper {{#link-to}}…{{/link-to}}. Pourquoi un block helper ? Parce que vous pouvez écrire du texte entre les balises comme s’il s’agissait de balises HTML.

/* /templates/index.hbs
*/
{{#link-to "users"}} Go to the users page {{/link-to}}

Le premier argument est le nom de la route vers laquelle pointe le lien. Le deuxième argument, optionnel, est un modèle. Le résultat est un <a> classique mais Ember gère automatiquement la classe active en fonction de la route active. link-to est parfait pour une menu par exemple. Vous pouvez en apprendre plus dans les guides.

Une autre approche pourrait être de dire à IndexRoute de rediriger vers UsersRoute. Encore une fois, c’est assez simple :

/* /routes/indexRoute.js
*/
App.IndexRoute = Ember.Route.extend({
  redirect: function(){
    this.transitionTo('users');
  }
});

Lorsque l’on visite la page d’accueil, on est immédiatement redirigé vers /#/users.

Route d’un utilisateur spécifique

Avant de gérer les segments dynamiques, nous devons créer un lien vers chaque utilisateur depuis le template users. Utilisons le block helper {{#link-to}} dans une boucle each qui parcourt les utilisateurs.

/* /templates/users.hbs
*/
{{#each user in controller}}
  <li>
    {{#link-to "user" user}}
      {{user.name}}
    {{/link-to}}
  </li>
{{/each}}

Le deuxième argument passé à link-to est le modèle à passer à UserRoute.

Occupons-nous maintenant du template d’un utilisateur spécifique. Il ressemble à ceci :

/* /templates/user.hbs
*/
<div class="user-profile">
  <img {{bind-attr src="avatarUrl"}} alt="Avatar de l'utilisateur" />
  <h2>{{name}}</h2>
  <span>{{email}}</span>
  <p>{{bio}}</p>
  <span>Création {{creationDate}}</span>
</div>

Notez que vous ne pouvez pas utiliser <img src="{{avatarUrl}}">, les données attachées à un attribut doivent utiliser le helper bind-attr. Vous pouvez par exemple écrire <img {{bind-attr height="imgHeight"}}> avec imgHeight qui serait une propriété calculée dans le contrôleur.

Vous trouverez tout ce dont vous avez besoin sur les attributs et les classes HTML dans les guides.

Jusque là, tout va bien mais rien ne se passe lorsque l’on clique sur le lien d’un utilisateur. Cela est dû au fait que nous avons dit au Router que nous voulions imbriquer UserRoute dans UsersRoute. Nous avons donc besoin d’un {{outlet}} dans lequel afficher le template d’un utilisateur.

/* /templates/users.hbs
*/
{{#each user in controller}}
{{/each}}

{{outlet}}

{{outlet}} est une sorte d’espace réservé dans lequel les autres templates peuvent être injectés lorsque l’on clique sur un {{#link-to}}. Cela permet d’imbriquer les vues.

Vous devriez maintenant voir le template d’un utilisateur s’afficher dans la page lorsque vous visitez l’URL /#/users/1.

Attendez une minute ! Nous n’avons déclaré ni UserRoute ni UserController et pourtant tout fonctionne ! Comment est-ce possible ? UserRoute est la version singulière de UsersRoute, Ember génère donc la route et le contrôleur pour nous (en mémoire). Merci aux conventions de nommage !

Pour une question de consistance, nous allons les déclrarer, juste pour voir à quoi il ressemblent :

/* /routes/userRoute.js
*/
App.UserRoute = Ember.Route.extend({
  model: function(params) {
    return this.store.find('user', params.user_id);
  }
});

/* /controllers/userController.js
*/
App.UserController = Ember.ObjectController.extend();

Pour en apprendre plus sur les segments dynamiques, rendez-vous dans les guides.

Modifier un utilisateur

Passons maintenant au formulaire de modification d’un utilisateur, imbriqué dans la page de ce dernier. Le template ressemble à ceci :

/* /templates/user/edit.hbs
*/
<div class="user-edit">
  <label>Choisissez votre avatar</label>
  {{input value=avatarUrl}}

  <label>Nom de l'utilisateur</label>
  {{input value=name}}

  <label>Email de l'utilisateur</label>
  {{input value=email}}

  <label>Bio de l'utilisateur</label>
  {{textarea value=bio}}
</div>

Arrêtons-nous une minute sur ces balises {{input}}. Le but de ce formulaire est de permettre la modification des données d’un utilisateur et ces balises input prennent en paramètre les propriétés du modèle pour s’y attacher.

Notez bien que l’on écrit value=model, sans les " ". Le helper {{input}} est un raccourcis pour {{Ember.TextField}}. Ember.js propose plusieurs vues prédéfinies, en particulier pour les éléments de formulaires.

Si vous visitez l’URL /#/users/1/edit de votre application, rien ne se passe. Nous avons de nouveau besoin d’un {{outlet}} pour imbriquer le template du formulaire dans celui de l’utilisateur.

/* /templates/user.hbs
*/
{{outlet}}

Le template est maintenant injecté dans la page mais les champs sont toujours vides. Nous devons dire à la route quel modèle utiliser.

/* /routes/userEditRoute.js
*/
App.UserEditRoute = Ember.Route.extend({
  model: function(){
    return this.modelFor('user');
  }
});

La méthode modelFor vous permet d’utiliser le modèle d’une autre route. Nous avons indiqué à UserEditRoute d’utiliser le modèle de UserRoute. Les champs sont maintenant remplis correctement avec les données du modèle. Essayez de les modifier, vous pourrez voir les modifications reportées en direct dans le template parent !

Notre première action

Ok, nous avons besoin d’un bouton sur lequel cliquer pour être redirigé de UserRoute vers UserEditRoute.

/* /templates/user.hbs
*/
<div class="user-profile">
  <button {{action "edit"}}>Modifier</button>

Nous venons d’ajouter un simple button qui lance notre première {{action}}. Les actions sont des événements qui lancent les méthodes associées dans le contrôleur courant. Si aucune méthode n’est trouvée, l’action remonte (bubble) les routes jusqu’à trouver quelque chose. Ce mécanisme est très bien expliqué dans les guides.

En d’autres termes, si nous cliquons (click) sur le button, il va lancer l’action edit qui se trouve dans le contrôleur. Ajoutons-la à UserController :

/* /controllers/userController.js
*/
App.UserController = Ember.ObjectController.extend({
  actions: {
    edit: function(){
      this.transitionToRoute('user.edit');
    }
  }
});

Les actions, que ce soit dans un contrôleur ou une route, sont stockées dans le hash actions. Ce n’est cependant pas le cas des actions par défaut comme click, doubleClick, mouseLeave ou dragStart. Le site d’Ember.js contient la liste complète de ces actions.

Notre action edit dit simplement “Va sur la route user.edit”. C’est à peu près tout.

TransitionTo ou TransitionToRoute ?

Notez que la transition depuis une route est différente de la transition depuis un contrôleur :

// depuis une route
this.transitionTo('your.route')
// depuis un contrôleur
this.transitionToRoute('your.route')

Sauvegarder les modifications apportées à l’utilisateur

Voyons maintenant comment sauvegarder nos modifications après avoir changé la valeur des données de l’utilisateur. Par sauvegarder, j’entends rendre persistant. Avec Ember-Data, cela signifie d’appeler save() sur le Store et de sauvegarder le nouveau record correspondant à l’utilisateur. Le Store va ensuite dire à l’adapter d’effectuer une requête AJAX PUT (si vous utilisez RESTAdapter).

Dans notre application, il s’agit d’un button “OK” qui sauvegarde les modifications et retourne à la route parente. Nous allons à nouveau utiliser une {{action}}.

/* /templates/user/edit.hbs
*/
<button {{action "save"}}> ok </button>
/* /controllers/userEditController.js
*/
App.UserEditController = Ember.ObjectController.extend({
  actions: {
    save: function(){
      var user = this.get('model');
      // cela indique à Ember-Data de sauvegarder le nouvel enregistrement
      user.save();
      // puis transite vers l'utilisateur courant
      this.transitionToRoute('user', user);
    }
  }
});

Notre mode “modification” fonctionne bien. Passons maintenant à la suppression d’un utilisateur.

Supprimer un utilisateur

Nous pouvons ajouter un button “Supprimer” à côté du bouton “Modifier” dans le template d’un utilisateur. Cette fois nous aurons une {{action}} delete définie dans UserController.

/* /templates/user.hbs
*/
<button {{action "delete"}}>Supprimer</button>
/* /controllers/userController.js
*/

actions: {
  delete: function(){
    // ceci indique à Ember-Data de supprimer l'utilisateur courant
    this.get('model').deleteRecord();
    this.get('model').save();
    // puis transite vers la route users
    this.transitionToRoute('users');
  }
}

Lorsque l’on clique sur le bouton “Supprimer”, l’utilisateur est directement supprimé. Un peu direct ; un message de confirmation comme “Êtes-vous sûr ?” avec des boutons “Oui” et “Non” seraient bienvenus. Pour ce faire, nous devons modifier notre {{action "delete"}} pour afficher confirm-box plutôt que de supprimer immédiatement l’utilisateur. Et, bien sûr, nous devons mettre confirm-box dans le template user.

/* /templates/user.hbs
*/
{{#if deleteMode}}
<div class="confirm-box">
  <h4>Sûr ?</h4>
  <button {{action "confirmDelete"}}> oui </button>
  <button {{action "cancelDelete"}}> non </button>
</div>
{{/if}}

Nous venons d’écrire notre premier {{if}} avec Handlebars. Il n’écrit div.confirm-box que lorsque la propriété deleteMode est à true. Nous devons définir deleteMode dans le contrôleur et modifier l’action delete pour qu’il passe deleteMode à true ou false. Notre UserController ressemble maintenant à ceci :

/* /controllers/userController.js
*/
App.UserController = Ember.ObjectController.extend({
  // la propriété deleteMode est à false par défaut
  deleteMode: false,

  actions: {
    delete: function(){
      // notre méthode delete change uniquement la valeur de deleteMode
      this.toggleProperty('deleteMode');
    },
    cancelDelete: function(){
      // remet deleteMode à false
      this.set('deleteMode', false);
    },
    confirmDelete: function(){
      // ceci indique à Ember-Data de supprimer l'utilisateur courant
      this.get('model').deleteRecord();
      this.get('model').save();
      // puis transite vers la route users
      this.transitionToRoute('users');
      // et remet deleteMode à false
      this.set('deleteMode', false);
    },
    // le méthode edit reste la même
    edit: function(){
      this.transitionToRoute('user.edit');
    }
  }
});

La suppression fonctionne maintenant comme il faut avec les boutons “Oui” et “Non”. Génial ! Il ne reste plus que la route de création à écrire.

Créer un utilisateur

Pour la création de l’utilisateur, essayons quelque chose de fun : réutilisons le template edit. Au final, le formulaire est exactement le même que celui de modification d’un utilisateur. Commençons par déclarer la route qui va retourner un objet vide dans son hook model :

/* /routes/usersCreateRoute.js
*/
App.UsersCreateRoute = Ember.Route.extend({
  model: function(){
    // le modèle de cette route est un nouvel Ember.Object vide
    return Em.Object.create({});
  },

  // dans le cas présent (la route create), nous pouvons réutiliser le template
  // user/edit associé avec usersCreateController
  renderTemplate: function(){
    this.render('user.edit', {
      controller: 'usersCreate'
    });
  }
});

La méthode renderTemplate nous permet d’associer un template spécifique à une route. Nous indiquons à UsersCreateRoute d’utiliser le template user.edit avec UsersCreateController. Vous pouvez en apprendre plus sur renderTemplate dans les guides.

Définissons maintenant une autre action save, mais dans UsersCreateController cette fois (souvenez-vous qu’une action va d’abord chercher une méthode correspondante dans le contrôleur courant).

/* /controllers/usersCreateController.js
*/
App.UsersCreateController = Ember.ObjectController.extend({
  actions: {
    save: function(){
      // nous donnons une date de création juste avant la sauvegarde
      this.get('model').set('creationDate', new Date());

      // crée un nouvel enregistrement et le sauvegarde dans le Store
      var newUser = this.store.createRecord('user', this.get('model'));
      newUser.save();

      // redirige vers l'utilisateur lui-même
      this.transitionToRoute('user', newUser);
    }
  }
});

Ajoutons maintenant un {{#link-to}} dans le template users pour pouvoir accéder au formulaire de création :

/* /templates/users.hbs
*/
{{#link-to "users.create" class="create-btn"}} Ajouter un utilisateur {{/link-to}}

C’est tout ce dont nous avons besoin pour créer des utilisateurs !

Formater les données avec les helpers

Nous avons déjà vu ce que sont les helpers. Voyons maintenant comment en créer un qui nous permette de formater une date toute moche en quelque chose de plus propre. La bibliothèque Moment.js est exactement ce dont nous avons besoin.

Récupérez Moment.js et chargez le dans la page. Nous allons ensuite définir notre premier helper :

/* /helpers/helpers.js
*/
Ember.Handlebars.helper('formatDate', function(date){
  return moment(date).fromNow();
});

Modifions le template user pour qu’il fasse appel au helper formatDate sur la propriété {{creationDate}} :

/* /templates/user.hbs
*/
<span>Création {{formatDate creationDate}}</span>

C’est tout ! Les dates devraient s’afficher sous la forme “2 days ago” (il y a deux jours), “One month ago” (il y a un mois), etc.

NDT : Moment.js permet d’utiliser d’autres langues que l’anglais, tout est expliqué dans la documentation.

Formater les données avec un BoundHelper

Dans le cas précédent, la date est fixe et ne risque pas de changer. Si nous avons par contre des données qui doivent être mises à jour (par exemple un prix formaté), il faut utiliser un BoundHelper au lien d’un helper classique.

/* /helpers/helpers.js
*/
Ember.Handlebars.registerBoundHelper('formatDate', function(date){
  return moment(date).fromNow();
});

Un BoundHelper sait se mettre à jour automatiquement lorsque les données changent. Vous pouvez en apprendre plus sur le sujet dans les guides.

Passer au LocalStorage Adapter

Notre application fonctionne bien, nous sommes donc prêts à passer aux choses sérieuses. Nous pourrions utiliser le RESTAdapter mais nous aurions du coup besoin d’un server REST sur lequel effectuer des requêtes GET, PUT, POST et DELETE. Nous allons plutôt utiliser le LSAdapter, un adapter externe que vous pouvez télécharger sur GitHub. Ajoutez-le dans votre page (juste après Ember-Data), commentez toutes les données FIXTURE et changez ApplicationAdapter pour DS.LSAdapter :

/* /store.js
*/
App.ApplicationAdapter = DS.LSAdapter;

Les données de vos utilisateurs seront maintenant stockées dans le local storage (stockage local du navigateur). C’est tout ! Sérieusement, c’est aussi simple que ça. Pour vous en assurer, ouvrez les Developer Tools dans votre navigateur et rendez-vous dans le panneau “Ressource”. Dans l’onglet “Local Storage” vous devriez trouver une entrée pour LSAdapter avec toutes les données de vos utilisateurs.

console-localstorage

Jouer avec les vues

Jusque là, nous n’avons déclaré aucune vue dans notre application, seulement des templates. Pourquoi nous soucier des vues ? Et bien, elles sont très puissantes pour gérer des événements, des animations ou des composants réutilisables.

jQuery et didInsertElement

Que devons-nous faire pour utiliser jQuery comme d’habitude avec les vues Ember.js ? Chaque vue ou composant a un hook didInsertElement qui nous indique que la vue a effectivement été chargée dans le DOM. Cela vous assure un accès aux éléments de la page depuis jQuery.

App.MyAwesomeComponent = Em.Component.extend({
  didInsertElement: function(){
    // this = la vue
    // this.$() = $(la vue)
    this.$().on('click', '.child .elem', function(){
      // quelque chose utilisant jQuery
    });
  }
});

Si vous avez des événements du style jQuery enregistrés dans didInsertElement, vous pouvez utiliser willDestroyElement pour les retirer après la suppression d’une vue dans le DOM, comme ceci :

App.MyAwesomeComponent = Em.Component.extend({
  didInsertElement: function(){
    this.$().on('click', '.child .elem', function(){
      // quelque chose utilisant jQuery
    });
  },
  willDestroyElement: function(){
    this.$().off('click');
  }
});

Panneaux latéraux avec className dynamique

La combinaison des propriétés calculées et de classes (className) dynamiques peut sembler une technique un peu folle mais ce n’est pas si terrible en réalité. L’idée est simplement d’ajouter ou de retirer une classe CSS sur un élément en fonction d’une propriété qui peut être true ou false. La classe CSS contient bien sûr une transition CSS.

Mettons que nous avons une div cachée dans le DOM. Lorsque cette div a la classe opened, elle s’affiche en glissant. Lorsqu’elle a la classe closed, elle glisse de nouveau pour se cacher. Un panneau latéral est l’exemple parfait, nous allons donc en écrire un.

Voici un JS Bin pour que vous puissiez tester le code :

Panneaux latérales réutilisables avec Ember.js - Smashing

Voici le détail de chaque onglet :

  • JavaScript
    Nous commençons par déclarer notre SidePanelComponent avec des classNames par défaut. Nous utilisons ensuite classNameBindings pour déterminer si isOpen est à true ou false de façon à retourner opened ou closed en fonction. Notre component a une action toggleSidepanel qui passe isOpen à true ou false.
  • HTML
    Les balises du panneau latéral. Vous remarquerez le bloc {{#side-panel}}…{{/side-panel}}, nous pouvons placer n’importe quoi dedans ce qui rend notre panneau latéral extrêmement puissant et réutilisable. Le bouton btn-toggle appelle l’action toggleSidepanel située dans le composant. Le {{#if isOpen}} ajoute un peu de logique en vérifiant la valeur de la propriété isOpen.
  • CSS
    Ici le but principal est de masquer le panneau latéral. La classe opened le fait glisser en position ouverte et la classe closed le fait glisser dans l’autre sens. L’animation est rendue possible parce que nous utilisons translate2D (transition:transform .3s ease).

Les guides contiennent de nombreux exemples sur comment lier des classes dans les composants ou dans les templates.

Modals avec layout et remontée d’événements

Cette technique est bien plus compliquée que la précédente. Elle implique bien plus de fonctionnalités d’Ember.js. L’idée est de faire remonter un événement d’une vue jusqu’à la route pour changer une propriété située dans un contrôleur quelque part dans l’application. Nous allons utiliser une View plutôt qu’un Component (pour mémoire, un composant est simplement une vue isolée).

Modals réutilisables avec Ember.js - Smashing

  • JavaScript
    modalView est le layout par défaut pour toutes nos modals. Elle contient deux méthodes, showModal et hideModal. La méthode showModal est appelée par une action qui remonte, en passant par le contrôleur puis par les routes, jusqu’à trouver l’action showModal correspondante. Nous avons placé showModal dans la route la plus haute possible, applicationRoute. Son seul but est de valuer la propriété modalVisible dans le contrôleur passé en second argument de l’action. Et oui, créer une propriété en même temps qu’on lui donne sa valeur est possible.
  • HTML
    Chaque modal a son propre template et nous utilisons le bloc {{#view App.ModalView}}…{{/view}} pour les encapsuler dans modal_layout. Les contrôleurs liés aux modals ne sont même pas déclarés, Ember.js les a en mémoire. Notez que le helper {{render}} accepte des arguments : le nom du template et le contrôleur généré pour ce template. Nous appelons par exemple le template modal01 et le contrôleur modal01 (auto-généré).
  • CSS
    Pour cet exemple, les modals doivent être présentes dans le DOM. Cela peut sembler contraignant mais réduit le coût d’affichage. Sans cela, Ember.js doit les injecter et les supprimer à chaque appel. Le second avantage concerne les transitions CSS. La classe shown applique deux transition : tout d’abord, la position verticale (la modal étant en dehors de l’écran par défaut), puis, après un court délai, l’opacité (ce qui réduit encore le coût d’affichage durant la transition).

Vous trouverez bien d’autres informations sur les événements, la remontée d’événements, les layouts et le helper {{render}} dans les guides.

Qu’est-ce qu’Ember-Data

Ember-Data est en beta au moment où j’écris ces lignes, faites donc attention si vous décidez de l’utiliser.

C’est une bibliothèque qui permet de récupérer les données stockées sur le serveur, de les retenir dans un Store, de les mettre à jour dans le navigateur et enfin des les renvoyer au serveur pour sauvegarde. Le Store peut être configuré avec différents adapters en fonction de votre back-end. Voici un schéma de l’architecture d’Ember-data.

ember-data-sketch

Le store

Le Store retient les informations chargées depuis le serveur (les enregistrements). Les routes et contrôleurs peuvent effectuer des requêtes sur le Store pour récupérer des enregistrements (records). Lorsqu’un enregistrement est appelé pour la première fois, le Store demande à l’adapter de le charger au travers du réseau. Le Store le garde ensuite en cache pour les prochains appels.

Les adapters

L’application effectue des requêtes sur le Store et l’adapter effectue des requêtes sur le back-end. Chaque adapter est fait pour un back-end particulier. On trouve par exemple le RESTAdapter qui permet de communiquer avec un API JSON et le LSAdapter qui permet d’utiliser le local storage du navigateur.

L’idée derrière Ember-Data est de pouvoir changer de back-end en changeant simplement l’adapter sans changer le code de votre application.

  • FixtureAdapter
    Le FixtureAdapter est parfait pour tester Ember et Ember-Data. Les fixtures sont des données d’exemple avec lesquelles vous pouvez travailler jusqu’à ce que votre application soit prête pour la production. Nous avons vu plus tôt dans cet article comment le configurer.
  • RESTAdapter
    Le RESTAdapter est l’adapter par défaut dans Ember-Data. Il permet d’effectuer des requêtes GET, PUT, POST et DELETE sur une API REST. Il repose sur un certain nombre de conventions JSON spécifiques. Utiliser cet adapter se fait comme ceci :

    App.ApplicationAdapter = DS.RESTAdapter.extend({ host: 'https://your.api.com' });

    Il y a bien plus à découvrir sur le RESTAdapter dans les guides.

  • Adapter personnalisé
    Vous pouvez utiliser un autre adapter que les deux par défaut (FixtureAdapter et RESTAdapter). On en trouve bon nombre sur Github. Il y a, par exemple, l’adapter LocalStorage dont on peut trouver une démo dans la Todo d’exemple des guides. Je l’utilise également dans la démo.

Sans utiliser Ember-Data

Dans cet article, j’ai choisi de parler d’Ember-Data parce qu’il est presque prêt et que c’est un des trucs les plus cool qui ont lieu dans le monde JavaScript en ce moment. Vous vous demandez peut être s’il est possible de s’en passer. La réponse est oui ! En fait, utiliser Ember.js sans Ember-Data est assez facile.

Il y a deux façons de le faire.

Vous pouvez utiliser d’autres bibliothèques pour prendre en charge la récupération et la persistance de vos modèles. Ember-Model, Ember-Resource, Ember-Restless et, plus récemment, EPF sont de bonnes alternatives. EmberWatch a rédigé un petit articles qui liste les “Alternatives à Ember-Data”.

Une autre façon de faire pourrait être de ne pas utiliser de bibliothèque. Dans ce cas, vous devez implémenter les méthodes de récupération des modèles via requêtes AJAX. “Ember Without Ember-Data”, par Robin Ward (le mec derrière Discourse), est une lecture intéressante. “Getting Into Ember.js, Part 3”, par Rey Bango sur Nettuts+ traite en particulier des modèles.

Voici par exemple comment définir une méthode statique sur un modèle en utilisant reopenClass :

/* /models/user.js
*/
// our own findStuff method inside the User model
App.User.reopenClass({
  findStuff: function(){
    // utilise une requête AJAX / Promises classique
    return $.getJSON("http://your.api.com/api").then(function(response) {
      var users = [];
      // crée de nouveaux Ember Objects et les stocke dans le tableau users
      response.users.forEach(function(user){
        users.push( App.User.create(user) );
      });
      // retourne le tableau plein d'Ember Objects
      return users;
    });
  }
});

Vous pouvez ensuite utiliser la méthode findStuff dans le hook model de nos routes :

/* /routes/usersRoute.js
*/
App.UsersRoute = Em.Route.extend({
  model: function(){
    return App.User.findStuff();
  }
});

Qu’est-ce que la précompilation de templates Handlebars ?

Pour faire simple, précompiler les templates veut dire prendre tous les templates et les transposer en chaines de caractères JavaScript puis les stocker dans Ember.TEMPLATES. Cela veut également dire qu’il y a un fichier en plus, contenant la version compilée de tous vos templates Handlebars, à charger dans votre page.

Pour une application assez simple, la précompilation peut être évitée. Si vous avez cependant trop de templates <script type="text/x-handlebars"> dans votre principal fichier HTML, la précompilation vous permettra de mieux organiser votre code.

De plus, précompiler vos templates vous permet d’utiliser la version runtime de Handlebars qui est plus légère que la version classique. Vous pouvez trouver les deux versions (standard et runtime) sur le site de Handlebars.

Conventions de nommage des templates

Les partials doivent commencer par un _. Vous devez donc déclarer un fichier _yourpartial.hbs ou, si vous ne précompilez pas vos templates, une balise <script type="text/x-handlebars" id="_yourpartial">.

Les composants doivent commencer par components/. Vous devez donc les stocker dans un dossier components/ ou, si vous ne précompilez pas vos templates, une balise <script type="text/x-handlebars" id="components/votre-composant">. Vous devez utiliser un tiret comme séparateur dans le nom des composants.

Vous pouvez cependant utiliser une propriété templateName dans les vues pour spécifier quel template associer avec une vue. Voici une déclaration de template :

<script type="text/x-handlebars" id="folder/some-template">
  Un template
</script>

Que vous pouvez associer à une vue particulière :

App.SomeView = Em.View.extend({
  templateName: 'folder/some-template'
});

Précompiler avec Grunt

Si vous utilisez Grunt, vous vous en servez probablement pour d’autres tâches liées à la construction (concatenation, compression, ce genre de choses). Dans ce cas, vous devez connaitre le fichier package.json qui vient avec Node.js et les modules Node. Je vais considérer que vous connaissez déjà Grunt.

Au moment où j’écris ceci, deux plugins Grunt sont disponibles pour transposer vos fichiers .hbs en fichier templates.js : grunt-ember-handlebars et grunt-ember-templates. Le deuxième semble un peu plus à jour que le premier.

J’ai écris un Gist pour chacun d’eux, pour vous aider avec la configuration :

Une fois configurés, vous devriez être à même de lancer grunt en ligne de commande et cela devrait produire le fichier templates.js. Chargez-le dans index.html (après ember.js) puis rendez-vous dans la console du navigateur et tapez Em.TEMPLATES. Vous devriez voir un hash contenant tous les templates compilés.

Notez qu’Ember.js n’a pas besoin du chemin complet vers un template ni l’extension du fichier. En d’autres termes, le nom du template devrait être users/create et non /assets/js/templates/users/create.hbs.

Les deux plugins fournissent des options pour gérer cela. Référez-vous aux guides respectifs ou jetez un oeil aux Gists ci-dessus. Vous devriez obtenir quelque chose dans ce genre :

console-templates

Exactement ce qu’il nous faut pour que tout marche correctement. C’est tout ce dont vous avez besoin pour précompiler avec Grunt.

Précompiler avec Rails

Précompiler avec Rails est la façon la plus simple de faire. La gem Ember-Rails se charge d’à peu près tout. Il fonctionne presque out-of-the-box. Suivez attentivement les instructions d’installation du readme sur GitHub et tout devrait bien se passer. Selon moi, Rails a la meilleure intégration Ember/Handlebars pour le moment.

Outils, astuces et ressources

L’Extension Chrome Ember

L’Extension Ember pour Chrome est très pratique. Une fois installée, un onglet “Ember” apparait près de l’onglet “Console”. Vous pouvez ensuite naviguer à travers vos contrôleurs, routes et vues. L’onglet “Data” vous permettra d’explorer vos enregistrements très simplement si vous utilisez Ember-Data.

console-ember-extension
Exploring your app’s objects has never been so easy.

Ember App Kit

Le Ember App Kit, maintenu par l’équipe Ember, vous permet de créer très rapidement une application Ember. Il contient Grunt pour compiler les assets, le lanceur de tests Kharma, Bower et le support des modules ES6.

Ember Tools

Le projet GitHub Ember Tools est un outils en ligne de commande pour créer des applications Ember. Prenez une minute pour regarder le GIF animé dans le readme et vous comprendrez pourquoi c’est si cool.

Développement et version minifié

Utilisez toujours le development build durant le développement, il contient beaucoup de commentaires, de tests unitaires et un tas de messages d’erreur utiles qui ont été supprimés dans la version minifié. Vous trouverez un lien vers chaque version sur le site d’Ember.js.

Astuces pour le debug

Ember.js fournit généralement des erreur humainement lisibles dans la console du navigateur (si vous utilisez bien la version de développement). Il peut être cependant difficile de deviner d’où vient l’erreur. Quelques méthodes bien pratiques sont {{log something}} et {{controller}} qui affiche le controller courant pour le template dans lequel nous appelons le helper.

Ou vous pouvez afficher chaque transition du Router comme ceci :

window.App = Ember.Application.create({
  LOG_TRANSITIONS: true
});

Les guides contiennet une liste exhaustive de ces petites methodes bien pratiques.

Commenter correctement dans Handlebars

Celui-là peut être frustrant. Ne commentez jamais une balise Handlebars avec un commentaire HTML classique. Si vous le faites, vous risquez de complètement casser l’application sans même savoir pourquoi.

// ne faites jamais ça
<!-- {{foo}} -->

// faites plutôt ça
{{!foo}}

Conclusion

J’espère que ce long article vous a permis de mieux comprendre cet excellent framework. Mais pour tout vous dire, on a à peine vu la partie émergée de l’iceberg. Il y a tellement plus à voir. Il y a par exemple le Router et sa nature asynchrone qui permet de gérer les modèles avec des promises (ce qui permet de créer très facilement un spinner de chargement). Il y a également le modèle objet, avec son héritage de classes ou d’instances, ou encore les mixins, observers, filtres, macros, collectionViews et composants, ou encore la gestion de dépendances entre contrôleurs et le paquet pour les tests. Et bien plus encore !

Je ne pouvais bien sûr pas vous parler de tout ça. Heureusement, les guides vous aideront sur tous ces sujets.

Happy Ember.js coding, folks!

Ressources

Remerciements

Un immense merci à Mathieu Breton et Philippe Castelli qui m’ont tous deux transmis tout ce qu’ils savaient sur Ember.js durant mon apprentissage. Et un grand merci à Tom Dale, qui m’a aidé à la relecture de ce bien long article.

Commentaires