Unpoly makes it easy to implement server-rendered forms where sections depend on the value of other fields.
Common use cases for dynamic forms include:
<select>
value fetches options for another <select>
.By using the [up-validate]
attribute we can implement logic like this on the server, using regular HTML templates
and no additional JavaScript.
Tip
For trivial effects (like toggling visibility) no server request is required. See Switching form state.
This is a form to purchase postage for international parcels:
The fields in this form have many dependencies between them:
We can implement this form with three [up-validate]
attributes and no additional JavaScript:
<form method="post" action="/purchases">
<fieldset>
<label for="continent">Continent</label>
<select name="continent" id="continent" up-validate="#country">...</select> <!-- mark: up-validate -->
</fieldset>
<fieldset>
<label for="country">Country</label>
<select name="country" id="country" up-validate="#price">...</select> <!-- mark: up-validate -->
</fieldset>
<fieldset>
<label for="weight">Weight</label>
<input name="weight" id="weight" up-validate="#price"> kg <!-- mark: up-validate -->
</fieldset>
<fieldset>
<label for="price">Price</label>
<output id="price">23 €</output>
</fieldset>
<button>Buy stamps</button>
</form>
When a field is changed, Unpoly will automatically submit the form with an additional X-Up-Validate
HTTP header. Upon seeing this header, the server is expected to render a new form state from the
form values in the request parameters. See this example
for control flow on the server.
When the server responds with the re-rendered form state, Unpoly will update the
target selector from the changed field's [up-validate]
attribute.
For instance, when the continent is field is changed, the country field is updated.
A field with [up-validate]
may update multiple fragments
by separating their target selectors with a comma.
For instance, when the user changes the continent, the following would update the price preview in addition to the country select:
<select name="continent" up-validate="#country, #price"> <!-- mark: #country, #price -->
...
</select>
To update another fragment in addition to the field's form group, include
the group in the target list.
You can refer to the changed field as :origin
:
<fieldset>
<select name="continent" up-validate="fieldset:has(:origin), #country, #price"> <!-- mark: fieldset:has(:origin) -->
...
</select>
</fieldset>
Custom implementations of dependent elements 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. In particular:
[up-validate]
or up.validate()
are batched into a single request with multiple targets.Let's walk through a challenging scenario using the postage form example above:
Once the last response is processed, all fields and the price preview show consistent values.
If you prefer to completely prevent user input during validation, give the form an
[up-watch-disable]
attribute. This will disable all form fields while validation requests are in flight:
<form method="post" action="/purchases" up-watch-disable> <!-- mark: up-watch-disable -->
...
</form>
You may also assign [up-watch-disable]
to individual fields, or any element that contains fields.
Also see disabling fields while working.
By default, validation requests will use [method]
and [action]
attributes from the form element.
You can render content from another server endpoint by setting an
[up-validate-url]
attribute on a form or field:
<form method="post" action="/order">
<input name="quantity" up-validate="#preview" up-validate-url="/preview-order"> <!-- mark: /preview-order -->
<div id="preview">
Order total: €190
</div>
</form>
Multiple validations to the same URL will be batched together.