Edit this page

up.form [up-validate]
HTML selector

Renders a new form state when a field changes, to show validation errors or update dependent fields.

When a form field with an [up-validate] attribute is changed, the form is submitted to the server which is expected to render a new form state from its current field values. The form group around the changed field is updated with the server response.

This gives the user quick feedback whether their change is valid, without the need to scroll for error messages or to backtrack to fields completed earlier.

Note

[up-validate] is a tool to implement highly dynamic forms that must update as the user is completing fields.
If you only need to validate forms after submission, you don't need [up-validate].

Marking fields for validation

Let's look at a standard registration form that asks for an e-mail and password. The form is organized into form groups of labels, inputs and an optional error message:

<form action="/users">

  <fieldset>
    <label for="email">E-mail</label>
    <input type="text" id="email" name="email">
  </fieldset>

  <fieldset>
    <label for="password">Password</label>
    <input type="password" id="password" name="password">
  </fieldset>

  <button type="submit">Register</button>

</form>

We have some data constraints that we want to validate as the user is filling in fields:

  • When the user changes the email field, we want to validate that the e-mail address is formatted correctly and is still available.
  • When the user changes the password field, we want to validate the minimum password length.

If validation fails we want to show validation errors as soon as the user blurs the field.

We're going to render validation errors using the following HTML:

<form action="/users">

  <fieldset>
    <label for="email" up-validate>E-mail</label>
    <input type="text" id="email" name="email" value="foo@bar.com">
    <div class="error">E-mail has already been taken!</div> <!-- mark-line -->
  </fieldset>

  <fieldset>
    <label for="password" up-validate>Password</label>
    <input type="password" id="password" name="password" value="secret">
    <div class="error">Password is too short!</div> <!-- mark-line -->
  </fieldset>

</form>

We can implement this by giving both fields an [up-validate] attribute:

<form action="/users">

  <fieldset>
    <label for="email" up-validate>E-mail</label> <!-- mark-phrase "up-validate" -->
    <input type="text" id="email" name="email">
  </fieldset>

  <fieldset>
    <label for="password" up-validate>Password</label> <!-- mark-phrase "up-validate" -->
    <input type="password" id="password" name="password">
  </fieldset>

  <button type="submit">Register</button>

</form>

Whenever a field with [up-validate] changes, the form is submitted to its [action] path with an additional X-Up-Validate HTTP header.

Backend protocol

When the user changes the email field in the registration form above, the following request will be sent:

POST /users HTTP/1.1
X-Up-Validate: email
X-Up-Target: fieldset:has(#email)
Content-Type: application/x-www-form-urlencoded

email=foo%40bar.com&password=secret

Upon seeing an X-Up-Validate header, the server is expected to validate (but not commit) the form submission and render a new form state from the request parameters.

This requires a change in the backend code that handles the form's [action] path. Until now the backend only had to handle two cases:

  1. The form was submitted with valid data. We create a new account and sign in the user.
  2. The form submission failed due to an invalid email or password. We re-render the form with error messages.

A Ruby on Rails implementation would look like this:

class UsersController < ApplicationController

  def create
    # Instantiate model from request parameters
    user_params = params.require(:user).permit(:email, :password)
    @user = User.new(user_params)

    if @user.save
      # Form is submitted successfully
      sign_in @user
    else
      # Submission failed
      render 'form', status: :unprocessable_entity
    end

  end

end

To honor the validation protocol, our backend needs to handle a third case:

  1. When seeing an X-Up-Validate header, render a new form state from request parameters

In our example backend above, this change could look like this:

class UsersController < ApplicationController

  def create
    # Instantiate model from request parameters
    user_params = params.require(:user).permit(:email, :password)
    @user = User.new(user_params)

    if request.headers['X-Up-Validate'] # mark-line
      @user.validate # mark-line
      render 'form' # mark-line
    elsif @user.save
      # Form is submitted successfully
      sign_in @user
    else
      # Submission failed
      render 'form', status: :unprocessable_entity
    end

  end

end

Tip

If you're using Python with Django, you may find the django-forms-dynamic package useful to implement this pattern.

The server is free to respond with any HTTP status code, regardless of the validation result. Unpoly will always consider a validation request to be successful, even if the server responds with a non-200 status code.

Upon seeing an X-Up-Validate header, the server now renders a new state form from request parameters, showing eventual validation errors and updating dependent fields:

<form action="/users">

  <fieldset>
    <label for="email" up-validate>E-mail</label>
    <input type="text" id="email" name="email" value="foo@bar.com">
    <div class="error">E-mail has already been taken!</div> <!-- mark-line -->
  </fieldset>

  <fieldset>
    <label for="password" up-validate>Password</label>
    <input type="password" id="password" name="password" value="secret">
    <div class="error">Password is too short!</div> <!-- mark-line -->
  </fieldset>

</form>

How validation results are displayed

[up-validate] always submits the entire form with its current field values to the form's [action] path. Typically only a fragment of the form is updated with the response. This minimizes the chance for loss of transient state like scroll positions, cursor selection or user input while the request is in flight.

By default Unpoly will only update the closest form group around the validating field. The example above, after changing the email field, only the <fieldset> around the field will be updated.

If the form is not structured into groups, the entire form will be updated.

Updating a different fragment

If you don't want to update the field's form group, you can set the [up-validate] attribute to any target selector:

<input type="text" name="email" up-validate=".email-errors"> <!-- mark-phrase ".email-errors" -->
<div class="email-errors"></div>

You may also update multiple fragments by separating their target selectors with a comma:

<input type="text" name="email" up-validate=".email-errors, .base-errors"> <!-- mark-phrase ".email-errors, .base-errors" -->

Updating dependent fields

The [up-validate] attribute is a useful tool to partially update a form when one fields depends on the value of another field.

See dependent fields for more details and examples.

Validating while typing

By default [up-validate] reacts to the change event that is emitted when the user is done editing and focuses the next field.

We can validate while the user is typing by setting an [up-watch-event="input"] attribute:

<input name="email" up-validate up-watch-event="input" up-watch-delay="100" up-keep>

Note that we set some additional watch options to improve the experience:

  • The [up-watch-delay] attribute to delay validation until the user has stopped typing for 100 milliseconds.
  • The [up-keep] attribute to preserve additional user input while the validation request is in flight.

If you're using this pattern a lot, you may want to configure a macro for it.

Preventing race conditions

Custom dynamic form implementations will often exhibit race conditions, e.g. when the user is quickly changing fields while requests are still in flight. Such issues are solved with [up-validate]. The form will eventually show a consistent state, regardless of how fast the user clicks or how slow the network is.

See preventing race conditions for more details.

Validating multiple fields

You can set [up-validate] on any element to validate all contained fields on change.

In the example above, instead of setting [up-validate] on each individual <input>, we can also set it on the <form>:

<form action="/users" up-validate> <!-- mark-phrase "up-validate" -->

  <fieldset>
    <label for="email" up-validate>E-mail</label>
    <input type="text" id="email" name="email">
  </fieldset>

  <fieldset>
    <label for="password" up-validate>Password</label>
    <input type="password" id="password" name="password">
  </fieldset>

  <button type="submit">Register</button>

</form>

Validating radio buttons

Multiple radio buttons with the same [name] produce a single value for the form.

To watch radio buttons group, use the [up-validate] attribute on an element that contains all radio button elements with a given name:

<fieldset up-validate>
  <input type="radio" name="format" value="html"> HTML format
  <input type="radio" name="format" value="pdf"> PDF format
  <input type="radio" name="format" value="txt"> Text format
</fieldset>

Programmatic validation

To update form fragments from your JavaScript, use the up.validate() function. You may combine [up-validate] and up.validate() within the same form. Their updates will be batched together in order to prevent race conditions.


Modifying attributes

[up-validate] optional

The target selector to update with the server response.

Defaults the closest form group around the validating field.

[up-watch-event='change'] optional

The event types to observe.

See which events to watch.

[up-watch-delay] optional

The number of miliseconds to wait between an observed event and validating.

See debouncing callbacks.

[up-watch-disable] optional

Whether to disable fields while validation is running.

See disabling fields while working.

[up-watch-feedback] optional

Whether to give navigation feedback while validating.

See showing feedback while working.