Skip to content
This repository has been archived by the owner on Mar 7, 2023. It is now read-only.

Standards Ember.js

Jérémy Buget edited this page Mar 22, 2017 · 9 revisions

Structure d'un composant

// 1. déclaration des imports
import Ember from 'ember';

// 2. déclaration des constantes (en capitales)
const HELLO = 'Hello ';

// 3. définition de fonctions utilitaires (au sein de la classe, préfixée par un '_' pour expliciter le fait que c'est une méthode privée non exposée)
function _getMessage(name) {
  return `${HELLO} ${name}!`;
}

// 4. déclaration du composant (directement dans l'export pour ne pas être tenté de réouvrir la classe)
export default Ember.Component.extend({

  // 5. injection de dépendances
  // on s'autorise à utiliser la convention Ember pour ne pas nommer inutilement la dépendance
  session: Ember.inject.service(),

  // 6. définition des attributs du composants, ex : classNames, tagName, etc.
  classNames: ['my-component'],
  tagName: 'section',

  // 7. déclaration des propriétés : on explicite toutes les variables, même si on n'est en théorie pas obligé de les déclarer si leur valeur initiale est "null", comme ici
  name: null,

  // 8. déclaration des computed properties
  message: Ember.computed('name', () => return _sayHello(this.get('name'))),

  // 9. Ember hooks
  init() {
    this.set('name', 'world');
  },

  // 10. actions
  actions: {
    printMessage() {
      console.log(this.get('message'));
    }
  }

});

Utiliser les propriétés classNames et tagName

Nous préconisons l'usage de la propriété Ember.Component#classNames qui permet d'assigner des classes CSS à l'élément HTML généré par Ember. Ce faisant, nous n'avons plus besoin de wrapper le composant dans un <div class="...">. De fait, le nombre d'éléments <div> s'en trouve réduit, ce qui est sain en terme de performance et de référencement (les pages bien structurées et performantes sont privilégiée par les moteurs d'indexation).

Par ailleurs, quand l'élément HTML correspondant au composant ne doit pas être un div (ex : quand il s'agit d'un élément header ou article), nous préconisons d'utiliser la propriété Ember.Component#tagName, qui remplace le type de l'élément HTML généré par Ember.

// app/components/feature-item.js
export default Ember.Component.extend({
  classNames: ['feature-item'],
  tagName: 'article'
});

// app/templates/feature-item.hbs
<div class="feature-item__icon-container">
  <img class="feature-item__icon" src="{{rootURL}}images/icon-{{feature.icon}}.svg">
</div>
<div class="feature-item__title">{{feature.title}}</div>
<div class="feature-item__description">{{feature.description}}</div>
<!-- HTML généré -->
<article id="ember597" class="feature-item ember-view">
  <div class="feature-item__icon-container">
    <img src="images/icon-cafe.svg" class="feature-item__icon">
  </div>
  <div class="feature-item__title">Vivez l’expérience PIX</div>
  <div class="feature-item__description">Un parcours d’évaluation convivial, accessible et interactif.</div>
</article>

Éviter les Positional params

Dans Ember.js, il existe une fonctionnalité positional params, qui permet de ne pas avoir à déclarer la propriété assignée dans le template du composant parent.

Si cette fonctionnalité peut paraître pratique a priori, dans la réalité nous avons rencontré plusieurs fois des problèmes, notamment pour les tests d'intégration. Depuis nous avons convenu de ne plus l'utiliser.

// app/templates/feature-item.hbs
<div class="feature-item__icon-container">
  <img class="feature-item__icon" src="{{rootURL}}images/icon-{{feature.icon}}.svg">
</div>
<div class="feature-item__title">{{feature.title}}</div>
<div class="feature-item__description">{{feature.description}}</div>

/* ------------ */
/* --- Good --- */
/* ------------ */

// app/components/feature-item.js
export default Ember.Component.extend({
  ...
});

// app/templates/feature-list.hbs
{{#each features as |feature|}}
  {{feature-item feature=feature}}
{{/each}}

/* ----------- */
/* --- Bad --- */
/* ----------- */

const FeatureItem = Ember.Component.extend({
  ...
});

FeatureItem.reopenClass({
  positionalParams: ['feature']
});

export default FeatureItem;

// app/templates/feature-list.hbs
{{#each features as |feature|}}
  {{feature-item feature}}
{{/each}}

Constantes VS. Ember.Component#init()

2 cas existent :

  1. la variable est une propriété du composant avec une affectation directe
  2. la variable n'est qu'un moyen / outil pour calculer un résultat ou une computed property

Dans le cas #1, la valeur doit être directement assignée dans la fonction init() :

// app/components/feature-list.js
import Ember from 'ember';

export default Ember.Component.extend({

  classNames: ['feature-list'],

  init() {
    this._super(...arguments);
    this.features = [{
      icon: 'cafe',
      title: 'Vivez l’expérience PIX',
      description: 'Un parcours d’évaluation convivial, accessible et interactif.'
    }, {
      icon: 'monde',
      title: 'PIX est pour tout le monde',
      description: 'Collégiens, lycéens, étudiants, professionnels, citoyens…'
    }, {
      icon: 'reference',
      title: 'PIX est la référence',
      description: 'La certification nationale de la culture numérique made in France au standard européen.'
    }, {
      icon: 'gratuit',
      title: 'PIX est gratuit',
      description: 'Entraînez-vous et progressez gratuitement à votre rythme avant d’être certifié.'
    }];
  }

});

Dans le cas #2, la valeur est assignée à une constante, en dehors de la classe-composant, et n'est donc pas exposée publiquement (via le export) :

// app/components/result-item.js
import Ember from 'ember';

// Cette valeur n'est pas une propriété du composant
const contentReference = {
  ok: {
    title: 'Réponse correcte',
    color: '#30d5b0'
  },
  ko: {
    title: 'Réponse incorrecte',
    color: '#ff4600'
  },
  aband: {
    title: 'Sans réponse',
    color: '#3e4149'
  },
  partially: {
    title: 'Réponse partielle',
    color: '#ffffff',
    custom: true
  },
  timedout: {
    title: 'Temps dépassé',
    color: '#ff0000'
  },
  default: {
    title: 'Correction automatique en cours de développement ;)',
    color: '#446eff'
  }
};

// Cette valeur non plus n'est pas une propriété du composant
const timeOutAfterRender = 1000;

export default Ember.Component.extend({

  didRender() {
    this._super(...arguments);
    Ember.run.debounce(this, function () {
      $('[data-toggle="tooltip"]').tooltip();
    }, timeOutAfterRender);
  },

  resultItemContent: Ember.computed('answer.result', function () {
    if (!this.get('answer.result')) return;
    return contentReference[this.get('answer.result')] || contentReference['default'];
  }),

  ...

});