Wiki source code of Async Form Validation
Version 2.1 by Marius Dumitru Florea on 2022/09/13
Hide last authors
author | version | line-number | content |
---|---|---|---|
![]() |
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="" /> | ||
![]() |
2.1 | 28 | <!-- This is where we display the validation error. --> |
![]() |
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}} | ||
![]() |
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}} |