Erstellen Sie in Tulip Ihre eigenen Widgets mit HTML, CSS und JavaScript
Users on Professional plans and above.
Please allow Outgoing access to Custom Widgets. See Networking requirements for a Tulip Cloud Deployment to learn more.* *.tulip-custom-widgets.com
Die Elemente, die Sie per Drag-and-Drop in Ihre App ziehen, werden als Widgets bezeichnet. Dabei kann es sich um Schaltflächen, Bilder, Eingabefelder und alles andere handeln, was in der Symbolleiste verfügbar ist.

Mit benutzerdefinierten Widgets können Sie Ihren eigenen Code schreiben und eigene Widgets erstellen, die Sie dann per Drag-and-Drop in Ihre Anwendung einfügen und mit denen Sie interagieren können. Diese Funktion ermöglicht es Tulip-Nutzern, die Grenzen des Möglichen in Tulip zu erweitern.
Sehen Sie sich das folgende Video an, um einen Überblick über diese Funktion zu erhalten:
Sehen Sie sich die verfügbaren benutzerdefinierten Widgets in der Tulip-Bibliothek an.
Grundlagen zu benutzerdefinierten Widgets
Den Verwaltungsbildschirm für benutzerdefinierte Widgets finden Sie auf der Seite mit Ihren Kontoeinstellungen. Hier können Sie Widgets erstellen und löschen. Mit dem Editor für benutzerdefinierte Widgets können Sie Ihren Widget-Code schreiben, Props und Events erstellen und eine Vorschau Ihres Widgets anzeigen. Das folgende Diagramm beschreibt, was Props und Events sind:

Props: Props sind Daten, die zwischen benutzerdefinierten Widgets und Tulip-Anwendungen ausgetauscht werden. Props werden im Anwendungseditor angezeigt und ermöglichen es App-Editoren, auszuwählen, welche Variablen, Tabelleneinträge oder andere Informationen mit dem Prop verknüpft werden sollen.

Ereignisse: Ereignisse sind Signale, die Ihr Widget an Ihre Anwendung senden kann. Ereignisse ermöglichen es Ihrer Anwendung, mit einem Trigger zu reagieren, und können Informationen mit sich führen.

Auf benutzerdefinierte Widgets kann über das Dropdown-Menü „Benutzerdefiniert“ im App-Editor zugegriffen werden.

Ein Widget erstellen
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.
Der Bildschirm des Widget-Editors ist in vier Bereiche unterteilt. Der Code, den Sie im Bereich unten links eingeben, wird im Vorschaubereich angezeigt, sobald Sie in diesen Bereich klicken. Prop-Werte können zu Testzwecken direkt im Vorschaubereich geändert werden.

Es gibt spezielle Funktionen für die Interaktion mit Props und Events.
Den Wert einer Prop abrufen
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.
//Den Wert einer Prop abrufen getValue('My Prop');``//Prop in einer Variablen speichern let myVariable = getValue('My Prop');
Eine Aktion ausführen, wenn sich ein Prop ändert
//Etwas tun, sobald sich der Wert eines Props ändert getValue('My Prop', (internalVariable) => { doSomething(internalVariable);});
Den Wert eines Props festlegen
//Den Wert einer Text-Prop auf „hello!“ setzen setValue('My Text Prop', 'hello!');
//Den Wert eines Elements in einem Objekt festlegen: setValue('My Object Prop', { 'Key inside object': 'new value' });```
### Ein Ereignis auslösen
```**Ein Ereignis auslösen** fireEvent('event', *payload*);
// Ein Ereignis ohne Nutzdaten auslösen: fireEvent('My Event');
//Ereignis mit Nutzdaten auslösen: fireEvent('My Event', myVariable);```
Das folgende Diagramm veranschaulicht den Informationsfluss durch ein benutzerdefiniertes Widget in einer App. Die Eingabevariable „data“ wird in der Eigenschaft gespeichert. Die Wertänderung löst ein Ereignis aus, bei dem ein Trigger anschließend Daten in der Ausgabevariable speichert.
{height="" width=""}
## Widget-Erstellungsdemo
{height="576" width="1024"}
## Apps importieren/exportieren
Weitere Informationen zu dieser Funktion finden Sie unter [„Importieren und Exportieren](/r230/docs/import-export) “.
### Export
1. Wählen Sie in der Übersicht der benutzerdefinierten Widgets das **Drei-Punkte-Menü** neben dem Widget aus, das Sie exportieren möchten.
{height="" width=""}
1. Wählen Sie **„Exportieren**“ aus.
{height="" width="800"}
### Import
1. Wählen Sie in der Übersicht der benutzerdefinierten Widgets oben das **Drei-Punkte-Menü** aus.
{height="" width="800"}
1. Wählen Sie **„Importieren**“ aus.
{height="" width="350"}
1. Suchen Sie die .json-Datei des benutzerdefinierten Widgets.
## Externe Bibliotheken aktivieren
Externe Bibliotheken erweitern die Funktionen benutzerdefinierter Widgets zusätzlich. Externe Bibliotheken übernehmen die manuelle Arbeit, HTML direkt in JavaScript zu schreiben. Externe Bibliotheken müssen für jedes Widget aktiviert werden, in dem Sie sie verwenden möchten.
1. Klicken Sie in Ihrem Widget auf das **Drei-Punkte-Menü** oben rechts auf der Seite.
{height="" width=""}
1. Wählen Sie **„Externe Bibliothek aktivieren**“.
{height="" width="250"}
1. Wählen Sie die **Erweiterung** aus, die Sie aktivieren möchten.
{height="" width=""}
Hier finden Sie eine kurze Beschreibung der Funktionen der einzelnen Erweiterungen:
* **jQuery** – ermöglicht eine optimierte Auswahl von HTML-Elementen sowie die Bearbeitung dieser Elemente.
* **D3 –** Der Goldstandard für die Datenvisualisierung; zwar eine steile Lernkurve, aber enorme Flexibilität.
* **Google Visualization –** Ein großartiges Tool für genau das, was der Name schon sagt: Visualisierung.
* **Lodash –** Lodash bietet eine Vielzahl von Tools, die die Arbeit mit JavaScript-Datentypen vereinfachen.
* **ChartJS –** Die wohl einfachste Bibliothek zur Erstellung von Diagrammen, mit unzähligen Online-Ressourcen.
* **Moment –** Moment bietet zahlreiche Werkzeuge für die Arbeit mit Datums- und Zeitangaben.
## Runtime-Version
Die Art und Weise, wie benutzerdefinierte Widgets in der neuen Runtime geladen werden, wurde aktualisiert (siehe [Verbesserungen bei benutzerdefinierten Widgets](/r230/docs/custom-widget-improvements)). Diese Änderung wirkt sich darauf aus, wann Sie zuverlässig auf die Eigenschaften Ihres Widgets zugreifen können, beispielsweise auf solche, die mit `` `getValue()` `` abgerufen werden.
So überprüfen Sie, welche Runtime-Version Ihr Widget verwendet:
* Auf der Seite **mit der Liste der benutzerdefinierten Widgets** oder im Editor: Wenn neben dem Namen des Widgets ein **„Legacy“-Symbol** angezeigt wird, verwendet das Widget die alte Laufzeitumgebung. Ist kein Symbol vorhanden, verwendet das Widget die neue Laufzeitumgebung.
* Sehen Sie im Seitenbereich „Informationen zum benutzerdefinierten Widget“ im Abschnitt **„Laufzeitumgebung“** nach.
* Wenn in den **Berechtigungseinstellungen** eines benutzerdefinierten Widgets der Schalter **„Laufzeitumgebung für benutzerdefinierte Widgets** “ aktiviert ist, verwendet das Widget die neue Laufzeitumgebung.
:::(Info) (NOTE)
By default, newly created Custom Widgets use the old runtime.
:::
Um die Laufzeitversion für ein benutzerdefiniertes Widget zu wechseln, verwenden Sie den Schalter **„Laufzeit für benutzerdefiniertes Widget“** in **den Berechtigungseinstellungen**.{height="" width=""}
## Beispiele
Im Folgenden finden Sie einige benutzerdefinierte Widgets aus der [Tulip-Bibliothek](https://tulip.co/library/), die die oben beschriebenen Funktionen veranschaulichen:
### Einfaches Timer-Widget
[Einfaches Timer-Widget](https://library.tulip.co/apps/simple-timer-widget)
Ein benutzerfreundliches Timer-Widget zur Erfassung der verstrichenen Zeit während App-Workflows. Bediener können den Timer direkt über die App-Oberfläche starten, stoppen und zurücksetzen, was ihn für die Zeitmessung bei Aufgaben oder Prozessen nützlich macht.
{height="" width=""}
### Radiales Messanzeige-Widget
[Radiales Messanzeige-Widget](https://library.tulip.co/apps/radial-gauge-widget)
Dieses Widget stellt numerische Werte visuell mit einer anpassbaren Radialanzeige dar. Es eignet sich ideal, um Kennzahlen wie Maschinengeschwindigkeit, Temperatur oder Fortschritt in einem schnellen, leicht verständlichen Format anzuzeigen.
{height="" width=""}
### Zeitachsen-Widget
[Zeitachsen-Widget](https://library.tulip.co/apps/timeline-widget)
Zeigt Ereignisse oder Schritte entlang einer horizontalen Zeitleiste an und hilft Benutzern dabei, Prozessphasen, abgeschlossene Aufgaben oder zeitbezogene Daten auf einen Blick zu visualisieren.
{height="" width=""}
## Bewährte Vorgehensweisen
### 1. Verhindern Sie unbeabsichtigte Nebenwirkungen durch Widget-Code, der beim Laden ausgeführt wird
Tulip führt den eigentlichen Code Ihres Widgets bei jeder Darstellung aus – nicht nur im Player, sondern auch im Editor für benutzerdefinierte Widgets, im App-Editor und in der Live-Vorschau einer Station. So erhalten Sie eine genaue Live-Vorschau Ihres Widgets. Jeder Code mit Nebenwirkungen (z. B. API-Aufrufe, Tabelleneinträge, Benachrichtigungen) auf der obersten Ebene Ihres Skripts wird bei jeder Darstellung des Widgets ausgeführt, einschließlich jedes Mal, wenn das Widget im Editor erneut geöffnet oder die Vorschau neu geladen wird.
Verwenden Sie für alles, was Auswirkungen auf die reale Welt hat, einen expliziten Trigger (z. B. einen Tastendruck, ein ` `` fireEvent` `` als Reaktion auf eine Benutzeraktion oder eine Prop-Änderung), anstatt es automatisch beim Laden auszulösen.
```// GUT: Wird nur ausgelöst, wenn die App dies explizit anfordert getValue('TriggerIncrement', (shouldIncrement) => { if (shouldIncrement) { fetch("https://api.example.com/increment-counter", { method: "POST" }); }});
// SCHLECHT: Wird bei jedem Laden des Widgets ausgelöst, auch im Editor und in der Vorschaufetch("https://api.example.com/increment-counter", { method: "POST" });```Wenn ein Widget Daten automatisch laden muss, verwende einen schreibgeschützten oder idempotenten Endpunkt, damit wiederholte Ladevorgänge keine unerwünschten Auswirkungen haben.
### 2. Testen Sie iterativ, ohne echte Nebenwirkungen auszulösen
Wenn Sie ein Widget entwickeln, das eine Produktions-API mit echten Nebenwirkungen aufruft, verweisen Sie auf einen Test- oder Staging-Endpunkt, bis Sie mit der Iteration fertig sind, und wechseln Sie dann vor der Veröffentlichung zu einem Produktions-Endpunkt. Dies verhindert doppelte Datensätze, Benachrichtigungen oder andere Nebenwirkungen beim Bearbeiten und Anzeigen einer Vorschau Ihres Widgets.
Für Aktionen, die auch nach der Veröffentlichung in der Produktionsumgebung erfolgen müssen, integrieren Sie eine Bestätigung in die Benutzeroberfläche des Widgets selbst, anstatt sich auf den Ladezeitpunkt zu verlassen. Indem Sie eine sichtbare Aktion (z. B. einen Klick auf eine Schaltfläche) erfordern, hat die Person, die das Widget betrachtet, die Kontrolle darüber, wann der Effekt eintritt.
```// Erfordert einen expliziten Klick, bevor die eigentliche Aktion ausgelöst wird,// unabhängig davon, wie oft das Widget selbst bereits gerendert wurde.document.getElementById('submitBtn')?.addEventListener('click', () => { fetch("https://api.example.com/submit-order", { method: "POST" });});
3. Prop-Verwaltung – Überprüfe den Prop-Wert
Stellen Sie sicher, dass Ihr benutzerdefiniertes Widget Daten zuverlässig verarbeitet. Props dienen dazu, Informationen an Ihr Widget zu übergeben, doch ihre Werte können sich ändern oder sind anfangs möglicherweise undefiniert. Auch wenn Props einen festgelegten Typ haben, können sie anfangs undefiniert sein, was zu Fehlern in Ihrem benutzerdefinierten Widget-Code führen kann. Die Einhaltung bewährter Muster beugt Fehlern vor und macht Ihr Widget robuster.
Im folgenden Beispiel wird eine Callback-Funktion als zweiter Parameter für getValue verwendet. Dieser Callback wird jedes Mal ausgeführt, wenn das benutzerdefinierte Widget den aktualisierten Prop-Wert erhält.
Da der Anfangswert der Prop möglicherweise undefiniert ist und später der aktualisierte Wert empfangen werden könnte, sollten Sie sicherstellen, dass Sie die Konfiguration erst dann anwenden, wenn der aktualisierte Wert empfangen wurde. Die Verwendung von getValue ohne Callback-Funktion gibt den Wert der Prop zu diesem Zeitpunkt der Codeausführung zurück.
getValue('config', function(config) { if (config) { applyConfiguration(config); }});
Das folgende Beispiel wird nicht empfohlen, da der Anfangswert der Prop möglicherweise undefiniert ist:const config = getValue('config');applyConfiguration(config);
4. Ereignisbehandlung – Daten validieren
Ereignisdaten haben einen Typ. Zwar führt das Auslösen eines Ereignisses mit einem falschen Datentyp nicht zum Absturz oder zum Abbruch der Ausführung Ihres benutzerdefinierten Widget-Codes, doch empfiehlt es sich, eigene Prüfmechanismen einzubauen, um sicherzustellen, dass die in fireEvent übermittelten Daten validiert sind und den richtigen Typ aufweisen.
Diese Vorgehensweise verhindert unerwartetes Verhalten und macht Ihren Code vorhersehbarer.
if (data != null && typeof data === 'string') { fireEvent(eventName, data);} else { // Optional: Behandlung des Falls mit ungültigen Daten.}
5. Verwende eine Callback-Funktion für den asynchronen Zugriff auf Props
Wenn du `setValue()` aufrufst, erfolgt die Aktualisierung der Prop asynchron. Das bedeutet, dass sie nicht sofort stattfindet, da dein benutzerdefiniertes Widget über einen Iframe mit der Hauptanwendung kommuniziert. Die `setValue()` -Anfrage wird gesendet, aber dein Code läuft weiter, ohne darauf zu warten, dass die Änderung wirksam wird. Wenn du versuchst, direkt danach `getValue()` zu verwenden, erhältst du wahrscheinlich den alten Wert.
Um die aktuellsten Daten zu erhalten, verwenden Sie `getValue()` immer mit einer Callback-Funktion. Der Callback wird erst ausgeführt, wenn die Prop erfolgreich aktualisiert wurde, wodurch sichergestellt ist, dass Sie mit den neuesten Informationen arbeiten.
Das folgende Beispiel veranschaulicht diese bewährte Vorgehensweise:```// GUT: Asynchron auf den Wert einer Eigenschaft zugreifenconst newConfig = { setting: 'value' };setValue('config', newConfig);
// Die Callback-Funktion wird ausgeführt, sobald die Aktualisierung der Prop abgeschlossen ist.getValue('config', function(updatedConfig) { console.log('Die Prop wurde aktualisiert. Die neue Konfiguration lautet:', updatedConfig); // Mit dem garantiert aktuellen Wert arbeiten applyConfiguration(updatedConfig);});```
Das folgende Beispiel zeigt, was Sie vermeiden sollten:```// SCHLECHT: Dies funktioniert möglicherweise nicht wie erwartetconst newConfig = { setting: 'value' };setValue('config', newConfig);
// Hier wird wahrscheinlich der alte Wert protokolliert, nicht der neue.const currentConfig = getValue('config'); ```
6. Verwenden Sie unterstützte Header für API-Aufrufe
Achten Sie bei externen API-Aufrufen aus benutzerdefinierten Widgets auf die HTTP-Header, um CORS-Probleme (Cross-Origin Resource Sharing) zu vermeiden. Benutzerdefinierte Widgets werden in einer iframe-Umgebung mit spezifischen Sicherheitsbeschränkungen ausgeführt.
Verwenden Sie nur Header, die ausdrücklich unterstützt werden. Das Setzen nicht unterstützter Header wie „Content-Type“ oder anderer benutzerdefinierter Header kann CORS-Fehler auslösen und dazu führen, dass Ihre API-Aufrufe nicht funktionieren.
Das folgende Beispiel veranschaulicht diese bewährte Vorgehensweise:// GUT: Einfacher API-Aufruf ohne benutzerdefinierte Headerfetch("https://akhatri.bulb.cloud/api/v3/w/1/tables", { headers: { Authorization: "<API-Schlüssel>", },}) .then((response) => response.json()) .then((data) => console.log(data)) .catch((error) => console.error("Fehler:", error));
Das folgende Beispiel zeigt, was Sie vermeiden sollten:// FALSCH: Das Setzen des „Content-Type“-Headers führt zu CORS-Problemenfetch("https://api.example.com/data", { headers: { Authorization: "<API-Schlüssel>", "Content-Type": "application/json", // Dies führt zu CORS-Fehlern },})
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. Initialisieren Sie Widgets basierend auf Ihrer Laufzeitumgebung
Weitere Informationen finden Sie unter „Laufzeitversion “.
Neue Laufzeitumgebung
In der neuen Laufzeitumgebung gibt das src-Attribut eine leere HTML-Shell-Datei zurück. Nach dem Laden sendet diese Shell-Datei eine Nachricht an die übergeordnete App. Die übergeordnete App sendet den Code für das benutzerdefinierte Widget, die Hilfsfunktionen (wie z. B. getValue()) sowie die anfänglichen Prop-Werte, die in den iframe eingefügt werden sollen.
Aus diesem Grund werden „DOMContentLoaded“ und „window.onload“ ausgelöst, sobald die leere Shell geladen ist – noch bevor Ihr Widget-Code eingefügt wird. Jeder an diese Ereignisse angehängte Handler wird zu spät registriert: Das Ereignis ist bereits ausgelöst worden, sodass der Callback niemals ausgeführt wird. Der Code innerhalb dieser Handler wird stillschweigend übersprungen, wie im folgenden Beispiel gezeigt:
document.addEventListener('DOMContentLoaded', function() { // Wird ausgelöst, wenn SHELL geladen wird, vor dem Einfügen des Benutzercodes // Der Benutzercode wurde noch gar nicht eingefügt! const myProp = getValue('myProp'); // FEHLER: getValue ist nicht definiert});
In „ custom widgets “ müssen Sie weder „DOMContentLoaded“ noch „window.onload“ verwenden. Tulip lädt Ressourcen in folgender Reihenfolge: Bibliotheken von Drittanbietern, Benutzer-HTML und anschließend JavaScript. Wenn Ihr JavaScript ausgeführt wird, ist das DOM bereits verfügbar. Sie können direkt auf DOM-Elemente zugreifen, ohne auf ein Ladeereignis zu warten, wie im folgenden Beispiel gezeigt:
// Kann bedenkenlos direkt aufgerufen werden – das DOM ist bereits geladen, wenn dies ausgeführt wird.const btn = document.getElementById('my-btn');
Alte Laufzeitumgebung
In der alten Laufzeit fügt das srcDoc-Attribut den Code des benutzerdefinierten Widgets zusammen mit Hilfsfunktionen wie getValue() und den anfänglichen Prop-Werten in den iframe ein. Da der Widget-Code von Beginn an Teil des Dokuments ist, stehen getValue() und Ihre Props sofort nach dem Laden des iframe-Inhalts zur Verfügung.Daher funktionieren `DOMContentLoaded und ``window.onload `` ` in der alten Laufzeit wie erwartet, wie das folgende Beispiel zeigt:
document.addEventListener('DOMContentLoaded', function() { // Wird ausgelöst, wenn die HTML-Datei des Benutzers bereit ist // Props sind bereits verfügbar const myProp = getValue('myProp'); // Funktioniert sofort});
{{snippet.Hast du gefunden, wonach du gesucht hast?}}
