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 }