Hide last authors
Oana Florea 14.1 1 {{box cssClass="floatinginfobox" title="**Contents**"}}
Marius Dumitru Florea 27.1 2 {{toc start="2"/}}
Oana Florea 14.1 3 {{/box}}
Ludovic Dubost 1.1 4
Marius Dumitru Florea 27.1 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.
Ludovic Dubost 1.1 6
Marius Dumitru Florea 39.1 7 Download the [[sample in XAR format>>attach:validation-sample.xar]].
Ludovic Dubost 1.1 8
Marius Dumitru Florea 27.1 9 == How does my form look like when validation errors are shown ==
Ludovic Dubost 7.1 10
Marius Dumitru Florea 27.1 11 {{image reference="formValidationEdit.png"/}}
Oana Florea 8.1 12
Marius Dumitru Florea 27.1 13 == Sample Pages ==
Ludovic Dubost 1.1 14
Marius Dumitru Florea 27.1 15 The following pages are used:
Ludovic Dubost 1.1 16
Marius Dumitru Florea 27.1 17 * //ValidationSample.WebHome// Home page where you can find this documentation
Silvia Macovei 10.1 18 * //ValidationSample.ValidationSampleClass// Class with definitions of fields, regular expressions and error message translations strings
Marius Dumitru Florea 27.1 19 * //ValidationSample.ValidationSampleSheet// Sheet presenting the document in create, edit and view mode including validation error messages
20 * //ValidationSample.ValidationSampleTemplate// Template of a document
Silvia Macovei 10.1 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
Marius Dumitru Florea 27.1 23 * //ValidationSample.Val//, //ValidationSample.Val_0// and //ValidationSample.Val_1// Sample documents
Ludovic Dubost 1.1 24
Marius Dumitru Florea 27.1 25 == How to create validations using regular expressions ==
Ludovic Dubost 1.1 26
Marius Dumitru Florea 27.1 27 To create validation first you need to define your class and set the regular expression to validate the fields you want to validate.
Ludovic Dubost 1.1 28
Valdis Vitolins 17.1 29 Then, to perform validation after a standard "Save" in the form, following code is needed:
Ludovic Dubost 1.1 30
Oana Florea 14.1 31 {{code language="html"}}
32 <input type="hidden" name="xvalidate" value="1" />
33 {{/code}}
Ludovic Dubost 1.1 34
Valdis Vitolins 18.1 35 {{info}}
36 The code above is sufficient to perform validation with regular expressions defined in the Class properties.
37 {{/info}}
Valdis Vitolins 17.1 38
Marius Dumitru Florea 27.1 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:
Ludovic Dubost 1.1 40
41 * first_name
Alexandra Ifrim 23.2 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.
Valdis Vitolins 18.1 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
Ludovic Dubost 1.1 52
Oana Florea 9.1 53 Other Validation Regular Expression examples:
Ludovic Dubost 1.1 54
Valdis Vitolins 18.1 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).*/}}}**
Ludovic Dubost 1.1 57
Marius Dumitru Florea 27.1 58 {{image reference="formValidationClass.png"/}}
Silvia Macovei 10.1 59
Marius Dumitru Florea 27.1 60 To trigger the validation dynamically, the following Velocity code should be called:
Silvia Macovei 10.1 61
Marius Dumitru Florea 32.1 62 {{code language="none"}}
Marius Dumitru Florea 27.1 63 ## this will launch a validation on a document. All errors are added to the context.
Silvia Macovei 10.1 64 $doc.validate()
65 {{/code}}
Ludovic Dubost 1.1 66
Marius Dumitru Florea 27.1 67 == How to create validations using a groovy script ==
Ludovic Dubost 1.1 68
Valdis Vitolins 17.1 69 To create complex validations you have to use a Groovy script.
70
Marius Dumitru Florea 27.1 71 === Invoking Groovy script ===
Valdis Vitolins 18.1 72
Valdis Vitolins 17.1 73 Groovy validation script can be invoked using two approaches:
74
Marius Dumitru Florea 27.1 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.
Ludovic Dubost 1.1 78 <input type="hidden" name="xvalidate" value="1" />
Marius Dumitru Florea 27.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
Valdis Vitolins 17.1 86 $doc.setValidationScript("ValidationSample.ValidationGroovy")
87 ## invoke document validation.
Marius Dumitru Florea 27.1 88 $doc.validate()
89 {{/code}}
90 )))
Valdis Vitolins 17.1 91
Valdis Vitolins 18.1 92 {{info}}
93 After document validation all errors are added to the context.
94 {{/info}}
Valdis Vitolins 17.1 95
Marius Dumitru Florea 27.1 96 === Groovy script sample ===
Valdis Vitolins 17.1 97
Ludovic Dubost 1.1 98 Here is the sample groovy script:
99
Oana Florea 14.1 100 {{warning}}
101 Do not use the **~{~{groovy}}** macro when creating your script, just paste your code in the wiki editor.
102 {{/warning}}
103
Caleb James DeLisle 13.1 104 {{code language="java"}}
Ludovic Dubost 1.1 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 }
Silvia Macovei 10.1 144 {{/code}}
Ludovic Dubost 1.1 145
Marius Dumitru Florea 27.1 146 == How to display validation error messages ==
Ludovic Dubost 1.1 147
Marius Dumitru Florea 27.1 148 The sheet can access the validation error messages using:
Ludovic Dubost 1.1 149
Marius Dumitru Florea 27.1 150 {{code language="none"}}
151 #foreach ($error in $xcontext.validationStatus.errors)
152 <p class="text-danger">$services.localization.render($error)</p>
Ludovic Dubost 1.1 153 #end
Marius Dumitru Florea 27.1 154 #foreach ($exception in $xcontext.validationStatus.exceptions)
155 <p class="text-danger">$exception</p>
Ludovic Dubost 1.1 156 #end
Silvia Macovei 10.1 157 {{/code}}
Ludovic Dubost 1.1 158
Marius Dumitru Florea 27.1 159 === Display the validation error messages next to the field ===
Ludovic Dubost 1.1 160
Marius Dumitru Florea 27.1 161 For a given field (e.g. first name) you can show the validation error message using:
Ludovic Dubost 1.1 162
Marius Dumitru Florea 27.1 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>
Ludovic Dubost 1.1 171 #end
Silvia Macovei 10.1 172 {{/code}}
Ludovic Dubost 1.1 173
Marius Dumitru Florea 27.1 174 == How to display the field pretty name with tooltip ==
Ludovic Dubost 1.1 175
Marius Dumitru Florea 27.1 176 We can use Bootstrap to show a tooltip after the field pretty name.
Ludovic Dubost 1.1 177
Marius Dumitru Florea 27.1 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>
Silvia Macovei 10.1 197 {{/code}}
Ludovic Dubost 1.1 198
Marius Dumitru Florea 27.1 199 In order to activate the tooltips you need to use some JavaScript code that you can put in a JavaScriptExtension object:
Ludovic Dubost 1.1 200
Marius Dumitru Florea 27.1 201 {{code language="js"}}
202 require(['jquery', 'bootstrap'], function($) {
203 // Activate all popovers.
204 $('[data-toggle="popover"]').popover();
205 });
Silvia Macovei 10.1 206 {{/code}}
Ludovic Dubost 1.1 207
Marius Dumitru Florea 27.1 208 == Complete presentation sheet of the document ==
Ludovic Dubost 1.1 209
Marius Dumitru Florea 27.1 210 The content of the final presentation sheet is:
Ludovic Dubost 1.1 211
Marius Dumitru Florea 27.1 212 {{code language="none"}}
213 {{velocity output="false"}}
214 #set ($xclass = $xwiki.getDocument('ValidationSample.ValidationSampleClass').xWikiClass)
215 #set ($validationMessages = $collectionstool.set)
Ludovic Dubost 1.1 216
Marius Dumitru Florea 27.1 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>
Ludovic Dubost 1.1 249 #end
250
Marius Dumitru Florea 27.1 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
Ludovic Dubost 1.1 275 #end
Marius Dumitru Florea 27.1 276 {{/velocity}}
Ludovic Dubost 1.1 277
Marius Dumitru Florea 27.1 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
Ludovic Dubost 1.1 296 </div>
Marius Dumitru Florea 27.1 297 {{/velocity}}
298 {{/code}}
Ludovic Dubost 1.1 299
Marius Dumitru Florea 27.1 300 And don't forget about the JavaScriptExtension object on the sheet page:
Ludovic Dubost 1.1 301
Marius Dumitru Florea 27.1 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 });
Silvia Macovei 10.1 309 {{/code}}
Caleb James DeLisle 11.1 310
Marius Dumitru Florea 36.1 311 == Client Side Validation ==
Oana Florea 14.1 312
Marius Dumitru Florea 36.1 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