Skip to content

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).

HookAvailable on
onClickgui.actions.button, gui.actions.custom
onChangegui.inputs.*, gui.layouts.tabs, gui.layouts.accordion
onLoadgui.inputs.dropdown, gui.inputs.list, gui.inputs.select
onFiltergui.inputs.dropdown
onBlurgui.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.

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} />;
}

The per-hook sections below show only the formDef and the relevant if (event.name === '…') branch — drop them into your framework’s wiring above.

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 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 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.

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.',
},
});
}
}

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} />

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.

There’s one asymmetry worth knowing about. The handler shape differs between onClick and every other hook:

HookWhat the handler receives
onClickevent.data — only the form data, no callback / update
onChange, onLoad, onFilter, onBlurThe full wrapped DxFormEventdata, 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.