DevExtreme, la suite à adopter de toute urgence pour développer des web apps (2/2)

Lors d’un nouveau projet de développement logiciel sur client léger, il est fréquent de recourir à des librairies d’éléments complets, évolutifs et personnalisables afin d’accroître la rentabilité en accélérant le temps en recherche et développement. 

Nous ne sommes jamais à l’abri de choisir un outil qui ne sera pas pérenne face aux évolutions et qui se fera oublier s’il n’évolue pas au rythme des exigences métiers. Je vous propose de découvrir DevExtreme, une suite de composants JavaScript et HTML5, plusieurs fois élue meilleure suite de composants multiplateformes. 

Pour aller plus loin

Je vous propose d’aller plus dans le détail dans la configuration du composant « Datagrid » en vous présentant l’utilisation d’une « Datasource » en lien direct avec son API, et plus précisément de l’objet typé « CustomStore » configurant les actions CRUD pour une communication automatique avec sa base de données. Pour l’exercice, nous ajouterons un moteur de base de données ainsi qu’un micro-framework pour les actions API avec les packages npm respectifs sqlite3 et express. Reprenons notre modèle de données précédant « ExampleData » et modifions très légèrement la vue HTML de l’exemple de la « Datagrid » ci-dessus.

Création de la base de données et développement de l'API

Dans cet exemple, nous ne détaillons pas tous les aspects de la création d’une base de données (BDD) et de son interface de programmation applicative (API). Le choix des technologies s’est porté autour d’une base de données SQLite et du Framework express afin de tout configurer simplement au travers d’un script JavaScript (js) pour Node.

Ainsi, en quelques lignes de scripts nous allons créer notre modèle de données ci-dessus « ExampleData » dans une base de données et implémenter les méthodes CRUD nous permettant par la suite de créer (Create), lire (Read), modifier (Update) et supprimer (Delete) les données de notre table « ExampleData ». Express mettra à disposition sur le port 3000 de notre localhost les méthodes d’actions CRUD développées.

Voici un aperçu succinct des méthode API en expressJS :

Let db = new sqlite3.Database('exampleData.db'); let restapi = express(); // Configure express server // And Creation DB with data [ … ] //#region API | CRUD Actions /** CREATE */ restapi.post("/add", (req, res) => { … }); /** READ */ restapi.get('/all', (req, res) => { … }); /** UPDATE */ restapi.put("/upd/:id", (req, res) => { … }); /** Delete */ restapi.delete("/del/:id", (req, res) => {   const itemId = req.params.id;   console.log("Delete item with id: ", itemId);   // Delete item   db.run('DELETE FROM exampleData WHERE id = $id', { $id: itemId }); }); //#endregion API | CRUD Actions

 

Configuration du "customstoreoptions"

Dans l’exemple ci-dessus de la Datagrid simple, nous avons pu constater combien il est facile de lier une source de données au composant au moyen de sa variable « DataSource ». Avec DevExtreme, il est tout aussi rapide de mettre en œuvre sa propre logique d’accès aux données au moyen du « CustomStore » et plus précisément du « CustomStoreOptions ». En effet, le composant « Datagrid » (ainsi que les autres fournis par la solution) peut prendre comme source de données une « DataSource » mais également un « CustomStore » … qui peuvent prendre tous deux comme paramètre optionnel un « CustomStoreOptions ». Nous définissons donc ces options de communication pour les mettre en lien avec notre API.

Déclaration du « CustonStoreOptions » :

// URL of express server let url: string = `http://localhost:3000/`; //#region DataSource | CRUD Actions // Define CustomStoreOptions let customStoreOpt : CustomStoreOptions = new CustomStoreOptions({         key: "id",         load: () => this.sendRequest(`${url}all`),         insert: values =>           this.sendRequest(`${url}add`, "POST", values),         update: (key, values) =>           this.sendRequest(`${url}upd/${key}`, "PUT", values),         remove: key =>           this.sendRequest(`${url}del/${key}`, "DELETE", {             key: key           }) }); // Define DataSource this.dataSource = new DataSource(customStoreOpt); //#endregion CustomStoreOptions | CRUD Actions

Dans cet extrait de code, nous pouvons voir que l’objet passé en paramètre de notre Datasource définit les méthodes à appeler lors des actions de CRUD tels que load, insert, update et remove, ainsi que la clé key, ici « id », permettant d’utiliser la valeur d’identification de l’objet de données remonté par le composant. Utilisons notre méthode générique « sendRequest » afin d’appeler notre API.

Méthode « sendRequest() » envoyant les requêtes d’action à l’API :

/** * Send Request * @param url * @param method * @param httpParams */ public sendRequest(url: string, method: string = "GET", httpParams: any = {}): any { let httpOptions = { withCredentials: false, body: httpParams }; let result; switch (method) { case "GET": result = this.http.get(url, httpOptions); break; case "PUT": result = this.http.put(url, httpParams, httpOptions); break; case "POST": result = this.http.post(url, httpParams, httpOptions); break; case "DELETE": result = this.http.delete(url, httpOptions); break; } return result.toPromise().then((result: any) => { return (method === "GET") ? { data: result.exampleData, totalCount: result.totalCount }; : result; }).catch(e => { throw e && e.error && e.error.Message; }); }

Tests et modifications de la vue HTML

Maintenant que nous avons câblé la source de données à notre API, il est temps d’implémenter le tout dans notre composant et de tester. C’est ici que la suite DevExtreme exprime toute son efficacité et tout son intérêt. En quelques lignes HTML, on peut définir toute la mécanique de cet outil « Datagrid ».

Tout d’abord, renseignons à notre composant que nous souhaitons activer le mode d’édition et que seul les champs « name » et « status » seront soumis à celle-ci.

dx-datagrid : ajout du mode « Editing » et des autorisations d’édition :

<!—Editing --> <dxo-editing mode="cell" [allowAdding]="true" [allowUpdating]="true" [allowDeleting]="true" ></dxo-editing> <!-- Columns --> <dxi-column dataField="id" [allowEditing]="false" caption="ID#" [visible]="true" width="50" alignment="center"> </dxi-column> <dxi-column dataField="name" caption="Name" [visible]="true" width="150"> </dxi-column> <dxi-column dataField="status" caption="Status" [visible]="true" width="100" alignment="center"> </dxi-column> <dxi-column dataField="custom" [allowEditing]="false" caption="Custom" cellTemplate="cellTemplate" [allowFiltering]="false" [allowSorting]="false" alignment="center" width="150"> </dxi-column>

Le Mode édition « dxo-editing> » permet ici de préciser quelles actions seront autorisées pour le composant et comment.

  • Nous autorisons l’ajout, la modification et la suppression.
  • Le mode d’édition est spécifié à « cell » pour cellule (voir figure ci-dessous)
  • Dans la définition de nos colonnes nous renseignons par l’attribut « allowEditing » (qui prend un boolean comme valeur) si oui ou non nous souhaitons autoriser la modification.

À la suite du changement de notre datagrid pour un mode édition, nous pouvons constater que des boutons d’action sont apparus dans la toolbar et qu’une colonne d’action tout à droite a également été rajoutée automatiquement par le composant.
Ici les boutons d’action « save » et « revert » de la toolbar sont grisés (désactivés) car aucune modification utilisateur n’a été relevée par le composant.

 

Dans notre configuration nous sommes en mode « batch», qui traite les modifications par lot de cellules. Nous pouvons donc modifier dans la « datagrid » les valeurs cellule par cellule, avant de sauvegarder ou d’annuler le tout au moyen des boutons d’action présent dans la toolbar. Visuellement, le composant encadre en vert les cellules modifiées et surlignes les lignes qui vont être effacées lors de la sauvegarde.

Après sauvegarde, nous nous retrouvons donc avec les données modifiées et supprimées comme préalablement décidé par les actions utilisateur ci-dessus. A noter que l’ajout se fera aussi par cellule dans une nouvelle ligne de Datagrid une fois l’action d’ajout appelée depuis le bouton + de la toolbar.

Ci-dessus, un exemple de la modification en mode « form » pour formulaire. Nous sommes devant un formulaire regroupant tous les champs d’une entrée « ExampleData », avec en bordure pleine les champs modifiables et en pointillé ceux qui ne le sont pas.
En mode formulaire, les boutons d’action « save » et « revert » de la toolbar ont disparu, et une action « edit » dans la colonne action rajoutée tout à droite de la « datagrid » a fait son apparition.

Le mode popup permet les actions de modification au travers d’une popup dédiée. Ci-dessus, un exemple de création en mode popup.
Dans tous les modes un popup de confirmation est ouvert avant suppression.

Il ne s’agit que de configurations simples dans cet exemple. Le composant de Datagrid est vraiment abouti est propose tout un panel d’outils qui se révéleront vraiment très utiles et appréciés des utilisateurs, tels que les actions de sélection des données, le groupement de données, leur export, la possibilité d’insérer des widgets DevExtreme comme un graphique une liste déroulante… la liste est longue. Vous vous en doutez, la plupart des outils proposés sont personnalisables, des templates html des composants (nous pourrions avoir par exemple des icônes à la place des liens d’action dans la colonne du mode édition), à la position horizontale des éléments rajoutés, comme la colonne d’action ou les boutons de la toolbar.

Personnalisation et ajout des actions d'export et suppression par sélection

Pour illustrer l’intérêt de ce genre d’outil, finissons cet exemple pratique par l’ajout des fonctionnalités d’export et de multi-sélection (pour suppression et choix des données à exporter) à notre Datagrid. Imaginez que votre applicatif utilisant une « Datagrid » est finalisé, entièrement fonctionnel, câblé comme il se doit à votre API et que vos besoins évoluent. Aujourd’hui, il vous serait très utile, d’exporter et/ou de supprimer les données remontées par votre grille de données (qui, comme vu ci-dessus, offre les fonctionnalités pour les ordonner, les filtrer…) et tout ceci par lot d’enregistrement au moyen d’une multi-sélection.  Avec ce composant de « Datagrid » proposé par DevExtreme, rien de plus facile, et surtout de plus rapide. C’est sur cet aspect entièrement évolutif et personnalisable que l’utilisation de cette librairie se révèle un énorme gain de temps dans les développements.

Sélection multiple et exportation des données

Dans notre déclaration de « Datagrid » côté html, nous ajoutons simplement les options d’export et de sélection comme suit :

<!-- Export --> <dxo-export [enabled]="true" fileName="ExampleData" [allowExportSelectedData]="true"></dxo-export> <!-- Selection --> <dxo-selection mode="multiple"></dxo-selection>

Dxo-selection est ajouté en mode multiple, l’utilisateur peut alors sélectionner une, plusieurs ou toutes les lignes de données de la « Datagrid».
Dxo-export est activé, nous précisons le nom du fichier exporté et nous activons la possibilité d’exporter les données par ensemble de lignes sélectionnées.

Nous utilisons les « checkbox » afin de sélectionner la ligne de données sur laquelle nous souhaitons agir (cette séléction respecte les comportements utilisateurs standards, à savoir la séléction de plusieurs lignes depuis la dernière séléctionnée en un clic au moyen du bouton « shift » associé à celui-ci et bien d’autres.

Dans la toolbar, l’icône d’exportation est apparue suite à l’ajout de cette fonctionnalité. Le choix d’un export par ligne sélectionnée est possible car l’attribut « allowExportSelectedData » a été passé à vrai.

L’exportation des données retourne un fichier au format .xlsx listant en en-tête les colonnes de la « datagrid », puis la liste de tout les enregistrements, ou de ceux sélectionnés.
Note : le résultat de l’export est personnalisable, plus d’info sur le site de l’éditeur

 

Personnalisation des éléments et suppression par lot

Maintenant que nous pouvons au travers de notre « Datagrid » chercher, filtrer, ordonner, ajouter, modifier, supprimer, sélectionner et exporter en lot ou entièrement nos données, il serait parfait de pouvoir rajouter notre dernier besoin : la suppression par lot. Nous allons utiliser tout ce que nous avons vu précédemment afin de rendre notre dernière tâche comme l’évidence même.

Pour cela, il suffit de rajouter un bouton d’action dans la toolbar et en profiter pour l’organiser, en personnalisant nos boutons « refresh » et « delete » à droite de ma toolbar, et laisser ceux du mode « Editing » à gauche.

Déclaration de notre bouton de suppression par lot de lignes sélectionnées :

// DxButton widget with composant configuration in options public deleteRowBtnToolbar: DxToolbarComponent["items"] = { location: "after", widget: "dxButton", locateInMenu: "auto", options: { disabled: !this.deleteBtnValid, icon: "trash", hint: "Delete Selected Rows", onInitialized: (args: any) => { // We recover the instance of DxButton this.deleteBtnToolbarInstance = args.component; }, onClick: (e: Event): void => this.deletedSelectedRow(e) } };

  • Nous déclarons un bouton de type items du composant DxToolbarComponent ;
  • Sa position est définie à « after », soit à droite de la toolbar après les boutons du mode édition, et nous lui passons des options très utiles telles que :
  • disabled : cette option permet de désactiver ce bouton de suppression ;
    Vu qu’il s’agit d’une action par lot, nous décidons de déclarer une variable booléenne deleteBtnValid qui nous informera par data-binding de l’état de selection de lignes de la datagrid ;
  • deleteBtnToolbarInstance : cette option nous permet de récupérer la référence à l’instance de ce widget bouton afin d’interagir avec ;
  • A l’événement « click » de ce bouton, nous appelons la méthode de suppression deletedSelectedRow() qui s’occupe d’envoyer l’ordre de suppression pour chaque enregistrement sélectionné.

Méthode de suppression des lignes sélectionnées :

/** * Delected Selected Rows */ private deletedSelectedRow(event: Event) { let promiseList: Array<any> = []; let rows: Array<ExampleData> = this._datagrid.instance.getSelectedRowsData(); // 1 - for each row selected rows.forEach(row => { // 2 - we add a promise for every call to our API promiseList.push(this.sendRequest(`${this.url}del/${row.id}`, "DELETE", { key: row.id })); }); Promise.all(promiseList).then(values => { // 3 - we send a notification after the good progress of our actions of suppression. const type = "success"; this._notifyConfig.message = "All selected rows deleted!"; notify(this._notifyConfig, type, 1500); // 4 - we deselect the selected rows this._datagrid.instance.clearSelection(); // 5 - we pass the button status to false in order to disable delete button this.deleteBtnValid = false; if (this.deleteBtnToolbarInstance !== null) { this.deleteBtnToolbarInstance.option({ disabled: !this.deleteBtnValid }); } // 6 - we reload the Datagrid this._datagrid.instance.getDataSource().reload(); }); }

La méthode deletedSelectedRow() supprime toutes les lignes qui ont été sélectionnées au sein de la « Datagrid ».

  1. Pour chaque ligne sélectionnée, récupérée dans l’instance de la datagrid grâce à la méthode getSelectedRowsData() ;
  2. Nous appelons la méthode sendRequest() avec le paramètre « DELETE » et enregistrons la promesse de cet appel dans un tableau de promesses ;
  3. Nous utiliserons la méthode all() de l’objet Promise afin d’effectuer un traitement global survenant après la résolution de toutes nos promesses précédemment enregistrées ;
  4. Une fois la notification envoyée informant du bon déroulement de la suppression de nos données, nous effaçons le tableau de selection retourné par la datagrid ;
  5. Nous désactivons le bouton d’action de la toolbar en passant la variable booléenne de son status à faux ;
  6. Enfin nous rechargeons la « Datagrid » pour qu’elle mette à jour ses données.

Dans la figure ci-dessus, les boutons d’actions « add » et « export » du mode édition se retrouvent bien à gauche de la toolbar, et nos boutons « refresh » et « delete » se positionnent bien après « after » à droite. Le bouton de suppression est grisé (ou désactivé car aucune ligne de données n’est sélectionnée).

Une fois une ou plusieurs lignes selectionnées, le bouton d’action de la toolbar s‘active.

Notre « Datagrid » rafraîchit ses données en appelant sa méthode « load » qui requête notre API par la méthode « GET », et nous renvoie un jeu de données actuelles. A noter que des options de pagination, de tri et autre peuvent accompagner les requêtes à notre API, et qu’il est tout à fait possible de requêter par exemple un jeu de 20 enregistrements à partir de la 40ème entrée en base de données.

Dans notre cas pratique, toutes les lignes ont bien été supprimées, et une notification nous informe du bon déroulement de cette action. Notre « Datagrid » nous renseigne bien qu’aucune donnée n’a été remontée, ce qui est le résultat que nous attendions.

Conclusion

Très complète et très bien documentée pour un large éventail de technologies, nous venons de parcourir succinctement quelques applications de DevExtreme. C’est une solution facile à prendre en main qui, une fois sa logique assimilée, permet une très grande personnalisation et une large évolutivité sur une multitude de développements, et ce, en un minimum de code. Que ce soit au niveau de ses composants, de sa mise en forme, de ses grilles de données, ou des formulaires et graphiques, en passant par l’éditeur de contenu, cette suite accroît véritablement la productivité d’un projet.

Sa licence libre de droit permet de s’amuser à manipuler les composants pour des applications non-commerciales. Quant à sa version payante (nécessaire à des fins commerciales pour 12 mois, et pour chaque développeur du projet), les tarifs réduits pour l’achat de lot et les réabonnements permettent une utilisation illimitée.

Toutefois, à la vue des offres commerciales, de la qualité des composants de cette suite, de la communauté, ainsi que de la documentation qui ne cesse de s’étoffer, il est vraiment intéressant au sein d’une entreprise de se positionner sur l’acquisition de ce genre de librairie, surtout au sein des projets utilisant des frameworks utilisant le data-binding tel qu’Angular.