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; the data 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. Each FormItem has properties in order to handle data:
    • bindTo specifies the property name within the data to bind the corresponding field
    • type 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 ValueDescription
checkboxA checkbox representing a boolean value.
comboboxLets the user select an item from a dropdown list of filterable items. Use the input field to filter the list.
customA custom input component to enable developers to specify their own input fields.
datePickerAn input to select dates.
fileAn input to select a file or folder from the local machine.
integerAn input for integer values.
multiComboboxLets the user select mulitple items from a filterable dropdown list. Use the input field to filter the list.
multiSelectLets the user select mulitple items from a dropdown list.
numberAn input for numeric values (integer or floating-point).
radioGroupA group of radio buttons (only one can be selected).
selectLets the user select an item from a dropdown list.
switchA switch component to toggle a boolean value.
textA textbox to enter text.
textareaA 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.

Server-side Validation

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


Submitting with Warnings