experts java & javascript

Supports / rencontre ExtJs Paris du 28.03.2018

Comme promis, vous trouverez plus bas l’ensemble des supports des présentations techniques de cette première réunion co-organisée par Barnsten, distributeur officiel, et Jnesis, partenaire officiel de Sencha en France et en Suisse.

Merci encore à tou(te)s les participant(e)s de cette première rencontre de développeurs ExtJS à Paris.

Pour ceux qui veulent aller plus loin, notamment sur la dimension test front-end, automatisation et outillage du build, n’hésitez pas à nous contacter via contact@jnesis.com.

Nous espérons que cet évènement donnera l’envie à l’un ou l’autre d’entre vous d’organiser rapidement des meetups sur Paris afin de réunir de manière informelle la communauté en Ile-de-France et d’échanger autour d’une pizza (ou autre) sur les bonnes pratiques ExtJS et les dernières évolutions du framework.

N’hésitez pas à nous suivre sur notre twitter @jnesis_fr, la communauté Linkedin ExtJS France, et sur le présent blog..

Barnsten et Jnesis – réunion de développeurs ExtJs à Paris

L’événement

Barnsten et Jnesis organisent une réunion de développeurs ExtJs le 28 mars après-midi à Paris, autour de thématiques techniques et stratégiques.

Suite au rapprochement de Sencha et d’Idera, une nouvelle organisation a émergé dans l’écosystème Sencha. Jnesis reste le partenaire officiel Sencha pour la France et Barnsten, déjà en charge de la commercialisation des produits Idera / Embarcadero sera dorénavant votre interlocuteur pour l’acquisition de produits Sencha.

Le programme de la réunion

Le programme de la demie-journée, très orienté échange de développeur à développeur, est le suivant :

Les horaires

13:30 – 14:00 Café d’accueil
14:00 – 14:30 Introduction Barnsten, Roadmap Sencha et l’avenir.
14:30 – 17:00 Conférences 

L’environnement de développement idéal pour ExtJS
De quoi doit-être constitué le cycle de développement idéal d’une application ExtJs, en partant des outils de développement jusqu’au cycle de déploiement?

Comment se place ExtJS dans l’écosystème javascript actuel ?
ExtJs dans la constellation actuelle de technologies Javascript, et comment aborder la problématique du choix du bon framework en fonction des besoins

ExtJS et le flow-programming avec Node-Red
Le flow programming offre des perspectives très intéressantes en terme de vitesse de développement, de lisibilité du développement et des processus. Présentation du produit node-red issu de la recherche IBM en lien avec une application de gestion en ExtJS.

17:00 – 17:30 Questions
17:30 – 18:30 Apéritif

S’inscrire

Pour découvrir le programme et participer à cet événement 100% gratuit, 100% en français et 100% pratique, il suffit de vous inscrire avec le lien suivant :

https://www.eventbrite.nl/e/billets-creez-data-intensive-web-apps-avec-sencha-extjs-paris-en-francais-43694871537?elqTrackId=F81F8D465063D313F7D88CD83FA66D52&elq=84904e025de543ec93801f55a702b300&elqaid=25135&elqat=1&elqCampaignId=9186

Y aller

 

Et pour être tenu à jour des derniers événements, suivez-nous sur notre Twitter !

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.

Internationalisation et Localisation avec Sencha Ext JS

Le constat

La gestion du multilinguisme (où internationalisation) dans les applications est un problème fonctionnel récurrent, qui avec Ext JS est souvent traité lors de la phase de développement. Le cas de Ext JS 6 ne déroge pas à la règle et introduit par ailleurs de nouvelles problématiques.

Avant de proposer des solutions concrètes, revenons sur la problématique en elle-même.

Il s’agit aujourd’hui de permettre aux utilisateurs de choisir leur langue préférée lors de l’utilisation de leurs applications. Pour utiliser un terme plus technique, on parle souvent de « i18n » ou « internationalisation »: c’est à dire la capacité qu’à une application à être traduisible. Attention, cela ne se cantonne pas simplement à des compétences en traduction de libellés mais englobe des considérations et contraintes techniques, des besoins fonctionnel, des contraintes de temps, etc…

Bien que Sencha fournisse une solution « i18n » fonctionnelle, cette dernière reste perfectible, notamment parce qu’elle introduit certaines contraintes et s’avère mal adaptée aux prérogatives des différents acteurs du monde de l’entreprise. Par ailleurs, la documentation est sommaire et montre un système monolithique, laissant peu de place aux adaptations.

La voie officielle Ext

Vous pouvez retrouver plus en détails la solution officielle, avec ses avantages et ses inconvénients, dans un poste de Saki sur son propre blog : http://extjs.eu/localization-of-ext-applications/. Ce qu’il est important de retenir à ce stade, c’est que cette solution :

  • demande l’écriture de fichiers de langues (singleton ou override) directement dans l’application pour qu’ils puissent ensuite être incorporés au « build » de production,
  • va générer, grâce à l’outil Sencha Cmd, autant de « build » de production de la même application que de langues définies dans la configuration,
  • nécessite de recharger entièrement l’application à chaque changement de langue,
  • ne permet pas facilement de charger des ressources supplémentaires avant le démarrage de l’application,
  • n’autorise pas plusieurs langues au même moment, sur des parties différentes de l’application

 

Externalisation des sources de langues

La problématique de l’internationalisation a de multiples facettes et les entreprises ne traitent pas forcément chacune d’elles. Avant de choisir une solution, il est important de faire le point, dès la conception, sur les véritables besoins de l’entreprise vis-à-vis de cette problématique. Vous pourrez retrouver un topo complet sur la question dans le slideshow de Vincent Munier, Technical Manager chez Jnesis, présenté lors des Sencha Days 2015 de Paris et Zurich.

Cependant, les entreprises d’une certaine taille arrivent généralement à la même conclusion : la gestion des langues est une problématique indépendante du framework de développement, qui met en jeu des profils et des workflow spécifiques de l’entreprise. Les traductions devraient se situer dans des fichiers externes à l’application ou récupérées au travers d’un ou plusieurs web-services.

Les principaux intérêts souvent mis en avant, sont que :

  • les modifications des sources de langues ne nécessitent pas de reconstruire l’application, elle sont prises en compte directement après le rechargement,
  • les personnes qui sont en charges de la traduction de l’application ne sont pas forcément les mêmes que celles qui sont en charge d’écrire le code.

Dans ces conditions, les compétences des uns ne sont donc pas forcément connues des autres et inversement. Si bien qu’il parait compliqué de demander aux traducteurs de venir directement modifier des fichiers JavaScript, même basiques. Dans l’idéal, les traductions sont mêmes assurées par un outil tier intégré ou non à l’applicatif.

Malheureusement, la solution officielle Ext JS ne permet pas nativement cette ouverture.

Les solutions que nous proposons vont nécessiter une adaptation de la manière dont le code Ext JS sera écrit. Il s’agit donc de bonnes pratiques à introduire dès le démarrage du projet, sous peine de devoir procéder à une longue phase de « refactoring ».

La solution élégante

Commençons par suivre les recommandations du guide de développement de Sencha ainsi que ce conseil Saki dans son article de blog : changer les données de traduction avant que notre vue principale soit construite.

Il s’agit là d’un procédé assez proche de ce que l’on pouvait faire avant la version 5 de Ext JS, lorsque l’on ajoutait des dépendances à des ressources spécifiques dans le fichier « index.html » de l’application.


Pour commencer, nous allons stocker les traductions sous forme de variables contenues dans une classe « singleton », comme ceci :

 

Cela va permettre d’y accéder depuis n’importe quel endroit de l’application. Comment ? Aussi simplement qu’en tapant Jnesis.Labels.button ou Jnesis.Labels.title.

L’intérêt de cette approche, en comparaison à l’utilisation « override », est de pouvoir centraliser l’ensemble des traductions dans un même fichier et de pouvoir mutualiser l’utilisation d’une même variable à différents endroits dans le code, comme indiqué plus haut.

L’idée maintenant est de venir surcharger ce fichier de langue par défaut (« Jnesis.Labels ») avec les traductions de la langue choisie.

Pour cela, deux cas sont envisagés :

  • récupérer des informations au travers de web-services :

Ou les données renvoyées par le serveur ressembleraient à ceci :

 

Il faudra alors parser les données JSON récupérées et surcharger notre singleton de traduction avec ces-dernières :

 

  • charger directement des fichiers de « override » Ext JS via la fonction Ext.Loader.loadScript():

 


Une fois le chargement de nos traductions mis en place, il faut maintenant affecter ses traductions aux différents labels, titres etc… de notre application. Pour cela, nous allons profiter des mécanismes d ‘héritage du framework et définir une nouvelle propriété (« localized » par exemple) sur le composant de base pour y gérer les attributs à traduire.

Cette propriété n’est qu’un simple objet JavaScript contenant des clés et des valeurs (rappelez-vous des Jnesis.Labels.button et Jnesis.Labels.title).

Et pour que la valorisation des variables de traductions ne se fasse pas au moment de la déclaration, mais uniquement lorsque l’objet « localized » est parcouru, nous allons passer ces clés sous forme de chaînes de caractères :

 

Il reste alors qu’à interpréter cette nouvelle propriété « localized » en réalisant une surcharge de la méthode « initComponent » de la classe de base « Ext.Component » et à évaluer les clés passées pour que cela puisse s’appliquer peu importe le composant Ext JS utilisé :


Grâce à cela, vous obtenez une solution qui fonctionne à n’importe qu’elle niveau de hiérarchie dans la déclaration de composant, aussi bien sur la configuration d’un conteneur que sur celle de ses items, et cela de tacon transparente.

Pour aller plus loin

La solution proposée fonctionne avec les versions 5 et 6 (classique) du framework Ext JS. Elle pourrait encore être améliorée sur certains points, vous trouverez ci-dessous les adaptations complémentaires possibles déjà réalisées par le passé par Jnesis :

  • Déporter les fonctions de chargement et leurs traitements associés dans une classe spécifique de type « Ext.mixin.Mashup » qui permet de s’assurer que l’ensemble de ces traitements sont réalisés avant le chargement en mémoire de la classe sur laquelle la « mixin » est posée.
  • Assurée une compatibilité avec la version moderne de Ext JS 6, la fonction « initComponent » n’existant pas, il faudrait regarder du coté de la propriété « config » et de la génération de méthode « apply » associée.
  • Charger et interpréter les variables de traduction de façon dynamique, sans avoir à recharger l’intégralité de l’application.

Le code source est disponible ici.