Using Forms
XMLUI has a robust form infrastructure allowing you to create forms without the hassle of managing, editing, validating, and saving the information you provide in the UI.
The following example demonstrates the fundamental XMLUI concepts regarding forms:
<App>
<Form data="{{ name: 'Joe', age: 43 }}">
<FlowLayout>
<H3>Customer information</H3>
<FormItem bindTo="name" label="Customer name" />
<FormItem bindTo="age" label="Age" type="integer" zeroOrPositive="true" />
</FlowLayout>
</Form>
</App>
The code can be broken down into the following:
- The
Form
component encapsulates the management of form UI elements and data handling; thedata
property is used to provide data to the form. In this example, the data is a customer's name and age. - The
FormItem
component manages an individual attribute in the data. The form in the example uses two fields: one for the name and another for the age. EachFormItem
has properties in order to handle data:bindTo
specifies the property name within thedata
to bind the corresponding fieldtype
determines the type of the input field that should be used in order to modify a given piece of data (number field, text area field, radio buttons, etc.)- other properties are generally either for styling or to provide further constraints and arguments for a given input field (e.g. accept only 0 or positive numbers if a field has the number field type)
Form with FormItems
The Form
component is designed to work with FormItem
components inside it to utilize all built-in features
that help in managing forms: displaying, validating, and saving data.
However, there is no restriction on what components you can use within a form.
<App>
<Form
data="{{ search: 'Seattle' }}"
onSubmit="toast.success('Searching!')"
saveLabel="Search">
Please specify the name to include in the search:
<FormItem bindTo="search" />
<Button
label="Select from dictionary"
onClick="toast('Displaying dictionary')" />
</Form>
</App>
Non-FormItem
input components are not integrated into the form infrastructure.
The following example shows this by placing a regular Checkbox
component in the Form
.
<App>
<Form
data="{{ search: 'Seattle', caseSensitive: 'false' }}"
onSubmit="(toSave) => toast.success('Searching for ' + JSON.stringify(toSave))"
saveLabel="Search" >
Please specify the name to include in the search:
<FormItem bindTo="search" />
<Checkbox label="Case sensitive?" initialValue="true" />
</Form>
</App>
When you click "Search", the saved data structure does not contain the caseSensitive
field.
Adding other input components (as helpers) may still be helpful. You can use them to manipulate form data. For example, you can use the value of a checkbox to add some logic, such as copying some data fields into other fields automatically.
Form Layouts
You can create any layout within a Form
component.
Instead of manually placing the components within a form,
you can nest a form's content into a FlowLayout
and use the width property of individual FormItem
components with percentage or star sizing.
FlowLayout
is a responsive component. It ensures that your form is displayed correctly in mobile view as well.
Single-Column Forms
Unless you specify an explicit item width, each nested FormItem
(within a FlowLayout
) will fill the entire width of the form.
<App>
<Form data="{{ firstname: 'Jake', lastname: 'Hard', jobTitle: 'janitor', experience: 'broom' }}">
<FlowLayout>
<FormItem label="Firstname" bindTo="firstname" />
<FormItem label="Lastname" bindTo="lastname" />
<FormItem label="Job Title" bindTo="jobTitle" />
<FormItem label="Experience" bindTo="experience" />
</FlowLayout>
</Form>
</App>
Two-Column Forms
Set each item's width to "50%" to create a two-column layout:
<App>
<Form
data="{{
firstname: 'Jake',
lastname: 'Hard',
jobTitle: 'janitor',
experience: 'broom'
}}">
<FlowLayout>
<FormItem label="Firstname" bindTo="firstname" width="50%" />
<FormItem label="Lastname" bindTo="lastname" width="50%" />
<FormItem label="Job Title" bindTo="jobTitle" width="50%" />
<FormItem label="Experience" bindTo="experience" width="50%" />
</FlowLayout>
</Form>
</App>
Multiple Items with Star Sizing
XMLUI has layout containers that support star sizing for their children. This feature tells a component to fill up all remaining space in a row if other sibling component have their sizes specified.
<App>
<Form
data="{{
title: 'Mr.',
firstname: 'Jake',
lastname: 'Hard',
jobTitle: 'janitor',
experience: 'broom'
}}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FlowLayout>
<HStack>
<FormItem label="Title" bindTo="title" width="100px" />
<FormItem label="Firstname" bindTo="firstname" width="*" />
<FormItem label="Lastname" bindTo="lastname" width="*" />
</HStack>
<FormItem label="Job Title" bindTo="jobTitle" width="50%" />
<FormItem label="Experience" bindTo="experience" width="50%" />
</FlowLayout>
</Form>
</App>
Input Components within FormItem
The FormItem
component acts as an intermediary layer between the Form and whatever input control component is rendered through it.
It is responsible for managing the data represented by the corresponding input component.
The type
property of a FormItem
specifies what input component to render.
If this property is empty, FormItem
uses a TextBox
as the input field.
The FormItem
supports these input components:
type Value | Description |
---|---|
checkbox | A checkbox representing a boolean value. |
combobox | Lets the user select an item from a dropdown list of filterable items. Use the input field to filter the list. |
custom | A custom input component to enable developers to specify their own input fields. |
datePicker | An input to select dates. |
file | An input to select a file or folder from the local machine. |
integer | An input for integer values. |
multiCombobox | Lets the user select mulitple items from a filterable dropdown list. Use the input field to filter the list. |
multiSelect | Lets the user select mulitple items from a dropdown list. |
number | An input for numeric values (integer or floating-point). |
radioGroup | A group of radio buttons (only one can be selected). |
select | Lets the user select an item from a dropdown list. |
switch | A switch component to toggle a boolean value. |
text | A textbox to enter text. |
textarea | A multiline textbox to enter text. |
FromItem
renders a read-only text if you use a type
value not in this table.
If you omit type
, FormItem
renders a textbox.
However, if you nest a custom component into FormItem
, it behaves as if you set type
to custom
.
The following sections contain examples of using FormItem
with different input types.
Checkbox
The checkbox
type represents a checkbox input component:
<App>
<Form data="{{ option1: true, option2: false, option3: true }}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FormItem type="checkbox" bindTo="option1" label="Option #1" />
<FormItem type="checkbox" bindTo="option2" label="Option #2" />
<FormItem type="checkbox" bindTo="option3" label="Option #3" />
</Form>
</App>
DatePicker
The datePicker
type represents a date picker input component:
<App>
<Form
data="{{ birthDate: '2021-04-08' }}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FormItem type="datePicker" bindTo="birthDate" label="Birthdate" />
</Form>
</App>
File
The file
type represents an input component allowing you to select one or multiple files:
<App>
<Form
data="{{ articles: null }}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FormItem type="file" bindTo="articles" label="Articles file" />
</Form>
</App>
Integer
The integer
type represents an input component for integer values:
<App>
<Form
data="{{ age: 30 }}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FormItem type="integer" bindTo="age" label="Age" />
</Form>
</App>
Number
The number
type represents an input component for numeric values (integer or floating-point):
<App>
<Form
data="{{ distance: 192.5 }}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FormItem type="number" bindTo="distance" label="Distance in miles" />
</Form>
</App>
RadioGroup
The radioGroup
type represents a group of mutually exclusive radio buttons (only one of them can be selected):
<App>
<Form
data="{{ title: 'Mr.' }}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FormItem type="radioGroup" bindTo="title" label="Title">
<Option label="Mr." value="Mr." />
<Option label="Mrs." value="Mrs." />
<Option label="Ms." value="Ms." />
<Option label="Dr." value="Dr." />
</FormItem>
</Form>
</App>
Select
The select
type represents a dropdown list; only the listed items can be selected:
<App>
<Form
data="{{ size: 'xs' }}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FormItem type="select" bindTo="size" label="Box size">
<Option label="Extra small" value="xs" />
<Option label="Small" value="sm" />
<Option label="Medium" value="md" />
<Option label="Large" value="lg" />
</FormItem>
</Form>
</App>
Switch
The switch
type represents a switch component to toggle a boolean value:
<App>
<Form
data="{{ showBorder: true, showText: false, hideShadow: true }}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FormItem type="switch" bindTo="showBorder" label="Show border" labelPosition="right" />
<FormItem type="switch" bindTo="showText" label="Show text" labelPosition="right" />
<FormItem type="switch" bindTo="hideShadow" label="Hide shadow" labelPosition="right" />
</Form>
</App>
Text
The text
type represents a textbox to enter textual data:
<App>
<Form
data="{{ name: 'Joe' }}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FormItem type="text" bindTo="name" label="Name" />
</Form>
</App>
TextArea
The textarea
type represents a multiline textbox to enter textual data:
<App>
<Form
data="{{ description: 'This is a description' }}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FormItem type="textarea" bindTo="description" label="Description" />
</Form>
</App>
Custom Input Components
You can create your custom input component leveraging the XMLUI forms infrastructure.
You can nest your custom component's markup into the wrapping FormItem
as the following sample shows:
<App>
<Form
data="{{ userAvailable: false }}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FormItem bindTo="userAvailable">
<HStack>
<Button
label="Toggle"
backgroundColor="{$value === false ? 'red' : 'green'}"
onClick="$setValue(!$value)"
/>
</HStack>
</FormItem>
</Form>
</App>
Custom input fields can use a simple API to communicate with the form infrastructure.
The $value
identifier represents the current value of the component.
The custom component can use the $setValue
method to change the component value.
The following example shows a toggle button created using a regular Button
component using the form API:
Referencing the Form Data
When you work with forms, the UI's appearance and behavior often depends on the current field values.
The $data
context variable allows you to access the current data within the form.
The following example demonstrates how you can enable a field according to another's value.
<App>
<Form data="{{ isEnabled: true, name: 'Joe' }}">
<FormItem label="Enable name" bindTo="isEnabled" type="switch" />
<FormItem enabled="{$data.isEnabled}" label="Name" bindTo="name" />
</Form>
</App>
You can reference field values in any other component within the form as well:
<App>
<Form data="{{ firstname: 'John', lastname: 'Doe' }}">
<FormItem label="Firstname" bindTo="firstname" />
<FormItem label="Lastname" bindTo="lastname" />
<Text>Full name: {$data.firstname} {$data.lastname}</Text>
</Form>
</App>
By updating any input field, the text with the full name will update accordingly:
Managing Data
The XMLUI forms infrastructure helps you manage the form data easily.
The data
property can be accessed directly and you can drill down to relevant data attributes:
<App>
<Form
data="{{
name: 'John smith',
address: { street: '96th Ave N', city: 'Seattle', zip: '98005' }
}}"
onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
<FormItem bindTo="name" label="Name" />
<FormItem bindTo="address.street" label="Street" />
</Form>
</App>
There are two ways to provide data to an XMLUI component. The first is by defining the data directly:
<Form data="{{ name: 'Joe', age: 43 }}" />
The second is to use a URL to get data from an API endpoint:
<Form data="/path/to/resource" />
The attributes of the provided data can be accessed using the FormItem
component via the bindTo
property.
The FormItem
is designed to work with the Form
component and access the value that it is "bound to" from an arbitrary depth in the component tree under the Form
.
<Form data="{{ name: 'Joe' }}">
<FormItem bindTo="name" />
</Form>
Submitting Data
By default, the Form
component provides a submit button to save the modified data.
How this data will be used can be customized via the onSubmit
event.
All examples below are equal:
<App>
<Form onSubmit="toast('Saved!')" />
<Form event:submit="toast('Saved!')" />
<Form>
<event name="submit" value="toast('Saved!')" />
</Form>
</App>
The event accepts either a block of code or a callback function. In both cases, you use our Javascript-like scripting language to describe the logic.
When working with the callback function, you can access a toSave
function parameter that contains the form data:
<Form
data="{{ name: 'Joe', age: 43 }}"
onSubmit="(toSave) => toast(JSON.stringify(toSave))" />
Validation
The Form
handles client-side validation as you edit the form and shows any issues under the input fields.
Server-side validation happens when the form data is sent to the server.
The Form
handles the server-side validation response and displays it in a summary or below input fields (depends on response).
A FormItem
has several properties related to validation, all of which will be outlined in the following sections.
minLength
This property defines the minimum length of a text input value.
<App>
<Form data="{{ name: 'Billy Bob' }}">
<FormItem bindTo="name" minLength="10" label="minLength" />
</Form>
</App>
Delete at least 5 characters from the input field below and click outside the field.
maxLength
This property defines the maximum length of a text input value.
<App>
<Form data="{{ name: 'Billy Bob' }}">
<FormItem bindTo="name" maxLength="11" label="maxLength" />
</Form>
</App>
The input must be no longer than the specified length. Try to enter more than 11 characters in the input field below - the input field will not let you.
Sometimes, you do not want to constrain the number of characters in the text box; however, you want longer text to be marked as invalid.
Set the syncToValidation
property to "false" in order to do so.
<App>
<Form data="{{ name: 'Billy Bob' }}">
<FormItem bindTo="name" maxLength="11" syncToValidation="false" label="maxLength" />
</Form>
</App>
Now, you can type more than 11 characters into the box. However, as you click outside, the validation message will indicate that the text is longer than expected.
minValue
The input value must be at least the specified value. The given value is inclusive.
<App>
<Form data="{{ age: 30 }}">
<FormItem bindTo="age" type="number" minValue="32" label="minValue" />
</Form>
</App>
maxValue
The input value must be smaller than the specified value. The given value is inclusive.
<App>
<Form data="{{ age: 30 }}" >
<FormItem bindTo="age" type="number" maxValue="29" label="maxValue" />
</Form>
</App>
pattern
Evaluate predefined regex patterns. These patterns are the following: "email", "url", "phone".
<App>
<Form data="{{
mobile: '+13456123456',
website: 'http://www.blogsite.com',
email: 'myemail@mail.com'
}}">
<FormItem bindTo="mobile" pattern="phone" label="mobilePattern" />
<FormItem bindTo="website" pattern="url" label="websitePattern" />
<FormItem bindTo="email" pattern="email" label="emailPattern" />
</Form>
</App>
See the pattern property of the FormItem for details.
regex
Evaluate a user-defined custom regex pattern.
<App>
<Form data="{{ password: 'hello' }}">
<!-- Only all uppercase letters are accepted -->
<FormItem bindTo="password" regex="/^[A-Z]+$/" label="regex" />
</Form>
</App>
When setting custom regular expressions, be sure only set the pattern itself. There is no need to provide dashes, which may result in incorrect validation.
Combining Multiple Validations
You also can combine more than one validation on a FormItem
:
<App>
<Form data="{{ site: 'http://www.example.com' }}">
<FormItem bindTo="site" minLength="10" maxLength="30" pattern="url" label="Multiple Validations" />
</Form>
</App>
Other Validation Properties
Validation-specific Severity
By default, all validations have a severity level of "error". You can set whether a validation should have a level of "warning" or "error".
Every validation type has a corresponding severity associated with it. See the FormItem article for each.
<App>
<Form data="{{ mobile: '+13456123456', website: 'http://www.blogsite.com' }}" >
<FormItem
bindTo="mobile"
pattern="phone"
patternInvalidSeverity="warning"
label="mobilePattern" />
<FormItem
bindTo="website"
pattern="url"
patternInvalidSeverity="error"
label="websitePattern" />
</Form>
</App>
Validation-specific Messages
Sets what message to display as the result of validation. Predefined validations already have a built-in message but you can customize it as necessary.
Every validation type has a corresponding severity associated with it. See the FormItem article for each.
<App>
<Form data="{{ age: 20 }}" >
<FormItem
bindTo="age"
type="number"
minValue="21"
rangeInvalidMessage="The given age is too low!"
label="Invalid Message" />
</Form>
</App>
Server-side Validation
The Form
component can receive and display a server-side validation response.
- Field related issues are shown just like client-side validation errors, removed when a field is edited.
- Non-field related issues are displayed in a validation summary view.

User Feedback with APICall
Submitting the form via the APICall
component triggers a visual confirmation of the operation's success.
If the submission is successful, a success Toast
component will appear for a few seconds.
Otherwise, an error Toast
component will show the error that occured.
<App>
<Form data="{{ name: 'Joe' }}">
<event name="submit">
<APICall
url="/api/contacts/name"
method="POST"
body="{$param}" />
</event>
<FlowLayout>
<H3>Customer Name</H3>
<FormItem bindTo="name" label="Customer name" />
</FlowLayout>
</Form>
<Form data="{{ age: 43 }}">
<event name="submit">
<APICall
url="/api/contacts/age"
method="POST"
body="{$param}" />
</event>
<FlowLayout>
<H3>Customer Age</H3>
<FormItem bindTo="age" label="Age" type="integer" zeroOrPositive="true" />
</FlowLayout>
</Form>
</App>
Using in Modal Dialogs
The ModalDialog
component supports Form
s as a first-class citizen component:
if a Form
is provided as a direct child to a ModalDialog
, it dialog's button row is replaced with the form's own button row.
See a working example in this article.
Submitting with Warnings
It is possible to save and submit a form with warnings present. If this is the case, the user will be prompted by a confirmation dialog to proceed with sending the data to the backend.
