Rent a car: Submitting the form
The reservation button currently fires the special 'submit' event, which runs form-level validation but doesn’t notify your application code. Let’s wire it to a real handler instead, so we can read the data and confirm the booking.
Step 1 — Emit a custom event from the button
Section titled “Step 1 — Emit a custom event from the button”Replace onClick: 'submit' with on: { click: 'handleReservation' }. The 'submit' literal triggers form-level validation and emits the event only when the form is valid; for arbitrary event names, use the legacy on: { click: '<eventName>' } shape.
Step 2 — Listen for it
Section titled “Step 2 — Listen for it”In your formEvent callback, match the event name and read event.data:
function handleFormEvent(event) { if (event.name === 'handleReservation') { console.log('Reservation:', event.data); alert(`Reserved ${event.data.car} from ${event.data.collectOffice}`); }}
<FormComponent formDef={formDef} formConfig={formConfig} formEvent={handleFormEvent}/>@Component({ template: ` <gui-form [formDef]="formDef" [formConfig]="formConfig" (formEvent)="onFormEvent($event)" ></gui-form> `,})export class AppPage { onFormEvent(event: Core.FormEvent) { if (event.name === 'handleReservation') { console.log('Reservation:', event.data); } }}@customElement('app-page')export class AppPage extends LitElement { override render() { return html` <gui-form .formDef=${this.formDef} .formConfig=${this.formConfig} @formEvent=${(e) => this.onFormEvent(e.detail)} ></gui-form> `; }
onFormEvent(event) { if (event.name === 'handleReservation') { console.log('Reservation:', event.data); } }}Result
Section titled “Result”import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.dropdown('car', { labelField: 'label', valueField: 'id', itemRenderer: 'carItemRenderer', inputDebounce: 300, label: 'Select car', validator: { type: 'string', required: true, }, onLoad: 'loadCars', onFilter: 'filterCars', }), gui.inputs.dropdown('collectOffice', { labelField: 'label', valueField: 'id', items: [ { id: 'lhr', label: 'London Heathrow', }, { id: 'cdg', label: 'Paris CDG', }, { id: 'fra', label: 'Frankfurt Main', }, ], label: 'Collect from office', validator: { type: 'string', required: true, }, }), gui.inputs.booleanInput('differentReturn', { label: 'Choose a different return location', }), gui.inputs.dropdown('returnOffice', { labelField: 'label', valueField: 'id', items: [ { id: 'lhr', label: 'London Heathrow', }, { id: 'cdg', label: 'Paris CDG', }, { id: 'fra', label: 'Frankfurt Main', }, ], label: 'Return to office', validator: { type: 'string', required: true, }, }), gui.inputs.rangeCalendar('rentalDates', { label: 'Rental dates', validator: { type: 'array', required: true, minItems: 2, maxItems: 2, }, }), gui.inputs.radiogroup('rentalType', { options: [ { label: 'Daily', value: 'daily', }, { label: 'Weekly', value: 'weekly', }, { label: 'Monthly', value: 'monthly', }, ], label: 'Rental type', validator: { type: 'string', required: true, }, }), gui.inputs.booleanInput('driverOver25', { label: 'Driver aged over 25', validator: { type: 'boolean', const: true, required: true, }, }), gui.inputs.booleanInput('hasDiscountCode', { label: 'I have a discount code', }), gui.inputs.textInput('discountCode', { label: 'Discount code', validator: { type: 'string', required: true, minLength: 4, }, }), gui.actions.button({ label: 'Reserve', uid: 'submit', onClick: 'handleReservation', }),];{ "states": { "differentReturn": "$form.differentReturn === true", "hasDiscount": "$form.hasDiscountCode === true" }, "form": [ { "kind": "input", "type": "dropdown", "path": "car", "label": "Select car", "validator": { "type": "string", "required": true }, "on": { "load": "loadCars", "filter": "filterCars" }, "props": { "labelField": "label", "valueField": "id", "itemRenderer": "carItemRenderer", "inputDebounce": 300 } }, { "kind": "input", "type": "dropdown", "path": "collectOffice", "label": "Collect from office", "validator": { "type": "string", "required": true }, "props": { "labelField": "label", "valueField": "id", "items": [ { "id": "lhr", "label": "London Heathrow" }, { "id": "cdg", "label": "Paris CDG" }, { "id": "fra", "label": "Frankfurt Main" } ] } }, { "kind": "input", "type": "toggle", "path": "differentReturn", "label": "Choose a different return location" }, { "kind": "input", "type": "dropdown", "path": "returnOffice", "label": "Return to office", "include": { "in": ["differentReturn"] }, "validator": { "type": "string", "required": true }, "props": { "labelField": "label", "valueField": "id", "items": [ { "id": "lhr", "label": "London Heathrow" }, { "id": "cdg", "label": "Paris CDG" }, { "id": "fra", "label": "Frankfurt Main" } ] } }, { "kind": "input", "type": "rangeCalendar", "path": "rentalDates", "label": "Rental dates", "validator": { "type": "array", "required": true, "minItems": 2, "maxItems": 2 } }, { "kind": "input", "type": "radiogroup", "path": "rentalType", "label": "Rental type", "validator": { "type": "string", "required": true }, "props": { "options": [ { "label": "Daily", "value": "daily" }, { "label": "Weekly", "value": "weekly" }, { "label": "Monthly", "value": "monthly" } ] } }, { "kind": "input", "type": "toggle", "path": "driverOver25", "label": "Driver aged over 25", "validator": { "type": "boolean", "const": true, "required": true } }, { "kind": "input", "type": "toggle", "path": "hasDiscountCode", "label": "I have a discount code" }, { "kind": "input", "type": "textinput", "path": "discountCode", "label": "Discount code", "include": { "in": ["hasDiscount"] }, "validator": { "type": "string", "required": true, "minLength": 4 } }, { "kind": "action", "type": "button", "label": "Reserve", "uid": "submit", "on": { "click": "handleReservation" } } ]}Fill the form, click Reserve, and the JSON payload arrives in your formEvent callback.
Wrapping up
Section titled “Wrapping up”You’ve now built a real-world form that uses:
- declarative widget composition with
gui.layouts.gridandgui.layouts.flex, - validators per field,
- named states with
includerules, - a custom item renderer,
- async data loading with
onLoad/onFilterandinputDebounce, - form events to capture the submit.
Where next?
Section titled “Where next?”- Features — deep dives into every cross-cutting capability.
- Form Definition API — the canonical reference for
gui.*. - Extending GolemUI — building your own widgets.
- Widgets Reference — every built-in widget.