Logo

Forms

XMLUI enables to create forms without the hassle of managing, editing, validating, and saving the information you provide in the UI.
This example demonstrates the core elements: Form and FormItem.
<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>
<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>
  • Form encapsulates the management of form UI elements and data handling
  • The data property holds the form's data
  • FormItem manages a piece of the data
    • bindTo specifies the property name within the data to bind the corresponding field
    • type determines the kind of input field needed for a given piece of data (number field, text area field, radio buttons, etc.)
    • other properties support styling or validation

Form Layouts

You can use any of XMLUI's layout mechanisms with a Form. Here is a single-column format using FlowLayout.
<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>
<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>
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>
<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>
Use star sizing to allocate widths flexibly. Here Firstname and Lastname equally share the space remaining after the 100-px-wide Title.
<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>
<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>

FormItem

FormItem is an intermediary layer between Form and an input component; it manages the data represented by that component. The type property of a FormItem specifies what input component to render.

Checkbox

<App>
  <Form data="{{ option1: true, option2: false, option3: true }}"
    onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
    <FormItem type="checkbox" bindTo="option1" label="Option #1" labelPosition="end" />
    <FormItem type="checkbox" bindTo="option2" label="Option #2" labelPosition="end" />
    <FormItem type="checkbox" bindTo="option3" label="Option #3" labelPosition="end" />
  </Form>
</App>
<App>
  <Form data="{{ option1: true, option2: false, option3: true }}"
    onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
    <FormItem type="checkbox" bindTo="option1" label="Option #1" labelPosition="end" />
    <FormItem type="checkbox" bindTo="option2" label="Option #2" labelPosition="end" />
    <FormItem type="checkbox" bindTo="option3" label="Option #3" labelPosition="end" />
  </Form>
</App>

DatePicker

<App>
  <Form
    data="{{ birthDate: '2021-04-08' }}"
    onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
    <FormItem type="datePicker" bindTo="birthDate" label="Birthdate" />
  </Form>
</App>
<App>
  <Form
    data="{{ birthDate: '2021-04-08' }}"
    onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
    <FormItem type="datePicker" bindTo="birthDate" label="Birthdate" />
  </Form>
</App>

File

Use file 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>
<App>
  <Form
    data="{{ articles: null }}"
    onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
    <FormItem type="file" bindTo="articles" label="Articles file" />
  </Form>
</App>

Integer

<App>
  <Form
    data="{{ age: 30 }}"
    onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
    <FormItem type="integer" bindTo="age" label="Age" />
  </Form>
</App>
<App>
  <Form
    data="{{ age: 30 }}"
    onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
    <FormItem type="integer" bindTo="age" label="Age" />
  </Form>
</App>

Number

<App>
  <Form
    data="{{ distance: 192.5 }}"
    onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
    <FormItem type="number" bindTo="distance" label="Distance in miles" />
  </Form>
</App>
<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

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

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

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

TextBox

<App>
  <Form
    data="{{ name: 'Joe' }}"
    onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
    <FormItem type="text" bindTo="name" label="Name" />
  </Form>
</App>
<App>
  <Form
    data="{{ name: 'Joe' }}"
    onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
    <FormItem type="text" bindTo="name" label="Name" />
  </Form>
</App>

TextArea

<App>
  <Form
    data="{{ description: 'This is a description' }}"
    onSubmit="(toSave) => toast.success(JSON.stringify(toSave))">
    <FormItem type="textarea" bindTo="description" label="Description" />
  </Form>
</App>
<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

You can create a custom input component that uses the XMLUI forms infrastructure.
<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>
<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>
$value represents the current value of the component. $setValue changes the value.

Provide data

You can define a Forms data structure and initial values directly.
<Form data="{{ name: 'Joe', age: 43 }}" />
Or via an API endpoint.
<Form data="/path/to/resource" />
Use the bindTo property to access fields in the structure.
<Form data="{{ name: 'Joe' }}">
  <FormItem bindTo="name" />
</Form>

Refer to data

The $data variable holds all the form's data. You can use values in $data to control FormItem properties. Here the Switchs value sets the enabled property of a FormItem.
<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>
<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>
Other components in the form can reference the form's data too. Here the Text updates reactively when input values change.
<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>
<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>
You can drill into $data to reference nested fields.
<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>
<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>

Validate data

The Form handles client-side validation, reporting issues interactively. Server-side validation happens when the form data is sent to the server. The Form handles the server's response and displays it in a summary or below input fields.
These are the FormItem validation properties.

minLength

<App>
  <Form data="{{ name: 'Billy Bob' }}">
    <FormItem bindTo="name" minLength="10" label="minLength" />
  </Form>
</App>
<App>
  <Form data="{{ name: 'Billy Bob' }}">
    <FormItem bindTo="name" minLength="10" label="minLength" />
  </Form>
</App>
Try submitting with fewer than 10 characters.

maxLength

<App>
  <Form data="{{ name: 'Billy Bob' }}">
    <FormItem bindTo="name" maxLength="11" label="maxLength" />
  </Form>
</App>
<App>
  <Form data="{{ name: 'Billy Bob' }}">
    <FormItem bindTo="name" maxLength="11" label="maxLength" />
  </Form>
</App>
Try entering more than 11 characters.

minValue

<App>
  <Form data="{{ age: 30 }}">
    <FormItem bindTo="age" type="number" minValue="32" label="minValue" />
  </Form>
</App>
<App>
  <Form data="{{ age: 30 }}">
    <FormItem bindTo="age" type="number" minValue="32" label="minValue" />
  </Form>
</App>
Try entering a number smaller than 32.

maxValue

<App>
  <Form data="{{ age: 30 }}" >
    <FormItem bindTo="age" type="number" maxValue="29" label="maxValue" />
  </Form>
</App>
<App>
  <Form data="{{ age: 30 }}" >
    <FormItem bindTo="age" type="number" maxValue="29" label="maxValue" />
  </Form>
</App>
Try entering a number larger than 32.

pattern

Evaluate predefined regex patterns: "email", "url", or "phone".
See the pattern property of FormItem.

regex

Evaluate a custom regex pattern.
<App>
  <Form data="{{ password: 'hello' }}">
    <!-- Only all uppercase letters are accepted -->
    <FormItem bindTo="password" regex="/^[A-Z]+$/" label="regex" />
  </Form>
</App>
<App>
  <Form data="{{ password: 'hello' }}">
    <!-- Only all uppercase letters are accepted -->
    <FormItem bindTo="password" regex="/^[A-Z]+$/" label="regex" />
  </Form>
</App>

Compound validation

You can use multiple validations.
<App>
  <Form data="{{ site: 'http://www.example.com' }}">
    <FormItem bindTo="site" minLength="10" maxLength="30"
      pattern="url" label="Multiple Validations" />
  </Form>
</App>
<App>
  <Form data="{{ site: 'http://www.example.com' }}">
    <FormItem bindTo="site" minLength="10" maxLength="30"
      pattern="url" label="Multiple Validations" />
  </Form>
</App>

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

Predefined validations have built-in messages that you can change.
<App>
  <Form data="{{ age: 20 }}" >
    <FormItem
      bindTo="age"
      type="number"
      minValue="21"
      rangeInvalidMessage="The given age is too low!"
      label="Invalid Message" />
  </Form>
</App>
<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

Submit data

By default the Form component provides a submit button to save the modified data.
<App>
  <Form onSubmit="toast('Saved!')" />
</App>
<App>
  <Form onSubmit="toast('Saved!')" />
</App>
The onSubmit accepts either a block of code or function. When you use a function it receives data in a parameter; in this example it's called toSave but you can use any name. The function can be defined inline, in a code-behind file, or in index.html attached to the global window variable. See the Code chapter for details.
<App>
  <Form
    data="{{ name: 'Joe', age: 43 }}"
    onSubmit="(d) => toast(JSON.stringify(d))"
  >
    <FormItem label="name" bindTo="name" />
    <FormItem label="age" bindTo="age" />
  </Form>
</App>
<App>
  <Form
    data="{{ name: 'Joe', age: 43 }}"
    onSubmit="(d) => toast(JSON.stringify(d))"
  >
    <FormItem label="name" bindTo="name" />
    <FormItem label="age" bindTo="age" />
  </Form>
</App>
To submit via an APICall, use the event helper tag to bridge between the form and the API. The Forms data attribute maps to the APICall's $param context variable. A Toast popup reports success or error.
<App>
  <Form data="{{ name: 'Joe', age: 43 }}">
    <event name="submit">
      <APICall
        url="/api/contacts"
        method="POST"
        body="{$param}" />
    </event>
    <FormItem bindTo="name" label="name" />
    <FormItem bindTo="age" label="age" />
  </Form>
</App>

Form in ModalDialog

ModalDialog supports Form as a first-class citizen component. When a Form nests directly in a ModalDialog, the dialog's button row is replaced with the form's own button row. When form submission is successful, the dialog closes.
This site is an XMLUI™ app.