Wiki source code of Creating a form with validation and tooltips
Version 26.3 by Vincent Massol on 2017/09/05
Show last authors
author | version | line-number | content |
---|---|---|---|
1 | {{box cssClass="floatinginfobox" title="**Contents**"}} | ||
2 | {{toc/}} | ||
3 | {{/box}} | ||
4 | |||
5 | This sample shows how to make a create and edit 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-1.0.xar]]. | ||
8 | |||
9 | You will need to add ##ValidationSample.Translations## to your translations bundles in the advanced section of the global preferences. | ||
10 | |||
11 | = How does my form look like when validation errors are shown = | ||
12 | |||
13 | {{image reference="formvalidationedit.png"/}} | ||
14 | |||
15 | = Documents, Class and Scripts = | ||
16 | |||
17 | * //ValidationSample.WebHome// Home page where you can create a new document and access the existing ones | ||
18 | * //ValidationSample.ValidationSampleClassSheet// Sheet presenting the document in create, edit and view mode including validation error messages | ||
19 | * //ValidationSample.CreateDoc// Page called on the submission of the create document form. This will validate and show the form with errors or will save the document. | ||
20 | * //ValidationSample.ValidationSampleClass// Class with definitions of fields, regular expressions and error message translations strings | ||
21 | * //ValidationSample.ValidationGroovy// Groovy validation script for complex validations | ||
22 | * //ValidationSample.ValidationSampleClassTemplate// Template of a document | ||
23 | * //ValidationSample.Translations// Translations of the texts, tooltips and error messages. This shows an example of the naming conventions for tooltips and pretty names | ||
24 | * //ValidationSample.Val//, //ValidationSample.Val_0//, //ValidationSample.Val_1// Sample documents | ||
25 | |||
26 | = How to create validations using regular expressions = | ||
27 | |||
28 | To create validation first you need to declare your class and set the regulare expression to validate the fields you want to validate. | ||
29 | |||
30 | Then, to perform validation after a standard "Save" in the form, following code is needed: | ||
31 | |||
32 | {{code language="html"}} | ||
33 | <input type="hidden" name="xvalidate" value="1" /> | ||
34 | {{/code}} | ||
35 | |||
36 | {{info}} | ||
37 | The code above is sufficient to perform validation with regular expressions defined in the Class properties. | ||
38 | {{/info}} | ||
39 | |||
40 | 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: | ||
41 | |||
42 | * first_name | ||
43 | **/^.{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. | ||
44 | **val_firstname_toolong** -> XWiki will lookup this translation string in the translations pages | ||
45 | * last_name | ||
46 | **/^.{2,20}$/** -> this field needs to be between 2 and 20 characters. | ||
47 | |||
48 | **{{{/.*@.*.com$/}}}** -> this field must contain **@** and finish with **.com** | ||
49 | * age | ||
50 | no validation set for age. This will be handled by the groovy script | ||
51 | * usphone | ||
52 | **{{{/^[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 | ||
53 | |||
54 | Other Validation Regular Expression examples: | ||
55 | |||
56 | * do not match //XWiki.XWikiGuest//, but allow it to be inside the string: **{{{/^(?!XWiki.XWikiGuest$).*/}}}** | ||
57 | * forbid //XWiki.XWikiGuest// anywhere in the string: **{{{/^(?!.*XWiki.XWikiGuest).*/}}}** | ||
58 | |||
59 | {{image reference="formvalidationclass.png"/}} | ||
60 | |||
61 | To call validation dynamically, the following Velocity code should be called: | ||
62 | |||
63 | {{code}} | ||
64 | ## this will launch a validation on a document. All errors are added to the context | ||
65 | $doc.validate() | ||
66 | {{/code}} | ||
67 | |||
68 | = How to create validations using a groovy script = | ||
69 | |||
70 | To create complex validations you have to use a Groovy script. | ||
71 | |||
72 | == Invoking Groovy script == | ||
73 | |||
74 | Groovy validation script can be invoked using two approaches: | ||
75 | |||
76 | |||
77 | ~1. Using HTML code in the form, the validation on a standard "Save" of a document: | ||
78 | {{code language="html"}}## Invoke validation | ||
79 | <input type="hidden" name="xvalidate" value="1" /> | ||
80 | ## Set the page, which Groovy script will be used for validation | ||
81 | <input type="hidden" name="xvalidation" value="ValidationSample.ValidationGroovy" />{{/code}} | ||
82 | |||
83 | 2. Or dynamically using Velocity code: | ||
84 | {{code}}## set the page, which Groovy script will be used for validation | ||
85 | $doc.setValidationScript("ValidationSample.ValidationGroovy") | ||
86 | ## invoke document validation. | ||
87 | $doc.validate(){{/code}} | ||
88 | |||
89 | {{info}} | ||
90 | After document validation all errors are added to the context. | ||
91 | {{/info}} | ||
92 | |||
93 | == Groovy script sample == | ||
94 | |||
95 | Here is the sample groovy script: | ||
96 | |||
97 | {{warning}} | ||
98 | Do not use the **~{~{groovy}}** macro when creating your script, just paste your code in the wiki editor. | ||
99 | {{/warning}} | ||
100 | |||
101 | {{code language="java"}} | ||
102 | import com.xpn.xwiki.validation.*; | ||
103 | import com.xpn.xwiki.*; | ||
104 | import com.xpn.xwiki.doc.*; | ||
105 | import com.xpn.xwiki.objects.*; | ||
106 | |||
107 | public class Val implements XWikiValidationInterface { | ||
108 | public boolean validateDocument(XWikiDocument doc, XWikiContext context) { | ||
109 | // You can log in the app server output to check your code | ||
110 | // System.out.println("validation is called"); | ||
111 | def res = true; | ||
112 | def obj = doc.getObject("ValidationSample.ValidationSampleClass"); | ||
113 | def first_name = obj.getStringValue("first_name"); | ||
114 | def last_name = obj.getStringValue("last_name"); | ||
115 | def age = obj.getIntValue("age"); | ||
116 | // You can log in the app server output to check your code | ||
117 | // System.out.println("Age: " + age); | ||
118 | // System.out.println("First name: " + first_name); | ||
119 | // System.out.println("Last name: " + last_name); | ||
120 | |||
121 | if (first_name.equals(last_name)) { | ||
122 | // You can log in the app server output to check your code | ||
123 | // System.out.println("firstname"); | ||
124 | // This stores the validation error message. The translation string is "val_firstname_lastname" | ||
125 | XWikiValidationStatus.addErrorToContext("ValidationSample.ValidationSampleClass", "", "", "val_firstname_lastname", context); | ||
126 | res = false; | ||
127 | } | ||
128 | if (age<20 || age>24) { | ||
129 | // You can log in the app server output to check your code | ||
130 | // System.out.println("age"); | ||
131 | // This stores the validation error message. The translation string is "val_age_incorrect" | ||
132 | XWikiValidationStatus.addErrorToContext("ValidationSample.ValidationSampleClass", "age", "Age", "val_age_incorrect", context); | ||
133 | res = false; | ||
134 | } | ||
135 | return res; | ||
136 | } | ||
137 | public boolean validateObject(BaseObject object, XWikiContext context) { | ||
138 | return true; | ||
139 | } | ||
140 | } | ||
141 | {{/code}} | ||
142 | |||
143 | = How to display validation error messages = | ||
144 | |||
145 | == Display all validation error message at the top of the document == | ||
146 | |||
147 | The following macro can be used: | ||
148 | |||
149 | {{code}} | ||
150 | ### this macro shows all the errors | ||
151 | #macro(showallerrors) | ||
152 | #if(($context.validationStatus.errors&&$context.validationStatus.errors.size()>0)||($context.validationStatus.exceptions&&$context.validationStatus.exceptions.size()>0)) | ||
153 | <div class="validation-errors" style="border: 1px solid grey; padding: 10px;"> | ||
154 | This is a recap of all errors in this page (change the form to show errors only at the top or only next to the fields): | ||
155 | |||
156 | #foreach($error in $context.validationStatus.errors) | ||
157 | <font color="red">$xwiki.parseMessage($error)</font><br /> | ||
158 | #end | ||
159 | |||
160 | #foreach($exp in $context.validationStatus.exceptions) | ||
161 | <font color="red">$exp</font><br /> | ||
162 | #end | ||
163 | </div> | ||
164 | #end | ||
165 | #end ## end showallerrors macro | ||
166 | {{/code}} | ||
167 | |||
168 | This is called using {{code}}#showallerrors(){{/code}}. | ||
169 | |||
170 | == Display the validation error messages next to the field == | ||
171 | |||
172 | The following macro can be used to show the validation error called ##$message##: | ||
173 | |||
174 | {{code}} | ||
175 | ### this macro shows a validation error message if it exists | ||
176 | #macro(showvalidationmessage $message) | ||
177 | #if($context.validationStatus.errors.contains($message)) | ||
178 | <font color="red">$xwiki.parseMessage($message)</font><br /> | ||
179 | #end | ||
180 | #end ## end showvalidationmessage | ||
181 | {{/code}} | ||
182 | |||
183 | This is called using {{code}}#showvalidationmessage("val_firstname_toolong"){{/code}}. | ||
184 | |||
185 | = How to create tooltips, mandatory icon and the pretty name = | ||
186 | |||
187 | The following macro shows a field including the mandatory field, the tooltip and the pretty name: | ||
188 | |||
189 | {{code}} | ||
190 | #macro(showfield $fieldname $mandatory) | ||
191 | #if($mandatory&&!$mode.equals("view")) | ||
192 | #set($mand = true) | ||
193 | #else | ||
194 | #set($mand = false) | ||
195 | #end | ||
196 | ## displayPrettyName will get the translation only with patch in https://jira.xwiki.org/browse/XWIKI-2383 | ||
197 | <dt>$valdoc.displayPrettyName($fieldname, $mand): #if($context.action=="inline")$valdoc.displayTooltip($fieldname)#end</dt> | ||
198 | <dd> | ||
199 | $valdoc.display($fieldname, $mode) | ||
200 | </dd> | ||
201 | #end ## end showfield macro | ||
202 | {{/code}} | ||
203 | |||
204 | This is called using {{code}}#showfield("first_name",true){{/code}}. | ||
205 | |||
206 | In addition the following code needs to be called at the end of the page: | ||
207 | |||
208 | {{code}} | ||
209 | ## this is necessary for the tooltips to work | ||
210 | $xwiki.addTooltipJS() | ||
211 | {{/code}} | ||
212 | |||
213 | == The tooltip == | ||
214 | |||
215 | {{code}} | ||
216 | $valdoc.displayTooltip("first_name") | ||
217 | {{/code}} | ||
218 | |||
219 | == The pretty name with mandatory icon == | ||
220 | |||
221 | {{code}} | ||
222 | $valdoc.displayPrettyName("first_name", true) | ||
223 | {{/code}} | ||
224 | |||
225 | == The pretty name without mandatory icon == | ||
226 | |||
227 | {{code}} | ||
228 | $valdoc.displayPrettyName("first_name") | ||
229 | $valdoc.displayPrettyName("first_name", false) | ||
230 | {{/code}} | ||
231 | |||
232 | = How to validate and save the document in CreateDoc = | ||
233 | |||
234 | {{code}} | ||
235 | #set($docname = $xwiki.getUniquePageName("ValidationSample", "Val")) | ||
236 | #set($valdoc = $xwiki.getDocument("ValidationSample.${docname}")) | ||
237 | #set($ok = $valdoc.setContent($xwiki.getDocument("ValidationSample.ValidationSampleClassTemplate").getContent())) | ||
238 | #set($ok = $valdoc.setParent("ValidationSample.WebHome")) | ||
239 | #set($ok = $valdoc.newObject("ValidationSample.ValidationSampleClass")) | ||
240 | #set($ok = $valdoc.updateObjectFromRequest("ValidationSample.ValidationSampleClass")) | ||
241 | ## this does not work yet because of bug https://jira.xwiki.org/browse/XWIKI-2382 | ||
242 | ## the validation script needs to be passed in the request | ||
243 | #set($ok = $valdoc.setValidationScript("ValidationSample.ValidationGroovy")) | ||
244 | #if($valdoc.validate()==true) | ||
245 | Ok it's good to save. | ||
246 | $valdoc.save() | ||
247 | You can access your new document [here>$docname]. | ||
248 | #else | ||
249 | <form action="CreateDoc" method="post"> | ||
250 | #set($mode="edit") | ||
251 | #includeInContext("ValidationSample.ValidationSampleClassSheet") | ||
252 | <input type="submit" value="Create" /> | ||
253 | </form> | ||
254 | #end | ||
255 | {{/code}} | ||
256 | |||
257 | = Complete presentation sheet of the document = | ||
258 | |||
259 | {{code}} | ||
260 | ### setting right mode for the sheet | ||
261 | ### setting $valdoc variable either already set or set to $doc | ||
262 | #if(!$mode) | ||
263 | #if($context.action=="inline") | ||
264 | #set($mode = "edit") | ||
265 | #else | ||
266 | #set($mode = "view") | ||
267 | #end | ||
268 | #end | ||
269 | #if(!$valdoc) | ||
270 | #set($valdoc = $doc) | ||
271 | #end | ||
272 | ## Force validation | ||
273 | <input type="hidden" name="xvalidate" value="1" /> | ||
274 | ## Set the validation script. This is necessary until the bug https://jira.xwiki.org/browse/XWIKI-2382 | ||
275 | <input type="hidden" name="xvalidation" value="ValidationSample.ValidationGroovy" /> | ||
276 | |||
277 | #### begin display macros | ||
278 | ### this macro shows a validation error message if it exists | ||
279 | #macro(showvalidationmessage $message) | ||
280 | #if($context.validationStatus.errors.contains($message)) | ||
281 | <font color="red">$xwiki.parseMessage($message)</font><br /> | ||
282 | #end | ||
283 | #end ## end showvalidationmessage | ||
284 | ### this macros displays a field and it's tooltip | ||
285 | #macro(showfield $fieldname $mandatory) | ||
286 | #if($mandatory&&!$mode.equals("view")) | ||
287 | #set($mand = true) | ||
288 | #else | ||
289 | #set($mand = false) | ||
290 | #end | ||
291 | ## displayPrettyName will get the translation only with patch in https://jira.xwiki.org/browse/XWIKI-2383 | ||
292 | <dt>$valdoc.displayPrettyName($fieldname, $mand): #if($context.action=="inline")$valdoc.displayTooltip($fieldname)#end</dt> | ||
293 | <dd> | ||
294 | $valdoc.display($fieldname, $mode) | ||
295 | </dd> | ||
296 | #end ## end showfield macro | ||
297 | ### this macro shows all the errors | ||
298 | #macro(showallerrors) | ||
299 | #if(($context.validationStatus.errors&&$context.validationStatus.errors.size()>0)||($context.validationStatus.exceptions&&$context.validationStatus.exceptions.size>0)) | ||
300 | <div class="validation-errors" style="border: 1px solid grey; padding: 10px;"> | ||
301 | This is a recap of all errors in this page (change the form to show errors only at the top or only next to the fields): | ||
302 | |||
303 | #foreach($error in $context.validationStatus.errors) | ||
304 | <font color="red">$xwiki.parseMessage($error)</font><br /> | ||
305 | #end | ||
306 | |||
307 | #foreach($exp in $context.validationStatus.exceptions) | ||
308 | <font color="red">$exp</font><br /> | ||
309 | #end | ||
310 | </div> | ||
311 | #end | ||
312 | #end ## end showallerrors macro | ||
313 | ##### end macros | ||
314 | |||
315 | $valdoc.use("ValidationSample.ValidationSampleClass") | ||
316 | #showallerrors() | ||
317 | #showvalidationmessage("val_firstname_toolong") | ||
318 | #showvalidationmessage("val_firstname_lastname") | ||
319 | #showfield("first_name",true) | ||
320 | #showvalidationmessage("val_lastname_toolong") | ||
321 | #showfield("last_name",true) | ||
322 | #showvalidationmessage("val_age_incorrect") | ||
323 | #showfield("age",true) | ||
324 | #showvalidationmessage("val_email_shouldbedotcom") | ||
325 | #showfield("email",true) | ||
326 | #showvalidationmessage("val_usphone_incorrectformat") | ||
327 | #showfield("usphone",true) | ||
328 | #showvalidationmessage("val_text_toolong") | ||
329 | #showfield("text",false) | ||
330 | |||
331 | ## this is necessary for the tooltips to work | ||
332 | $xwiki.addTooltipJS() | ||
333 | {{/code}} | ||
334 | |||
335 | = Client side validation with LiveValidation = | ||
336 | |||
337 | It's important to have server side validation for security and because not everyone uses Javascript but client side validation while the user types can improve the user's experience and save server load from forms submitted over and over again. | ||
338 | To do validation on the client side you have to use the LiveValidation Javascript code. You can define your own style for validation error messages or you can use the style sheet which is used by [[XWiki.Registration>>extensions:Extension.Administration Application#HVerifiedRegistrationPage28SinceEnterprise2.2M229]]. | ||
339 | |||
340 | Here is a simple example of how to use LiveValidation in XWiki: | ||
341 | |||
342 | {{code}} | ||
343 | {{velocity}} | ||
344 | $xwiki.get('jsfx').use('uicomponents/widgets/validation/livevalidation_prototype.js') | ||
345 | $xwiki.get('ssfx').use('uicomponents/widgets/validation/livevalidation.css') | ||
346 | {{/velocity}} | ||
347 | {{html}} | ||
348 | <form action=""> | ||
349 | <label for="helloField">Say hello to LiveValidation:</label> | ||
350 | <input id="helloField" length="20" type="text"> | ||
351 | </form> | ||
352 | <script> | ||
353 | /* <![CDATA[ */ | ||
354 | document.observe('dom:loaded', function() { | ||
355 | var helloField = new LiveValidation("helloField", { validMessage: "Hi There.", wait: 500} ); | ||
356 | helloField.add( Validate.Presence, { failureMessage: "Say Something, anything..."} ); | ||
357 | helloField.add( Validate.Format, { pattern: /^[Hh]ello$/, failureMessage: "How about saying 'Hello'?"} ); | ||
358 | });// ]]> | ||
359 | </script> | ||
360 | {{/html}} | ||
361 | {{/code}} | ||
362 | |||
363 | The result is this: | ||
364 | |||
365 | {{velocity}} | ||
366 | $xwiki.get('jsfx').use('uicomponents/widgets/validation/livevalidation_prototype.js') | ||
367 | $xwiki.get('ssfx').use('uicomponents/widgets/validation/livevalidation.css') | ||
368 | {{/velocity}} | ||
369 | |||
370 | {{html}} | ||
371 | |||
372 | <form action=""> | ||
373 | <label for="helloField">Say hello to LiveValidation:</label> | ||
374 | <input id="helloField" length="20" type="text"> | ||
375 | </form> | ||
376 | <script> | ||
377 | /* <![CDATA[ */ | ||
378 | document.observe('dom:loaded', function() { | ||
379 | var helloField = new LiveValidation("helloField", { validMessage: "Hi There.", wait: 500} ); | ||
380 | helloField.add( Validate.Presence, { failureMessage: "Say Something, anything..."} ); | ||
381 | helloField.add( Validate.Format, { pattern: /^[Hh]ello$/, failureMessage: "How about saying 'Hello'?"} ); | ||
382 | });// ]]> | ||
383 | </script> | ||
384 | |||
385 | {{/html}} | ||
386 | |||
387 | This example shows validation of [[presence>>http://livevalidation.com/documentation#ValidatePresence]] (something must be written) and validation of [[format>>http://livevalidation.com/documentation#ValidateFormat]] (testing against a regular expression). | ||
388 | Notice how the first line of Javascript says **new LiveValidation("helloField"**. This binds the validation to field with the id "helloField". Text validation also works on TextAreas. | ||
389 | There are more types of validation which are documented in the [[LiveValidation documentation>>http://livevalidation.com/documentation]]. | ||
390 | To change the look and feel of the error message, you may define your own CSS [[skin extension>>platform:DevGuide.SkinExtensionsTutorial]] instead of using the one provided. | ||
391 | All validation messages are of the class (% class="LV_validation_message" %)##LV_validation_message##(%%). The error messages are of the class (% class="LV_invalid" %)##LV_invalid##(%%) and the valid messages are of class (% class="LV_valid" %)##LV_valid##(%%). LiveValidation will also detect submit buttons and bind to them, blocking them if fields are invalid. There are ways around this such as using prototype's [[Event.stopObserving>>http://prototypejs.org/doc/latest/dom/Event/stopObserving/]] function. |