Widget reference

Element widget: Python-logica direct op je dashboard

De Element widget is de meest flexibele dashboardwidget in Dashview. Zij voert een gepubliceerd Element uit en kan een renderbare widgetconfig, interactieve UI of beide teruggeven. Daarmee kun je dynamische widgets bouwen die standaard widgettypes combineren met Python-logica en eigen knoppen of formulieren.

Element widget: Python-logica direct op je dashboard overzicht
Element widget: Python-logica direct op je dashboard overzicht.

1. Wanneer kies je voor een Element widget?

Dynamische visualisatie

Gebruik deze widget wanneer het uiteindelijke widgettype of de inhoud pas tijdens runtime bepaald kan worden.

Python-first logica

Geschikt voor complexe berekeningen, custom renderinglogica of output die niet netjes in een gewone chart of tabel past.

Herbruikbaar bouwblok

Wanneer hetzelfde Element ook in een automation of datasource-helperflow terugkomt, houd je logica gecentraliseerd.

Niet voor

Gebruik geen Element widget voor simpele KPI’s, tabellen of charts die met standaardopties al onderhoudbaar zijn.

2. Runtime-instellingen

Optie Verplicht Beschrijving
elementIdJaHet Element dat de widget aanstuurt. Zonder deze waarde kan de widget niet draaien.
elementVersionNeeLaat leeg voor de laatste gepubliceerde versie of pin bewust een specifieke release.
entrypointNeeOverride op de standaard entrypoint als je Element meerdere uitvoerpaden heeft.
timeoutSecondsNeeBeschermt het dashboard tegen te lange runs; houd dit laag genoeg voor een prettige UX.
tableNameNeeNaam voor inline row-output wanneer het Element naast de widgetconfig ook rijen terugstuurt.

3. Filterdoorgifte en params

Een Element widget kan de actieve dashboardcontext meekrijgen. Dat maakt haar krachtig, maar ook gevoelig voor verkeerde aannames. Test daarom altijd met echte paginafilters.

{
  "from": "2026-03-01",
  "to": "2026-03-31",
  "segment": "enterprise",
  "showWarnings": true
}

4. Thema context

Bij elke Element widget-run voegt Dashview runtime metadata toe aan params.__dashview. Gebruik dit om HTML of CSS passend te maken voor lightmode en darkmode.

{
  "__dashview": {
    "theme": {
      "mode": "dark",
      "isDark": true,
      "isLight": false
    },
    "widget": {
      "id": "widget_dynamic_html",
      "type": "element"
    }
  }
}
def run(context, params):
    theme = (params.get("__dashview") or {}).get("theme") or {}
    dark = theme.get("mode") == "dark"
    bg = "#111827" if dark else "#ffffff"
    fg = "#f9fafb" if dark else "#111827"

    return {
        "widget": {
            "id": "theme_aware_html",
            "type": "html",
            "options": {
                "content": f"<div style='background:{bg};color:{fg}'>Theme aware</div>",
                "sanitizeHtml": True
            }
        }
    }

5. Resultaatcontract

Wat het Element terug moet geven

  • Een renderbare widgetconfig, meestal onder result.widget.
  • Of een interactieve UI onder result.ui, bijvoorbeeld met tekst, knoppen of formulieren.
  • Je mag result.widget en result.ui tegelijk teruggeven. De interactieve UI staat standaard onder de widget; gebruik placement: "before" om haar erboven te tonen.
  • Optioneel extra rows of feed-data als de uiteindelijke widget die zelf nodig heeft.
  • Een stabiele, begrijpelijke outputvorm zodat het dashboard voorspelbaar blijft bij volgende releases.

Wat je beter vermijdt

  • Een tweede Element widget als resultaat. Dat maakt de runtimeketen onnodig moeilijk te begrijpen.
  • Te zware of te trage renderlogica voor elke dashboardrefresh.
  • Verborgen afhankelijkheden van secrets, runtime vars of filters zonder documentatie.

6. Interactieve UI

Met result.ui kan een Element widget kleine interactieve applicaties renderen zonder aparte frontendcode. Ondersteunde nodes zijn container, row, text, html, button, form, tabs, divider en spacer. Formuliervelden ondersteunen text, textarea, number, select, checkbox en hidden. Nodes mogen optioneel className en een beperkte style-map meegeven voor layout en presentatie, bijvoorbeeld om een row als CSS-grid te renderen en echte Dashview-knoppen in dezelfde tabelregel te plaatsen.

Gebruik lokale tabs om vooraf geladen, geautoriseerde panelen te wisselen zonder het Element opnieuw uit te voeren.

def run(context, params):
    dashview = params.get("__dashview") or {}
    interaction = dashview.get("interaction")
    state = dashview.get("state") or {"count": 0}

    if interaction and interaction.get("id") == "increment":
        state["count"] = int(state.get("count") or 0) + 1

    return {
        "widget": {
            "type": "html",
            "options": {"content": "<p>De normale widget-output blijft werken.</p>"}
        },
        "state": state,
        "ui": {
            "type": "container",
            "placement": "after",
            "children": [
                {"type": "text", "variant": "title", "text": f"Teller: {state['count']}"},
                {"type": "button", "id": "increment", "label": "Verhoog", "severity": "success"},
                {
                    "type": "form",
                    "id": "feedback",
                    "label": "Feedback",
                    "fields": [
                        {"id": "message", "label": "Bericht", "type": "textarea", "required": True},
                        {"id": "urgent", "label": "Urgent", "type": "checkbox"}
                    ],
                    "submitLabel": "Verstuur"
                }
            ]
        }
    }

Een klik of formulier-submit voert hetzelfde Element opnieuw uit. Dashview stuurt de actie mee als params.__dashview.interaction en de vorige state als params.__dashview.state. HTML-nodes worden door de frontend gesaneerd en zijn bedoeld voor markup; JavaScript en inline event handlers worden niet uitgevoerd.

Element-apps: UI-versie 2

Met UI-versie 2 bouw je zelfstandige interactieve apps met lokale status, formulieren, bewerkbare tabellen, dialoogvensters, navigatie, opgeslagen concepten en JavaScript in een sandbox. Bestaande Element-widgets blijven werken. Hieronder vind je de API-referentie met uitvoerbare voorbeelden.

Schakel de appweergave in met result.ui.version = 2. Bestaande UI-resultaten blijven ondersteund. Beide weergaven behouden formulierconcepten en de lokale tabselectie na actieresultaten; geef resetForms: true of resetTabs: true terug om ze te wissen. Bij een andere uitvoeringscontext van de widget begint een nieuwe appsessie. Een app kan naast een native result.widget staan; ui.placement kiest ervoor of erna.

Dialogen in native Element-apps zijn niet-modaal: ze hebben geen donkere achtergrond en blokkeren het scrollen van de pagina niet.

Widgethoogte en scrollbare tabellen

Zet fitHeight: true op de UI-root om een native Element-app binnen de widgethoogte te houden. Tabpanelen delen de resterende hoogte. Geef een tabel style: {"flex-grow":"1","flex-basis":"0","min-height":"0"} in een paneelcontainer met overflow: hidden om alleen de tabel te laten scrollen en omliggende bediening zichtbaar te houden. Vereist frontendondersteuning; standaard blijft de hele app scrollbaar.

Native apps can set ui.autoRefresh = {intervalSeconds: 60, enabled: {"$ref":"state.liveEnabled"}, action: {type:"execute", id:"refresh", statusDisplay:"inline"}}. The interval must be an integer from 10 to 86400 seconds. Refresh runs only while the page is visible, enabled, and no app action or confirmation is active. It uses the normal authorized Element action path and schedules the next interval after completion. Closing or changing the widget disposes the timer; it does not cancel an already running server request. Use a native checkbox bound to state.liveEnabled to pause/resume. Requires a supporting frontend; use automations for unattended work.

Dashboardwidgets met app-status verbinden

Een Element-app kan lokale rijen leveren aan KPI-, tabel- of grafiekwidgets op hetzelfde dashboard. Zet op de Elementwidget de eigenschap stateFeeds, bijvoorbeeld {"items":"items"}. Zet op de ontvangende widget elementData: {"widgetId":"app-id","feed":"items"} en configureer de normale kolommen of meetwaarde. Beide eigenschappen staan bovenaan de widgetconfiguratie. Configureer de producer via Elementopties → State-feeds: voeg feeds toe of verwijder ze, geef iedere feed een unieke naam en een statuspad zoals rows of results.items, of kies Expressie (JSON). Kies Standaard (snapshot), Snapshot of Live als updatemodus; dit bepaalt niet het uitvoeringsinterval. Bestaande koppelingen en expressies worden ingeladen. Ongeldige paden/JSON, dubbele of lege namen en meer dan 16 feeds blokkeren opslaan. Werk bij hernoemen ook de bindingen van gekoppelde widgets bij. De ontvangende elementData-binding blijft via widget-JSON beschikbaar.

In datasourcekeuzes verschijnen feeds als Element · widgettitel · feednaam (view). Zonder widgettitel wordt het Element-id gebruikt, met spaties in plaats van underscores en koppeltekens; daarna het widget-id. De interne datasource-identiteit en bestaande bindingen blijven gelijk.

With a supporting frontend, an Element widget can opt into stateFeedUpdateMode: "live". Non-empty updates keep the datasource identity and use the normal realtime refresh path, preserving native widget state. Each publication replaces the full row snapshot; it does not append rows. Unchanged projections send no update. Empty/error feeds clear output and release the old identity, and page/user/permission changes still dispose all feeds. The default "snapshot" mode keeps immutable datasource identities for each update. Live mode is for latest-state displays, not immutable historical query snapshots.

Een feed bevat een statuspad of declaratieve expressie met state als bron. Een object wordt één rij; een array wordt een lijst rijen. Alleen scalaire kolommen worden doorgegeven. Limieten: 16 feeds, 10.000 rijen en 256 KiB per projectie. Lokale wijzigingen, Python-antwoorden en herstelde concepten werken de widgets bij; lege lijsten wissen oude resultaten. Deze feeds blijven binnen de browsersessie en zijn geen gedeelde tenantdatasources of automatiseringsbronnen. Het Element moet bronrechten en verplichte filters blijven afdwingen. Deploy eerst een frontend die deze koppelingen ondersteunt.

Lokale status, voorwaarden en koppelingen

{"$ref":"state.selected.name"} leest een waarde. Beschikbare hoofdpaden zijn state, forms, route en jobs; rijacties ontvangen ook row en rowIndex. Expressies gebruiken {"$op":"add","args":[...]}. Operatoren: eq, ne, and, or, not, gt, gte, lt, lte, add, multiply, length, concat, coalesce, sum. Gebruik voor een totaal {"$op":"sum","args":[{"$ref":"state.rows"},"amount"]}. Tekst wordt nooit als code uitgevoerd. UI-knopen en velden ondersteunen expressies voor visible en disabled. Bij formuliervelden geeft value een standaardwaarde; bind: "state.name" maakt een directe tweerichtingskoppeling.

def run(context, params):
    runtime = params.get("__dashview") or {}
    state = runtime.get("state") or {"count": 0, "name": ""}
    interaction = runtime.get("interaction") or {}
    if interaction.get("id") == "save_name":
        # Controleer alle waarden en rechten opnieuw in Python voordat je schrijft.
        name = str((interaction.get("values") or {}).get("name", "")).strip()
        if not name:
            return {"fieldErrors": {"profile.name": "Voer een naam in."}}
        state["name"] = name
        return {"state": state, "notifications": ["Naam bijgewerkt"]}

    return {
        "state": state,
        "ui": {
            "version": 2, "type": "container", "id": "app",
            "persistence": {"scope": "user", "restore": True},
            "children": [
                {"type": "text", "text": {"$ref": "state.count"}},
                {"type": "button", "label": "Lokaal verhogen", "action": {
                    "type": "set", "path": "count",
                    "value": {"$op": "add", "args": [{"$ref": "state.count"}, 1]}
                }},
                {"type": "form", "id": "profile", "fields": [
                    {"id": "name", "label": "Naam", "type": "text",
                     "bind": "state.name", "required": True, "maxLength": 100}
                ], "action": {"type": "execute", "id": "save_name"}},
                {"type": "button", "label": "Concept opslaan",
                 "action": {"type": "save", "scope": "user"}}
            ]
        }
    }

Bediening en acties

Knooptypen: container, row, view, text, html, button, form, table, tabs, dialog, drawer, divider, spacer, progress. Gebruik stabiele unieke ID's, vooral voor formulieren, weergaven, tabbladen, dialoogvensters en zijpanelen. De indeling gebruikt de ondersteunde style-eigenschappen en bestaande className-klassen. HTML-knopen worden opgeschoond en voeren geen scripts uit.

Navigatie, gedeeltelijke resultaten en achtergrondwerk

Een view-knoop wordt getoond als zijn ID overeenkomt met route.view. Stel ui.initialView in en navigeer met weergave/parameters; de app houdt een eigen teruggeschiedenis bij. De dashboard-URL bewaart de route onder element-{widgetId}, zodat je deze kunt delen. Gebruik record-ID's in routeparameters, geen gevoelige gegevens; een directe link geeft geen extra toegang.

Python-acties kunnen alleen state, een oppervlakkige statePatch, fieldErrors, notifications of uiPatch: [{id: "summary", node: {...}}] teruggeven. Een UI-patch vervangt de benoemde deelboom en behoudt diens ID. Gelijktijdige of vertraagde volledige statusresultaten overschrijven geen nieuwere lokale bewerkingen. Geef na geslaagde wijzigingen refreshDataSources: ["source_id"] terug om toegestane gerelateerde data te verversen.

Externe acties draaien op de achtergrond terwijl lokale bediening bruikbaar blijft (vier gelijktijdige acties; dezelfde actie-ID kan niet overlappen). Losse aanroepen tonen voortgang zonder percentage. Werk in meerdere stappen retourneert job: {status: "running", message: "Halverwege", progress: 50, next: {id: "next_step", values: {cursor: 10}}}; de volgende aanroep begint na het resultaat, niet op een timer. Geef een voltooide of mislukte status terug om te stoppen. Iedere aanroep behoudt de huidige gebruikersrechten. Een keten heeft maximaal 1.000 stappen en stopt met vervolgstappen als de widget sluit of van context verandert; een al lopende Python-aanroep wordt niet geannuleerd. Bewaar expliciete controlepunten op de server voor hervatbaar werk. Gebruik automations voor werk dat ook na het sluiten van het dashboard moet doorgaan.

Laat bij een lang verzoek de volledige weergave staan: voeg activateTab: {id: "sections", value: "reports"} toe aan de execute-actie. Dit selecteert direct het bestaande, zichtbare en ingeschakelde tabblad zonder de weergave of formulierconcepten te vervangen. Met statusDisplay: "inline" vervalt de automatische taakstatus; toon zelf voortgang en fouten via jobs.<actionId>.status en jobs.<actionId>.label. Een progress-node zonder waarde toont onbepaalde voortgang. Controleer naast de taakstatus ook capabilities.inlineJobStatus om dubbele indicatoren in oudere frontends te voorkomen. Andere acties behouden hun standaardstatusweergave. Dit wijzigt geen toegangsrechten.

Opgeslagen concepten

Opslaan en laden gebruiken backendopslag binnen de tenant. Gebruikersbereik is privé voor de huidige gebruiker, de pagina/widget/Element-koppeling en de vingerafdruk van de toegangsrechten. Gewijzigde rechten of verplichte filters maken het vorige concept ongeldig. Tenantbereik wordt alleen gedeeld door onbeperkte Element-auteurs (bewerkrechten voor Elements, toegang tot alle datasources, geen verplichte filters). Demogebruikers kunnen niet opslaan. Dit biedt geen manier om gefilterde records te delen of schrijfrechten van datasources te omzeilen.

ui.persistence: {scope: "user", restore: true} herstelt een concept bij het openen; opslaan blijft expliciet. Status, formulierconcepten en de huidige approute worden opgeslagen, met maximaal 192 KiB en optimistische revisiecontrole. Een conflicterende opslagactie geeft een fout in plaats van een andere wijziging te overschrijven. Laad het nieuwste concept en verwerk de verschillen voordat je opnieuw opslaat. Zowel de frontend als de backendversie met appstatusopslag moet uitgerold zijn.

JavaScript-apps in een sandbox

Geef result.app = {html, css, javascript} terug in plaats van declaratieve UI om eigen JavaScript uit te voeren. Het geïsoleerde iframe biedt een Promise-gebaseerde window.dashview-brug: execute(id, values), getContext(), getState(), setState(state), save(scope), load(scope), navigate(view, params) en notify(message). De context bevat de huidige dashboardfilters en themakleuren, nooit inloggegevens. Vraag na een Python-actie of laadactie de nieuwste status op.

return {"state": {"count": 0}, "app": {
    "html": '<button id="add">Eén toevoegen</button><output id="count"></output>',
    "css": 'button { border-radius: 12px; padding: 12px; }',
    "javascript": """
        const state = await dashview.getState();
        const counter = document.getElementById('count');
        counter.textContent = state.count;
        document.getElementById('add').onclick = async () => {
            state.count += 1;
            await dashview.setState(state);
            counter.textContent = state.count;
        };
    """
}}

Het frame heeft een afgeschermde oorsprong, geen toegang tot de DOM van de bovenliggende pagina en een beperkend CSP dat externe scripts, verbindingen, ingebedde frames en eval blokkeert. Python-uitvoering en datatoegang lopen nog steeds via de bestaande, aan de widget gekoppelde backend. Publiceer alleen vertrouwde Element-code, net als bij Python-Elements. Zie de iframe-sandboxreferentie voor het isolatiemodel van de browser. Declaratieve UI- en apppayloads zijn beperkt tot 256 KiB en UI-bomen tot 1000 knopen.

7. Aanbevolen werkwijze

  1. Bouw en test het Element eerst los in de Elements-sectie.
  2. Publiceer daarna pas een stabiele versie.
  3. Koppel die gepubliceerde versie in de widget-editor.
  4. Controleer met echte dashboardfilters en tenantrechten of de widget exact de verwachte output geeft.
  5. Gebruik een pinned versie voor kritische dashboards wanneer voorspelbaarheid belangrijker is dan automatisch meeliften op de laatste publish.

8. Veelvoorkomende problemen

Probleem Wat meestal helpt
Widget slaat niet opControleer of elementId gevuld is en extra params geldig JSON-object zijn.
Runtimefout bij openen dashboardControleer publish-status, runtime vars/secrets en of het Element nog een geldige widgetconfig retourneert.
Output verandert onverwachtPin een specifieke elementVersion in plaats van altijd de nieuwste release te gebruiken.
Filters lijken niet mee te gaanControleer filterdoorgifte, filterbron en de sleutel waaronder filters in params belanden.

8. Editoruitleg

De widget is pas af wanneer zowel de editorconfig als de teruggegeven runtimeweergave klopt.