1 /**
  2  * @namespace The alv namespace.
  3  */
  4 var alv = {};
  5 
  6 
  7 /**
  8  * @namespace Utility module.
  9  */
 10 alv.util = {
 11     /**
 12      * @description Trim leading and trailing whitespace characters from a string value.
 13      * @param {String} pVal The value to be trimmed.
 14      * @returns {String} The trimmed value of pVal.
 15      */
 16     trim: function (pVal) {
 17         return pVal.replace(/^\s+|\s+$/g, "");
 18     },
 19     /**
 20      * @description Replace a character on position x within a given string.
 21      * @param {String} pString The string that contains the character to be replaced.
 22      * @param {Number} pIndex The position of the character that will be replaced.
 23      * @param {String|Number} pChar The new character value.
 24      * @returns {String} The string with one character replaced.
 25      */
 26     replaceCharInString: function (pString, pIndex, pChar) {
 27         return pString.substr(0, pIndex) + pChar.toString() + pString.substr(pIndex + pChar.toString().length);
 28     },
 29     /**
 30      * @description Get the value of an APEX page item if an ID selector is passed as parameter, otherwise return the parameter value.
 31      * @param {String} pItem Can be either an APEX page item ID selector or a fixed value.
 32      * @returns {String} The value of a page item or a fixed value.
 33      */
 34     getPageItemValue: function (pItem) {
 35         if (String(pItem).substring(0, 2) === "#P") {
 36             return $(pItem).val();
 37         }
 38         return pItem;
 39     },
 40     /**
 41      * @description Get the result of JavaScript expression.
 42      * @param {String} pExpression A JavaScript expression in the form of a string that will be evaluated with eval().
 43      * @returns {Boolean} The boolean result of the expression.
 44      */
 45     getConditionResult: function (pExpression) {
 46         var expressionResult = true;
 47         if (pExpression.length) {
 48             expressionResult = eval(pExpression);
 49         }
 50         return expressionResult;
 51     },
 52     /**
 53      * @description Get the numeric value of a string. Return an empty string if pVal is empty.
 54      * @param {String} pVal The numeric value as a string.
 55      * @returns {Number|String} The numeric value of pVal or an empty string.
 56      */
 57     getNumberFromString: function (pVal) {
 58         if (String(pVal).length) {
 59             return Number(pVal);
 60         }
 61         return "";
 62     },
 63     /**
 64      * @description Format a DD/MM/YYYY date represented as a string to a date object.
 65      * @param {String} pVal The date value as a string.
 66      * @returns {Date} The date value as a date object.
 67      */
 68     getDateFromString: function (pVal) {
 69         var dateArray = pVal.split("/");
 70         var year = parseInt(dateArray[2]);
 71         var month = parseInt(dateArray[1], 10);
 72         var day = parseInt(dateArray[0], 10);
 73 
 74         return new Date(year, month - 1, day);
 75     },
 76     /**
 77      * @description Convert a string date in a given date format to the DD/MM/YYYY date format.
 78      * @param {String} pDate The date to be converted.
 79      * @param {String} pDateFormat The date format of pDate.
 80      * @returns {String} The date value in the DD/MM/YYYY format.
 81      */
 82     convertDate: function (pDate, pDateFormat) {
 83         var dateFormat = pDateFormat.toUpperCase();
 84         var dateFormatSeparator = dateFormat.replace(/[A-Z]+/g, "");
 85         var dateSeparator = pDate.replace(/\d+/g, "");
 86         var dayPart;
 87         var monthPart;
 88         var yearPart;
 89 
 90         if (pDate.length === pDateFormat.length && dateSeparator === dateFormatSeparator) {
 91             if (dateFormat.indexOf("DD") === -1) {
 92                 dayPart = "xx";
 93             } else {
 94                 dayPart = pDate.substring(dateFormat.indexOf("DD"), dateFormat.indexOf("DD") + 2);
 95             }
 96 
 97             if (dateFormat.indexOf("MM") === -1) {
 98                 monthPart = "xx";
 99             } else {
100                 monthPart = pDate.substring(dateFormat.indexOf("MM"), dateFormat.indexOf("MM") + 2);
101             }
102 
103             if (dateFormat.indexOf("YYYY") === -1) {
104                 if (dateFormat.indexOf("RRRR") === -1) {
105                     if (dateFormat.indexOf("YY") === -1) {
106                         if (dateFormat.indexOf("RR") === -1) {
107                             yearPart = "xxxx";
108                         } else {
109                             yearPart = pDate.substring(dateFormat.indexOf("RR"), dateFormat.indexOf("RR") + 2);
110                         }
111                     } else {
112                         yearPart = pDate.substring(dateFormat.indexOf("YY"), dateFormat.indexOf("YY") + 2);
113                     }
114                 } else {
115                     yearPart = pDate.substring(dateFormat.indexOf("RRRR"), dateFormat.indexOf("RRRR") + 4);
116                 }
117             } else {
118                 yearPart = pDate.substring(dateFormat.indexOf("YYYY"), dateFormat.indexOf("YYYY") + 4);
119             }
120         }
121 
122         return dayPart + "/" + monthPart + "/" + yearPart;
123     }
124 };
125 
126 
127 /**
128  * @namespace Validators module.
129  */
130 alv.validators = {
131     util: alv.util,
132 
133     /**
134      * @description Check whether a value is empty.
135      * @param {String|Number|RegExp} pVal The value to be tested.
136      * @returns {Boolean}
137      */
138     isEmpty: function (pVal) {
139         return pVal === "";
140     },
141     /**
142      * @description Check for equality between two values.
143      * @param {String} pVal The first value.
144      * @param {String} pVal2 The second value.
145      * @returns {Boolean}
146      */
147     isEqual: function (pVal, pVal2) {
148         return pVal === pVal2;
149     },
150     /**
151      * @description Check whether a value matches a regular expression.
152      * @param {String|Number} pVal The value to be tested.
153      * @param {String|RegExp} pRegex The regular expression.
154      * @returns {Boolean}
155      */
156     regex: function (pVal, pRegex) {
157         return new RegExp(pRegex).test(pVal) || this.isEmpty(pVal);
158     },
159     /**
160      * @description Check whether a value contains only alphanumeric characters.
161      * @param {String} pVal The value to be tested.
162      * @returns {Boolean}
163      */
164     isAlphanumeric: function (pVal) {
165         return this.regex(pVal, /^[a-z0-9]+$/i);
166     },
167     /**
168      * @description Check whether a value is a valid number.
169      * @param {Number} pVal The value to be tested.
170      * @returns {Boolean}
171      */
172     isNumber: function (pVal) {
173         return this.regex(pVal, /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/);
174     },
175     /**
176      * @description Check whether a value contains only digit characters.
177      * @param {String} pVal The value to be tested.
178      * @returns {Boolean}
179      */
180     isDigit: function (pVal) {
181         return this.regex(pVal, /^\d+$/);
182     },
183     /**
184      * @description Check whether a value is a valid e-mail address.
185      * @param {String} pVal The value to be tested.
186      * @returns {Boolean}
187      */
188     isEmail: function (pVal) {
189         return this.regex(pVal, /^[^\s@]+@[^\s@]+\.[^\s@]+$/);
190     },
191     /**
192      * @description Check whether a value is a valid URL.
193      * @param {String} pVal The value to be tested.
194      * @returns {Boolean}
195      */
196     isUrl: function (pVal) {
197         return this.regex(pVal, /(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/);
198     },
199     /**
200      * @description Check whether a value is a valid date.
201      * @param {String} pVal The value to be tested.
202      * @param {String} pDateFormat The date format of pVal.
203      * @returns {Boolean}
204      */
205     isDate: function (pVal, pDateFormat) {
206         var dateRegex = new RegExp("^(3[01]|[12][0-9]|0?[1-9])/(1[0-2]|0?[1-9])/(?:[0-9]{2})?[0-9]{2}$");
207         var convertedDate = this.util.convertDate(pVal, pDateFormat);
208 
209         if (convertedDate.match(dateRegex)) {
210             var dateArray = convertedDate.split("/");
211             var year = parseInt(dateArray[2]);
212             var month = parseInt(dateArray[1], 10);
213             var day = parseInt(dateArray[0], 10);
214             var date = new Date(year, month - 1, day);
215 
216             if (((date.getMonth() + 1) === month) && (date.getDate() === day) && (date.getFullYear() === year)) {
217                 return true;
218             }
219         }
220         return this.isEmpty(pVal);
221     },
222     /**
223      * @description Check whether the character length of a value is not lower than pMin.
224      * @param {String} pVal The value to be tested.
225      * @param {Number} pMin The minimum character length.
226      * @returns {Boolean}
227      */
228     minLength: function (pVal, pMin) {
229         return pVal.length >= pMin || this.isEmpty(pVal);
230     },
231     /**
232      * @description Check whether the character length of a value is not higher than pMax.
233      * @param {String} pVal The value to be tested.
234      * @param {Number} pMax The maximum character length.
235      * @returns {Boolean}
236      */
237     maxLength: function (pVal, pMax) {
238         return pVal.length <= pMax || this.isEmpty(pVal);
239     },
240     /**
241      * @description Check whether the character length of a value lies between pMin and pMax.
242      * @param {String} pVal The value to be tested.
243      * @param {Number} pMin The minimum character length.
244      * @param {Number} pMax The maximum character length.
245      * @returns {Boolean}
246      */
247     rangeLength: function (pVal, pMin, pMax) {
248         return this.minLength(pVal, pMin) && this.maxLength(pVal, pMax) || this.isEmpty(pVal);
249     },
250     /**
251      * @description Check whether a numeric value is not lower than pMin.
252      * @param {Number} pVal The value to be tested.
253      * @param {Number} pMin The minimum number.
254      * @returns {Boolean}
255      */
256     minNumber: function (pVal, pMin) {
257         if (!this.isEmpty(pVal) && !this.isEmpty(pMin)) {
258             if (this.isNumber(pVal) && this.isNumber(pMin)) {
259                 return pVal >= pMin;
260             }
261         }
262         return true;
263     },
264     /**
265      * @description Check whether a numeric value is not higher than pMax.
266      * @param {Number} pVal The value to be tested.
267      * @param {Number} pMax The maximum number.
268      * @returns {Boolean}
269      */
270     maxNumber: function (pVal, pMax) {
271         if (!this.isEmpty(pVal) && !this.isEmpty(pMax)) {
272             if (this.isNumber(pVal) && this.isNumber(pMax)) {
273                 return pVal <= pMax;
274             }
275         }
276         return true;
277     },
278     /**
279      * @description Check whether a numeric value lies between pMin and pMax.
280      * @param {Number} pVal The value to be tested.
281      * @param {Number} pMin The minimum number.
282      * @param {Number} pMax The maximum number.
283      * @returns {Boolean}
284      */
285     rangeNumber: function (pVal, pMin, pMax) {
286         if (!this.isEmpty(pVal) && !this.isEmpty(pMin) && !this.isEmpty(pMax)) {
287             if (this.isNumber(pVal) && this.isNumber(pMin) && this.isNumber(pMax)) {
288                 if (pMax >= pMin) {
289                     return this.minNumber(pVal, pMin) && this.maxNumber(pVal, pMax);
290                 }
291             }
292         }
293         return true;
294     },
295     /**
296      * @description Check whether the amount of selected checkboxes is not lower than pMin.
297      * @param {String} pChoices The ID selector of the checkbox group that will be validated.
298      * @param {Number} pMin The minimum amount of selected checkboxes.
299      * @param {Boolean} pEmptyAllowed Indicate whether it is allowed to have no checkbox selected.
300      * @returns {Boolean}
301      */
302     minCheck: function (pChoices, pMin, pEmptyAllowed) {
303         var totalChecked = $(pChoices).filter(':checked').length;
304 
305         if (pEmptyAllowed) {
306             return this.minNumber(totalChecked, pMin) || totalChecked === 0;
307         } else {
308             return this.minNumber(totalChecked, pMin);
309         }
310     },
311     /**
312      * @description Check whether the amount of selected checkboxes is not higher than pMax.
313      * @param {String} pChoices The ID selector of the checkbox group that will be validated.
314      * @param {Number} pMax The maximum amount of selected checkboxes.
315      * @returns {Boolean}
316      */
317     maxCheck: function (pChoices, pMax) {
318         var totalChecked = $(pChoices).filter(':checked').length;
319         return this.maxNumber(totalChecked, pMax) || totalChecked === 0;
320     },
321     /**
322      * @description Check whether the amount of selected checkboxes lies pMin and pMax.
323      * @param {String} pChoices The ID selector of the checkbox group that will be validated.
324      * @param {Number} pMin The minimum amount of selected checkboxes.
325      * @param {Number} pMax The maximum amount of selected checkboxes.
326      * @returns {Boolean}
327      */
328     rangeCheck: function (pChoices, pMin, pMax) {
329         var totalChecked = $(pChoices).filter(':checked').length;
330         return this.rangeNumber(totalChecked, pMin, pMax) || totalChecked === 0;
331     },
332     /**
333      * @description Check whether a date does not lie before pMin.
334      * @param {String} pVal The date to be tested.
335      * @param {String} pMin The minimum date.
336      * @param {String} pDateFormat The date format.
337      * @returns {Boolean}
338      */
339     minDate: function (pVal, pMin, pDateFormat) {
340         var date = new Date();
341         var minimumDate = new Date();
342 
343         if (!this.isEmpty(pVal) && !this.isEmpty(pMin)) {
344             if (this.isDate(pVal, pDateFormat) && this.isDate(pMin, pDateFormat)) {
345                 date = this.util.getDateFromString(this.util.convertDate(pVal, pDateFormat));
346                 minimumDate = this.util.getDateFromString(this.util.convertDate(pMin, pDateFormat));
347 
348                 return date >= minimumDate;
349             }
350         }
351         return true;
352     },
353     /**
354      * @description Check whether a date does not lie after pMax.
355      * @param {String} pVal The date to be tested.
356      * @param {String} pMax The maximum date.
357      * @param {String} pDateFormat The date format.
358      * @returns {Boolean}
359      */
360     maxDate: function (pVal, pMax, pDateFormat) {
361         var date = new Date();
362         var maximumDate = new Date();
363 
364         if (!this.isEmpty(pVal) && !this.isEmpty(pMax)) {
365             if (this.isDate(pVal, pDateFormat) && this.isDate(pMax, pDateFormat)) {
366                 date = this.util.getDateFromString(this.util.convertDate(pVal, pDateFormat));
367                 maximumDate = this.util.getDateFromString(this.util.convertDate(pMax, pDateFormat));
368 
369                 return date <= maximumDate;
370             }
371         }
372         return true;
373     },
374     /**
375      * @description Check whether a date lies between pMin and pMax.
376      * @param {String} pVal The date to be tested.
377      * @param {String} pMin The minimum date.
378      * @param {String} pMax The maximum date.
379      * @param {String} pDateFormat The date format.
380      * @returns {Boolean}
381      */
382     rangeDate: function (pVal, pMin, pMax, pDateFormat) {
383         var date = new Date();
384         var minimumDate = new Date();
385         var maximumDate = new Date();
386 
387         if (!this.isEmpty(pVal) && !this.isEmpty(pMin) && !this.isEmpty(pMax)) {
388             if (this.isDate(pVal, pDateFormat) && this.isDate(pMin, pDateFormat) && this.isDate(pMax, pDateFormat)) {
389                 date = this.util.getDateFromString(this.util.convertDate(pVal, pDateFormat));
390                 minimumDate = this.util.getDateFromString(this.util.convertDate(pMin, pDateFormat));
391                 maximumDate = this.util.getDateFromString(this.util.convertDate(pMax, pDateFormat));
392 
393                 if (maximumDate >= minimumDate) {
394                     return date >= minimumDate && date <= maximumDate;
395                 }
396             }
397         }
398         return true;
399     }
400 };
401 
402 
403 /**
404  * @namespace The APEX Live Validation jQuery plugin.
405  */
406 (function ($, util, validators) {
407     "use strict";
408 
409     $.fn.alv = function (method, options) {
410         var constants = {
411             'pluginId': "be.ctb.jq.alv",
412             'pluginName': "APEX Live Validation",
413             'pluginPrefix': "alv",
414 
415             // apex related
416             'apexCheckboxClass': 'checkbox_group',
417             'apexRadioClass': 'radio_group',
418             'apexShuttleClass': 'shuttle'
419         };
420         $.extend(constants, {
421             // element data keys
422             'validationEvents': constants.pluginPrefix + "-valEvents",
423             'validationResults': constants.pluginPrefix + "-valResults",
424             'origClickEvent': constants.pluginPrefix + "-origClickEvent",
425 
426             // validation identifiers
427             'notEmptyClass': constants.pluginPrefix + "-notEmpty",
428             'itemTypeClass': constants.pluginPrefix + "-itemType",
429             'equalClass': constants.pluginPrefix + "-equal",
430             'regexClass': constants.pluginPrefix + "-regex",
431             'charLengthClass': constants.pluginPrefix + "-charLength",
432             'numberSizeClass': constants.pluginPrefix + "-numberSize",
433             'dateOrderClass': constants.pluginPrefix + "-dateOrder",
434             'totalCheckedClass': constants.pluginPrefix + "-totalChecked",
435 
436             // css classes
437             'itemErrorClass': constants.pluginPrefix + "-item-error",
438             'labelErrorClass': constants.pluginPrefix + "-label-error",
439             'errorMsgClass': constants.pluginPrefix + "-error-msg"
440         });
441 
442         var settings = {
443             'validate': 'notEmpty',
444             'triggeringEvent': 'blur',
445             'condition': '',
446             'validationMinLength': 0,
447             'errorMsg': '',
448             'errorMsgLocation': 'after',
449             'allowWhitespace': true,
450             'itemType': '',
451             'dateFormat': '',
452             'min': '',
453             'max': '',
454             'equal': '',
455             'regex': '',
456             'formsToSubmit': ''
457         };
458 
459         var methods = {
460             /**
461              * @description Public function to validate one or more page items.
462              * @param {Object} options The validation settings.
463              */
464             init: function (options) {
465                 var element = $(this);
466                 bindSettings(element, options);
467                 init(element);
468             },
469             /**
470              * @description Public function to validate one or more forms.
471              * @param {Object} options The validation settings.
472              */
473             validateForm: function (options) {
474                 var element = $(this);
475                 bindSettings(element, options);
476                 validateFormBeforeSubmit(element);
477             },
478             /**
479              * @description Public function to remove existing validations from an element.
480              */
481             remove: function () {
482                 var element = $(this);
483                 if (restorePluginSettings(element)) {
484                     method();
485                 }
486             }
487         };
488 
489         function restorePluginSettings(element) {
490             var elem = $(element);
491             if (typeof elem.data(constants.pluginId) !== "undefined") {
492                 $.extend(settings, elem.data(constants.pluginId));
493                 return true;
494             }
495             return false;
496         }
497 
498         function extendSettings(options) {
499             if (options) {
500                 $.extend(settings, options);
501             }
502         }
503 
504         function bindSettings(element, options) {
505             extendSettings(options);
506             $(element).data(constants.pluginId, settings);
507         }
508 
509         return $(this).each(function () {
510             if (methods[method]) {
511                 return methods[method].call($(this), options);
512             } else if (typeof method === "object" || !method) {
513                 return methods.init.call($(this), method);
514             } else {
515                 $.error("Method " + method + " does not exist on jQuery. " + constants.pluginName);
516                 return false;
517             }
518         });
519 
520         function init(element) {
521             var elem = $(element);
522             var elemSelector = '#' + elem.attr('id');
523             var bodyElem = $('body');
524             var triggeringEvent = settings.triggeringEvent + '.' + constants.pluginPrefix;
525             var changeEvent = 'change' + '.' + constants.pluginPrefix;
526 
527             switch (settings.validate) {
528                 case 'notEmpty':
529                     if (elem.hasClass(constants.apexCheckboxClass) ||
530                         elem.hasClass(constants.apexRadioClass) ||
531                         elem.hasClass(constants.apexShuttleClass) ||
532                         elem.prop('tagName') === 'SELECT' ||
533                         elem.attr('type') === 'file') {
534                         if (settings.triggeringEvent !== 'change') {
535                             triggeringEvent = triggeringEvent + ' ' + changeEvent;
536                         }
537                     }
538                     bodyElem.delegate(elemSelector, triggeringEvent, isEmptyHandler);
539                     break;
540                 case 'itemType':
541                     if (settings.itemType === 'date') {
542                         if (settings.triggeringEvent !== 'change') {
543                             triggeringEvent = triggeringEvent + ' ' + changeEvent;
544                         }
545                     }
546                     bodyElem.delegate(elemSelector, triggeringEvent, itemTypeHandler);
547                     break;
548                 case 'equal':
549                     bodyElem.delegate(elemSelector, triggeringEvent, isEqualHandler);
550                     break;
551                 case 'regex':
552                     bodyElem.delegate(elemSelector, triggeringEvent, regexHandler);
553                     break;
554                 case 'charLength':
555                     bodyElem.delegate(elemSelector, triggeringEvent, charLengthHandler);
556                     break;
557                 case 'numberSize':
558                     bodyElem.delegate(elemSelector, triggeringEvent, numberSizeHandler);
559                     break;
560                 case 'dateOrder':
561                     if (settings.triggeringEvent !== 'change') {
562                         triggeringEvent = triggeringEvent + ' ' + changeEvent;
563                     }
564                     bodyElem.delegate(elemSelector, triggeringEvent, dateOrderHandler);
565                     break;
566                 case 'totalChecked':
567                     bodyElem.delegate(elemSelector, changeEvent, totalCheckedHandler);
568                     break;
569                 default:
570             }
571 
572             addValidationEvent(elem, triggeringEvent);
573             return element;
574         }
575 
576         function addValidationEvent(pElem, pEvent) {
577             var elem = $(pElem);
578             var elemValidationEvents = elem.data(constants.validationEvents);
579             var eventExists = false;
580 
581             if (typeof elemValidationEvents !== "undefined") {
582                 $.each(elemValidationEvents.split(" "), function (index, value) {
583                     if (value === pEvent) {
584                         eventExists = true;
585                     }
586                 });
587                 if (!eventExists) {
588                     elem.data(constants.validationEvents, elemValidationEvents + " " + pEvent);
589                 }
590             } else {
591                 elem.data(constants.validationEvents, pEvent);
592             }
593         }
594 
595 
596         // VALIDATION HANDLERS
597         function isEmptyHandler() {
598             var itemEmpty;
599             var emptyMsg = setMsg(settings.errorMsg, "value required");
600 
601             if (allowValidation(this, constants.notEmptyClass)) {
602                 if ($(this).hasClass(constants.apexCheckboxClass) ||
603                     $(this).hasClass(constants.apexRadioClass)) {
604                     itemEmpty = !validators.minCheck($(this).find(':checkbox, :radio'), 1, false);
605                 } else if ($(this).hasClass(constants.apexShuttleClass)) {
606                     itemEmpty = !$(this).find('select.shuttle_right').children().length;
607                 } else if ($(this).prop('tagName') === 'SELECT' || $(this).attr('type') === 'file') {
608                     itemEmpty = validators.isEmpty(this.value);
609                 } else {
610                     if (settings.allowWhitespace) {
611                         itemEmpty = validators.isEmpty(this.value);
612                     } else {
613                         itemEmpty = validators.isEmpty(util.trim(this.value));
614                     }
615                 }
616 
617                 if (itemEmpty && util.getConditionResult(settings.condition)) {
618                     addValidationResult($(this), constants.notEmptyClass, "0");
619                     showMessage(this, emptyMsg);
620                 } else {
621                     addValidationResult($(this), constants.notEmptyClass, "1");
622                     hideMessage(this);
623                 }
624             }
625         }
626 
627         function isEqualHandler() {
628             var equalMsg = setMsg(settings.errorMsg, "values do not equal");
629 
630             if (allowValidation(this, constants.equalClass)) {
631                 if (validators.minLength(this.value, settings.validationMinLength)) {
632                     if (!validators.isEqual(this.value, $(settings.equal).val()) && util.getConditionResult(settings.condition)) {
633                         addValidationResult($(this), constants.equalClass, "0");
634                         showMessage(this, equalMsg);
635                     } else {
636                         addValidationResult($(this), constants.equalClass, "1");
637                         hideMessage(this);
638                     }
639                 }
640             }
641         }
642 
643         function regexHandler() {
644             var regexMsg = setMsg(settings.errorMsg, "invalid value");
645 
646             if (allowValidation(this, constants.regexClass)) {
647                 if (validators.minLength(this.value, settings.validationMinLength)) {
648                     if (!validators.regex(this.value, settings.regex) && util.getConditionResult(settings.condition)) {
649                         addValidationResult($(this), constants.regexClass, "0");
650                         showMessage(this, regexMsg);
651                     } else {
652                         addValidationResult($(this), constants.regexClass, "1");
653                         hideMessage(this);
654                     }
655                 }
656             }
657         }
658 
659         function itemTypeHandler() {
660             var itemTypeOk;
661             var itemTypeErrorMsg;
662 
663             if (allowValidation(this, constants.itemTypeClass)) {
664                 if (validators.minLength(this.value, settings.validationMinLength)) {
665                     switch (settings.itemType) {
666                         case 'alphanumeric':
667                             itemTypeOk = validators.isAlphanumeric(this.value);
668                             itemTypeErrorMsg = setMsg(settings.errorMsg, "not an alphanumeric value");
669                             break;
670                         case 'number':
671                             itemTypeOk = validators.isNumber(this.value);
672                             itemTypeErrorMsg = setMsg(settings.errorMsg, "not a valid number");
673                             break;
674                         case 'digit':
675                             itemTypeOk = validators.isDigit(this.value);
676                             itemTypeErrorMsg = setMsg(settings.errorMsg, "not a valid digit combination");
677                             break;
678                         case 'email':
679                             itemTypeOk = validators.isEmail(this.value);
680                             itemTypeErrorMsg = setMsg(settings.errorMsg, "not a valid e-mail address");
681                             break;
682                         case 'url':
683                             itemTypeOk = validators.isUrl(this.value);
684                             itemTypeErrorMsg = setMsg(settings.errorMsg, "not a valid URL");
685                             break;
686                         case 'date':
687                             itemTypeOk = validators.isDate(this.value, settings.dateFormat);
688                             itemTypeErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "not a valid date (&1)"), settings.dateFormat);
689                             break;
690                         default:
691                     }
692 
693                     if (!itemTypeOk && util.getConditionResult(settings.condition)) {
694                         addValidationResult($(this), constants.itemTypeClass, "0");
695                         showMessage(this, itemTypeErrorMsg);
696                     } else {
697                         addValidationResult($(this), constants.itemTypeClass, "1");
698                         hideMessage(this);
699                     }
700                 }
701             }
702         }
703 
704         function charLengthHandler() {
705             var charLengthOk;
706             var charLengthErrorMsg;
707 
708             if (allowValidation(this, constants.charLengthClass)) {
709                 if (validators.minLength(this.value, settings.validationMinLength)) {
710                     if (validators.isEmpty(settings.max)) {
711                         charLengthOk = validators.minLength(this.value, settings.min);
712                         charLengthErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "value length too short - min. &1"), settings.min);
713                     } else if (validators.isEmpty(settings.min)) {
714                         charLengthOk = validators.maxLength(this.value, settings.max);
715                         charLengthErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "value length too long - max. &1"), settings.max);
716                     } else {
717                         charLengthOk = validators.rangeLength(this.value, settings.min, settings.max);
718                         charLengthErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "invalid value length - between &1 and &2 only"), settings.min, settings.max);
719                     }
720 
721                     if (!charLengthOk && util.getConditionResult(settings.condition)) {
722                         addValidationResult($(this), constants.charLengthClass, "0");
723                         showMessage(this, charLengthErrorMsg);
724                     } else {
725                         addValidationResult($(this), constants.charLengthClass, "1");
726                         hideMessage(this);
727                     }
728                 }
729             }
730         }
731 
732         function numberSizeHandler() {
733             var numberSizeOk;
734             var numberSizeErrorMsg;
735             var value = util.getNumberFromString(this.value);
736             var min = util.getNumberFromString(util.getPageItemValue(settings.min));
737             var max = util.getNumberFromString(util.getPageItemValue(settings.max));
738 
739             if (allowValidation(this, constants.numberSizeClass)) {
740                 if (validators.minLength(this.value, settings.validationMinLength)) {
741                     if (validators.isEmpty(settings.max)) {
742                         numberSizeOk = validators.minNumber(value, min);
743                         numberSizeErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "number too small - min. &1"), min);
744                     } else if (validators.isEmpty(settings.min)) {
745                         numberSizeOk = validators.maxNumber(value, max);
746                         numberSizeErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "number too large - max. &1"), max);
747                     } else {
748                         numberSizeOk = validators.rangeNumber(value, min, max);
749                         numberSizeErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "invalid number size - between &1 and &2 only"), min, max);
750                     }
751 
752                     if (!numberSizeOk && util.getConditionResult(settings.condition)) {
753                         addValidationResult($(this), constants.numberSizeClass, "0");
754                         showMessage(this, numberSizeErrorMsg);
755                     } else {
756                         addValidationResult($(this), constants.numberSizeClass, "1");
757                         hideMessage(this);
758                     }
759                 }
760             }
761         }
762 
763         function totalCheckedHandler() {
764             var totalCheckedOk;
765             var totalCheckedErrorMsg;
766             var choices = $(this).find(':checkbox, :radio');
767 
768             if (allowValidation(this, constants.totalCheckedClass)) {
769                 if (validators.isEmpty(settings.max)) {
770                     totalCheckedOk = validators.minCheck(choices, settings.min, true);
771                     totalCheckedErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "please select at least &1 choice(s)"), settings.min);
772                 } else if (validators.isEmpty(settings.min)) {
773                     totalCheckedOk = validators.maxCheck(choices, settings.max);
774                     totalCheckedErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "please select no more than &1 choice(s)"), settings.max);
775                 } else {
776                     totalCheckedOk = validators.rangeCheck(choices, settings.min, settings.max);
777                     totalCheckedErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "please select between &1 and &2 choice(s)"), settings.min, settings.max);
778                 }
779 
780                 if (!totalCheckedOk && util.getConditionResult(settings.condition)) {
781                     addValidationResult($(this), constants.totalCheckedClass, "0");
782                     showMessage(this, totalCheckedErrorMsg);
783                 } else {
784                     addValidationResult($(this), constants.totalCheckedClass, "1");
785                     hideMessage(this);
786                 }
787             }
788         }
789 
790         function dateOrderHandler() {
791             var dateOrderOk;
792             var dateOrderErrorMsg;
793             var min = util.getPageItemValue(settings.min);
794             var max = util.getPageItemValue(settings.max);
795 
796             if (allowValidation(this, constants.dateOrderClass)) {
797                 if (validators.minLength(this.value, settings.validationMinLength)) {
798                     if (validators.isEmpty(settings.max)) {
799                         dateOrderOk = validators.minDate(this.value, min, settings.dateFormat);
800                         dateOrderErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "this date should lie after &1"), min);
801                     } else if (validators.isEmpty(settings.min)) {
802                         dateOrderOk = validators.maxDate(this.value, max, settings.dateFormat);
803                         dateOrderErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "this date should lie before &1"), max);
804                     } else {
805                         dateOrderOk = validators.rangeDate(this.value, min, max, settings.dateFormat);
806                         dateOrderErrorMsg = replaceMsgVars(setMsg(settings.errorMsg, "this date should lie between &1 and &2"), min, max);
807                     }
808 
809                     if (!dateOrderOk && util.getConditionResult(settings.condition)) {
810                         addValidationResult($(this), constants.dateOrderClass, "0");
811                         showMessage(this, dateOrderErrorMsg);
812                     } else {
813                         addValidationResult($(this), constants.dateOrderClass, "1");
814                         hideMessage(this);
815                     }
816                 }
817             }
818         }
819 
820 
821         // ERROR MESSAGE
822         function showMessage(pElem, pMessage) {
823             var elem = $(pElem);
824             var errorMsgHtml = "<span class=\"" + constants.errorMsgClass + " " + pElem.id + "\">" + pMessage + "</span>";
825 
826             if (elem.hasClass(constants.itemErrorClass)) {
827                 var errorMsgElem = $('span.' + constants.errorMsgClass + '.' + pElem.id);
828                 var errorMsgElemIndex = errorMsgElem.index();
829                 var elemIndex = elem.index();
830 
831                 if (errorMsgElemIndex < elemIndex && settings.errorMsgLocation === 'before') {
832                     errorMsgElem.text(pMessage);
833                 } else if (errorMsgElemIndex < elemIndex && settings.errorMsgLocation === 'after') {
834                     errorMsgElem.remove();
835                     elem.after(errorMsgHtml);
836                 } else if (errorMsgElemIndex > elemIndex && settings.errorMsgLocation === 'after') {
837                     errorMsgElem.text(pMessage);
838                 } else {
839                     errorMsgElem.remove();
840                     elem.before(errorMsgHtml);
841                 }
842             } else {
843                 elem.addClass(constants.itemErrorClass);
844                 $('[for=' + pElem.id + ']').addClass(constants.labelErrorClass);
845                 if (settings.errorMsgLocation === 'before') {
846                     elem.before(errorMsgHtml);
847                 } else {
848                     elem.after(errorMsgHtml);
849                 }
850             }
851         }
852 
853         function hideMessage(pElem) {
854             var elem = $(pElem);
855             if (elem.hasClass(constants.itemErrorClass)) {
856                 elem.removeClass(constants.itemErrorClass);
857                 $('[for=' + pElem.id + ']').removeClass(constants.labelErrorClass);
858                 $('span.' + constants.errorMsgClass + '.' + pElem.id).remove();
859             }
860         }
861 
862         function setMsg(customMsg, defaultMsg) {
863             if (!validators.isEmpty(customMsg)) {
864                 return customMsg;
865             }
866             return defaultMsg;
867         }
868 
869         function replaceMsgVars(pMessage) {
870             var errorMsg = pMessage;
871             for (var i = 1, j = arguments.length; i < j; i++) {
872                 errorMsg = errorMsg.replace("&" + i, arguments[i]);
873             }
874             return errorMsg;
875         }
876 
877 
878         // ERROR CONTROL
879         function allowValidation(pElem, pKey) {
880             var allowValidation = true;
881             var elem = $(pElem);
882             var elemValidationResults = elem.data(constants.validationResults);
883 
884             if (typeof elemValidationResults !== "undefined") {
885                 if (elemValidationResults.indexOf(pKey) === -1) {
886                     $.each(elemValidationResults.split(" "), function (index, value) {
887                         if (allowValidation === true && value.slice(-1) !== "1") {
888                             allowValidation = false;
889                         }
890                     });
891                 } else {
892                     elem.removeData(constants.validationResults);
893                 }
894             } else {
895                 addValidationResult(pElem, pKey, "1");
896             }
897 
898             return allowValidation;
899         }
900 
901         function addValidationResult(pElem, pValidation, pResult) {
902             var elem = $(pElem);
903             var elemValidationResults = elem.data(constants.validationResults);
904             var resultExists = false;
905             var validationResult = pValidation + ":" + pResult;
906 
907             if (typeof elemValidationResults !== "undefined") {
908                 $.each(elemValidationResults.split(" "), function (index, value) {
909                     if (value.substr(0, value.indexOf(":")) === pValidation) {
910                         var resultIndex = elemValidationResults.indexOf(value) + value.length - 1;
911                         elemValidationResults = util.replaceCharInString(elemValidationResults, resultIndex, pResult);
912                         elem.data(constants.validationResults, elemValidationResults);
913                         resultExists = true;
914                     }
915                 });
916                 if (!resultExists) {
917                     elem.data(constants.validationResults, elemValidationResults + " " + validationResult);
918                 }
919             } else {
920                 elem.data(constants.validationResults, validationResult);
921             }
922         }
923 
924 
925         // FORM VALIDATION
926         function formHasErrors(pForms) {
927             var formHasErrors = false;
928             var formElem;
929             var formElems = $(pForms).find('input, textarea, select, fieldset');
930 
931             $.each(formElems, function () {
932                 formElem = $(this);
933                 if (typeof formElem.data(constants.validationEvents) !== "undefined") {
934                     $.each(formElem.data(constants.validationEvents).split(" "), function (index, value) {
935                         formElem.trigger(value);
936                     });
937                 }
938             });
939 
940             if (formElems.hasClass(constants.itemErrorClass)) {
941                 $(formElems).filter('.' + constants.itemErrorClass).first().focus();
942                 formHasErrors = true;
943             }
944 
945             return formHasErrors;
946         }
947 
948         function validateFormBeforeSubmit(pFiringElem) {
949             var firingElem = $(pFiringElem);
950             var origClickEvent;
951             var fixErrorsMsg = setMsg(settings.errorMsg, "Please fix all errors before continuing");
952             var bodyElem = $('body');
953             var messageBoxId = "#alv-msg-box";
954             var msgBox = '<div class="alv-alert-msg"><a href="#" class="alv-close" onclick="$(\'' + messageBoxId + '\').children().fadeOut();return false;">x</a><p>' + fixErrorsMsg + '</p></div>';
955 
956             if (firingElem.length) {
957                 if (firingElem.prop('tagName') === "A") {
958                     origClickEvent = firingElem.attr('href');
959                     firingElem.data(constants.origClickEvent, origClickEvent);
960                     firingElem.removeAttr('href');
961                 } else {
962                     origClickEvent = firingElem.attr('onclick');
963                     firingElem.data(constants.origClickEvent, origClickEvent);
964                     firingElem.removeAttr('onclick');
965                 }
966 
967                 bodyElem.delegate('#' + firingElem.attr('id'), 'click', function () {
968                     if (!formHasErrors(settings.formsToSubmit)) {
969                         eval($(this).data(constants.origClickEvent));
970                     } else {
971                         if (!$(messageBoxId).length) {
972                             bodyElem.append('<div id="' + messageBoxId.substring(1) + '"></div>');
973                         }
974                         $(messageBoxId).html(msgBox);
975                     }
976                 });
977             }
978         }
979     }
980 })(jQuery, alv.util, alv.validators);