Last modified by Alexander Colesnicov on 2020/01/28

Show last authors
1 {{box cssClass="floatinginfobox" title="**Contents**"}}
2 {{toc start="2"/}}
3 {{/box}}
4
5 This sample shows how to create a form with validation and tooltips. It demonstrates regular expression validation as well as a more complex validation using a groovy script.
6
7 Download the [[sample in XAR format>>attach:validation-sample.xar]].
8
9 == How does my form look like when validation errors are shown ==
10
11 {{image reference="formValidationEdit.png"/}}
12
13 == Sample Pages ==
14
15 The following pages are used:
16
17 * //ValidationSample.WebHome// Home page where you can find this documentation
18 * //ValidationSample.ValidationSampleClass// Class with definitions of fields, regular expressions and error message translations strings
19 * //ValidationSample.ValidationSampleSheet// Sheet presenting the document in create, edit and view mode including validation error messages
20 * //ValidationSample.ValidationSampleTemplate// Template of a document
21 * //ValidationSample.ValidationGroovy// Groovy validation script for complex validations
22 * //ValidationSample.Translations// Translations of the texts, tooltips and error messages. This shows an example of the naming conventions for tooltips and pretty names
23 * //ValidationSample.Val//, //ValidationSample.Val_0// and //ValidationSample.Val_1// Sample documents
24
25 == How to create validations using regular expressions ==
26
27 To create validation first you need to define your class and set the regular expression to validate the fields you want to validate.
28
29 Then, to perform validation after a standard "Save" in the form, following code is needed:
30
31 {{code language="html"}}
32 <input type="hidden" name="xvalidate" value="1" />
33 {{/code}}
34
35 {{info}}
36 The code above is sufficient to perform validation with regular expressions defined in the Class properties.
37 {{/info}}
38
39 Pay attention to the Validation Regular Expression and Validation Message fields. The first one is [[a Java Regular Expression pattern>>http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html]] and the second one is a translation string. For the sample class we created we have:
40
41 * first_name
42 **/^.{2,20}$/** -> this field needs to be between 2 characters and 20 characters. If the field can have new lines, enable the [[dotall mode>>http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#DOTALL]] by adding an **s** at the end of the regex (##/^.{2,20}$/s##), otherwise a regex that contains a new line will not pass validation.
43 **val_firstname_toolong** -> XWiki will lookup this translation string in the translations pages
44 * last_name
45 **/^.{2,20}$/** -> this field needs to be between 2 and 20 characters.
46 * email
47 **{{{/.*@.*.com$/}}}** -> this field must contain **@** and finish with **.com**
48 * age
49 no validation set for age. This will be handled by the groovy script
50 * usphone
51 **{{{/^[0-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$/}}}** -> the phone number must be made of digits separated by - in the form 000-000-0000
52
53 Other Validation Regular Expression examples:
54
55 * do not match //XWiki.XWikiGuest//, but allow it to be inside the string: **{{{/^(?!XWiki.XWikiGuest$).*/}}}**
56 * forbid //XWiki.XWikiGuest// anywhere in the string: **{{{/^(?!.*XWiki.XWikiGuest).*/}}}**
57
58 {{image reference="formValidationClass.png"/}}
59
60 To trigger the validation dynamically, the following Velocity code should be called:
61
62 {{code language="none"}}
63 ## this will launch a validation on a document. All errors are added to the context.
64 $doc.validate()
65 {{/code}}
66
67 == How to create validations using a groovy script ==
68
69 To create complex validations you have to use a Groovy script.
70
71 === Invoking Groovy script ===
72
73 Groovy validation script can be invoked using two approaches:
74
75 1. Using HTML code in the form, the validation on a standard "Save" of a document:(((
76 {{code language="html"}}
77 ## Force server side validation.
78 <input type="hidden" name="xvalidate" value="1" />
79 ## Specify the page that holds the Groovy script that should be used for validation.
80 <input type="hidden" name="xvalidation" value="ValidationSample.ValidationGroovy" />
81 {{/code}}
82 )))
83 1. Or dynamically using Velocity code:(((
84 {{code language="none"}}
85 ## set the page, which Groovy script will be used for validation
86 $doc.setValidationScript("ValidationSample.ValidationGroovy")
87 ## invoke document validation.
88 $doc.validate()
89 {{/code}}
90 )))
91
92 {{info}}
93 After document validation all errors are added to the context.
94 {{/info}}
95
96 === Groovy script sample ===
97
98 Here is the sample groovy script:
99
100 {{warning}}
101 Do not use the **~{~{groovy}}** macro when creating your script, just paste your code in the wiki editor.
102 {{/warning}}
103
104 {{code language="java"}}
105 import com.xpn.xwiki.validation.*;
106 import com.xpn.xwiki.*;
107 import com.xpn.xwiki.doc.*;
108 import com.xpn.xwiki.objects.*;
109
110 public class Val implements XWikiValidationInterface {
111 public boolean validateDocument(XWikiDocument doc, XWikiContext context) {
112 // You can log in the app server output to check your code
113 // System.out.println("validation is called");
114 def res = true;
115 def obj = doc.getObject("ValidationSample.ValidationSampleClass");
116 def first_name = obj.getStringValue("first_name");
117 def last_name = obj.getStringValue("last_name");
118 def age = obj.getIntValue("age");
119 // You can log in the app server output to check your code
120 // System.out.println("Age: " + age);
121 // System.out.println("First name: " + first_name);
122 // System.out.println("Last name: " + last_name);
123
124 if (first_name.equals(last_name)) {
125 // You can log in the app server output to check your code
126 // System.out.println("firstname");
127 // This stores the validation error message. The translation string is "val_firstname_lastname"
128 XWikiValidationStatus.addErrorToContext("ValidationSample.ValidationSampleClass", "", "", "val_firstname_lastname", context);
129 res = false;
130 }
131 if (age<20 || age>24) {
132 // You can log in the app server output to check your code
133 // System.out.println("age");
134 // This stores the validation error message. The translation string is "val_age_incorrect"
135 XWikiValidationStatus.addErrorToContext("ValidationSample.ValidationSampleClass", "age", "Age", "val_age_incorrect", context);
136 res = false;
137 }
138 return res;
139 }
140 public boolean validateObject(BaseObject object, XWikiContext context) {
141 return true;
142 }
143 }
144 {{/code}}
145
146 == How to display validation error messages ==
147
148 The sheet can access the validation error messages using:
149
150 {{code language="none"}}
151 #foreach ($error in $xcontext.validationStatus.errors)
152 <p class="text-danger">$services.localization.render($error)</p>
153 #end
154 #foreach ($exception in $xcontext.validationStatus.exceptions)
155 <p class="text-danger">$exception</p>
156 #end
157 {{/code}}
158
159 === Display the validation error messages next to the field ===
160
161 For a given field (e.g. first name) you can show the validation error message using:
162
163 {{code language="none"}}
164 #set ($fieldName = 'first_name')
165 #set ($xclass = $xwiki.getDocument('ValidationSample.ValidationSampleClass').xWikiClass)
166 #set ($fieldDefinition = $xclass.get($fieldName))
167 #set ($validationMessage = $fieldDefinition.getValue('validationMessage'))
168 #set ($hasError = $xcontext.validationStatus.errors.contains($validationMessage))
169 #if ($hasError)
170 <p class="text-danger">$services.localization.render($validationMessage)</p>
171 #end
172 {{/code}}
173
174 == How to display the field pretty name with tooltip ==
175
176 We can use Bootstrap to show a tooltip after the field pretty name.
177
178 {{code language="none"}}
179 #set ($fieldName = 'first_name')
180 #set ($localClassReference = 'ValidationSample.ValidationSampleClass')
181 #set ($mandatory = true)
182 <dt>
183 <label for="${localClassReference}_0_$fieldName">
184 $doc.displayPrettyName($fieldName)##
185 #if ($mandatory && $xcontext.action == 'edit')
186 <sup class="text-danger">*</sup>
187 #end
188 </label>
189 #set ($tooltipKey = "${localClassReference}_${fieldName}_tooltip")
190 #if ($services.localization.get($tooltipKey) && $xcontext.action == 'edit')
191 <a href="#tooltip" data-toggle="popover" data-trigger="focus"
192 data-content="$escapetool.xml($services.localization.render($tooltipKey))">
193 $services.icon.renderHTML('info')
194 </a>
195 #end
196 </dt>
197 {{/code}}
198
199 In order to activate the tooltips you need to use some JavaScript code that you can put in a JavaScriptExtension object:
200
201 {{code language="js"}}
202 require(['jquery', 'bootstrap'], function($) {
203 // Activate all popovers.
204 $('[data-toggle="popover"]').popover();
205 });
206 {{/code}}
207
208 == Complete presentation sheet of the document ==
209
210 The content of the final presentation sheet is:
211
212 {{code language="none"}}
213 {{velocity output="false"}}
214 #set ($xclass = $xwiki.getDocument('ValidationSample.ValidationSampleClass').xWikiClass)
215 #set ($validationMessages = $collectionstool.set)
216
217 #**
218 * This macros displays a field and it's tool tip.
219 *#
220 #macro (showField $fieldName $mandatory)
221 #set ($fieldDefinition = $xclass.get($fieldName))
222 #set ($validationMessage = $fieldDefinition.getValue('validationMessage'))
223 #set ($discard = $validationMessages.add($validationMessage))
224 #set ($hasError = $xcontext.validationStatus.errors.contains($validationMessage))
225 #set ($localClassReference = $services.model.serialize($xclass.reference, 'local'))
226 <dt class="form-group#if ($hasError) has-error#end">
227 <label class="control-label" for="${localClassReference}_0_$fieldName">
228 $doc.displayPrettyName($fieldName)##
229 #if ($mandatory && $xcontext.action == 'edit')
230 <sup class="text-danger">*</sup>
231 #end
232 </label>
233 #set ($tooltipKey = "${localClassReference}_${fieldName}_tooltip")
234 #if ($services.localization.get($tooltipKey) && $xcontext.action == 'edit')
235 <a href="#tooltip" data-toggle="popover" data-trigger="focus"
236 data-content="$escapetool.xml($services.localization.render($tooltipKey))">
237 $services.icon.renderHTML('info')
238 </a>
239 #end
240 <span class="xHint">$fieldDefinition.getValue('hint')</span>
241 </dt>
242 <dd class="form-group#if ($hasError) has-error#end">
243 #set ($output = $doc.display($fieldName))
244 $stringtool.removeEnd($stringtool.removeStart($output, '{{html clean="false" wiki="false"}}'), '{{/html}}')
245 #if ($hasError)
246 <div class="text-danger">$escapetool.xml($services.localization.render($validationMessage))</div>
247 #end
248 </dd>
249 #end
250
251 #**
252 * This macro shows all the remaining errors (that are not bound to a particular form field).
253 *#
254 #macro (showRemainingErrors)
255 #set ($remainingValidationMessages = [])
256 #foreach ($error in $xcontext.validationStatus.errors)
257 #if (!$validationMessages.contains($error))
258 #set ($discard = $remainingValidationMessages.add($error))
259 #end
260 #end
261 #if ($remainingValidationMessages.size() > 0 ||
262 ($xcontext.validationStatus.exceptions && $xcontext.validationStatus.exceptions.size() > 0))
263 <div class="box errormessage">
264 Validation errors
265 <ul>
266 #foreach ($error in $xcontext.validationStatus.errors)
267 <li>$escapetool.xml($services.localization.render($error))</li>
268 #end
269 #foreach ($exception in $xcontext.validationStatus.exceptions)
270 <li>$escapetool.xml($services.localization.render($exception))</li>
271 #end
272 </ul>
273 </div>
274 #end
275 #end
276 {{/velocity}}
277
278 {{velocity}}
279 #set ($discard = $xwiki.jsx.use('ValidationSample.ValidationSampleSheet'))
280 {{html clean="false"}}
281 <div class="xform">
282 ## Force server side validation.
283 <input type="hidden" name="xvalidate" value="1" />
284 ## Set the validation script.
285 <input type="hidden" name="xvalidation" value="ValidationSample.ValidationGroovy" />
286 #set ($discard = $doc.use('ValidationSample.ValidationSampleClass'))
287 <dl>
288 #showField('first_name', true)
289 #showField('last_name', true)
290 #showField('age', true)
291 #showField('email', true)
292 #showField('usphone', true)
293 #showField('text', false)
294 </dl>
295 #showRemainingErrors
296 </div>
297 {{/velocity}}
298 {{/code}}
299
300 And don't forget about the JavaScriptExtension object on the sheet page:
301
302 {{code language="js"}}
303 require(['jquery', 'bootstrap'], function($) {
304 // Activate all popovers.
305 $('[data-toggle="popover"]').popover();
306 // Focus the first field with validation error.
307 $('.form-group.has-error input, .form-group.has-error textarea, .form-group.has-error select').first().focus();
308 });
309 {{/code}}
310
311 == Client Side Validation ==
312
313 It's important to have server side validation for security (the client side validation can be easily bypassed) but client side validation as you type can improve the user's experience and save server load from forms submitted over and over again. To do validation on the client side we recommend using the standard [[HTML5 form validation>>https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation]] as much as possible or a jQuery plugin such as [[jQuery Validation Plugin>>https://github.com/jquery-validation/jquery-validation/]].

Get Connected