Tech

Comment profiter de Font Awesome 5 dans les applications Ext JS 6 ?

Objectif

Dans cet article nous allons vous montrer comment à Jnesis nous intégrons Font Awesome 5 aux application Ext JS 6.

Qu’est ce que Font Awesome 5 ?

Font Awesome 5 a été publié en Décembre 2017 et offre de nombreuses évolutions par rapport à la version 4. Pour n’en citer que quelques unes :

  • De nouvelles icônes
  • 4 jeux de caractères : solid (fas), regular (far), light (fal) et brand (fab) au lieu d’un seul (fa)
  • Toujours une version GPL mais aussi une version PRO payante avec les icônes « light » (fal) et parfois les icônes regular (far)
  • Toujours la possibilité de contrôler le style des caractères avec CSS mais aussi maintenant avec SVG

Comment Ext JS intègre t-il Font Awesome ?

Le thème utilisé a son importance. Il peut s’agir d’un thème utilisant de façon native le package Font Awesome intégré à Ext JS (packages/font-awesome) comme c’est le cas du thème par défaut Triton, cf theme-triton/package.json :

"requires": [
        "font-awesome",
        "font-ext"
],

Il peut aussi au contraire s’agir d’un thème où Font Awesome est optionnel comme le thème Neptune. Dans ce cas le requires se fait dans app.json :

"requires": [
        "font-awesome"
],

Le package Ext JS font-awesome utilise Font Awesome 4.7, il va donc falloir adapter nos applications pour utiliser Font Awesome 5.

Pour cet article, notre application de test consistera en une application basique nommée MyApp générée via un simple :

sencha generate app

Il s’agira d’une application universelle (universal) sur la base du thème Ext JS 6 par défaut : triton.

Elle va comprendre l’icône fa-user de Font Awesome 4.7 et l’icône fa-glasses apportée par Font Awesome 5.

Nous utiliserons l’icône fa-user dans ses 4 versions, la version Font Awesome 4 et les 3 versions Font Awesome 5 : solid, regular et light.

Lorsque l’on utilise uniquement Font Awesome 4.7, voilà ce que l’on obtient :

On remarque que les préfixes fas, far et fal sont « ignorés » par Font Awesome 4.7 et que fa-glasses est inconnu.

Passer à Font Awesome 5 – Solution rapide

Téléchargez le fichier font-awesome-531.zip ici

Dézippez l’ensemble dans un répertoire packages/local/font-awesome-531 au niveau de votre application ou de votre workspace.

Modifiez le fichier app.json de votre application pour utiliser font-awesome-531 au lieu de font-awesome.

"requires": [
        "font-awesome-531"
],

Rebuildez, et voilà le résultat !

Avec le thème Triton, la solution permet toujours d’utiliser les icônes de Font Awesome 4.

Le caractère fal fa-user (PRO) sera inconnu mais c’est le caractère du jeu Font Awesome 4 qui va être utilisé.

Attention tout de même, si l’on avait utilisé le thème Neptune, la solution ne permettrait plus d’utiliser les icônes de Font Awesome 4.

Le caractère fal fa-user (PRO) sera inconnu et rien ne va le remplacer.

Version payante (PRO) de Font Awesome 5

Sans l’avoir nous même essayée, la procédure pour intégrer également les icônes PRO devrait être très similaire. Il suffirait vraisemblablement d’ajouter les fichiers complémentaires au répertoire resources/font (EOT, TTF, WOFF, SVG et WOFF2). Voir la solution détaillée en bas de page.

Notes et limitations

  • N’utilisez plus le préfixe de police x-fa
  • Seul le thème Triton vous permettra encore d’utiliser également les icônes Font Awesome 4
  • Les glyphes Font Awesome 5.3.1 devront utilisés les familles de Font suivantes :
    • ‘Font Awesome Free’
    • ‘Font Awesome Brands’
    • [Probablement quelque chose comme ‘Font Awesome Pro’] pour les icônes de la version payante

Exemple :

{
    title: 'glyph alipay xf642@\'Font Awesome 5 Brands\'',
    glyph:'xf642@\'Font Awesome 5 Brands\'',
    bind: {
        html: '{loremIpsum}'
    }
}

Que contient notre package?

Si vous ne souhaitez pas utiliser le package que nous avons conçu parce que :

  • vous voulez essayer par vous-même
  • ou simplement comprendre en détail comment notre solution fonctionne

voici la procédure à suivre en détails…

La procédure consiste à ajouter Font Awesome 5.3.1 au workspace ou à l’application sous forme d’un nouveau package que nous l’appellerons font-awesome-531.

Ce nouveau package vient REMPLACER la référence à l’ancien package nommé font-awesome. Pour autant, dans le cas du thème triton, nous ferons en sorte que notre nouveau package intègre les version 5.3.1 ET 4.7, les caractères des 2 versions resteront utilisables pour le thème triton.

  • Selon que votre application fasse partie d’un workspace ou non, générez le package font-awesome-531 au niveau du workspace OU de votre application
sencha generate package font-awesome-531
  • Copiez le fichier ext/packages/font-awesome/sass/etc/all.scss vers font-awesome-531/sass/etc (il s’agira normalement plutôt de remplacer un fichier vide)
  • Copiez le fichier ext/packages/font-awesome/sass/src/all.scss vers font-awesome-531/sass/src
  • Créez un répertoire fonts dans packages/local/font-awesome-531/resources
  • Copiez les fichiers ext/packages/font-awesome/ressources/fonts/ vers font-awesome-531/resources/fonts pour également ajouter les icônes Font Awesome 4 dans votre nouveau package.
  • Téléchargez Font Awesome 5.3.1 ici
  • Copiez tous les fichiers depuis fontawesome-free-5.3.1-web/webfonts vers packages/local/font-awesome-531/resources/fonts (EOT, TTF, WOFF, SVG et WOFF2)
  • Copier les fichiers sass depuis fontawesome-free-5.3.1-web/scss vers font-awesome-531/sass/etc
  • Modifiez le fichier font-awesome-531/sass/src/all.scss comme suit :
// All rule-generating imports go here vs in etc/all.scss so that they will
// cascade after the framework rules.  This ensures that where the framework provides
// a default icon, but also provides an iconCls to override it (for example tree icons)
// the fa rules will override the framework ones (assuming specificity of fa rules is
// the same or greater than the framework rules)
@import "../etc/regular.scss";
@import "../etc/solid.scss";
@import "../etc/brands.scss";
@import "../etc/_variables";
@import "../etc/_core.scss";
@import "../etc/_larger.scss";
@import "../etc/_fixed-width.scss";
@import "../etc/_list.scss";
@import "../etc/_bordered-pulled.scss";
@import "../etc/_animated.scss";
@import "../etc/_rotated-flipped.scss";
@import "../etc/_stacked.scss";
@import "../etc/_icons.scss";
 
// The "x-fa" class should be used in Ext JS applications instead
// of "fa".  The "fa" class sets some properties that may collide
// with theme properties, whereas "x-fa" sets only the font-family.
.#{$prefix}fa:before {
    font-family: 'FontAwesome' !important;
}
 
.far {
    font-family: 'Font Awesome 5 Free' !important;
    font-weight: 400;
}
.fas {
    font-family: 'Font Awesome 5 Free' !important;
    font-weight: 900;
}
.fab {
    font-family: 'Font Awesome 5 Brands' !important;
}
//Surcharge de fa-content
@function fa-content($fa-var) {
  @return "#{ $fa-var }";
}
  • Modifiez le fichier package.json du nouveau package comme suit en replaçant le bloc « sass » par ce qui suit (ceci ajoute également le bloc « resources » pour que font-awesome-531 soit ajouté au ressource du build) :

"sass" : {
    "namespace": "FontAwesome531",
    "etc": [
        "${package.dir}/sass/etc/all.scss"
     ],
    "var": [
        "${package.dir}/sass/var"
    ],
    "src": [
        "${package.dir}/sass/src",
        "${package.dir}/sass/src/all.scss"
    ]
},
"resources": [{
    "path": "${package.dir}/resources",
    "output": "shared"
}]
  • Il ne restera plus qu’à modifier le fichier app.json de votre application pour utiliser font-awesome-531 au lieu de font-awesome:
"requires": [
        "font-awesome-531"
],

Un bouton dans l’entête d’une colonne de grille Ext JS

Dans cet article, nous allons créer un bouton dans une entête de colonne d’action de grille Ext JS.

Le constat

Dans une logique d’affichage et de manipulation d’ensembles de données, un exemple d’interface utilisateur revient souvent: une grille d’affichage des données couplée à un formulaire d’ajout ou d’édition d’enregistrement.
Plusieurs possibilités s’offrent alors en terme d’expérience utilisateur. Par exemple, une barre d’outils (toolbar) au-dessus ou en-dessous de la grille:
Grille avec une barre d’outils contenant des boutons d’action
Grille avec une barre d'outils contenant des boutons d'action
Ce à quoi nous allons nous intéresser ici est une solution d’interface entièrement “intégrée” à la grille elle-même. Elle comprendra un bouton d’ajout dans l’entête d’une colonne d’action. Les cellules de cette colonne elles, contiendront les boutons d’édition et de suppression d’enregistrement.
Grille avec une colonne d'action comportant un bouton d'ajout dans son entête
Ceci permet d’obtenir un design plus épuré et actuel que la précédente interface.

Le problème

L’API Ext JS pour les colonnes des grilles permet d’ajouter facilement des composants à leur entête via la config items:

{ 
  xtype: 'grid', 
  columns: [{ 
    items: [{ 
      xtype: 'button', 
      iconCls: 'x-fa fa-plus', 
      ui: 'default-toolbar' 
    }] 
  }, ...] 
}

Le problème est que cette méthode ne fonctionnera pas pour les colonnes d’action xtype: 'actioncolumn'

En effet, la propriété items de ce type de colonne sert à passer les configurations des icônes affichées dans les cellules de ces colonnes.

{
xtype: 'grid',
  columns: [{
    xtype: 'actioncolumn',
      items: [{
        iconCls: 'x-fa fa-pencil' // icone dans les cellules de la colonne
      }, {
       iconCls: 'x-fa fa-trash'
    }]
  }, ...]
}

Les solutions

  • Utiliser une colonne de template (templatecolumn)

La première solution serait d’utiliser une templatecolumn. C’est un type de colonne permettant facilement d’afficher un template Ext JS au sein de ses cellules. Dans ce cas, il serait simple de créer les balises affichant les icônes d’actions via un template HTML. Le problème de la propriété itemsde l’actioncolumn ne se poserait alors plus :

{
  xtype: 'grid',
  columns: [{
    xtype: 'templatecolumn',
    items: [{
      xtype: 'button',
      iconCls: 'x-fa fa-plus',
      ui: 'default-toolbar'
    }],
    tpl: ''
  }]
}

Cependant, peuvent arriver des complications quand il s’agira de gérer séparément le clic sur chaque icône, leur activation/désactivation ou encore un tooltip qui leur est propre.
La colonne de type actioncolumn, via les fonctions isActionDisabled(), handler(), ou encore geTtip(), permet de faciliter ces interactions de manière dynamique. Pour arriver à cela, il faudrait recoder manuellement ces différents comportements pour une colonne de type templatecolumn

  • Créer un plugin pour la colonne d’action

La seconde solution, plus propre et modulaire, serait de créer un plugin à appliquer à n’importe quelle actioncolumn de notre application. Celui-ci permettrait d’ajouter une configuration de bouton à son entête.
On garderait alors toutes les fonctionnalités d’une actioncolumn standard en ayant le résultat final recherché.

  • Nous créons notre plugin

Une classe Ext JS qui hérite de Ext.AbstractPlugin:

Ext.define('MyApp.plugin.ActionColumnHeaderButtonPlugin', {
  extend: 'Ext.AbstractPlugin',
  alias: 'plugin.actioncolumnheaderbuttonplugin',
  actionColumnHeaderId: null,
  config: {
    buttonConfig: null
  }

L’alias nous permettra de facilement identifier le plugin que l’on souhaitera rajouter plus tard à notre colonne.
Nous lui passons une propriété permettant d’identifier le conteneur de notre bouton dans l’entête de la colonne cible.
Enfin, nous déclarons une configuration de bouton, qui sera passée lors de la déclaration du plugin sur notre colonne.

  • Nous l’initialisons

Comme pour tout plugin, en surchargeant la méthode abstraite init()

 

, on est en capacité de récupérer le composant sur lequel le plugin est posé et d’intéragir avec lui.
Dans cette fonction, nous allons manuellement affecter le composant sur lequel nous posons le plugin, au plugin lui-même.

this.setCmp(cmp);

Ensuite, nous allons écouter l’événement viewready de la grille contenant la colonne (la logique exécutée lors de cet événement sera présentée plus loin dans l’article).

var ownerGrid = cmp.getView().ownerGrid;
ownerGrid.on('viewready', this.onViewReady, this);

Nous créons un identifiant unique basé sur l’identifiant de la colonne elle-même.

this.actionColumnHeaderId = cmp.getId() + '-actioncolumnheaderCt';

Enfin, nous injectons dans le corps de l’entête de colonne, un bloc HTML ayant pourid l’identifiant créé plus haut, afin de facilement y avoir accès au sein des fonctions exposées par notre plugin.

cmp.setText('<div id="' + this.actionColumnHeaderId + '"></div>');

Le code complet de la méthode init() de notre plugin:

init: function (cmp) {
  this.setCmp(cmp);
  var ownerGrid = cmp.getView().ownerGrid;
  ownerGrid.on('viewready', this.onViewReady, this);
  this.actionColumnHeaderId = cmp.getId() + '-actioncolumnheaderCt';
  cmp.setText(' div id="' + this.actionColumnHeaderId + '"></div>'); }
  • Nous ajoutons notre bouton lorsque la grille est créée

Une fois que la grille est rendue, elle lance un événement viewready que nous écoutons dans notre plugin.
Au déclenchement de cet événement, nous allons créer notre bouton à partir de la configuration passée dans buttonConfig:

var button = Ext.create('Ext.button.Button', this.getButtonConfig());

Et nous injectons ce composant dans le conteneur inséré à l’initialisation du plugin:

button.render(view.el.down('#' + this.actionColumnHeaderId));

Le code complet de notre méthode onViewReady():

onViewReady: function (view) {
  var button = Ext.create('Ext.button.Button', this.getButtonConfig());
  button.render(view.el.down('#' + this.actionColumnHeaderId));
}
  • Affectons le plugin à notre colonne d’action
columns: [{
  xtype: 'actioncolumn',
  plugins: [{
    ptype: 'actioncolumnheaderbuttonplugin',
    buttonConfig: {
      iconCls: 'x-fa fa-plus',
      handler: 'onAddButtonClick'
    }
  }],
  items: [{
    iconCls: 'x-fa fa-pencil'
  }, {
    iconCls: 'x-fa fa-trash'
  }]
}]

De ce fait, nous pourrons avoir accès à l’ensemble des propriétés et fonctions disponibles pour les items de notre actioncolumn, tout en paramétrant un bouton dans l’entête de notre colonne.
Vous pouvez voir le code complet avec quelques ajustements, et son résultat sur en accédant à ce fiddle.

 

Personnalisation du plugin RowEditing dans ExtJs

Le constat

Parmi les plugins de l’écosystème Extjs, il en est un particulièrement pratique : le plugin rowediting.
Ce plugin étend les fonctionnalités des grilles en permettant l’édition de chacune de ses lignes. On évite ainsi le développement d’un formulaire de saisie, ce qui, d’un point de vue ergonomique et économique, peut être très intéressant.

Mais comment modifier l’ergonomie des boutons du plugin en remplaçant le texte des boutons par des icônes ?

En réalité, il n’existe pas de mécanisme dans la documentation du plugin permettant d’atteindre cet objectif. Il est nécessaire d’avoir recours à un override pour y parvenir.

La mise en oeuvre devrait donc suivre les étapes suivantes:

  • mise en place du store, de la grille et des différentes fonctionnalités (ajout d’éléments dans le store par exemple)
  • mise en place du plugin rowediting
  • customisation du plugin

Comme c’est souvent le cas dans le développement logiciel, tout ne s’est pas déroulé comme prévu.
Nous avons constaté:

  • un problème d’affichage des boutons du plugin.
  • dans le cas où la grille est vide, et que sont ajoutés des éléments manuellement dans son store, l’édition de certaines lignes pose problème, puisque les boutons sont en partie masqués par le header de la grille.

 

Notre solution

Après quelques recherches, nous ne sommes pas les seuls à avoir constaté ces problèmes. Un ticket de bug a été ouvert à ce sujet, mais aucune solution officielle n’est actuellement disponible. Une solution est toutefois proposée sur le forum Sencha à l’adresse suivante : https://www.sencha.com/forum/showthread.php?305665-RowEditing-Buttons-not-visible.

Cette solution, complétée par l’ajout d’un minHeight sur la grille règle notre problème.

La customisation des boutons demande:

  • une surcharge du constructeur de la classe Ext.grid.RowEditorButtons
  • l’ajout de propriétés dans la surcharge de la classe Ext.grid.RowEditor:

La propriété useButtonText  permet de signifier que l’on ne souhaite pas utiliser le texte et permet donc d’afficher les icones.
Les propriétés saveBtnIconCls et cancelBtnIconCls permettent de renseigner la propriété iconCls du bouton visé. On peut alors personnaliser ces boutons comme on le souhaite, comme indiqué dans l’exemple ci-dessous:

plugins: {
    rowediting: {
        clicksToMoveEditor: 1,
        autoCancel: false,
        useButtonText: false,
        saveBtnIconCls: 'fa fa-check green-color',
        cancelBtnIconCls: 'fa fa-times red-color'
    }
}

Et le rendu final des boutons:

 

Le code source est disponible ici.