Events
Each widget kind exposes a small set of event hooks. Every event has a string name the form emits through formEvent; your application’s event-handler map catches the name and reacts (or, in the demos below, dispatches an OVERRIDE_WIDGET_PROP to push feedback right back into the form).
| Hook | Available on |
|---|---|
onClick | gui.actions.button, gui.actions.custom |
onChange | gui.inputs.*, gui.layouts.tabs, gui.layouts.accordion |
onLoad | gui.inputs.dropdown, gui.inputs.list, gui.inputs.select |
onFilter | gui.inputs.dropdown |
onBlur | gui.inputs.* |
actionType: 'submit' | gui.actions.button — validates the form on click, emits through the dedicated formSubmit output |
Each value is a flat camelCase prop on the shortcut — onClick: () => 'eventName', onBlur: 'eventName', etc. The DX layer translates them all into the lower-level on: { click | change | blur | … : 'name' } block the engine consumes.
The OVERRIDE_WIDGET_PROP action used throughout these examples targets a widget by path (the widget’s data-binding key, for control kinds) or by uid (the widget’s unique ID, for any kind). Use uid when the target is a display widget such as gui.displays.alert — those have no data path.
Wiring formEvent on the form component
Section titled “Wiring formEvent on the form component”Every demo on this page uses the same shape: a formDef array, a handleFormEvent function that switches on event.name, and a formEvent binding on the form component. Each framework wires the binding slightly differently — the handleFormEvent body itself is identical:
import type { FormEvent } from '@golemui/core';import { GuiForm } from '@golemui/gui-react';
function handleFormEvent(event: FormEvent) { // see each per-hook section below for the case body.}
const config = { formDef };
export function MyForm() { return <GuiForm config={config} formEvent={handleFormEvent} />;}import { Component } from '@angular/core';import { CommonModule } from '@angular/common';import type { FormEvent } from '@golemui/core';import { FormComponent } from '@golemui/gui-angular';
@Component({ imports: [CommonModule, FormComponent], selector: 'app-my-form', template: `<gui-form [config]="config" (formEvent)="handleFormEvent($event)"></gui-form>`,})export class MyForm { protected config = { formDef };
protected handleFormEvent(event: FormEvent) { // see each per-hook section below for the case body. }}import { html, LitElement } from 'lit';import { customElement } from 'lit/decorators.js';import type { FormEvent } from '@golemui/core';import '@golemui/gui-lit';
@customElement('my-form')export class MyForm extends LitElement { config = { formDef };
override createRenderRoot() { return this; }
handleFormEvent(event: FormEvent) { // see each per-hook section below for the case body. }
override render() { return html` <gui-form .config=${this.config} @formEvent=${(e: CustomEvent<FormEvent>) => this.handleFormEvent(e.detail)} ></gui-form> `; }}<script setup lang="ts">import type { FormEvent } from '@golemui/core';import { GuiForm } from '@golemui/gui-vue';
const config = { formDef };
function handleFormEvent(event: FormEvent) { // see each per-hook section below for the case body.}</script>
<template> <GuiForm :config="config" @form-event="handleFormEvent" /></template>import '@golemui/gui-components/index.css';import '@golemui/gui-lit';
function handleFormEvent(event) { // see each per-hook section below for the case body.}
const form = document.getElementById('app-form');form.config = { formDef };form.addEventListener('formEvent', (e) => handleFormEvent(e.detail));The per-hook sections below show only the formDef and the relevant if (event.name === '…') branch — drop them into your framework’s wiring above.
onClick
Section titled “onClick”Buttons fire onClick events. The handler matches by event.name and dispatches overrides via event.callback.
const formDef = [ gui.actions.button({ label: 'Click me', onClick: () => 'evClick', }), gui.inputs.textInput('evClickResult', { label: 'Last click', readonly: true, hint: 'Click the button.', }),];
function handleFormEvent(event: FormEvent) { if (event.name === 'evClick') { const time = new Date().toLocaleTimeString(); event.callback({ type: 'OVERRIDE_WIDGET_PROP', payload: { path: 'evClickResult', prop: 'hint', value: `Clicked at ${time}.`, }, }); }}onChange
Section titled “onChange”onChange fires whenever an input’s value changes. The handler reads event.data to see the live form state and can update sibling widgets.
const formDef = [ gui.inputs.textInput('evSource', { label: 'Type something', onChange: 'evChange', }), gui.inputs.textInput('evChangeResult', { label: 'Live value', readonly: true, hint: 'Type something to see live changes.', }),];
function handleFormEvent(event: FormEvent) { if (event.name === 'evChange') { const text = String(event.data.evSource ?? ''); event.callback({ type: 'OVERRIDE_WIDGET_PROP', payload: { path: 'evChangeResult', prop: 'hint', value: text ? `Current value: "${text}"` : 'Type something to see live changes.', }, }); }}onLoad and onFilter
Section titled “onLoad and onFilter”onLoad fires once when a widget mounts — the typical use is fetching items for a dropdown. onFilter fires when the user types into the dropdown’s search field; event.detail carries the query, and the handler returns a filtered list. They’re naturally paired:
const formDef = [ gui.inputs.dropdown('evColorPick', { label: 'Pick a color', items: [], labelField: 'label', valueField: 'value', height: 120, inputDebounce: 300, onLoad: 'evLoadColors', onFilter: 'evFilterColors', }),];
const ALL_COLORS = [ { value: 'red', label: 'Red' }, { value: 'green', label: 'Green' }, { value: 'blue', label: 'Blue' }, { value: 'yellow', label: 'Yellow' }, { value: 'orange', label: 'Orange' }, { value: 'purple', label: 'Purple' },];
function handleFormEvent(event: FormEvent) { if (event.name === 'evLoadColors') { setTimeout(() => { event.callback({ type: 'OVERRIDE_WIDGET_PROP', payload: { path: 'evColorPick', prop: 'items', value: ALL_COLORS }, }); }, 250); }
if (event.name === 'evFilterColors') { const q = String(event.detail ?? '').toLowerCase(); const filtered = q ? ALL_COLORS.filter((c) => c.label.toLowerCase().includes(q)) : ALL_COLORS; event.callback({ type: 'OVERRIDE_WIDGET_PROP', payload: { path: 'evColorPick', prop: 'items', value: filtered }, }); }}evLoadColors waits ~250 ms (a fake remote fetch) then dispatches OVERRIDE_WIDGET_PROP to fill the dropdown’s items. evFilterColors reads event.detail for the typed query and replaces the items with a filtered subset.
onBlur
Section titled “onBlur”Fires when focus leaves an input. Useful for validating-on-blur, marking a field as touched in your application state, or recording analytics events.
const formDef = [ gui.inputs.textInput('evEmailBlur', { label: 'Email', onBlur: 'evBlur', }), gui.inputs.textInput('evBlurResult', { label: 'Status', readonly: true, hint: 'Tab out of the email field above.', }),];
function handleFormEvent(event: FormEvent) { if (event.name === 'evBlur') { event.callback({ type: 'OVERRIDE_WIDGET_PROP', payload: { path: 'evBlurResult', prop: 'hint', value: 'You tabbed out of the email field.', }, }); }}actionType: 'submit'
Section titled “actionType: 'submit'”Setting actionType: 'submit' on a button makes the click trigger form-level validation first: every input is touched, validators run, and the result only emits if the whole form is valid — through the dedicated formSubmit output on the form component (not formEvent).
import type { FormSubmitEvent } from '@golemui/core';
const formDef = [ gui.inputs.textInput('evEmail', { label: 'Email', validator: { required: true, format: 'email', messages: { required: 'An email is required', format: 'Enter a valid email', }, }, }), gui.actions.button({ label: 'Submit', actionType: 'submit' }), gui.inputs.textInput('evSubmittedEmail', { label: 'Result', readonly: true, hint: 'Enter a valid email and click Submit.', }),];
function handleFormSubmit(event: FormSubmitEvent) { // event.data contains the validated form data console.log('Submitted:', event.data.evEmail);}Wire formSubmit on the form component:
<GuiForm config={config} formSubmit={handleFormSubmit} /><gui-form [config]="config" (formSubmit)="onFormSubmit($event)"></gui-form>html`<gui-form .config=${this.config} @formSubmit=${this.onFormSubmit}></gui-form>`<GuiForm :config="config" @form-submit="onFormSubmit" />form.addEventListener('formSubmit', (e) => onFormSubmit(e.detail));Try clicking submit while the email is empty or invalid — validation messages appear and formSubmit never fires. Enter a valid email and click again to see the result.
onClick vs. the other hooks
Section titled “onClick vs. the other hooks”There’s one asymmetry worth knowing about. The handler shape differs between onClick and every other hook:
| Hook | What the handler receives |
|---|---|
onClick | event.data — only the form data, no callback / update |
onChange, onLoad, onFilter, onBlur | The full wrapped DxFormEvent — data, detail, callback, update |
onClick always receives a function — either (data) => 'eventName' (routes through formEvent) or (data) => void (side-effecty callback). For click-driven dispatch back into the form, return an event name string and handle it in your application’s formEvent callback.
onChange / onLoad / onFilter / onBlur can be either a string event name or a runtime function that reads live data and dispatches overrides (event.update({ path, prop: value })) without leaving the form definition. See Runtime Functions.
See also
Section titled “See also”- Features / Form Events — handling events in your application code.
- Runtime Functions — event handlers as runtime callbacks.
- Extending GolemUI / Widgets / Sending events — emitting events from a custom widget.