Wiki source code of Async Form Validation

Version 2.1 by Marius Dumitru Florea on 2022/09/13

Hide last authors
Marius Dumitru Florea 1.1 1 When working with HTML forms a recurring need is to be able to validate the form data. This is normally done both:
2
3 * on the client-side (when the user inputs the data or before the data is submitted) and
4 * on the server-side (after the data is submitted).
5
6 On the client-side, the validation can be done both:
7
8 * synchronously (e.g. when checking if the mandatory fields are filled and if the field values match the expected format)
9 * and asynchronously, when the JavaScript code doesn't have all the information needed for the validation so it has to make one or more HTTP requests to the server-side (e.g. to check if the typed page name exists already)
10
11 When a form is validated asynchronously we usually want to:
12
13 * postpone the submit until all validations are executed (until there are no pending validations)
14 ** allow the user to trigger the form submit even if there are pending validations, but disable the form while waiting for them to be executed
15 * prevent the submit if there are failed validations (e.g. by disabling the submit button)
16 * be able to abort / replace a previous validation for a specific field when the user changes its value
17 ** re-enable the submit button when a failed validation is replaced, if there are no other failed validations
18 * be able to delay the validation in order to give the user the chance to type (we don't want to validate after each keystroke because HTTP requests are expensive)
19
20 This can be achieved using the ##xwiki-form-validation-async## JavaScript module that provides a jQuery plugin. Suppose you have the following HTML form:
21
22 {{code language="html"}}
23 <form>
24 <!-- The fieldset is needed in order to be able to disable the entire form easily. -->
25 <fieldset>
26 <!-- This is the field that needs to be validated asynchronously. -->
27 <input type="text" name="title" value="" />
Marius Dumitru Florea 2.1 28 <!-- This is where we display the validation error. -->
Marius Dumitru Florea 1.1 29 <span class="xErrorMsg hidden"></span>
30 <input type="submit" value="Submit" />
31 </fieldset>
32 </form>
33 {{/code}}
34
35 You can implement asynchronous validation like this:
36
37 {{code language="js"}}
38 require(['jquery', 'xwiki-form-validation-async'], function($) {
39 const titleInput = $('input[name=title]');
40 const titleError = titleInput.next('.xErrorMsg');
41
42 const validateTitle = () => {
43 if (!titleInput.val()) {
44 return Promise.reject('Please enter the title.');
45 } else {
46 return new Promise((resolve, reject) => {
47 // Perform the asynchronous validation, calling resolve() or reject('error message') when done.
48 ...
49 });
50 }
51 };
52
53 titleInput.on('input', () => {
54 // Hide the last error message whenever the user changes the value.
55 titleError.addClass('hidden');
56 // Show a visual indicator while the validation is in progress.
57 titleInput.addClass('loading');
58 // Schedule the asynchronous validation after 500ms. The namespace is used to prevent replacing validations added by
59 // other modules.
60 titleInput.validateAsync(validateTitle, /* delay: */ 500, /* namespace: */ 'myModule').catch(error => {
61 // Show the error message. Note that this code is executed only if the validation has not become outdated since it
62 // was scheduled.
63 titleError.removeClass('hidden').text(error);
64 }).finally(() => {
65 // Remove the visual indicator once the validation is done. Note that this code is executed only if the validation
66 // has not become outdated since it was scheduled.
67 titleInput.removeClass('loading');
68 });
69 });
70 });
71 {{/code}}
Marius Dumitru Florea 2.1 72
73 The ##validateAsync## function can be called in multiple ways:
74
75 {{code language="js"}}
76 // Schedule a validation after 500ms, specifying the namespace.
77 $('#myFormField').validateAsync(() => Promise.resolve(), 500, 'myModule')
78
79 // You can omit the namespace if you know for sure that other modules don't add validations to the same field.
80 $('#myFormField').validateAsync(() => Promise.resolve(), 500)
81
82 // You can also pass directly a Promise, instead of a function returning a Promise. This means the validation is
83 // scheduled right away (for the next event cycle). This is useful if you want to force a quick validation when the
84 // submit is triggered.
85 $('#myFormField').validateAsync(Promise.resolve(), 'myApp')
86
87 // Same, without the namespace.
88 $('#myFormField').validateAsync(Promise.resolve())
89 {{/code}}

Get Connected