Démarrer avec le Custom Widget Builder : exemples simples

Le "Custom Widget Builder " permet de créer ses propres widgets/vues personnalisées, directement à l’intérieur de Grist, moyennant l’écriture d’un peu de code html et javascript. Si on le souhaite, on pourra ensuite publier ce code pour le partager à la communauté. Mais commençons par le commencement…

Ajouter le « custom widget builder »

  • Cliquer sur Nouveau > Ajouter une vue > Personnalisée > Custom widget builder, en sélctionnant la table source - c’est celle dans laquelle on veut ajouter des données - ou à partir de laquelle on veut les récupérer - ou les deux.
  • Dans le panneau de création > Niveau d’accès, autoriser l’accès aux document

Configurer le custom widget builder

  • au niveau du widget cliquer sur les « … » puis « Ouvrir la configuration »

  • observer qu’il y a plusieurs onglets : un onglet « HTML » qui va contenir le code HTML (définit les éléments qu’on va voir à l’écran) et un onglet JavaScript pour le code JavaScript (c’est un peu le code intelligent pour faire nos fonctions et rendre le tout dynamique)

  • De base, l’onglet HTML contient des éléments d’exemple - qu’on pourra supprimer pour remplacer avec nos éléments.

  • Et l’onglet JavaScript contient les fonctions de base ready, onRecord et onRecords

La liste de toutes les fonctions disponibles est dans la doc : grist-plugin-api - Grist Help Center

Nous allons dans les prochains posts explorer les fonctions de base visibles dans le code javascript.

4 « J'aime »

Fonction ready

grist.ready({ requiredAccess: 'none' });

Obligatoire, elle indique que le widget est prêt à recevoir des messages du monde extérieur.

On y inclut la propriété requiredAccess qui spécifie le niveau d’accès requis.

C’est le niveau d’accès au document, visible dans le panneau de création.

Si votre custom widget a besoin de faire des actions de lecture et d’écriture sur la table source, vous aurez besoin d’indiquer « full » pour demander à l’utilisateur·ice de donner tous les droits d’accès au widget.

grist.ready({ requiredAccess: 'full' });

Si vous avez besoin uniquement de lire la table, vous pouvez indiquer
grist.ready({ requiredAccess: 'read table'});

Fonction onRecord

onRecord sera appelé lorsqu’on se déplace d’une ligne à l’autre dans la vue à laquelle est liée le widget, ou lorsqu’une valeur de la ligne sélectionnée change.

Le code contenu dans la fonction (entre les {}) sera alors exécuté.

Il faut pour cela avoir au préalable lié le widget à la vue :
Dans le panneau de création > onglet Table > Données source > « Sélectionner par », choisir la table source.
Cela permettra de sélectionner la ligne entière de la table qu’on clique sur une ligne.

Syntaxe :

grist.onRecord(record => {
  // Code pour faire quelque chose quand le curseur a été déplacé.
});

Tuto 1 : Afficher des textes issus d’une table dans le custom widget

Objectif : quand on parcourt la table liée, on affiche dans le custom widget le texte de la ligne sélectionnée.

Étapes :

  • On crée une table avec une colonne « MonNom »
  • On ajoute le custom widget builder
  • on pense bien à le lier à la vue de la table (« Sélectionner par »)
  • on ouvre la configuration
  • dans l’onglet « html » on enlève tout ce qui était contenu dans la balise body du code par défaut, et on ajoute <label id="name"></label> qui va permettre d’afficher un texte.

Pour pouvoir identifier notre composant, on lui attribue un identifiant id="nom" (on aurait pu l’appeler id="patate", c’est comme on veut).
Le code html est le suivant :


<html>
<head>
<script src="https://docs.getgrist.com/grist-plugin-api.js"></script>
</head>

<body>
<label id="nom"></label>
</body>
</html>

:

  • Au niveau de l’onglet javascript, on écrit le code js suivant :
grist.ready({ requiredAccess: 'full' });

const labelDansLeWidget = document.querySelector("#nom");
let nomIssuDeLaTable = ""

grist.onRecord(record => {
  nomIssuDeLaTable = record.MonNom;
  labelDansLeWidget.textContent = nomIssuDeLaTable;
});

Explication :

  • const labelDansLeWidget = document.querySelector("#nom");permet d’enregistrer notre élément <label id="nom"> dans une constante
  • let nomIssuDeLaTable = "" permettra d’enregistrer le nom sélectionné dans la table dans une variable
  • et dans la fonction onRecord, un bout de code qui sera appelé chaque fois qu’on change de ligne :
    nomIssuDeLaTable = record.MonNom; : va modifier la variable nomIssuDeLaTable avec la valeur de la colonne MonNompour la ligne sélectionnée
    labelDansLeWidget.textContent = nomIssuDeLaTable; : OÙ LA MAGIE OPÈRE :magic_wand: : va modifier l’attribut texte de la variable labelDansLeWidget avec la valeur de nomIssuDeLaTable précédemment attribuée

On clique sur « Preview » puis « Enregistrer ». Et voilà !

Démo : https://grist.incubateur.anct.gouv.fr/o/tutos-templates/rvyQtf7ECqp3/Ex-custom-widget

Tuto 2 : Ajouter une ligne ou modifier le texte d’une ligne depuis le custom widget

Cette fois-ci, on veut permettre à l’utilisateur·ice d’entrer un texte dans un champ de saisie du custom widget, et de cliquer sur un bouton pour soit l’ajouter dans une nouvelle ligne de la table, soit modifier une ligne existante.

  • on ajoute le custom widget builder
  • on pense bien à le lier à la vue de la table (« Sélectionner par »)
  • on ouvre la configuration
  • dans l’onglet « html » on enlève tout ce qui était contenu dans la balise body du code par défaut, et on ajoute notre champ de saisie <input> et nos deux boutons <button> ; chacun ayant son identifiant. Le code est le suivant :
  <div style="padding:4px"><input id="input" placeholder="Taper un texte"></input></div>
  <div style="padding:4px"><button id="updateBtn">Mettre à jour la ligne</button></div>
  <div style="padding:4px"><button id="addBtn">Ajouter une ligne</button></div>

  • dans l’onglet javascript, on va d’abord récupérer nos différents composants hmtl grâce à leurs identifiants :
const textInput = document.getElementById('input');
const updateButton = document.getElementById('updateBtn');
const addButton = document.getElementById('addBtn');
  • On définit ensuite des variables pour stocker la valeur du texte saisi par l’utilisateur·ice, et l’id de la ligne sélectionnée dans la table
let text = ""
let rowId = null
  • On déclare ensuite notre fameuse fonction onRecord, pour récupérer l’identifiant de la ligne sélectionnée dans la vue de la table liée
grist.onRecord(record => {
  rowId = record.id
});
  • Enfin, on définit ce qui se produit lors du clic sur les boutons

« Mettre à jour la ligne » : on ajoute un « écouteur d’événement » sur le bouton de mise à jour, qui, à chaque clic, va déclencher le code de mise à jour. Ce dernier utilise la constante selectedTable pour récupérer la table sélectionnée (liée au custom widget), la fonction update pour mettre cette table à jour et, l’identifiant de ligne récupéré précédemment via la fonction onRecord, pour connaître la ligne à mettre à jour.

updateButton.addEventListener('click', async () => {
  await grist.selectedTable.update({
      id: rowId,
      fields: {
        MonNom: textInput.value
      }
  }) 
});

« Ajouter une ligne » : même concept mais on utilise la fonction native create appliquée à selectedTable. Le code est le suivant :

addButton.addEventListener('click', async () => {
  // Add a row
  await grist.selectedTable.create({
      fields: {
        MonNom: textInput.value
      }
  }) 
});

Pour définir la nouvelle valeur du champ, on utilise :

Si on veut mettre à jour plusieurs champs, on ajoutera plusieurs champs de saisie dans notre html et plusieurs clés/valeurs dans l’attribut fields, par exemple :

 await grist.selectedTable.update({
      id: rowId,
     fields: {
        MonNom: ["l", textInput.value],
        MonAdresse: adresseInput.value
      }

Tuto bonus express : modifier un élément de couleur quand on sélectionne un code couleur dans la table

C’est l’exemple du webinaire de GristLabs.

Très similaire au tuto 1, sauf qu’au lieu d’afficher un texte issu de la table, on va récupérer dans la table un code couleur qui va changer la couleur d’un composant html.

Dans le code html, ajouter dans le body l’élément html suivant qui permet d’afficher une couleur :
<input type="color" id="color">

Et le code js suivant

const colorElement = document.querySelector("#color");

grist.onRecord(record => {
  const value = record.Color;
  colorElement.value = value;
});

qui va permettre de modifier, quand on parcourt les lignes de la table source, la couleur de l’élément html.

Et c’est tout :blush:

changecolor

Mappage de colonnes

Dans les tutos précédents, nous avons appelé directement nos colonnes par leurs identifiants dans le code javascript. Par exemple, nous avons écrit :

 nomIssuDeLaTable = record.MonNom;

Cela fonctionne très bien. Cependant, que se passe-t-il si on change le libellé de colonne dans notre table ?
:drum: :drum:
Notre code ne fonctionne plus.
C’est logique car on va chercher un id de colonne « MonNom » qui n’existe plus… Quand on renomme une colonne, il faut donc toujours penser à changer l’id de colonne dans le code du custom widget. Ce qui peut être source d’erreur en cas d’oubli ou de faute de frappe.

Par ailleurs, si on veut réutiliser notre custom widget sur une autre table, ou le publier pour que d’autres personnes puissent l’utiliser, cela demandera, de la même manière, d’éditer à la fois les libellés de colonne dans la table et les ids dans le code.

Pour éviter cet écueil, Grist nous propose une solution : le mappage de colonne. Cette option permet d’afficher sur l’interface, dans le panneau de création, des menus déroulants pour choisir la colonne qu’on souhaite attribuer à chaque variable.

Par exemple, pour le widget personnalisé Geocode, les colonnes à mapper apparaissent ainsi :

Avec le mappage de colonnes, le nom de colonne est dynamique et mis à jour automatiquement en cas de modification.

Marche à suivre pour proposer le mappage de colonnes :

La liste des colonnes attendues peut être indiquée à Grist dans la fonction ready du custom widget vue précédemment, via la propriété columns :

grist.ready({
   columns:  ['TitreChampAMapper1', 'TitreChampAMapper2']
});

On utilise ensuite la fonction d’assistance mapColumnNames dans la fonction onRecord ou onRecords, pour faire notre traitement :

grist.onRecord(async (record) => {
    // On récupère les colonnes mappées
    const mapped = grist.mapColumnNames(record);

    if (mapped) {
        // Si les colonnes ont bien été mappées,
         // faire un traitement avec mapped.TitreChampAMapper1;
    } else {
        // Sinon, on affiche un message d'erreur dans la console
        console.error("Toutes les colonnes n'ont pas été mappées.");
    }

});

Il est possible d’indiquer davantage d’options lorsqu’on déclare les colonnes dans la fonction ready : on peut spécifier si le mappage est obligatoire ou facultatif, obliger un certain type (texte, numérique…), ajouter une description… La liste de toutes les options est ici :

columns: [
  {
    name: "Lien", // Titre du champ
    title: "Lien de l'image", // Nom de champ convivial.
    optional: false, // Est-ce un champ optionnel.
    type: "Text", // Quel type de colonne nous attendons.
    description: "Un texte" // Description du champ.
    allowMultiple: false // Permet l'attribution de plusieurs colonnes.
  }
]

Tuto : afficher dans le custom widget les textes issus de 2 colonnes d’une table , avec le mappage de colonne

  • Créer une table avec des colonnes « MonNom » et « MonAdresse »
  • Ajouter le Custom Widget Builder basé sur cette table et lié à cette table (« sélectionner par »)
  • Ajouter le code html suivant au custom widget, avec deux label qui nous permettront d’afficher un nom et une adresse :
<html>
<head>
<script src="https://docs.getgrist.com/grist-plugin-api.js"></script>
</head>

<body>
<label id="nom"></label>
<label id="adresse"></label>
</body>
</html>
  • Dans l’onglet js :
grist.ready({
   columns:  ['Nom', 'Adresse']
});

grist.onRecord(async (record) => {
    // On récupère les colonnes mappées
    const mapped = grist.mapColumnNames(record);

    if (mapped) {
        // Si les colonnes ont bien été mappées,
        // on affiche les valeurs de nos colonnes dans nos éléments html
        document.getElementById('nom').innerText = mapped.Nom;
        document.getElementById('adresse').innerText = mapped.Adresse;
    } else {
        // Sinon, on affiche un message d'erreur dans la console
        console.error("Toutes les colonnes n'ont pas été mappées.");
    }
});
grist.ready({columns: [
  {
    name: "Nom",
    optional: false, 
    type: "Text",
    description: "Plus d'infos ici" 
  },
 {
    name: "Adresse",
    optional: false, 
    type: "Text",
    description: "Toujours plus" 
  },
]});

Démo ici : Ex - custom widget - Grist

Fonction onRecords

onRecords va récupérer les enregistrements de la table source et s’exécuter à nouveau lorsque les données ont changé.

Le code contenu dans la fonction (entre les {}) sera alors exécuté.

Attention, quand vous ajoutez une colonne à la table source, elle est par défaut masquée dans la vue personnalisée, il faut la démasquer !

Syntaxe :

grist.onRecords(table => {
  // Code qui sera executé lorsque les données dans la table ont changé.
});

Tuto : afficher les données d’une table dans le widget

  • ajouter une table avec une colonne pré-remplie de quelques données
  • ajouter le custom widget basé sur cette table (pas besoin dans ce cas de le lier avec « Sélectionner par »)
  • dans le code html, ajouter une balise label qui nous permettra d’afficher nos données :
    <label id="data"></label>
  • dans le code js, assigner cet élément html à une constante :
    const labelDansLeWidget = document.querySelector("#data");
  • puis ajouter la fonction onRecords :
grist.onRecords(table => {
  labelDansLeWidget.textContent = JSON.stringify(table);
});

  • Modifier une valeur dans la table et observer que la donnée du widget est bien mise à jour ! :partying_face:

Démo : Ex - custom widget - Grist

1 « J'aime »

Fonction onOptions

Il existe également un évènement onOptionsqui va se déclencher quand la configuration a changé.
Syntaxe :

grist.onOptions(function (options, interaction) {
  // La configuration a changé.
});

On peut réaliser de beaux custom widgets grâce au Custom Widget Builder,comme ce formulaire de saisie créé par @FlorentBallu :

Lien : Référence insérée via API non reconnue par Grist en tant que telle

ou ce widget d’envoi de mails créé par Amandine :

Lien :

Notre imagination est la seule limite ! (et un peu nos compétences en développement aussi :slight_smile: )

On peut ensuite partager sa création à la communauté, en publiant son code sur des forges comme GitHub ou GitLab, explications ici :

Joyeux gristage !

Liens utiles :

Webinaire GristLabs :

3 « J'aime »