Widgets personnalisés

Prev Next

Créez vos propres widgets dans Tulip à l’aide de HTML, CSS et JavaScript

Who can use this feature

Users on Professional plans and above.

Requirement to use Custom Widget

Please allow Outgoing access to Custom Widgets. See Networking requirements for a Tulip Cloud Deployment to learn more.* *.tulip-custom-widgets.com

Les éléments que vous glissez-déposez dans votre application sont appelés « widgets ». Il peut s’agir de boutons, d’images, de champs de saisie et de tout autre élément disponible dans la barre d’outils.

Les widgets personnalisés vous permettent d’écrire votre propre code et de créer vos propres widgets, que vous pouvez ensuite glisser-déposer dans votre application et avec lesquels vous pouvez interagir. Cette fonctionnalité permet aux utilisateurs de Tulip de repousser les limites de ce qui est possible dans Tulip.

Regardez la vidéo suivante pour découvrir cette fonctionnalité :

Découvrez les widgets personnalisés disponibles dans la bibliothèque Tulip.

Notions de base sur les widgets personnalisés

L'écran de gestion des widgets personnalisés se trouve dans la page des paramètres de votre compte. C'est là que vous pouvez créer et supprimer des widgets. L'éditeur de widgets personnalisés vous permet d'écrire le code de votre widget, de créer des props et des événements, et de prévisualiser votre widget. Vous trouverez ci-dessous un schéma décrivant ce que sont les props et les événements :

Props : les props sont des données partagées entre les widgets personnalisés et les applications Tulip. Les props seront exposés dans l’éditeur d’application et permettront aux éditeurs d’application de sélectionner les variables, les enregistrements de table ou toute autre information à associer au prop.

Événements : les événements sont des signaux que votre widget peut envoyer à votre application. Ils permettent à votre application de réagir à l’aide d’un déclencheur et peuvent véhiculer des informations.

Les widgets personnalisés sont accessibles via le menu déroulant « Personnalisé » dans l’éditeur d’application.

Créer un widget

Permission Required

Only users with access to account settings (i.e. Account Owners) or users with specific permission to access the custom widget page can use the custom widget editor.

L'écran de l'éditeur de widget est divisé en quatre sections. Le code que vous écrivez dans la section en bas à gauche apparaîtra dans la section d'aperçu dès que vous cliquerez dans cette dernière. Les valeurs des propriétés peuvent être modifiées directement dans la section d'aperçu à des fins de test.

Il existe des fonctions spéciales permettant d’interagir avec les props et les événements.

Récupérer la valeur d’une propriété

Warning

It is not guaranteed that the prop being used by a widget will load before that widget loads, so all props associated with Table record fields, Aggregations, or any other dynamic value should include logic to support cases where they are null at the point the widget is loaded. See this section for details on triggering logic when a prop changes.

// Récupérer la valeur d'une propriété getValue('My Prop');``// Stocker la propriété dans une variable let myVariable = getValue('My Prop');

Effectuer une action lorsqu’une propriété change

//Effectuer une action chaque fois que la valeur d'une prop change getValue('My Prop', (internalVariable) => { doSomething(internalVariable);});

Définir la valeur d’une propriété


//Définir la valeur d'une propriété de type texte sur « hello! » setValue('My Text Prop', 'hello!');

// Définir la valeur d'un élément dans un objet setValue('My Object Prop', { 'Key inside object': 'new value' });```

### Déclencher un événement

```**Déclencher un événement** fireEvent('événement', *chargeUtile*);

// Déclencher un événement sans charge utile fireEvent('My Event');

//Déclencher un événement avec une charge utile fireEvent('My Event', myVariable);```

Le diagramme suivant illustre le flux d’informations au sein d’un widget personnalisé dans une application. Les données de la variable d’entrée sont stockées dans la propriété. Le changement de valeur déclenche un événement, à la suite duquel un déclencheur stocke les données dans la variable de sortie.

![Custom Widget App Setup Diagram](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widget%20App%20Setup%20Diagram.jpg){height="" width=""}

## Démonstration de création d'un widget

![Video](https://www.youtube.com/embed/H08-W2M2Ypo?rel=0){height="576" width="1024"}

## Importation/exportation d’applications

Consultez la section [Importation et exportation](/r230/docs/import-export) pour en savoir plus sur cette fonctionnalité.

### Exporter

1. Dans la vue d'ensemble des widgets personnalisés, sélectionnez le **menu à trois points** situé à côté du widget que vous souhaitez exporter.

![](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widgets%20Overview_477703991.png){height="" width=""}

1. Sélectionnez « **Exporter** ».

![](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widgets%20Overview_477704053.png){height="" width="800"}

### Import

1. Dans l'aperçu des widgets personnalisés, sélectionnez le **menu à trois points** en haut de l'écran.

![](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widgets%20Overview_477704175.png){height="" width="800"}

1. Sélectionnez « **Importer** ».

![](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widgets%20Overview_477704228.png){height="" width="350"}

1. Recherchez le fichier .json du widget personnalisé.

## Activer les bibliothèques externes

Les bibliothèques externes élargissent encore davantage les possibilités des widgets personnalisés. Elles prennent en charge le travail manuel consistant à écrire du code HTML directement en JavaScript. Les bibliothèques externes doivent être activées pour chaque widget dans lequel vous souhaitez les utiliser.

1. Dans votre widget, cliquez sur le **menu à trois points** situé en haut à droite de la page.

![](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widgets%20Overview_477684182.png){height="" width=""}

1. Sélectionnez « **Activer la bibliothèque externe** ».

![](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widgets%20Overview_477684247.png){height="" width="250"}

1. Sélectionnez l'**extension** que vous souhaitez activer.

![Screenshot 2025-02-06 at 9.13.14 AM](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Screenshot%202025-02-06%20at%209.13.14%20AM.png){height="" width=""}

Voici une description sommaire des fonctionnalités de chaque extension :

* **jQuery**: permet une sélection plus fluide des éléments HTML, ainsi que leur manipulation.
* **D3 -** La référence en matière de visualisation de données : une courbe d'apprentissage abrupte, mais une flexibilité exceptionnelle.
* **Google Visualization :** un excellent outil, comme son nom l'indique, pour la visualisation.
* **Lodash -** Lodash fournit une multitude d'outils qui facilitent le travail avec les types de données JavaScript.
* **ChartJS -** Sans doute la bibliothèque de graphiques la plus simple qui soit, avec une multitude de ressources en ligne.
* **Moment -** Moment propose une multitude d'outils pour manipuler les dates et les heures.

## Version du runtime

Le chargement des widgets personnalisés dans le nouveau runtime a été mis à jour (voir [Améliorations apportées aux widgets personnalisés](/r230/docs/custom-widget-improvements)). Ce changement a une incidence sur le moment où vous pouvez accéder de manière fiable aux propriétés de votre widget, telles que celles récupérées avec `getValue()`.

Pour connaître la version du runtime utilisée par votre widget :

* Sur la page **de liste des widgets personnalisés** ou dans l'éditeur, si le widget comporte un badge **« Legacy »** à côté de son nom, cela signifie qu'il utilise l'ancien runtime. S'il n'y a pas de badge, le widget utilise le nouveau runtime.
* Dans le panneau latéral d’informations du widget personnalisé, consultez la section « **Runtime** ».
* Dans les **paramètres d’autorisation** d’un widget personnalisé, si le bouton « **Runtime du widget personnalisé** » est activé, cela signifie que le widget utilise le nouveau runtime.

:::(Info) (NOTE)
By default, newly created Custom Widgets use the old runtime.
:::

Pour changer de version de runtime pour un widget personnalisé, utilisez le bouton « **Runtime du widget personnalisé** » dans **les paramètres d’autorisation**.![Custom widget runtime toggle](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20widget%20runtime%20toggle.png){height="" width=""}

## Exemples

Vous trouverez ci-dessous plusieurs widgets personnalisés issus de la [bibliothèque Tulip](https://tulip.co/library/) qui illustrent les fonctionnalités décrites ci-dessus :

### Widget de minuterie simple

[Widget de minuterie simple](https://library.tulip.co/apps/simple-timer-widget)

Un widget de minuterie convivial permettant de suivre le temps écoulé au cours des workflows de l’application. Les opérateurs peuvent démarrer, arrêter et réinitialiser la minuterie directement depuis l’interface de l’application, ce qui s’avère utile pour chronométrer des tâches ou des processus.

![Screenshot 2025-07-02 at 15.33.20.png](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Screenshot%202025-07-02%20at%2015.33.20.png){height="" width=""}

### Widget de jauge radiale

[Widget de jauge radiale](https://library.tulip.co/apps/radial-gauge-widget)

Ce widget représente visuellement des valeurs numériques à l'aide d'un indicateur radial personnalisable. Il est idéal pour afficher des indicateurs tels que la vitesse d'une machine, la température ou la progression d'une tâche, dans un format rapide et facile à comprendre.

![Screenshot 2025-07-02 at 15.34.22.png](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Screenshot%202025-07-02%20at%2015.34.22.png){height="" width=""}

### Widget Chronologie

[Widget « Chronologie »](https://library.tulip.co/apps/timeline-widget)

Affiche des événements ou des étapes sur une chronologie horizontale, aidant ainsi les utilisateurs à visualiser d’un seul coup d’œil les phases d’un processus, les tâches terminées ou les données temporelles.

![Screenshot 2025-07-02 at 15.34.59.png](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Screenshot%202025-07-02%20at%2015.34.59.png){height="" width=""}

## Bonnes pratiques

### 1. Évitez les effets indésirables liés au code du widget qui s'exécute au chargement

Tulip exécute le code réel de votre widget à chaque fois qu'il est affiché — non seulement dans le Player, mais aussi dans l'éditeur de widgets personnalisés, l'éditeur d'applications et l'aperçu en direct d'une station. Cela vous offre un aperçu en direct et précis de votre widget. Tout code ayant des effets secondaires (par exemple, des appels d’API, des écritures dans des tables, des notifications) situé au niveau supérieur de votre script s’exécute à chaque rendu du widget, y compris chaque fois que le widget se rouvre dans l’éditeur ou que l’aperçu se recharge.

Conditionnez tout élément ayant un effet concret derrière un déclencheur explicite (par exemple, un clic sur un bouton, un ` `` fireEvent` `` en réponse à une action de l’utilisateur ou une modification de propriété) plutôt que de le déclencher automatiquement au chargement.

```// BON : ne se déclenche que lorsque l’application le demande explicitement getValue('TriggerIncrement', (shouldIncrement) => { if (shouldIncrement) { fetch("https://api.example.com/increment-counter", { method: "POST" }); }});

// MAUVAIS : se déclenche à chaque chargement du widget, y compris dans l’éditeur et l’aperçufetch("https://api.example.com/increment-counter", { method: "POST" });```Si un widget doit charger des données automatiquement, utilisez un point de terminaison en lecture seule ou idempotent afin que les chargements répétés ne provoquent pas d’effets indésirables.

### 2. Testez de manière itérative sans déclencher de véritables effets secondaires

Lors du développement d’un widget qui appelle une API de production entraînant de véritables effets secondaires, pointez vers un point de terminaison de test ou de préproduction jusqu’à ce que vous ayez terminé vos itérations, puis basculez vers un point de terminaison de production avant la publication. Cela évite la duplication d’enregistrements, les notifications ou d’autres effets secondaires liés à la modification et à la prévisualisation de votre widget.

Pour les actions qui doivent s’effectuer en production même après la publication, intégrez une confirmation dans l’interface utilisateur du widget lui-même plutôt que de vous fier au moment du chargement. En exigeant une action visible (par exemple, un clic sur un bouton), la personne qui consulte le widget contrôle le moment où l’effet se produit.

```// Nécessite un clic explicite avant que l’action réelle ne se déclenche,// quel que soit le nombre de fois où le widget lui-même a été affiché document.getElementById('submitBtn')?.addEventListener('click', () => { fetch("https://api.example.com/submit-order", { method: "POST" });});

3. Gestion des props — Valider la valeur d’une prop

Assurez-vous que votre widget personnalisé gère les données de manière fiable. Les props permettent de transmettre des informations à votre widget, mais leurs valeurs peuvent changer ou être initialement indéfinies. Même si les props ont un type défini, elles peuvent être initialement indéfinies, ce qui peut entraîner des erreurs dans le code de votre widget personnalisé. Suivre une bonne pratique permet d’éviter les erreurs et rend votre widget plus robuste.

L’exemple ci-dessous utilise une fonction de rappel comme deuxième paramètre de la méthode getValue. Ce rappel s’exécutera chaque fois que le widget personnalisé recevra la valeur mise à jour de la prop.

Comme la valeur initiale de la prop peut être indéfinie et qu’elle peut par la suite recevoir la valeur mise à jour, vous devez vous assurer d’appliquer la configuration dès que la valeur mise à jour est reçue. L’utilisation de getValue sans fonction de rappel renvoie la valeur de la prop à ce moment précis de l’exécution du code.

getValue('config', function(config) { if (config) { applyConfiguration(config); }});

L'exemple suivant n'est pas recommandé car la valeur initiale de la propriété peut être indéfinie :const config = getValue('config');applyConfiguration(config);

4. Gestion des événements – Validation des données

Les données d’événement ont un type. Bien que le déclenchement d’un événement avec un type de données incorrect ne provoque pas de plantage ni n’interrompe l’exécution du code de votre widget personnalisé, il est recommandé de mettre en place vos propres vérifications pour vous assurer que les données envoyées via fireEvent sont validées et du type correct.

Cette pratique permet d’éviter tout comportement inattendu et rend votre code plus prévisible.

if (data != null && typeof data === 'string') { fireEvent(eventName, data);} else { // Gérer éventuellement le cas de données non valides.}

5. Utilisez une fonction de rappel pour l’accès asynchrone aux props

Lorsque vous appelez `setValue()`, la mise à jour de la prop est asynchrone. Cela signifie qu’elle ne se produit pas immédiatement, car votre widget personnalisé communique avec l’application principale depuis un iframe. La requête `setValue()` est envoyée, mais votre code continue de s’exécuter sans attendre que la modification prenne effet. Si vous essayez d’utiliser getValue() juste après, vous obtiendrez probablement l’ancienne valeur.

Pour obtenir les données les plus récentes, utilisez toujours getValue() avec une fonction de rappel. La fonction de rappel n’est exécutée que lorsque la propriété a été mise à jour avec succès, ce qui vous garantit de travailler avec les informations les plus récentes.

L’exemple suivant illustre cette bonne pratique :```// BONNE PRATIQUE : accéder à la valeur d’une propriété de manière asynchroneconst newConfig = { setting: 'value' };setValue('config', newConfig);

// La fonction de rappel s'exécutera une fois la mise à jour de la propriété terminée.getValue('config', function(updatedConfig) { console.log('La propriété a été mise à jour. La nouvelle configuration est :', updatedConfig); // Effectuer une action avec la nouvelle valeur garantie applyConfiguration(updatedConfig);});```

L'exemple suivant montre ce qu'il faut éviter :```// MAUVAIS : cela risque de ne pas fonctionner comme prévuconst newConfig = { setting: 'value' };setValue('config', newConfig);

// Cela affichera probablement l’ancienne valeur, et non la nouvelle.const currentConfig = getValue('config'); ```

6. Utilisez les en-têtes pris en charge pour les appels API

Lorsque vous effectuez des appels API externes à partir de widgets personnalisés, faites attention aux en-têtes HTTP pour éviter les problèmes liés au CORS (Cross-Origin Resource Sharing). Les widgets personnalisés s’exécutent dans un environnement iframe soumis à des restrictions de sécurité spécifiques.

N’utilisez que les en-têtes explicitement pris en charge. La définition d’en-têtes non pris en charge, tels que Content-Type ou d’autres en-têtes personnalisés, peut déclencher des erreurs CORS et empêcher le bon fonctionnement de vos appels API.

L’exemple suivant illustre cette bonne pratique:// BON : Appel d’API simple sans en-têtes personnalisésfetch("https://akhatri.bulb.cloud/api/v3/w/1/tables", { headers: { Authorization: "<clé API>", },}) .then((response) => response.json()) .then((data) => console.log(data)) .catch((error) => console.error("Erreur :", error));

L'exemple suivant montre ce qu'il faut éviter:// MAUVAIS : la définition de l'en-tête Content-Type entraînera des problèmes CORSfetch("https://api.example.com/data", { headers: { Authorization: "<clé API>", "Content-Type": "application/json", // Cela provoquera des erreurs CORS },})

Troubleshoot CORS error

If you encounter CORS errors, first remove any custom headers you've set (especially Content-Type) and test with minimal headers. The iframe security model restricts which headers can be set from custom widgets.

7. Initialisez les widgets en fonction de votre environnement d'exécution

Voir Version du runtime pour plus d’informations.

Nouveau runtime

Dans le nouvel environnement d’exécution, l’attribut src renvoie un fichier HTML « shell » vide. Une fois chargé, ce fichier « shell » envoie un message à l’application parente. L’application parente envoie le code du widget personnalisé, les fonctions utilitaires (telles que getValue()) et les valeurs initiales des props à injecter dans l’iframe.

De ce fait, les événements DOMContentLoaded et window.onload se déclenchent lors du chargement du fichier shell vide, avant l’injection du code de votre widget. Tout gestionnaire associé à ces événements est enregistré trop tard : l’événement s’est déjà produit, de sorte que la fonction de rappel ne s’exécute jamais. Le code contenu dans ces gestionnaires est ignoré sans avertissement, comme le montre l’exemple ci-dessous :

document.addEventListener('DOMContentLoaded', function() { // Se déclenche lorsque SHELL se charge, avant l’injection du code utilisateur // Le code utilisateur n’a même pas encore été injecté ! const myProp = getValue('myProp'); // ERREUR : getValue n’est pas défini});

Vous n’avez pas besoin d’utiliser DOMContentLoaded ou window.onload dans custom widgets. Tulip charge les ressources dans l’ordre suivant : bibliothèques tierces, code HTML de l’utilisateur, puis JavaScript. Au moment où votre code JavaScript s’exécute, le DOM est déjà disponible. Vous pouvez accéder directement aux éléments du DOM sans attendre un événement de chargement, comme le montre l’exemple ci-dessous :

// Appel direct sans risque — le DOM est déjà chargé lorsque ce code s'exécute.const btn = document.getElementById('my-btn');

Ancien moteur d’exécution

Dans l’ancien runtime, l’attribut srcDoc injecte le code du widget personnalisé dans l’iframe, ainsi que des fonctions utilitaires telles que getValue() et les valeurs initiales des props. Comme le code du widget fait partie du document dès le début, getValue() et vos props sont disponibles dès que le contenu de l’iframe est chargé.Par conséquent, DOMContentLoaded et window.onload fonctionnent comme prévu dans l’ancien runtime, comme le montre l’exemple suivant :

document.addEventListener('DOMContentLoaded', function() { // Se déclenche lorsque le code HTML de l'utilisateur est prêt // Les propriétés sont déjà disponibles const myProp = getValue('myProp'); // Fonctionne immédiatement});

{{snippet.Avez-vous trouvé ce que vous cherchiez ?}}