Rent a car: Search as you type
Hard-coding the car list works for a demo but real applications fetch it from a service. The dropdown widget has two events that hook into async data:
onLoad— fired when the dropdown is opened with no items loaded yet.onFilter— fired as the user types in the search input.
Wire both to event names, and handle them in your application’s formEvent callback.
Step 1 — A simple service stub
Section titled “Step 1 — A simple service stub”Pretend you have a service that returns cars matching a query string. We’ll fake it with a setTimeout to emulate network latency:
type Car = { id: string; label: string; img: string; price: number };
const ALL_CARS: Car[] = [ { id: 'compact', label: 'Compact', img: '🚗', price: 35 }, { id: 'suv', label: 'SUV', img: '🚙', price: 75 }, { id: 'convertible', label: 'Convertible', img: '🏎️', price: 110 }, { id: 'luxury', label: 'Luxury', img: '🚘', price: 180 }, { id: 'minivan', label: 'Minivan', img: '🚐', price: 95 }, { id: 'pickup', label: 'Pickup', img: '🛻', price: 85 },];
function searchCars(query: string): Promise<Car[]> { return new Promise((resolve) => { setTimeout(() => { const q = query.toLowerCase(); resolve(ALL_CARS.filter((c) => c.label.toLowerCase().includes(q))); }, 250); });}Step 2 — Wire onLoad and onFilter
Section titled “Step 2 — Wire onLoad and onFilter”In the form definition, attach event handlers:
import { gui } from '@golemui/gui-shared';
gui.inputs.dropdown('car', { label: 'Select car', labelField: 'label', valueField: 'id', itemRenderer: 'carItemRenderer', inputDebounce: 300, on: { load: 'loadCars', filter: 'filterCars' },});The inputDebounce: 300 prop tells the dropdown to wait 300ms after the user stops typing before firing onFilter.
Step 3 — Handle the events
Section titled “Step 3 — Handle the events”In your formEvent callback, populate the dropdown by emitting an update for the dropdown’s items:
async function handleFormEvent(event) { if (event.name === 'loadCars' || event.name === 'filterCars') { const query = event.detail ?? ''; const cars = await searchCars(query); event.update({ path: 'car', items: cars }); }}Pass handleFormEvent to <gui-form> via the formEvent callback.
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: 'submit', }),];{ "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": "submit" } } ]}Type in the dropdown — the list filters server-side after a 300ms pause.
See also
Section titled “See also”- Features / Form Events — every event hook.
- Widgets Reference / Dropdown — every dropdown prop.