001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krad.uif.util;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.kuali.rice.core.api.config.property.ConfigurationService;
020 import org.kuali.rice.krad.datadictionary.validation.constraint.BaseConstraint;
021 import org.kuali.rice.krad.datadictionary.validation.constraint.CaseConstraint;
022 import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint;
023 import org.kuali.rice.krad.datadictionary.validation.constraint.MustOccurConstraint;
024 import org.kuali.rice.krad.datadictionary.validation.constraint.PrerequisiteConstraint;
025 import org.kuali.rice.krad.datadictionary.validation.constraint.SimpleConstraint;
026 import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint;
027 import org.kuali.rice.krad.datadictionary.validation.constraint.WhenConstraint;
028 import org.kuali.rice.krad.service.KRADServiceLocator;
029 import org.kuali.rice.krad.uif.UifConstants;
030 import org.kuali.rice.krad.uif.field.InputField;
031 import org.kuali.rice.krad.uif.view.FormView;
032 import org.kuali.rice.krad.uif.view.View;
033 import org.kuali.rice.krad.uif.control.TextControl;
034
035 import java.text.MessageFormat;
036 import java.util.ArrayList;
037 import java.util.EnumSet;
038 import java.util.List;
039
040 /**
041 * This class contains all the methods necessary for generating the js required to perform validation client side.
042 * The processAndApplyConstraints(InputField field, View view) is the key method of this class used by
043 * InputField to setup its client side validation mechanisms.
044 *
045 * @author Kuali Rice Team (rice.collab@kuali.org)
046 */
047 public class ClientValidationUtils {
048 // used to give validation methods unique signatures
049 private static int methodKey = 0;
050
051 // list used to temporarily store mustOccurs field names for the error
052 // message
053 private static List<List<String>> mustOccursPathNames;
054
055 public static final String LABEL_KEY_SPLIT_PATTERN = ",";
056
057
058 public static final String PREREQ_MSG_KEY = "prerequisite";
059 public static final String POSTREQ_MSG_KEY = "postrequisite";
060 public static final String MUSTOCCURS_MSG_KEY = "mustOccurs";
061 public static final String GENERIC_FIELD_MSG_KEY = "general.genericFieldName";
062
063 public static final String ALL_MSG_KEY = "general.all";
064 public static final String ATMOST_MSG_KEY = "general.atMost";
065 public static final String AND_MSG_KEY = "general.and";
066 public static final String OR_MSG_KEY = "general.or";
067
068 private static ConfigurationService configService = KRADServiceLocator.getKualiConfigurationService();
069
070 //Enum representing names of rules provided by the jQuery plugin
071 public static enum ValidationMessageKeys{
072 REQUIRED("required"),
073 MIN_EXCLUSIVE("minExclusive"),
074 MAX_INCLUSIVE("maxInclusive"),
075 MIN_LENGTH("minLengthConditional"),
076 MAX_LENGTH("maxLengthConditional");
077
078 private ValidationMessageKeys(String name) {
079 this.name = name;
080 }
081
082 private final String name;
083
084 @Override
085 public String toString() {
086 return name;
087 }
088
089 public static boolean contains(String name){
090 for (ValidationMessageKeys element : EnumSet.allOf(ValidationMessageKeys.class)) {
091 if (element.toString().equalsIgnoreCase(name)) {
092 return true;
093 }
094 }
095 return false;
096 }
097 }
098
099 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("\"", """);
113 }
114 if(message.contains("'")){
115 message = message.replace("'", "'");
116 }
117 if(message.contains("\\")){
118 message = message.replace("\\", "\");
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 }