View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.util;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.config.property.ConfigurationService;
20  import org.kuali.rice.krad.datadictionary.validation.constraint.BaseConstraint;
21  import org.kuali.rice.krad.datadictionary.validation.constraint.CaseConstraint;
22  import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint;
23  import org.kuali.rice.krad.datadictionary.validation.constraint.MustOccurConstraint;
24  import org.kuali.rice.krad.datadictionary.validation.constraint.PrerequisiteConstraint;
25  import org.kuali.rice.krad.datadictionary.validation.constraint.SimpleConstraint;
26  import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint;
27  import org.kuali.rice.krad.datadictionary.validation.constraint.WhenConstraint;
28  import org.kuali.rice.krad.service.KRADServiceLocator;
29  import org.kuali.rice.krad.uif.UifConstants;
30  import org.kuali.rice.krad.uif.field.InputField;
31  import org.kuali.rice.krad.uif.view.FormView;
32  import org.kuali.rice.krad.uif.view.View;
33  import org.kuali.rice.krad.uif.control.TextControl;
34  
35  import java.text.MessageFormat;
36  import java.util.ArrayList;
37  import java.util.EnumSet;
38  import java.util.List;
39  
40  /**
41   * This class contains all the methods necessary for generating the js required to perform validation client side.
42   * The processAndApplyConstraints(InputField field, View view) is the key method of this class used by
43   * InputField to setup its client side validation mechanisms.
44   * 
45   * @author Kuali Rice Team (rice.collab@kuali.org)
46   */
47  public class ClientValidationUtils {
48  	// used to give validation methods unique signatures
49  	private static int methodKey = 0;
50  	
51  	// list used to temporarily store mustOccurs field names for the error
52  	// message
53  	private static List<List<String>> mustOccursPathNames;
54  	
55  	public static final String LABEL_KEY_SPLIT_PATTERN = ",";
56  	
57  	
58  	public static final String PREREQ_MSG_KEY = "prerequisite";
59  	public static final String POSTREQ_MSG_KEY = "postrequisite";
60  	public static final String MUSTOCCURS_MSG_KEY = "mustOccurs";
61  	public static final String GENERIC_FIELD_MSG_KEY = "general.genericFieldName";
62  	
63  	public static final String ALL_MSG_KEY = "general.all";
64  	public static final String ATMOST_MSG_KEY = "general.atMost";
65  	public static final String AND_MSG_KEY = "general.and";
66  	public static final String OR_MSG_KEY = "general.or";
67  	
68  	private static ConfigurationService configService = KRADServiceLocator.getKualiConfigurationService();
69  	
70  	//Enum representing names of rules provided by the jQuery plugin
71  	public static enum ValidationMessageKeys{
72  		REQUIRED("required"), 
73  		MIN_EXCLUSIVE("minExclusive"), 
74  		MAX_INCLUSIVE("maxInclusive"),
75  		MIN_LENGTH("minLengthConditional"),
76  		MAX_LENGTH("maxLengthConditional");
77  		
78  		private ValidationMessageKeys(String name) {
79  			this.name = name;
80  		}
81  		
82  		private final String name;
83  		
84  		@Override
85  		public String toString() {
86  			return name;
87  		}
88  		
89  		public static boolean contains(String name){
90              for (ValidationMessageKeys element : EnumSet.allOf(ValidationMessageKeys.class)) {
91                  if (element.toString().equalsIgnoreCase(name)) {
92                      return true;
93                  }
94              }
95              return false;
96  		}
97  	}
98  	
99  	public static String generateMessageFromLabelKey(List<String> params, String labelKey){
100 		String message = "NO MESSAGE";
101 		if(StringUtils.isNotEmpty(labelKey)){
102 			message = configService.getPropertyValueAsString(labelKey);
103 			if(params != null && !params.isEmpty() && StringUtils.isNotEmpty(message)){
104 			    message = MessageFormat.format(message, params.toArray());
105 			}
106 		}
107 		if(StringUtils.isEmpty(message)){
108 		    message = labelKey;
109 		}
110 		//replace characters that might cause issues with their equivalent html codes
111 		if(message.contains("\"")){
112 		    message = message.replace("\"", "&quot;");
113 		}
114 		if(message.contains("'")){
115 		    message = message.replace("'", "&#39;");
116 		}
117 		if(message.contains("\\")){
118 		    message = message.replace("\\", "&#92;");
119 		}
120 		return message;
121 	}
122 
123 	/**
124 	 * Generates the js object used to override all default messages for validator jquery plugin with custom
125 	 * messages derived from the configService.
126 	 * 
127 	 * @return
128 	 */
129 	public static String generateValidatorMessagesOption(){
130 		String mOption = "";
131 		String keyValuePairs = "";
132 		for(ValidationMessageKeys element : EnumSet.allOf(ValidationMessageKeys.class)){
133 			String key = element.toString();
134 			String message = configService.getPropertyValueAsString(UifConstants.Messages.VALIDATION_MSG_KEY_PREFIX + key);
135 			if(StringUtils.isNotEmpty(message)){
136 				keyValuePairs = keyValuePairs + "\n" + key + ": '"+ message + "',";
137 				
138 			}
139 			
140 		}
141 		keyValuePairs = StringUtils.removeEnd(keyValuePairs, ",");
142 		if(StringUtils.isNotEmpty(keyValuePairs)){
143 			mOption="{" + keyValuePairs + "}";
144 		}
145 		
146 		return mOption;
147 	}
148 	
149 	/**
150 	 * Returns the add method jquery validator call for the regular expression
151 	 * stored in validCharactersConstraint.
152 	 * 
153 	 * @param validCharactersConstraint
154 	 * @return js validator.addMethod script
155 	 */
156 	public static String getRegexMethod(InputField field, ValidCharactersConstraint validCharactersConstraint) {
157 		String message = generateMessageFromLabelKey(validCharactersConstraint.getValidationMessageParams(), validCharactersConstraint.getLabelKey());
158 		String key = "validChar-" + field.getBindingInfo().getBindingPath() + methodKey;
159 		
160 		String regex = validCharactersConstraint.getValue();
161 		//replace characters known to cause issues if not escaped
162 		if(regex.contains("\\\\")){
163 		    regex = regex.replaceAll("\\\\", "\\\\\\\\");
164 		}
165 		if(regex.contains("/")){
166 		    regex = regex.replace("/", "\\/");
167 		}
168 		
169         return "\njQuery.validator.addMethod(\"" + ScriptUtils.escapeName(key)
170                 + "\", function(value, element) {\n "
171                 + "return this.optional(element) || /" + regex + "/.test(value);"
172                 + "}, \"" + message + "\");";
173 	}
174 	
175 	/**
176      * Returns the add method jquery validator call for the regular expression
177      * stored in validCharactersConstraint that explicitly checks a boolean.  Needed because one method
178      * accepts params and the other doesn't.
179      * 
180      * @param validCharactersConstraint
181      * @return js validator.addMethod script
182      */
183     public static String getRegexMethodWithBooleanCheck(InputField field, ValidCharactersConstraint validCharactersConstraint) {
184         String message = generateMessageFromLabelKey(validCharactersConstraint.getValidationMessageParams(), validCharactersConstraint.getLabelKey());
185         String key = "validChar-" + field.getBindingInfo().getBindingPath() + methodKey;
186         
187         String regex = validCharactersConstraint.getValue();
188         //replace characters known to cause issues if not escaped
189         if(regex.contains("\\\\")){
190             regex = regex.replaceAll("\\\\", "\\\\\\\\");
191         }
192         if(regex.contains("/")){
193 		    regex = regex.replace("/", "\\/");
194 		}
195         
196         return "\njQuery.validator.addMethod(\"" + ScriptUtils.escapeName(key)
197                 + "\", function(value, element, doCheck) {\n if(doCheck === false){return true;}else{"
198                 + "return this.optional(element) || /" + regex + "/.test(value);}"
199                 + "}, \"" + message + "\");";
200     }
201 	
202 
203 	/**
204 	 * This method processes a single CaseConstraint. Internally it makes calls
205 	 * to processWhenConstraint for each WhenConstraint that exists in this
206 	 * constraint. It adds a "dependsOn" css class to this field for the field
207 	 * which the CaseConstraint references.
208 	 * 
209 	 * @param view
210 	 * @param andedCase
211 	 *            the boolean logic to be anded when determining if this case is
212 	 *            satisfied (used for nested CaseConstraints)
213 	 */
214 	public static void processCaseConstraint(InputField field, View view, CaseConstraint constraint, String andedCase) {
215 		if (constraint.getOperator() == null) {
216 			constraint.setOperator("equals");
217 		}
218 
219 		String operator = "==";
220 		if (constraint.getOperator().equalsIgnoreCase("not_equals") || constraint.getOperator().equalsIgnoreCase("not_equal")) {
221 			operator = "!=";
222 		}
223 		else if (constraint.getOperator().equalsIgnoreCase("greater_than_equal")) {
224 			operator = ">=";
225 		}
226 		else if (constraint.getOperator().equalsIgnoreCase("less_than_equal")) {
227 			operator = "<=";
228 		}
229 		else if (constraint.getOperator().equalsIgnoreCase("greater_than")) {
230 			operator = ">";
231 		}
232 		else if (constraint.getOperator().equalsIgnoreCase("less_than")) {
233 			operator = "<";
234 		}
235 		else if (constraint.getOperator().equalsIgnoreCase("has_value")) {
236 			operator = "";
237 		}
238 		// add more operator types here if more are supported later
239 
240 		field.getControl().addStyleClass("dependsOn-" + ScriptUtils.escapeName(constraint.getPropertyName()));
241 
242 		if (constraint.getWhenConstraint() != null && !constraint.getWhenConstraint().isEmpty()) {
243 			//String fieldPath = field.getBindingInfo().getBindingObjectPath() + "." + constraint.getPropertyName();
244             String fieldPath = constraint.getPropertyName();
245 		    for (WhenConstraint wc : constraint.getWhenConstraint()) {
246 				processWhenConstraint(field, view, constraint, wc, ScriptUtils.escapeName(fieldPath), operator, andedCase);
247 			}
248 		}
249 	}
250 	
251 
252 
253 	/**
254 	 * This method processes the WhenConstraint passed in. The when constraint
255 	 * is used to create a boolean statement to determine if the constraint will
256 	 * be applied. The necessary rules/methods for applying this constraint are
257 	 * created in the createRule call. Note the use of the use of coerceValue js
258 	 * function call.
259 	 * 
260 	 * @param view
261 	 * @param wc
262 	 * @param fieldPath
263 	 * @param operator
264 	 * @param andedCase
265 	 */
266 	private static void processWhenConstraint(InputField field, View view, CaseConstraint caseConstraint, WhenConstraint wc, String fieldPath,
267 			String operator, String andedCase) {
268 		String ruleString = "";
269 		// prerequisite constraint
270 
271 		String booleanStatement = "";
272 		if (wc.getValues() != null) {
273 
274 			String caseStr = "";
275 			if (!caseConstraint.isCaseSensitive()) {
276 				caseStr = ".toUpperCase()";
277 			}
278 			for (int i = 0; i < wc.getValues().size(); i++) {
279 				if (operator.isEmpty()) {
280 					// has_value case
281 					if (wc.getValues().get(i) instanceof String
282 							&& ((String) wc.getValues().get(i)).equalsIgnoreCase("false")) {
283 						booleanStatement = booleanStatement + "(coerceValue('" + fieldPath + "') == '')";
284 					}
285 					else {
286 						booleanStatement = booleanStatement + "(coerceValue('" + fieldPath + "') != '')";
287 					}
288 				}
289 				else {
290 					// everything else
291 					booleanStatement = booleanStatement + "(coerceValue('" + fieldPath + "')" + caseStr + " "
292 							+ operator + " \"" + wc.getValues().get(i) + "\"" + caseStr + ")";
293 				}
294 				if ((i + 1) != wc.getValues().size()) {
295 					booleanStatement = booleanStatement + " || ";
296 				}
297 			}
298 
299 		}
300 
301 		if (andedCase != null) {
302 			booleanStatement = "(" + booleanStatement + ") && (" + andedCase + ")";
303 		}
304 
305 		if (wc.getConstraint() != null && StringUtils.isNotEmpty(booleanStatement)) {
306 			ruleString = createRule(field, wc.getConstraint(), booleanStatement, view);
307 		}
308 
309 		if (StringUtils.isNotEmpty(ruleString)) {
310 			addScriptToPage(view, field, ruleString);
311 		}
312 	}
313 	
314 
315 
316 	/**
317 	 * Adds the script to the view to execute on a jQuery document ready event.
318 	 * 
319 	 * @param view
320 	 * @param script
321 	 */
322 	public static void addScriptToPage(View view, InputField field, String script) {
323         String prefixScript = "";
324         
325         if (field.getOnDocumentReadyScript() != null) {
326             prefixScript = field.getOnDocumentReadyScript();
327         }
328         field.setOnDocumentReadyScript(prefixScript + "\n" + "runValidationScript(function(){" + script + "});");
329 	}
330 	
331 	/**
332 	 * Determines which fields are being evaluated in a boolean statement, so handlers can be
333 	 * attached to them if needed, returns these names in a list.
334 	 * 
335 	 * @param statement
336 	 * @return
337 	 */
338 	private static List<String> parseOutFields(String statement){
339 	    List<String> fieldNames = new ArrayList<String>();
340 	    String[] splits = StringUtils.splitByWholeSeparator(statement, "coerceValue('");
341 	    for(String s: splits){
342 	        String fieldName = StringUtils.substringBefore(s, "'");
343 	        fieldNames.add(fieldName);
344 	    }
345 	    return fieldNames;
346 	}
347 
348 	/**
349 	 * This method takes in a constraint to apply only when the passed in
350 	 * booleanStatement is valid. The method will create the necessary addMethod
351 	 * and addRule jquery validator calls for the rule to be applied to the
352 	 * field when the statement passed in evaluates to true during runtime and
353 	 * this field is being validated. Note the use of custom methods for min/max
354 	 * length/value.
355 	 * 
356 	 * @param field
357 	 *            the field to apply the generated methods and rules to
358 	 * @param constraint
359 	 *            the constraint to be applied when the booleanStatement
360 	 *            evaluates to true during validation
361 	 * @param booleanStatement
362 	 *            the booleanstatement in js - should return true when the
363 	 *            validation rule should be applied
364 	 * @param view
365 	 * @return
366 	 */
367 	@SuppressWarnings("boxing")
368 	private static String createRule(InputField field, Constraint constraint, String booleanStatement, View view) {
369 		String rule = "";
370 		int constraintCount = 0;
371 		if (constraint instanceof BaseConstraint && ((BaseConstraint) constraint).getApplyClientSide()) {
372 			if (constraint instanceof SimpleConstraint) {
373 				if (((SimpleConstraint) constraint).getRequired() != null && ((SimpleConstraint) constraint).getRequired()) {
374 					rule = rule + "required: function(element){\nreturn (" + booleanStatement + ");}";
375 					//special requiredness indicator handling
376 					String showIndicatorScript = "";
377 					for(String checkedField: parseOutFields(booleanStatement)){
378 					    showIndicatorScript = showIndicatorScript + 
379 					        "setupShowReqIndicatorCheck('"+ checkedField +"', '" + field.getBindingInfo().getBindingPath() + "', " + "function(){\nreturn (" + booleanStatement + ");});\n";
380 					}
381 					addScriptToPage(view, field, showIndicatorScript);
382 					
383 					constraintCount++;
384 				}
385 				if (((SimpleConstraint) constraint).getMinLength() != null) {
386 					if (constraintCount > 0) {
387 						rule = rule + ",\n";
388 					}
389 					rule = rule + "minLengthConditional: [" + ((SimpleConstraint) constraint).getMinLength()
390 							+ ", function(){return " + booleanStatement + ";}]";
391 					constraintCount++;
392 				}
393 				if (((SimpleConstraint) constraint).getMaxLength() != null) {
394 					if (constraintCount > 0) {
395 						rule = rule + ",\n";
396 					}
397 					rule = rule + "maxLengthConditional: [" + ((SimpleConstraint) constraint).getMaxLength()
398 							+ ", function(){return " + booleanStatement + ";}]";
399 					constraintCount++;
400 				}
401 
402 				if (((SimpleConstraint) constraint).getExclusiveMin() != null) {
403 					if (constraintCount > 0) {
404 						rule = rule + ",\n";
405 					}
406 					rule = rule + "minExclusive: [" + ((SimpleConstraint) constraint).getExclusiveMin()
407 							+ ", function(){return " + booleanStatement + ";}]";
408 					constraintCount++;
409 				}
410 
411 				if (((SimpleConstraint) constraint).getInclusiveMax() != null) {
412 					if (constraintCount > 0) {
413 						rule = rule + ",\n";
414 					}
415 					rule = rule + "maxInclusive: [" + ((SimpleConstraint) constraint).getInclusiveMax()
416 							+ ", function(){return " + booleanStatement + ";}]";
417 					constraintCount++;
418 				}
419 
420 				rule = "jq('[name=\"" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').rules(\"add\", {" + rule + "\n});";
421 			}
422 			else if (constraint instanceof ValidCharactersConstraint) {
423 				String regexMethod = "";
424 				String methodName = "";
425 				if(StringUtils.isNotEmpty(((ValidCharactersConstraint)constraint).getValue())) {
426 					regexMethod = ClientValidationUtils.getRegexMethodWithBooleanCheck(field, (ValidCharactersConstraint) constraint) + "\n";
427 					methodName = "validChar-" + field.getBindingInfo().getBindingPath() + methodKey;
428 					methodKey++;
429 				}
430 				else {
431 					if(StringUtils.isNotEmpty(((ValidCharactersConstraint)constraint).getLabelKey())){
432 						methodName = ((ValidCharactersConstraint)constraint).getLabelKey();
433 					}
434 				}
435 				if (StringUtils.isNotEmpty(methodName)) {
436 					rule = regexMethod + "jq('[name=\"" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').rules(\"add\", {\n\"" + methodName
437 							+ "\" : function(element){return (" + booleanStatement + ");}\n});";
438 				}
439 			}
440 			else if (constraint instanceof PrerequisiteConstraint) {
441 				processPrerequisiteConstraint(field, (PrerequisiteConstraint) constraint, view, booleanStatement);
442 			}
443 			else if (constraint instanceof CaseConstraint) {
444 				processCaseConstraint(field, view, (CaseConstraint) constraint, booleanStatement);
445 			}
446 			else if (constraint instanceof MustOccurConstraint) {
447 				processMustOccurConstraint(field, view, (MustOccurConstraint) constraint, booleanStatement);
448 			}
449 		}
450 		return rule;
451 	}
452 	
453 
454 
455 	/**
456 	 * This method is a simpler version of processPrerequisiteConstraint
457 	 * 
458 	 * @see ClientValidationUtils#processPrerequisiteConstraint(org.kuali.rice.krad.uif.field.InputField, PrerequisiteConstraint, View, String)
459 	 * @param constraint
460 	 * @param view
461 	 */
462 	public static void processPrerequisiteConstraint(InputField field, PrerequisiteConstraint constraint, View view) {
463 		processPrerequisiteConstraint(field, constraint, view, "true");
464 	}
465 
466 	/**
467 	 * This method processes a Prerequisite constraint that should be applied
468 	 * when the booleanStatement passed in evaluates to true.
469 	 * 
470 	 * @param constraint
471 	 *            prerequisiteConstraint
472 	 * @param view
473 	 * @param booleanStatement
474 	 *            the booleanstatement in js - should return true when the
475 	 *            validation rule should be applied
476 	 */
477 	public static void processPrerequisiteConstraint(InputField field, PrerequisiteConstraint constraint, View view, String booleanStatement) {
478 		if (constraint != null && constraint.getApplyClientSide().booleanValue()) {
479 		    String dependsClass = "dependsOn-" + ScriptUtils.escapeName(constraint.getPropertyName());
480 		    String addClass = "jq('[name=\""+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').addClass('" + dependsClass + "');" +
481 		        "jq('[name=\""+ ScriptUtils.escapeName(constraint.getPropertyName()) + "\"]').addClass('" + "dependsOn-" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "');";
482 			addScriptToPage(view, field, addClass + getPrerequisiteStatement(field, view, constraint, booleanStatement)
483 					+ getPostrequisiteStatement(field, constraint, booleanStatement));
484 	        //special requiredness indicator handling
485 	        String showIndicatorScript = "setupShowReqIndicatorCheck('"+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) +"', '" + ScriptUtils.escapeName(constraint.getPropertyName()) + "', " + "function(){\nreturn (coerceValue('" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "') && " + booleanStatement + ");});\n";
486 	        addScriptToPage(view, field, showIndicatorScript);
487 		}
488 	}
489 
490 	/**
491 	 * This method creates the script necessary for executing a prerequisite
492 	 * rule in which this field occurs after the field specified in the
493 	 * prerequisite rule - since it requires a specific set of UI logic. Builds
494 	 * an if statement containing an addMethod jquery validator call. Adds a
495 	 * "dependsOn" css class to this field for the field specified.
496 	 * 
497 	 * @param constraint
498 	 *            prerequisiteConstraint
499 	 * @param booleanStatement
500 	 *            the booleanstatement in js - should return true when the
501 	 *            validation rule should be applied
502 	 * @return
503 	 */
504 	private static String getPrerequisiteStatement(InputField field, View view, PrerequisiteConstraint constraint, String booleanStatement) {
505 		methodKey++;
506 		String message = "";
507 		if(StringUtils.isEmpty(constraint.getLabelKey())){
508 			message = configService.getPropertyValueAsString(UifConstants.Messages.VALIDATION_MSG_KEY_PREFIX + "prerequisite");
509 		}
510 		else{
511 			message = generateMessageFromLabelKey(constraint.getValidationMessageParams(), constraint.getLabelKey());
512 		}
513 		if(StringUtils.isEmpty(message)){
514 			message = "prerequisite - No message";
515 		}
516 		else{
517 			InputField requiredField = (InputField) view.getViewIndex().getDataFieldByPath(constraint.getPropertyName());
518 			if(requiredField != null && StringUtils.isNotEmpty(requiredField.getLabel())){
519 				message = MessageFormat.format(message, requiredField.getLabel());
520 			}
521 			else{
522 				message = MessageFormat.format(message, configService.getPropertyValueAsString(GENERIC_FIELD_MSG_KEY));
523 			}
524 		}
525 		
526 		// field occurs before case
527 		String methodName = "prConstraint-" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + methodKey;
528 		String addClass = "jq('[name=\""+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').addClass('" + methodName + "');\n";
529 		String method = "\njQuery.validator.addMethod(\""+ methodName +"\", function(value, element) {\n" +
530 			" if(" + booleanStatement + "){ return (this.optional(element) || (coerceValue('" + ScriptUtils.escapeName(constraint.getPropertyName()) + "')));}else{return true;} " +
531 			"}, \"" + message + "\");";
532 		
533 		String ifStatement = "if(occursBefore('" + ScriptUtils.escapeName(constraint.getPropertyName()) + "','" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) +
534 		"')){" + addClass + method + "}";
535 		return ifStatement;
536 	}
537 
538 	/**
539 	 * This method creates the script necessary for executing a prerequisite
540 	 * rule in which this field occurs before the field specified in the
541 	 * prerequisite rule - since it requires a specific set of UI logic. Builds
542 	 * an if statement containing an addMethod jquery validator call.
543 	 * 
544 	 * @param constraint
545 	 *            prerequisiteConstraint
546 	 * @param booleanStatement
547 	 *            the booleanstatement in js - should return true when the
548 	 *            validation rule should be applied
549 	 * @return
550 	 */
551 	private static String getPostrequisiteStatement(InputField field, PrerequisiteConstraint constraint, String booleanStatement) {
552 		// field occurs after case
553 		String message = "";
554 		if(StringUtils.isEmpty(constraint.getLabelKey())){
555 			message = configService.getPropertyValueAsString(UifConstants.Messages.VALIDATION_MSG_KEY_PREFIX + "postrequisite");
556 		}
557 		else{
558 			message = generateMessageFromLabelKey(constraint.getValidationMessageParams(), constraint.getLabelKey());
559 		}
560 		
561 		if(StringUtils.isEmpty(constraint.getLabelKey())){
562 			if(StringUtils.isNotEmpty(field.getLabel())){
563 				message = MessageFormat.format(message, field.getLabel());
564 			}
565 			else{
566 				message = MessageFormat.format(message, configService.getPropertyValueAsString(GENERIC_FIELD_MSG_KEY));
567 			}
568 			
569 		}
570 		
571 		String function = "function(element){\n" +
572 			"return (coerceValue('"+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "') && " + booleanStatement + ");}";
573 		String postStatement = "\nelse if(occursBefore('" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "','" + ScriptUtils.escapeName(constraint.getPropertyName()) +
574 			"')){\njq('[name=\""+ ScriptUtils.escapeName(constraint.getPropertyName()) +
575 			"\"]').rules(\"add\", { required: \n" + function 
576 			+ ", \nmessages: {\nrequired: \""+ message +"\"}});}\n";
577 		
578 		return postStatement;
579 
580 	}
581 
582 	/**
583 	 * This method processes the MustOccurConstraint. The constraint is only
584 	 * applied when the booleanStatement evaluates to true during validation.
585 	 * This method creates the addMethod and add rule calls for the jquery
586 	 * validation plugin necessary for applying this constraint to this field.
587 	 * 
588 	 * @param view
589 	 * @param mc
590 	 * @param booleanStatement
591 	 *            the booleanstatement in js - should return true when the
592 	 *            validation rule should be applied
593 	 */
594 	public static void processMustOccurConstraint(InputField field, View view, MustOccurConstraint mc, String booleanStatement) {
595 		methodKey++;
596 		mustOccursPathNames = new ArrayList<List<String>>();
597 		// TODO make this show the fields its requiring
598 		String methodName = "moConstraint-" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + methodKey;
599 		String method = "\njQuery.validator.addMethod(\""+ methodName +"\", function(value, element) {\n" +
600 		" if(" + booleanStatement + "){return (this.optional(element) || ("+ getMustOccurStatement(field, mc) + "));}else{return true;}" +
601 		"}, \"" + getMustOccursMessage(view, mc) +"\");";
602 		String rule = method + "jq('[name=\""+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').rules(\"add\", {\n\"" + methodName + "\": function(element){return (" + booleanStatement + ");}\n});";
603 		addScriptToPage(view, field, rule);
604 	}
605 
606 	/**
607 	 * This method takes in a MustOccurConstraint and returns the statement used
608 	 * in determining if the must occurs constraint has been satisfied when this
609 	 * field is validated. Note the use of the mustOccurCheck method. Nested
610 	 * mustOccurConstraints are ored against the result of the mustOccurCheck by
611 	 * calling this method recursively.
612 	 * 
613 	 * @param constraint
614 	 * @return
615 	 */
616 	@SuppressWarnings("boxing")
617 	private static String getMustOccurStatement(InputField field, MustOccurConstraint constraint) {
618 		String statement = "";
619 		List<String> attributePaths = new ArrayList<String>();
620 		if (constraint != null && constraint.getApplyClientSide()) {
621 			String array = "[";
622 			if (constraint.getPrerequisiteConstraints() != null) {
623 				for (int i = 0; i < constraint.getPrerequisiteConstraints().size(); i++) {
624 					field.getControl().addStyleClass("dependsOn-"
625 							+ constraint.getPrerequisiteConstraints().get(i).getPropertyName());
626 					array = array + "'" + ScriptUtils.escapeName(constraint.getPrerequisiteConstraints().get(i).getPropertyName()) + "'";
627 					attributePaths.add(constraint.getPrerequisiteConstraints().get(i).getPropertyName());
628 					if (i + 1 != constraint.getPrerequisiteConstraints().size()) {
629 						array = array + ",";
630 					}
631 
632 				}
633 			}
634 			array = array + "]";
635 			statement = "mustOccurTotal(" + array + ", " + constraint.getMin() + ", " + constraint.getMax() + ")";
636 			//add min to string list
637 			if(constraint.getMin()!=null){
638 				attributePaths.add(constraint.getMin().toString());
639 			}
640 			else{
641 				attributePaths.add(null);
642 			}
643 			//add max to string list
644 			if(constraint.getMax()!=null){
645 				attributePaths.add(constraint.getMax().toString());
646 			}
647 			else{
648 				attributePaths.add(null);
649 			}
650 			
651 			mustOccursPathNames.add(attributePaths);
652 			if(StringUtils.isEmpty(statement)){
653 				statement = "0";
654 			}
655 			if (constraint.getMustOccurConstraints() != null) {
656 				for (MustOccurConstraint mc : constraint.getMustOccurConstraints()) {
657 					statement = "mustOccurCheck(" + statement + " + " + getMustOccurStatement(field, mc) +
658 						", " + constraint.getMin() + ", " + constraint.getMax() + ")";
659 				}
660 			}
661 			else{
662 				statement = "mustOccurCheck(" + statement +
663 				", " + constraint.getMin() + ", " + constraint.getMax() + ")";
664 			}
665 		}
666 		return statement;
667 	}
668 
669 	
670 	/**
671 	 * Generates a message for the must occur constraint (if no label key is specified).  
672 	 * This message is most accurate when must occurs is a single
673 	 * or double level constraint.  Beyond that, the message will still be accurate but may be confusing for
674 	 * the user - this auto-generated message however will work in MOST use cases.
675 	 * 
676 	 * @param view
677 	 * @return
678 	 */
679 	private static String getMustOccursMessage(View view, MustOccurConstraint constraint){
680 		String message = "";
681 		if(StringUtils.isNotEmpty(constraint.getLabelKey())){
682 			message = generateMessageFromLabelKey(constraint.getValidationMessageParams(), constraint.getLabelKey());
683 		}
684 		else{
685 			String and = configService.getPropertyValueAsString(AND_MSG_KEY);
686 			String or = configService.getPropertyValueAsString(OR_MSG_KEY);
687 			String all = configService.getPropertyValueAsString(ALL_MSG_KEY);
688 			String atMost = configService.getPropertyValueAsString(ATMOST_MSG_KEY);
689 			String genericLabel = configService.getPropertyValueAsString(GENERIC_FIELD_MSG_KEY);
690 			String mustOccursMsg = configService.getPropertyValueAsString(
691 			        UifConstants.Messages.VALIDATION_MSG_KEY_PREFIX + MUSTOCCURS_MSG_KEY);
692 			//String postfix = configService.getPropertyValueAsString(VALIDATION_MSG_KEY_PREFIX + MUSTOCCURS_POST_MSG_KEY);
693 			String statement="";
694 			for(int i=0; i< mustOccursPathNames.size(); i++){
695 				String andedString = "";
696 				
697 				List<String> paths = mustOccursPathNames.get(i);
698 				if(!paths.isEmpty()){
699 					//note that the last 2 strings are min and max and rest are attribute paths
700 					String min = paths.get(paths.size()-2);
701 					String max = paths.get(paths.size()-1);
702 					for(int j=0; j<paths.size()-2;j++){
703 						InputField field = (InputField) view.getViewIndex().getDataFieldByPath(paths.get(j).trim());
704 						String label = genericLabel;
705 						if(field != null && StringUtils.isNotEmpty(field.getLabel())){
706 							label = field.getLabel();
707 						}
708 						if(min.equals(max)){
709 							if(j==0){
710 								andedString = label;
711 							}
712 							else if(j==paths.size()-3){
713 								andedString = andedString + " " + and + " " + label;
714 							}
715 							else{
716 								andedString = andedString + ", " + label;
717 							}
718 						}
719 						else{
720 							andedString = andedString + "<li>" + label + "</li>";
721 						}
722 					}
723 					if(min.equals(max)){
724 						andedString = "<li>" + andedString + "</li>";
725 					}
726 					andedString="<ul>" + andedString + "</ul>";
727 				
728 					if(StringUtils.isNotEmpty(min) && StringUtils.isNotEmpty(max) && !min.equals(max)){
729 						andedString = MessageFormat.format(mustOccursMsg, min + "-" + max) + "<br/>" +andedString;
730 					}
731 					else if(StringUtils.isNotEmpty(min) && StringUtils.isNotEmpty(max) && min.equals(max) && i==0){
732 						andedString = MessageFormat.format(mustOccursMsg, all) + "<br/>" +andedString;
733 					}
734 					else if(StringUtils.isNotEmpty(min) && StringUtils.isNotEmpty(max) && min.equals(max) && i!=0){
735 						//leave andedString as is
736 					}
737 					else if(StringUtils.isNotEmpty(min)){
738 						andedString = MessageFormat.format(mustOccursMsg, min) + "<br/>" +andedString;
739 					}
740 					else if(StringUtils.isNotEmpty(max)){
741 						andedString = MessageFormat.format(mustOccursMsg, atMost + " " + max) + "<br/>" +andedString;
742 					}
743 				}
744 				if(StringUtils.isNotEmpty(andedString)){
745 					if(i==0){
746 						statement = andedString;
747 					}
748 					else{
749 						statement = statement + or.toUpperCase() + andedString;
750 					}
751 				}
752 			}
753 			if(StringUtils.isNotEmpty(statement)){
754 				message = statement;
755 			}
756 		}
757 		
758 		return message;
759 	}
760 
761 	/**
762 	 * This method processes all the constraints on the InputField passed in and adds all the necessary
763 	 * jQuery and js required (validator's rules, methods, and messages) to the View's onDocumentReady call.
764 	 * The result is js that will validate all the constraints contained on an InputField during user interaction
765 	 * with the field using the jQuery validation plugin and custom code.
766 	 * 
767 	 * @param field
768 	 */
769 	@SuppressWarnings("boxing")
770 	public static void processAndApplyConstraints(InputField field, View view) {
771 		methodKey = 0;
772         if (view instanceof FormView && ((FormView) view).isValidateClientSide()) {
773             if(field.getSimpleConstraint() != null && field.getSimpleConstraint().getApplyClientSide()){
774                 if ((field.getRequired() != null) && (field.getRequired().booleanValue())) {
775                     field.getControl().addStyleClass("required");
776                 }
777         
778                 if (field.getExclusiveMin() != null) {
779                     if (field.getControl() instanceof TextControl && ((TextControl) field.getControl()).getDatePicker() != null) {
780                         ((TextControl) field.getControl()).getDatePicker().getComponentOptions().put("minDate", field.getExclusiveMin());
781                     }
782                     else{
783                         String rule = "jq('[name=\""+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').rules(\"add\", {\n minExclusive: ["+ field.getExclusiveMin() + "]});";
784                         addScriptToPage(view, field, rule);
785                     }
786                 }
787         
788                 if (field.getInclusiveMax() != null) {
789                     if (field.getControl() instanceof TextControl && ((TextControl) field.getControl()).getDatePicker() != null) {
790                         ((TextControl) field.getControl()).getDatePicker().getComponentOptions().put("maxDate", field.getInclusiveMax());
791                     }
792                     else{
793                         String rule = "jq('[name=\""+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').rules(\"add\", {\n maxInclusive: ["+ field.getInclusiveMax() + "]});";
794                         addScriptToPage(view, field, rule);
795                     }
796                 }
797             }
798     
799             if (field.getValidCharactersConstraint() != null && field.getValidCharactersConstraint().getApplyClientSide()) {
800                 if(StringUtils.isNotEmpty(field.getValidCharactersConstraint().getValue())) {
801                     // set regex value takes precedence
802                     addScriptToPage(view, field, ClientValidationUtils.getRegexMethod(field, field.getValidCharactersConstraint()));
803                     field.getControl().addStyleClass("validChar-" + field.getBindingInfo().getBindingPath()+ methodKey);
804                     methodKey++;
805                 }
806                 else {
807                     //blindly assume that if there is no regex value defined that there must be a method by this name
808                     if(StringUtils.isNotEmpty(field.getValidCharactersConstraint().getLabelKey())){
809                         field.getControl().addStyleClass(field.getValidCharactersConstraint().getLabelKey());
810                     }
811                 }
812             }
813     
814             if (field.getCaseConstraint() != null && field.getCaseConstraint().getApplyClientSide()) {
815                 processCaseConstraint(field, view, field.getCaseConstraint(), null);
816             }
817     
818             if (field.getDependencyConstraints() != null) {
819                 for (PrerequisiteConstraint prc : field.getDependencyConstraints()) {
820                     processPrerequisiteConstraint(field, prc, view);
821                 }
822             }
823     
824             if (field.getMustOccurConstraints() != null) {
825                 for (MustOccurConstraint mc : field.getMustOccurConstraints()) {
826                     processMustOccurConstraint(field, view, mc, "true");
827                 }
828             }
829             
830         }
831     }
832 }