From 2d9ba48ffcc4b527824145461dfcdfaec3eb86d4 Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Mon, 5 Oct 2015 00:55:37 -0400 Subject: [PATCH] 3rd party addon validation #57, #66, #67 - Can now validate external 3rd party addons, asked in issue #57, #66, #67 (like: ngTagsInput, Angular Multiselect, Dropdown multi-select, etc....) - Also fixed some French translations #73 --- bower.json | 2 +- dist/angular-validation.min.js | 10 +- index.html | 1 + locales/validation/fr.json | 48 ++-- more-examples/addon-3rdParty/app.js | 47 ++++ more-examples/addon-3rdParty/index.html | 139 ++++++++++ more-examples/angular-ui-calendar/index.html | 6 +- package.json | 2 +- protractor/angularUI_spec.js | 56 +++++ protractor/conf.js | 6 +- protractor/thirdParty_spec.js | 125 +++++++++ readme.md | 3 +- src/validation-common.js | 142 ++++++----- src/validation-directive.js | 251 +++++++++++++++---- src/validation-rules.js | 8 +- src/validation-service.js | 4 +- 16 files changed, 697 insertions(+), 153 deletions(-) create mode 100644 more-examples/addon-3rdParty/app.js create mode 100644 more-examples/addon-3rdParty/index.html create mode 100644 protractor/angularUI_spec.js create mode 100644 protractor/thirdParty_spec.js diff --git a/bower.json b/bower.json index 55d463b..644e4a8 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-validation-ghiscoding", - "version": "1.4.8", + "version": "1.4.9", "author": "Ghislain B.", "description": "Angular-Validation Directive and Service (ghiscoding)", "main": [ diff --git a/dist/angular-validation.min.js b/dist/angular-validation.min.js index 2061833..23a203f 100644 --- a/dist/angular-validation.min.js +++ b/dist/angular-validation.min.js @@ -2,11 +2,11 @@ * Angular-Validation Directive and Service (ghiscoding) * http://github.com/ghiscoding/angular-validation * @author: Ghislain B. - * @version: 1.4.8 + * @version: 1.4.9 * @license: MIT - * @build: Sat Sep 12 2015 19:53:31 GMT-0400 (Eastern Daylight Time) + * @build: Sun Oct 04 2015 22:42:40 GMT-0400 (Eastern Daylight Time) */ -angular.module("ghiscoding.validation",["pascalprecht.translate"]).directive("validation",["$timeout","validationCommon",function(e,i){return{restrict:"A",require:"ngModel",link:function(a,t,n,l){function o(e){var i=c.getFormElementByName(l.$name);i.isValidationCancelled?l.$setValidity("validation",!0):d(e.target.value,10)}function d(i,n){var o="undefined"!=typeof n?n:c.typingLimit,d=c.getFormElementByName(l.$name);return c.validate(i,!1),c.isFieldRequired()||""!==i&&null!==i&&"undefined"!=typeof i?(d&&(d.isValidationCancelled=!1),(i||c.isFieldRequired())&&l.$setValidity("validation",!1),""!==i&&"undefined"!=typeof i||"NUMBER"!==t.prop("type").toUpperCase()?"SELECT"===t.prop("tagName").toUpperCase()?(l.$setValidity("validation",c.validate(i,!0)),i):("undefined"!=typeof i&&(c.updateErrorMsg(""),e.cancel(m),m=e(function(){a.$evalAsync(l.$setValidity("validation",c.validate(i,!0)))},o)),i):(e.cancel(m),l.$setValidity("validation",c.validate(i,!0)),i)):(u(),i)}function r(){l.$formatters.shift(),l.$parsers.shift(),u(),c.removeFromValidationSummary(n.name)}function u(){var i=c.getFormElementByName(l.$name);i&&(i.isValidationCancelled=!0),e.cancel(m),c.updateErrorMsg(""),l.$setValidity("validation",!0),"function"==typeof o&&t.unbind("blur",o)}function s(){var e=l.$viewValue||"";l.$setValidity("validation",c.validate(e,!1));var i=c.getFormElementByName(l.$name);i&&(i.isValidationCancelled=!1),t.bind("blur",o)}var m,o,c=new i(a,t,n,l);a.$evalAsync(function(){l.$formatters.unshift(d),l.$parsers.unshift(d)}),n.$observe("disabled",function(e){e?(u(),c.removeFromValidationSummary(n.name)):s()}),t.on("$destroy",function(){r()}),a.$watch(function(){return t.attr("validation")},function(e){"undefined"==typeof e||""===e?r():(c.defineValidation(),s())}),t.bind("blur",o)}}}]); -angular.module("ghiscoding.validation").factory("validationCommon",["$rootScope","$translate","validationRules",function(e,t,a){function r(){var e=this,t={};e.validators=[],e.typingLimit=F,e.validatorAttrs.hasOwnProperty("debounce")?e.typingLimit=parseInt(e.validatorAttrs.debounce,10):e.validatorAttrs.hasOwnProperty("typingLimit")?e.typingLimit=parseInt(e.validatorAttrs.typingLimit,10):k&&k.hasOwnProperty("debounce")&&(e.typingLimit=parseInt(k.debounce,10));var r=e.validatorAttrs.rules||e.validatorAttrs.validation;if(r.indexOf("pattern=/")>=0){var n=r.match(/pattern=(\/.*\/[igm]*)(:alt=(.*))?/);if(!n||n.length<3)throw'Regex validator within the validation needs to be define with an opening "/" and a closing "/", please review your validator.';var i=n[1],o=n[2]?n[2].replace(/\|(.*)/,""):"",l=i.match(new RegExp("^/(.*?)/([gimy]*)$")),s=new RegExp(l[1],l[2]);t={altMsg:o,message:o.replace(/:alt=/,""),pattern:s},r=r.replace("pattern="+i,"pattern")}else if(r.indexOf("regex:")>=0){var n=r.match("regex:(.*?):regex");if(n.length<2)throw'Regex validator within the validation needs to be define with an opening "regex:" and a closing ":regex", please review your validator.';var p=n[1].split(":=");t={message:p[0],pattern:p[1]},r=r.replace(n[0],"regex:")}var m=r.split("|");if(m){e.bFieldRequired=r.indexOf("required")>=0?!0:!1;for(var d=0,u=m.length;u>d;d++){var c=m[d].split(":"),f=m[d].indexOf("alt=")>=0?!0:!1;e.validators[d]=a.getElementValidators({altText:f===!0?2===c.length?c[1]:c[2]:"",customRegEx:t,rule:c[0],ruleParams:f&&2===c.length?null:c[1]})}}return e}function n(e){return b(L,"fieldName",e)}function i(e){return e?$(L,"formName",e):L}function o(){return k}function l(e,t,a,r){this.scope=e,this.elm=t,this.ctrl=r,this.validatorAttrs=a,v(t,a,r,e),this.defineValidation()}function s(){var e=this;return e.bFieldRequired}function p(e,t){var a={};for(var r in e)a[r]=e[r];for(var r in t)a[r]=t[r];return a}function m(e){var t=O(L,"fieldName",e);t>=0&&L.splice(t,1)}function d(e,t){var a=this,r=A(e,a),n=t||q,i=O(n,"field",e);if(i>=0&&n.splice(i,1),i=O(q,"field",e),i>=0&&q.splice(i,1),a.scope.$validationSummary=q,r&&(r.$validationSummary=$(q,"formName",r.$name)),k&&k.controllerAs&&(k.controllerAs.$validationSummary=q,r)){var o=r.$name.indexOf(".")>=0?r.$name.split(".")[1]:r.$name;k.controllerAs[o]&&(k.controllerAs[o].$validationSummary=$(q,"formName",r.$name))}return q}function u(e){k.displayOnlyLastErrorMsg=e}function c(e){var t=this;return k=p(k,e),t}function f(e,a){var r=this;a&&a.obj&&(r=a.obj,r.validatorAttrs=a.obj.attrs);var n=a&&a.elm?a.elm:r.elm,i=n&&n.attr("name")?n.attr("name"):null;if("undefined"==typeof i||null===i){var o=n?n.attr("ng-model"):"unknown";throw'Angular-Validation Service requires you to have a (name="") attribute on the element to validate... Your element is: ng-model="'+o+'"'}var l=a&&a.translate?t.instant(e):e,s=i.replace(/[|&;$%@"<>()+,\[\]\{\}]/g,""),p=null;if(r.validatorAttrs&&r.validatorAttrs.hasOwnProperty("validationErrorTo")){var m=r.validatorAttrs.validationErrorTo.charAt(0),d="."===m||"#"===m?r.validatorAttrs.validationErrorTo:"#"+r.validatorAttrs.validationErrorTo;p=angular.element(document.querySelector(d))}p&&0!==p.length||(p=angular.element(document.querySelector(".validation-"+s)));var u=a&&a.isSubmitted?a.isSubmitted:!1;a&&!a.isValid&&(u||r.ctrl.$dirty||r.ctrl.$touched)?p.length>0?p.html(l):n.after(''+l+""):p.html("")}function g(e,a){var r,i,o=this,l=!0,s=!0,p="";"undefined"==typeof e&&(e="");for(var m=o.ctrl&&o.ctrl.$name?o.ctrl.$name:o.attrs&&o.attrs.name?o.attrs.name:o.elm.attr("name"),d=n(m),u=o.validatorAttrs.rules||o.validatorAttrs.validation,c=0,f=o.validators.length;f>c;c++){if(i=o.validators[c],"autoDetect"===i.type&&(i=x(e)?{condition:i.conditionNum,message:i.messageNum,params:i.params,type:"conditionalNumber"}:{pattern:i.patternLength,message:i.messageLength,params:i.params,type:"regex"}),"conditionalDate"===i.type){var g=l=!1;if(e instanceof Date?g=!0:(r=new RegExp(i.pattern),g=(!i.pattern||"/\\S+/"===i.pattern.toString()||u&&"required"===i.pattern)&&null===e?!1:r.test(e)),g){var v=i.dateType,b=e instanceof Date?e:E(e,v).getTime();if(2==i.params.length){var $=E(i.params[0],v).getTime(),O=E(i.params[1],v).getTime(),S=R(i.condition[0],b,$),A=R(i.condition[1],b,O);l=S&&A?!0:!1}else{var N=E(i.params[0],v).getTime();l=R(i.condition,b,N)}}}else if("conditionalNumber"===i.type)if(2==i.params.length){var S=R(i.condition[0],parseFloat(e),parseFloat(i.params[0])),A=R(i.condition[1],parseFloat(e),parseFloat(i.params[1]));l=S&&A?!0:!1}else l=R(i.condition,parseFloat(e),parseFloat(i.params[0]));else if("matching"===i.type){var w=i.params[0],V=o.scope.$eval(w),T=angular.element(document.querySelector('[name="'+w+'"]')),F=i,L=o.ctrl,q=n(o.ctrl.$name);l=R(i.condition,e,V)&&!!e,T&&T.attr("friendly-name")?i.params[1]=T.attr("friendly-name"):i.params.length>1&&(i.params[1]=i.params[1]),o.scope.$watch(w,function(e,a){var r=R(F.condition,L.$viewValue,e);if(e!==a){if(r)h(o,q,"",!0,!0);else{q.isValid=!1;var n=F.message;F.altText&&F.altText.length>0&&(n=F.altText.replace("alt=","")),t(n).then(function(e){p=" "+(F&&F.params?String.format(e,F.params):e),h(o,q,p,r,!0)})}L.$setValidity("validation",r)}},!0)}else if("remote"===i.type){if(e&&a){o.ctrl.$processing=!0;var C=null,G=i.params[0];if(-1===G.indexOf("."))C=o.scope[G];else{var M=G.split(".");C=o.scope;for(var j=0,D=M.length;D>j;j++)C=C[M[j]]}var I="function"==typeof C?C():null;if(U.length>1)for(;U.length>0;){var P=U.pop();"function"==typeof P.abort&&P.abort()}if(U.push(I),!I||"function"!=typeof I.then)throw"Remote Validation requires a declared function (in your Controller) which also needs to return a Promise, please review your code.";o.ctrl.$setValidity("remote",!1),function(e){I.then(function(t){t=t.data||t,U.pop(),o.ctrl.$processing=!1;var r=p+" ";"boolean"==typeof t?l=t?!0:!1:"object"==typeof t&&(l=t.isValid?!0:!1),l===!1&&(d.isValid=!1,r+=t.message||e,h(o,d,r,!1,a)),l===!0&&s===!0&&(d.isValid=!0,o.ctrl.$setValidity("remote",!0),h(o,d,"",!0,a))})}(i.altText)}}else{var H=o.attrs?o.attrs.ngDisabled:o.validatorAttrs.ngDisabled;o.elm.prop("disabled")||o.scope.$eval(H)?l=!0:"string"==typeof e&&""===e&&"NUMBER"===o.elm.prop("type").toUpperCase()?l=!1:(r=new RegExp(i.pattern),l=(!i.pattern||"/\\S+/"===i.pattern.toString()||u&&"required"===i.pattern)&&null===e?!1:r.test(e))}(!o.bFieldRequired&&!e||o.elm.prop("disabled")||o.scope.$eval(H))&&(l=!0),l||(s=!1,function(e,r,n){var i=n.message;n.altText&&n.altText.length>0&&(i=n.altText.replace("alt=","")),t(i).then(function(t){p.length>0&&k.displayOnlyLastErrorMsg?p=" "+(n&&n.params?String.format(t,n.params):t):p+=" "+(n&&n.params?String.format(t,n.params):t),h(o,e,p,s,a)})["catch"](function(){n.altText&&n.altText.length>0&&(p.length>0&&k.displayOnlyLastErrorMsg?p=" "+i:p+=" "+i,h(o,e,p,s,a))})}(d,l,i))}return l&&(y(o,""),o.updateErrorMsg("",{isValid:l})),d&&(d.isValid=s,s&&(d.message="")),s}function v(e,a,r,n){var i=a.name?a.name:e.attr("name"),o=A(i,{scope:n}),l=a&&a.friendlyName?t.instant(a.friendlyName):"",s={fieldName:i,friendlyName:l,elm:e,attrs:a,ctrl:r,scope:n,isValid:!1,message:"",formName:o?o.$name:null},p=O(L,"fieldName",e.attr("name"));return p>=0?L[p]=s:L.push(s),L}function h(e,t,a,r,n){a=a.trim(),y(t,a),t&&(t.message=a),(e.validatorAttrs.preValidateFormElements||k.preValidateFormElements)&&(t&&"function"==typeof e.ctrl.$setTouched&&t.ctrl.$setTouched(),e.ctrl.$dirty===!1&&f(a,{isSubmitted:!0,isValid:r,obj:t})),n&&t&&!t.isValid?e.updateErrorMsg(a,{isValid:r,obj:t}):t&&t.isValid&&y(t,"")}function y(e,a){if("undefined"!=typeof e&&null!=e){var r=e.ctrl&&e.ctrl.$name?e.ctrl.$name:e.attrs&&e.attrs.name?e.attrs.name:e.elm.attr("name"),n=A(r,e),i=O(q,"field",r);if(i>=0&&""===a)q.splice(i,1);else if(""!==a){var o=e.attrs&&e.friendlyName?t.instant(e.friendlyName):"",l={field:r,friendlyName:o,message:a,formName:n?n.$name:null};i>=0?q[i]=l:q.push(l)}if(e.scope.$validationSummary=q,n&&(n.$validationSummary=$(q,"formName",n.$name)),k&&k.controllerAs&&(k.controllerAs.$validationSummary=q,n)){var s=n.$name.indexOf(".")>=0?n.$name.split(".")[1]:n.$name,p=k.controllerAs[s]?k.controllerAs[s]:e.elm.controller()[s];p.$validationSummary=$(q,"formName",n.$name)}return q}}function b(e,t,a){if(e)for(var r=0;rr;r++)t[a[r]]&&(t=t[a[r]]);return t}function A(e,t){for(var a=document.getElementsByName(e),r=null,n=0;n=0?S(i.name,t.scope):t.scope[i.name]))return"undefined"==typeof r.$name&&(r.$name=i.name),r}if(i&&i.name){var o={$name:i.name,specialNote:"Created by Angular-Validation for Isolated Scope usage"};if(k&&k.controllerAs&&i.name.indexOf(".")>=0){var l=i.name.split(".");return t.scope[l[0]][l[1]]=o}return t.scope[i.name]=o}return null}function x(e){return!isNaN(parseFloat(e))&&isFinite(e)}function E(e,t){var a="",r="-",n=[],i=[],o="",l="",s="";switch(t.toUpperCase()){case"EURO_LONG":case"EURO-LONG":a=e.substring(0,10),r=e.substring(2,3),n=N(a,r),s=n[0],l=n[1],o=n[2],i=e.length>8?e.substring(9).split(":"):null;break;case"UK":case"EURO":case"EURO_SHORT":case"EURO-SHORT":case"EUROPE":a=e.substring(0,8),r=e.substring(2,3),n=N(a,r),s=n[0],l=n[1],o=parseInt(n[2])<50?"20"+n[2]:"19"+n[2],i=e.length>8?e.substring(9).split(":"):null;break;case"US_LONG":case"US-LONG":a=e.substring(0,10),r=e.substring(2,3),n=N(a,r),l=n[0],s=n[1],o=n[2],i=e.length>8?e.substring(9).split(":"):null;break;case"US":case"US_SHORT":case"US-SHORT":a=e.substring(0,8),r=e.substring(2,3),n=N(a,r),l=n[0],s=n[1],o=parseInt(n[2])<50?"20"+n[2]:"19"+n[2],i=e.length>8?e.substring(9).split(":"):null;break;case"ISO":default:a=e.substring(0,10),r=e.substring(4,5),n=N(a,r),o=n[0],l=n[1],s=n[2],i=e.length>10?e.substring(11).split(":"):null}var p=i&&3===i.length?i[0]:0,m=i&&3===i.length?i[1]:0,d=i&&3===i.length?i[2]:0;return new Date(o,l-1,s,p,m,d)}function N(e,t){var a=[];switch(t){case"/":a=e.split("/");break;case".":a=e.split(".");break;case"-":default:a=e.split("-")}return a}function R(e,t,a){var r=!1;switch(e){case"<":r=a>t?!0:!1;break;case"<=":r=a>=t?!0:!1;break;case">":r=t>a?!0:!1;break;case">=":r=t>=a?!0:!1;break;case"!=":case"<>":r=t!=a?!0:!1;break;case"!==":r=t!==a?!0:!1;break;case"=":case"==":r=t==a?!0:!1;break;case"===":r=t===a?!0:!1;break;default:r=!1}return r}function w(){return this.replace(/^\s+|\s+$/g,"")}function V(){var e=Array.isArray(arguments[0])?arguments[0]:arguments;return this.replace(/{(\d+)}/g,function(t,a){return"undefined"!=typeof e[a]?e[a]:t})}function T(e){var t=Array.isArray(arguments[1])?arguments[1]:Array.prototype.slice.call(arguments,1);return e.replace(/{(\d+)}/g,function(e,a){return"undefined"!=typeof t[a]?t[a]:e})}var F=1e3,L=[],k={resetGlobalOptionsOnRouteChange:!0},U=[],q=[];e.$on("$routeChangeStart",function(){k.resetGlobalOptionsOnRouteChange&&(k={displayOnlyLastErrorMsg:!1,preValidateFormElements:!1,isolatedScope:null,scope:null,resetGlobalOptionsOnRouteChange:!0},L=[],q=[])});var C=function(e,t,a,r){this.bFieldRequired=!1,this.validators=[],this.typingLimit=F,this.scope=e,this.elm=t,this.ctrl=r,this.validatorAttrs=a,e&&e.$validationOptions&&(k=e.$validationOptions),e&&(k.isolatedScope||k.scope)&&(this.scope=k.isolatedScope||k.scope,k=p(e.$validationOptions,k)),"undefined"==typeof k.resetGlobalOptionsOnRouteChange&&(k.resetGlobalOptionsOnRouteChange=!0),this.elm&&this.validatorAttrs&&this.ctrl&&this.scope&&(v(this.elm,this.validatorAttrs,this.ctrl,this.scope),this.defineValidation())};return C.prototype.arrayFindObject=b,C.prototype.defineValidation=r,C.prototype.getFormElementByName=n,C.prototype.getFormElements=i,C.prototype.getGlobalOptions=o,C.prototype.isFieldRequired=s,C.prototype.initialize=l,C.prototype.mergeObjects=p,C.prototype.removeFromValidationSummary=d,C.prototype.removeFromFormElementObjectList=m,C.prototype.setDisplayOnlyLastErrorMsg=u,C.prototype.setGlobalOptions=c,C.prototype.updateErrorMsg=f,C.prototype.validate=g,String.prototype.trim=w,String.prototype.format=V,String.format=T,C}]); -angular.module("ghiscoding.validation").factory("validationRules",[function(){function e(e){var a="undefined"!=typeof e.altText?e.altText.replace("alt=",""):null,t=e.hasOwnProperty("customRegEx")?e.customRegEx:null,s=e.hasOwnProperty("rule")?e.rule:null,n=e.hasOwnProperty("ruleParams")?e.ruleParams:null,r={};switch(s){case"accepted":r={pattern:/^(yes|on|1|true)$/i,message:"INVALID_ACCEPTED",type:"regex"};break;case"alpha":r={pattern:/^([a-zа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ])+$/i,message:"INVALID_ALPHA",type:"regex"};break;case"alphaSpaces":case"alpha_spaces":r={pattern:/^([a-zа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ\s])+$/i,message:"INVALID_ALPHA_SPACE",type:"regex"};break;case"alphaNum":case"alpha_num":r={pattern:/^([a-zа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9])+$/i,message:"INVALID_ALPHA_NUM",type:"regex"};break;case"alphaNumSpaces":case"alpha_num_spaces":r={pattern:/^([a-zа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9\s])+$/i,message:"INVALID_ALPHA_NUM_SPACE",type:"regex"};break;case"alphaDash":case"alpha_dash":r={pattern:/^([a-zа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9_-])+$/i,message:"INVALID_ALPHA_DASH",type:"regex"};break;case"alphaDashSpaces":case"alpha_dash_spaces":r={pattern:/^([a-zа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9\s_-])+$/i,message:"INVALID_ALPHA_DASH_SPACE",type:"regex"};break;case"between":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between:1,5";r={patternLength:"^(.|[\\r\\n]){"+_[0]+","+_[1]+"}$",messageLength:"INVALID_BETWEEN_CHAR",conditionNum:[">=","<="],messageNum:"INVALID_BETWEEN_NUM",params:[_[0],_[1]],type:"autoDetect"};break;case"betweenLen":case"between_len":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_len:1,5";r={pattern:"^(.|[\\r\\n]){"+_[0]+","+_[1]+"}$",message:"INVALID_BETWEEN_CHAR",params:[_[0],_[1]],type:"regex"};break;case"betweenNum":case"between_num":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_num:1,5";r={condition:[">=","<="],message:"INVALID_BETWEEN_NUM",params:[_[0],_[1]],type:"conditionalNumber"};break;case"boolean":r={pattern:/^(true|false|0|1)$/i,message:"INVALID_BOOLEAN",type:"regex"};break;case"checked":r={pattern:/^true$/i,message:"INVALID_CHECKBOX_SELECTED",type:"regex"};break;case"creditCard":case"credit_card":r={pattern:/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})$/,message:"INVALID_CREDIT_CARD",type:"regex"};break;case"dateEuroLong":case"date_euro_long":r={pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_EURO_LONG",type:"regex"};break;case"dateEuroLongBetween":case"date_euro_long_between":case"betweenDateEuroLong":case"between_date_euro_long":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_date_euro_long:01-01-1990,31-12-2015";r={condition:[">=","<="],dateType:"EURO_LONG",params:[_[0],_[1]],pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_EURO_LONG_BETWEEN",type:"conditionalDate"};break;case"dateEuroLongMax":case"date_euro_long_max":case"maxDateEuroLong":case"max_date_euro_long":r={condition:"<=",dateType:"EURO_LONG",params:[n],pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_EURO_LONG_MAX",type:"conditionalDate"};break;case"dateEuroLongMin":case"date_euro_long_min":case"minDateEuroLong":case"min_date_euro_long":r={condition:">=",dateType:"EURO_LONG",params:[n],pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_EURO_LONG_MIN",type:"conditionalDate"};break;case"dateEuroShort":case"date_euro_short":r={pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.]\d\d$/,message:"INVALID_DATE_EURO_SHORT",type:"regex"};break;case"dateEuroShortBetween":case"date_euro_short_between":case"betweenDateEuroShort":case"between_date_euro_short":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_date_euro_short:01-01-90,31-12-15";r={condition:[">=","<="],dateType:"EURO_SHORT",params:[_[0],_[1]],pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.]\d\d$/,message:"INVALID_DATE_EURO_SHORT_BETWEEN",type:"conditionalDate"};break;case"dateEuroShortMax":case"date_euro_short_max":case"maxDateEuroShort":case"max_date_euro_short":r={condition:"<=",dateType:"EURO_SHORT",params:[n],pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.]\d\d$/,message:"INVALID_DATE_EURO_SHORT_MAX",type:"conditionalDate"};break;case"dateEuroShortMin":case"date_euro_short_min":case"minDateEuroShort":case"min_date_euro_short":r={condition:">=",dateType:"EURO_SHORT",params:[n],pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.]\d\d$/,message:"INVALID_DATE_EURO_SHORT_MIN",type:"conditionalDate"};break;case"dateIso":case"date_iso":r={pattern:/^(19|20)\d\d([-])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/,message:"INVALID_DATE_ISO",type:"regex"};break;case"dateIsoBetween":case"date_iso_between":case"betweenDateIso":case"between_date_iso":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_date_iso:1990-01-01,2000-12-31";r={condition:[">=","<="],dateType:"ISO",params:[_[0],_[1]],pattern:/^(19|20)\d\d([-])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/,message:"INVALID_DATE_ISO_BETWEEN",type:"conditionalDate"};break;case"dateIsoMax":case"date_iso_max":case"maxDateIso":case"max_date_iso":r={condition:"<=",dateType:"ISO",params:[n],pattern:/^(19|20)\d\d([-])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/,message:"INVALID_DATE_ISO_MAX",type:"conditionalDate"};break;case"dateIsoMin":case"date_iso_min":case"minDateIso":case"min_date_iso":r={condition:">=",dateType:"ISO",params:[n],pattern:/^(19|20)\d\d([-])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/,message:"INVALID_DATE_ISO_MIN",type:"conditionalDate"};break;case"dateUsLong":case"date_us_long":r={pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_US_LONG",type:"regex"};break;case"dateUsLongBetween":case"date_us_long_between":case"betweenDateUsLong":case"between_date_us_long":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_date_us_long:01/01/1990,12/31/2015";r={condition:[">=","<="],dateType:"US_LONG",params:[_[0],_[1]],pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_US_LONG_BETWEEN",type:"conditionalDate"};break;case"dateUsLongMax":case"date_us_long_max":case"maxDateUsLong":case"max_date_us_long":r={condition:"<=",dateType:"US_LONG",params:[n],pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_US_LONG_MAX",type:"conditionalDate"};break;case"dateUsLongMin":case"date_us_long_min":case"minDateUsLong":case"min_date_us_long":r={condition:">=",dateType:"US_LONG",params:[n],pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_US_LONG_MIN",type:"conditionalDate"};break;case"dateUsShort":case"date_us_short":r={pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.]\d\d$/,message:"INVALID_DATE_US_SHORT",type:"regex"};break;case"dateUsShortBetween":case"date_us_short_between":case"betweenDateUsShort":case"between_date_us_short":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_date_us_short:01/01/90,12/31/15";r={condition:[">=","<="],dateType:"US_SHORT",params:[_[0],_[1]],pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.]\d\d$/,message:"INVALID_DATE_US_SHORT_BETWEEN",type:"conditionalDate"};break;case"dateUsShortMax":case"date_us_short_max":case"maxDateUsShort":case"max_date_us_short":r={condition:"<=",dateType:"US_SHORT",params:[n],pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.]\d\d$/,message:"INVALID_DATE_US_SHORT_MAX",type:"conditionalDate"};break;case"dateUsShortMin":case"date_us_short_min":case"minDateUsShort":case"min_date_us_short":r={condition:">=",dateType:"US_SHORT",params:[n],pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.]\d\d$/,message:"INVALID_DATE_US_SHORT_MIN",type:"conditionalDate"};break;case"different":case"differentInput":case"different_input":var e=n.split(",");r={condition:"!=",message:"INVALID_INPUT_DIFFERENT",params:e,type:"matching"};break;case"digits":r={pattern:"^\\d{"+n+"}$",message:"INVALID_DIGITS",params:[n],type:"regex"};break;case"digitsBetween":case"digits_between":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: digits_between:1,5";r={pattern:"^\\d{"+_[0]+","+_[1]+"}$",message:"INVALID_DIGITS_BETWEEN",params:[_[0],_[1]],type:"regex"};break;case"email":r={pattern:/^[-\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9#~!$%^&*_=+\/`\|}{\'?]+(\.[-\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9#~!$%^&*_=+\/`\|}{\'?]+)*@([\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9_][-\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9_]*(\.[-\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9_]+)*([\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ]+)|(\.[\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i,message:"INVALID_EMAIL",type:"regex"};break;case"exactLen":case"exact_len":r={pattern:"^(.|[\\r\\n]){"+n+"}$",message:"INVALID_EXACT_LEN",params:[n],type:"regex"};break;case"float":r={pattern:/^\d*\.{1}\d+$/,message:"INVALID_FLOAT",type:"regex"};break;case"floatSigned":case"float_signed":r={pattern:/^[-+]?\d*\.{1}\d+$/,message:"INVALID_FLOAT_SIGNED",type:"regex"};break;case"iban":r={pattern:/^[a-zA-Z]{2}\d{2}\s?([0-9a-zA-Z]{4}\s?){4}[0-9a-zA-Z]{2}$/i,message:"INVALID_IBAN",type:"regex"};break;case"in":case"inList":case"in_list":var c=n.replace(/,/g,"|");r={pattern:"^(\\b("+c+")\\b)$",message:"INVALID_IN_LIST",params:[n],type:"regex"};break;case"int":case"integer":r={pattern:/^\d+$/,message:"INVALID_INTEGER",type:"regex"};break;case"intSigned":case"integerSigned":case"int_signed":case"integer_signed":r={pattern:/^[+-]?\d+$/,message:"INVALID_INTEGER_SIGNED",type:"regex"};break;case"ip":case"ipv4":r={pattern:/^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$/,message:"INVALID_IPV4",type:"regex"};break;case"ipv6":r={pattern:/^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/i,message:"INVALID_IPV6",type:"regex"};break;case"match":case"matchInput":case"match_input":case"same":var e=n.split(",");r={condition:"===",message:"INVALID_INPUT_MATCH",params:e,type:"matching"};break;case"max":r={patternLength:"^(.|[\\r\\n]){0,"+n+"}$",messageLength:"INVALID_MAX_CHAR",conditionNum:"<=",messageNum:"INVALID_MAX_NUM",params:[n],type:"autoDetect"};break;case"maxLen":case"max_len":r={pattern:"^(.|[\\r\\n]){0,"+n+"}$",message:"INVALID_MAX_CHAR",params:[n],type:"regex"};break;case"maxNum":case"max_num":r={condition:"<=",message:"INVALID_MAX_NUM",params:[n],type:"conditionalNumber"};break;case"min":r={patternLength:"^(.|[\\r\\n]){"+n+",}$",messageLength:"INVALID_MIN_CHAR",conditionNum:">=",messageNum:"INVALID_MIN_NUM",params:[n],type:"autoDetect"};break;case"minLen":case"min_len":r={pattern:"^(.|[\\r\\n]){"+n+",}$",message:"INVALID_MIN_CHAR",params:[n],type:"regex"};break;case"minNum":case"min_num":r={condition:">=",message:"INVALID_MIN_NUM",params:[n],type:"conditionalNumber"};break;case"notIn":case"not_in":case"notInList":case"not_in_list":var c=n.replace(/,/g,"|");r={pattern:"^((?!\\b("+c+")\\b).)+$",message:"INVALID_NOT_IN_LIST",params:[n],type:"regex"};break;case"numeric":r={pattern:/^\d*\.?\d+$/,message:"INVALID_NUMERIC",type:"regex"};break;case"numericSigned":case"numeric_signed":r={pattern:/^[-+]?\d*\.?\d+$/,message:"INVALID_NUMERIC_SIGNED",type:"regex"};break;case"pattern":case"regex":r={pattern:t.pattern,message:"INVALID_PATTERN",params:[t.message],type:"regex"};break;case"remote":r={message:"",params:[n],type:"remote"};break;case"required":r={pattern:/\S+/,message:"INVALID_REQUIRED",type:"regex"};break;case"size":r={patternLength:"^(.|[\\r\\n]){"+n+"}$",messageLength:"INVALID_EXACT_LEN",conditionNum:"==",messageNum:"INVALID_EXACT_NUM",params:[n],type:"autoDetect"};break;case"url":r={pattern:/^(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:\/~\+#]*[\w\-\@?^=%&\/~\+#])?/i,message:"INVALID_URL",type:"regex"};break;case"time":r={pattern:/^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/,message:"INVALID_TIME",type:"regex"}}return r.altText=a,r}var a={getElementValidators:e};return a}]); +angular.module("ghiscoding.validation",["pascalprecht.translate"]).directive("validation",["$q","$timeout","validationCommon",function(a,e,i){return{restrict:"A",require:"ngModel",link:function(t,r,n,l){function o(i,n){var o=a.defer(),d=!1,m="undefined"!=typeof n?n:p.typingLimit,s=p.getFormElementByName(l.$name);if(Array.isArray(i)){if(b=[],y="",m=0,i.length>0)return"function"==typeof s.ctrl.$setTouched&&s.ctrl.$setTouched(),u(i,typeof i);m=0}return p.validate(i,!1),p.isFieldRequired()||""!==i&&null!==i&&"undefined"!=typeof i?(s&&(s.isValidationCancelled=!1),(i||p.isFieldRequired())&&l.$setValidity("validation",!1),""!==i&&"undefined"!=typeof i||!r.prop("type")||"NUMBER"!==r.prop("type").toUpperCase()?"SELECT"===r.prop("tagName").toUpperCase()?(d=p.validate(i,!0),l.$setValidity("validation",d),o.resolve({isFieldValid:d,formElmObj:s,value:i}),o.promise):("undefined"!=typeof i&&(0===n?(d=p.validate(i,!0),t.$evalAsync(l.$setValidity("validation",d)),o.resolve({isFieldValid:d,formElmObj:s,value:i})):(p.updateErrorMsg(""),e.cancel(c),c=e(function(){d=p.validate(i,!0),t.$evalAsync(l.$setValidity("validation",d)),o.resolve({isFieldValid:d,formElmObj:s,value:i})},m))),o.promise):(e.cancel(c),d=p.validate(i,!0),l.$setValidity("validation",d),o.resolve({isFieldValid:d,formElmObj:s,value:i}),o.promise)):(f(),o.resolve({isFieldValid:!0,formElmObj:s,value:i}),o.promise)}function d(a,e,i){var t=o(a,0);t&&"function"==typeof t.then&&(b.push(t),parseInt(e)===i-1&&b.forEach(function(a){a.then(function(a){switch(E){case"all":if(a.isFieldValid===!1){var e=p.getGlobalOptions();a.formElmObj.translatePromise.then(function(i){y.length>0&&e.displayOnlyLastErrorMsg?y="["+a.value+"] :: "+(a.formElmObj.validator&&a.formElmObj.validator.params?String.format(i,a.formElmObj.validator.params):i):y+=" ["+a.value+"] :: "+(a.formElmObj.validator&&a.formElmObj.validator.params?String.format(i,a.formElmObj.validator.params):i),p.updateErrorMsg(y,{isValid:!1}),p.addToValidationSummary(a.formElmObj,y)})}break;case"one":default:a.isFieldValid===!0&&(l.$setValidity("validation",!0),f())}})}))}function m(a){var e=p.getFormElementByName(l.$name),i="undefined"!=typeof a.target.value?a.target.value:l.$modelValue;e.isValidationCancelled?l.$setValidity("validation",!0):o(i,10)}function u(a,e){var i=a.length;if("string"===e)for(var t in a)d(a[t],t,i);else if("object"===e)for(var t in a)if(a.hasOwnProperty(t)){var r=a[t];for(var n in r)if(r.hasOwnProperty(n)){if(g&&n!==g)continue;d(r[n],t,i)}}}function s(){f(),p.removeFromValidationSummary($);var a=p.arrayFindObject(V,"elmName",l.$name);a&&a.watcherHandler()}function f(){var a=p.getFormElementByName(l.$name);a&&(a.isValidationCancelled=!0),e.cancel(c),p.updateErrorMsg(""),l.$setValidity("validation",!0),"function"==typeof m&&r.unbind("blur",m)}function v(){var a=l.$viewValue||"";Array.isArray(a)||l.$setValidity("validation",p.validate(a,!1));var e=p.getFormElementByName(l.$name);e&&(e.isValidationCancelled=!1),"function"==typeof m&&r.unbind("blur",m),r.bind("blur",m)}var c,p=new i(t,r,n,l),y="",b=[],V=[],$=n.name,E=n.hasOwnProperty("validArrayRequireHowMany")?n.validArrayRequireHowMany:"one",g=n.hasOwnProperty("validationArrayObjprop")?n.validationArrayObjprop:null,h=t.$watch(function(){return l.$modelValue},function(a){o(a)},!0);V.push({elmName:$,watcherHandler:h}),n.$observe("disabled",function(a){a?(f(),p.removeFromValidationSummary($)):v()}),r.on("$destroy",function(){s()}),t.$watch(function(){return r.attr("validation")},function(a){"undefined"==typeof a||""===a?s():(p.defineValidation(),v())}),r.bind("blur",m)}}}]); +angular.module("ghiscoding.validation").factory("validationCommon",["$rootScope","$translate","validationRules",function(e,t,a){function r(e,a){if("undefined"!=typeof e&&null!=e){var r=e.ctrl&&e.ctrl.$name?e.ctrl.$name:e.attrs&&e.attrs.name?e.attrs.name:e.elm.attr("name"),n=A(r,e),i=O(q,"field",r);if(i>=0&&""===a)q.splice(i,1);else if(""!==a){var o=e.attrs&&e.friendlyName?t.instant(e.friendlyName):"",l={field:r,friendlyName:o,message:a,formName:n?n.$name:null};i>=0?q[i]=l:q.push(l)}if(e.scope.$validationSummary=q,n&&(n.$validationSummary=$(q,"formName",n.$name)),k&&k.controllerAs&&(k.controllerAs.$validationSummary=q,n)){var s=n.$name.indexOf(".")>=0?n.$name.split(".")[1]:n.$name,p=k.controllerAs[s]?k.controllerAs[s]:e.elm.controller()[s];p.$validationSummary=$(q,"formName",n.$name)}return q}}function n(){var e=this,t={};e.validators=[],e.typingLimit=F,e.validatorAttrs.hasOwnProperty("debounce")?e.typingLimit=parseInt(e.validatorAttrs.debounce,10):e.validatorAttrs.hasOwnProperty("typingLimit")?e.typingLimit=parseInt(e.validatorAttrs.typingLimit,10):k&&k.hasOwnProperty("debounce")&&(e.typingLimit=parseInt(k.debounce,10));var r=e.validatorAttrs.rules||e.validatorAttrs.validation;if(r.indexOf("pattern=/")>=0){var n=r.match(/pattern=(\/.*\/[igm]*)(:alt=(.*))?/);if(!n||n.length<3)throw'Regex validator within the validation needs to be define with an opening "/" and a closing "/", please review your validator.';var i=n[1],o=n[2]?n[2].replace(/\|(.*)/,""):"",l=i.match(new RegExp("^/(.*?)/([gimy]*)$")),s=new RegExp(l[1],l[2]);t={altMsg:o,message:o.replace(/:alt=/,""),pattern:s},r=r.replace("pattern="+i,"pattern")}else if(r.indexOf("regex:")>=0){var n=r.match("regex:(.*?):regex");if(n.length<2)throw'Regex validator within the validation needs to be define with an opening "regex:" and a closing ":regex", please review your validator.';var p=n[1].split(":=");t={message:p[0],pattern:p[1]},r=r.replace(n[0],"regex:")}var m=r.split("|");if(m){e.bFieldRequired=r.indexOf("required")>=0?!0:!1;for(var d=0,u=m.length;u>d;d++){var c=m[d].split(":"),f=m[d].indexOf("alt=")>=0?!0:!1;e.validators[d]=a.getElementValidators({altText:f===!0?2===c.length?c[1]:c[2]:"",customRegEx:t,rule:c[0],ruleParams:f&&2===c.length?null:c[1]})}}return e}function i(e){return b(L,"fieldName",e)}function o(e){return e?$(L,"formName",e):L}function l(){return k}function s(e,t,a,r){this.scope=e,this.elm=t,this.ctrl=r,this.validatorAttrs=a,h(t,a,r,e),this.defineValidation()}function p(){var e=this;return e.bFieldRequired}function m(e,t){var a={};for(var r in e)a[r]=e[r];for(var r in t)a[r]=t[r];return a}function d(e){var t=O(L,"fieldName",e);t>=0&&L.splice(t,1)}function u(e,t){var a=this,r=A(e,a),n=t||q,i=O(n,"field",e);if(i>=0&&n.splice(i,1),i=O(q,"field",e),i>=0&&q.splice(i,1),a.scope.$validationSummary=q,r&&(r.$validationSummary=$(q,"formName",r.$name)),k&&k.controllerAs&&(k.controllerAs.$validationSummary=q,r)){var o=r.$name.indexOf(".")>=0?r.$name.split(".")[1]:r.$name;k.controllerAs[o]&&(k.controllerAs[o].$validationSummary=$(q,"formName",r.$name))}return q}function c(e){k.displayOnlyLastErrorMsg=e}function f(e){var t=this;return k=m(k,e),t}function g(e,a){var r=this;a&&a.obj&&(r=a.obj,r.validatorAttrs=a.obj.attrs);var n=a&&a.elm?a.elm:r.elm,i=n&&n.attr("name")?n.attr("name"):null;if("undefined"==typeof i||null===i){var o=n?n.attr("ng-model"):"unknown";throw'Angular-Validation Service requires you to have a (name="") attribute on the element to validate... Your element is: ng-model="'+o+'"'}var l=a&&a.translate?t.instant(e):e,s=i.replace(/[|&;$%@"<>()+,\[\]\{\}]/g,""),p=null;if(r.validatorAttrs&&r.validatorAttrs.hasOwnProperty("validationErrorTo")){var m=r.validatorAttrs.validationErrorTo.charAt(0),d="."===m||"#"===m?r.validatorAttrs.validationErrorTo:"#"+r.validatorAttrs.validationErrorTo;p=angular.element(document.querySelector(d))}p&&0!==p.length||(p=angular.element(document.querySelector(".validation-"+s)));var u=a&&a.isSubmitted?a.isSubmitted:!1;a&&!a.isValid&&(u||r.ctrl.$dirty||r.ctrl.$touched)?p.length>0?p.html(l):n.after(''+l+""):p.html("")}function v(e,a){var n,o,l=this,s=!0,p=!0,m="";"undefined"==typeof e&&(e="");for(var d=l.ctrl&&l.ctrl.$name?l.ctrl.$name:l.attrs&&l.attrs.name?l.attrs.name:l.elm.attr("name"),u=i(d),c=l.validatorAttrs.rules||l.validatorAttrs.validation,f=0,g=l.validators.length;g>f;f++){if(o=l.validators[f],"autoDetect"===o.type&&(o=x(e)?{condition:o.conditionNum,message:o.messageNum,params:o.params,type:"conditionalNumber"}:{pattern:o.patternLength,message:o.messageLength,params:o.params,type:"regex"}),"conditionalDate"===o.type){var v=s=!1;if(e instanceof Date?v=!0:(n=new RegExp(o.pattern),v=(!o.pattern||"/\\S+/"===o.pattern.toString()||c&&"required"===o.pattern)&&null===e?!1:n.test(e)),v){var h=o.dateType,b=e instanceof Date?e:N(e,h).getTime();if(2==o.params.length){var $=N(o.params[0],h).getTime(),O=N(o.params[1],h).getTime(),S=R(o.condition[0],b,$),A=R(o.condition[1],b,O);s=S&&A?!0:!1}else{var E=N(o.params[0],h).getTime();s=R(o.condition,b,E)}}}else if("conditionalNumber"===o.type)if(2==o.params.length){var S=R(o.condition[0],parseFloat(e),parseFloat(o.params[0])),A=R(o.condition[1],parseFloat(e),parseFloat(o.params[1]));s=S&&A?!0:!1}else s=R(o.condition,parseFloat(e),parseFloat(o.params[0]));else if("matching"===o.type){var V=o.params[0],w=l.scope.$eval(V),T=angular.element(document.querySelector('[name="'+V+'"]')),F=o,L=l.ctrl,q=i(l.ctrl.$name);s=R(o.condition,e,w)&&!!e,T&&T.attr("friendly-name")?o.params[1]=T.attr("friendly-name"):o.params.length>1&&(o.params[1]=o.params[1]),l.scope.$watch(V,function(e,a){var r=R(F.condition,L.$viewValue,e);if(e!==a){if(r)y(l,q,"",!0,!0);else{q.isValid=!1;var n=F.message;F.altText&&F.altText.length>0&&(n=F.altText.replace("alt=","")),t(n).then(function(e){m=" "+(F&&F.params?String.format(e,F.params):e),y(l,q,m,r,!0)})}L.$setValidity("validation",r)}},!0)}else if("remote"===o.type){if(e&&a){l.ctrl.$processing=!0;var C=null,G=o.params[0];if(-1===G.indexOf("."))C=l.scope[G];else{var M=G.split(".");C=l.scope;for(var j=0,D=M.length;D>j;j++)C=C[M[j]]}var P="function"==typeof C?C():null;if(U.length>1)for(;U.length>0;){var I=U.pop();"function"==typeof I.abort&&I.abort()}if(U.push(P),!P||"function"!=typeof P.then)throw"Remote Validation requires a declared function (in your Controller) which also needs to return a Promise, please review your code.";l.ctrl.$setValidity("remote",!1),function(e){P.then(function(t){t=t.data||t,U.pop(),l.ctrl.$processing=!1;var r=m+" ";"boolean"==typeof t?s=t?!0:!1:"object"==typeof t&&(s=t.isValid?!0:!1),s===!1&&(u.isValid=!1,r+=t.message||e,y(l,u,r,!1,a)),s===!0&&p===!0&&(u.isValid=!0,l.ctrl.$setValidity("remote",!0),y(l,u,"",!0,a))})}(o.altText)}}else{var H=l.attrs?l.attrs.ngDisabled:l.validatorAttrs.ngDisabled;l.elm.prop("disabled")||l.scope.$eval(H)?s=!0:"string"==typeof e&&""===e&&l.elm.prop("type")&&"NUMBER"===l.elm.prop("type").toUpperCase()?s=!1:(n=new RegExp(o.pattern,o.patternFlag),s=(!o.pattern||"/\\S+/"===o.pattern.toString()||c&&"required"===o.pattern)&&null===e?!1:n.test(e))}(!l.bFieldRequired&&!e||l.elm.prop("disabled")||l.scope.$eval(H))&&(s=!0),s||(p=!1,function(e,r,n){var i=n.message;n.altText&&n.altText.length>0&&(i=n.altText.replace("alt=",""));var o=t(i);e.translatePromise=o,e.validator=n,o.then(function(t){m.length>0&&k.displayOnlyLastErrorMsg?m=" "+(n&&n.params?String.format(t,n.params):t):m+=" "+(n&&n.params?String.format(t,n.params):t),y(l,e,m,p,a)})["catch"](function(){n.altText&&n.altText.length>0&&(m.length>0&&k.displayOnlyLastErrorMsg?m=" "+i:m+=" "+i,y(l,e,m,p,a))})}(u,s,o))}return s&&(r(l,""),l.updateErrorMsg("",{isValid:s})),u&&(u.isValid=p,p&&(u.message="")),p}function h(e,a,r,n){var i=a.name?a.name:e.attr("name"),o=A(i,{scope:n}),l=a&&a.friendlyName?t.instant(a.friendlyName):"",s={fieldName:i,friendlyName:l,elm:e,attrs:a,ctrl:r,scope:n,isValid:!1,message:"",formName:o?o.$name:null},p=O(L,"fieldName",e.attr("name"));return p>=0?L[p]=s:L.push(s),L}function y(e,t,a,n,i){a=a.trim(),t&&t.isValidationCancelled===!0&&(a=""),r(t,a),t&&(t.message=a),(e.validatorAttrs.preValidateFormElements||k.preValidateFormElements)&&(t&&"function"==typeof e.ctrl.$setTouched&&t.ctrl.$setTouched(),e.ctrl.$dirty===!1&&g(a,{isSubmitted:!0,isValid:n,obj:t})),i&&t&&!t.isValid?e.updateErrorMsg(a,{isValid:n,obj:t}):t&&t.isValid&&r(t,"")}function b(e,t,a){if(e)for(var r=0;rr;r++)t[a[r]]&&(t=t[a[r]]);return t}function A(e,t){if(k&&k.formName){var a=document.querySelector('[name="'+k.formName+'"]');return a.$name=k.formName,a}for(var r=document.getElementsByName(e),a=null,n=0;n=0?S(i.name,t.scope):t.scope[i.name]))return"undefined"==typeof a.$name&&(a.$name=i.name),a}if(i&&i.name){var o={$name:i.name,specialNote:"Created by Angular-Validation for Isolated Scope usage"};if(k&&k.controllerAs&&i.name.indexOf(".")>=0){var l=i.name.split(".");return t.scope[l[0]][l[1]]=o}return t.scope[i.name]=o}return null}function x(e){return!isNaN(parseFloat(e))&&isFinite(e)}function N(e,t){var a="",r="-",n=[],i=[],o="",l="",s="";switch(t.toUpperCase()){case"EURO_LONG":case"EURO-LONG":a=e.substring(0,10),r=e.substring(2,3),n=E(a,r),s=n[0],l=n[1],o=n[2],i=e.length>8?e.substring(9).split(":"):null;break;case"UK":case"EURO":case"EURO_SHORT":case"EURO-SHORT":case"EUROPE":a=e.substring(0,8),r=e.substring(2,3),n=E(a,r),s=n[0],l=n[1],o=parseInt(n[2])<50?"20"+n[2]:"19"+n[2],i=e.length>8?e.substring(9).split(":"):null;break;case"US_LONG":case"US-LONG":a=e.substring(0,10),r=e.substring(2,3),n=E(a,r),l=n[0],s=n[1],o=n[2],i=e.length>8?e.substring(9).split(":"):null;break;case"US":case"US_SHORT":case"US-SHORT":a=e.substring(0,8),r=e.substring(2,3),n=E(a,r),l=n[0],s=n[1],o=parseInt(n[2])<50?"20"+n[2]:"19"+n[2],i=e.length>8?e.substring(9).split(":"):null;break;case"ISO":default:a=e.substring(0,10),r=e.substring(4,5),n=E(a,r),o=n[0],l=n[1],s=n[2],i=e.length>10?e.substring(11).split(":"):null}var p=i&&3===i.length?i[0]:0,m=i&&3===i.length?i[1]:0,d=i&&3===i.length?i[2]:0;return new Date(o,l-1,s,p,m,d)}function E(e,t){var a=[];switch(t){case"/":a=e.split("/");break;case".":a=e.split(".");break;case"-":default:a=e.split("-")}return a}function R(e,t,a){var r=!1;switch(e){case"<":r=a>t?!0:!1;break;case"<=":r=a>=t?!0:!1;break;case">":r=t>a?!0:!1;break;case">=":r=t>=a?!0:!1;break;case"!=":case"<>":r=t!=a?!0:!1;break;case"!==":r=t!==a?!0:!1;break;case"=":case"==":r=t==a?!0:!1;break;case"===":r=t===a?!0:!1;break;default:r=!1}return r}function V(){return this.replace(/^\s+|\s+$/g,"")}function w(){var e=Array.isArray(arguments[0])?arguments[0]:arguments;return this.replace(/{(\d+)}/g,function(t,a){return"undefined"!=typeof e[a]?e[a]:t})}function T(e){var t=Array.isArray(arguments[1])?arguments[1]:Array.prototype.slice.call(arguments,1);return e.replace(/{(\d+)}/g,function(e,a){return"undefined"!=typeof t[a]?t[a]:e})}var F=1e3,L=[],k={resetGlobalOptionsOnRouteChange:!0},U=[],q=[];e.$on("$routeChangeStart",function(){k.resetGlobalOptionsOnRouteChange&&(k={displayOnlyLastErrorMsg:!1,preValidateFormElements:!1,isolatedScope:null,scope:null,resetGlobalOptionsOnRouteChange:!0},L=[],q=[])});var C=function(e,t,a,r){this.bFieldRequired=!1,this.validators=[],this.typingLimit=F,this.scope=e,this.elm=t,this.ctrl=r,this.validatorAttrs=a,e&&e.$validationOptions&&(k=e.$validationOptions),e&&(k.isolatedScope||k.scope)&&(this.scope=k.isolatedScope||k.scope,k=m(e.$validationOptions,k)),"undefined"==typeof k.resetGlobalOptionsOnRouteChange&&(k.resetGlobalOptionsOnRouteChange=!0),this.elm&&this.validatorAttrs&&this.ctrl&&this.scope&&(h(this.elm,this.validatorAttrs,this.ctrl,this.scope),this.defineValidation())};return C.prototype.addToValidationSummary=r,C.prototype.arrayFindObject=b,C.prototype.defineValidation=n,C.prototype.getFormElementByName=i,C.prototype.getFormElements=o,C.prototype.getGlobalOptions=l,C.prototype.isFieldRequired=p,C.prototype.initialize=s,C.prototype.mergeObjects=m,C.prototype.removeFromValidationSummary=u,C.prototype.removeFromFormElementObjectList=d,C.prototype.setDisplayOnlyLastErrorMsg=c,C.prototype.setGlobalOptions=f,C.prototype.updateErrorMsg=g,C.prototype.validate=v,String.prototype.trim=V,String.prototype.format=w,String.format=T,C}]); +angular.module("ghiscoding.validation").factory("validationRules",[function(){function e(e){var a="undefined"!=typeof e.altText?e.altText.replace("alt=",""):null,t=e.hasOwnProperty("customRegEx")?e.customRegEx:null,s=e.hasOwnProperty("rule")?e.rule:null,n=e.hasOwnProperty("ruleParams")?e.ruleParams:null,r={};switch(s){case"accepted":r={pattern:/^(yes|on|1|true)$/i,message:"INVALID_ACCEPTED",type:"regex"};break;case"alpha":r={pattern:/^([a-zа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ])+$/i,message:"INVALID_ALPHA",type:"regex"};break;case"alphaSpaces":case"alpha_spaces":r={pattern:/^([a-zа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ\s])+$/i,message:"INVALID_ALPHA_SPACE",type:"regex"};break;case"alphaNum":case"alpha_num":r={pattern:/^([a-zа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9])+$/i,message:"INVALID_ALPHA_NUM",type:"regex"};break;case"alphaNumSpaces":case"alpha_num_spaces":r={pattern:/^([a-zа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9\s])+$/i,message:"INVALID_ALPHA_NUM_SPACE",type:"regex"};break;case"alphaDash":case"alpha_dash":r={pattern:/^([a-zа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9_-])+$/i,message:"INVALID_ALPHA_DASH",type:"regex"};break;case"alphaDashSpaces":case"alpha_dash_spaces":r={pattern:/^([a-zа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9\s_-])+$/i,message:"INVALID_ALPHA_DASH_SPACE",type:"regex"};break;case"between":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between:1,5";r={patternLength:"^(.|[\\r\\n]){"+_[0]+","+_[1]+"}$",messageLength:"INVALID_BETWEEN_CHAR",conditionNum:[">=","<="],messageNum:"INVALID_BETWEEN_NUM",params:[_[0],_[1]],type:"autoDetect"};break;case"betweenLen":case"between_len":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_len:1,5";r={pattern:"^(.|[\\r\\n]){"+_[0]+","+_[1]+"}$",message:"INVALID_BETWEEN_CHAR",params:[_[0],_[1]],type:"regex"};break;case"betweenNum":case"between_num":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_num:1,5";r={condition:[">=","<="],message:"INVALID_BETWEEN_NUM",params:[_[0],_[1]],type:"conditionalNumber"};break;case"boolean":r={pattern:/^(true|false|0|1)$/i,message:"INVALID_BOOLEAN",type:"regex"};break;case"checked":r={pattern:/^true$/i,message:"INVALID_CHECKBOX_SELECTED",type:"regex"};break;case"creditCard":case"credit_card":r={pattern:/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})$/,message:"INVALID_CREDIT_CARD",type:"regex"};break;case"dateEuroLong":case"date_euro_long":r={pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_EURO_LONG",type:"regex"};break;case"dateEuroLongBetween":case"date_euro_long_between":case"betweenDateEuroLong":case"between_date_euro_long":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_date_euro_long:01-01-1990,31-12-2015";r={condition:[">=","<="],dateType:"EURO_LONG",params:[_[0],_[1]],pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_EURO_LONG_BETWEEN",type:"conditionalDate"};break;case"dateEuroLongMax":case"date_euro_long_max":case"maxDateEuroLong":case"max_date_euro_long":r={condition:"<=",dateType:"EURO_LONG",params:[n],pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_EURO_LONG_MAX",type:"conditionalDate"};break;case"dateEuroLongMin":case"date_euro_long_min":case"minDateEuroLong":case"min_date_euro_long":r={condition:">=",dateType:"EURO_LONG",params:[n],pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_EURO_LONG_MIN",type:"conditionalDate"};break;case"dateEuroShort":case"date_euro_short":r={pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.]\d\d$/,message:"INVALID_DATE_EURO_SHORT",type:"regex"};break;case"dateEuroShortBetween":case"date_euro_short_between":case"betweenDateEuroShort":case"between_date_euro_short":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_date_euro_short:01-01-90,31-12-15";r={condition:[">=","<="],dateType:"EURO_SHORT",params:[_[0],_[1]],pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.]\d\d$/,message:"INVALID_DATE_EURO_SHORT_BETWEEN",type:"conditionalDate"};break;case"dateEuroShortMax":case"date_euro_short_max":case"maxDateEuroShort":case"max_date_euro_short":r={condition:"<=",dateType:"EURO_SHORT",params:[n],pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.]\d\d$/,message:"INVALID_DATE_EURO_SHORT_MAX",type:"conditionalDate"};break;case"dateEuroShortMin":case"date_euro_short_min":case"minDateEuroShort":case"min_date_euro_short":r={condition:">=",dateType:"EURO_SHORT",params:[n],pattern:/^(0[1-9]|[12][0-9]|3[01])[-\/\.](0[1-9]|1[012])[-\/\.]\d\d$/,message:"INVALID_DATE_EURO_SHORT_MIN",type:"conditionalDate"};break;case"dateIso":case"date_iso":r={pattern:/^(19|20)\d\d([-])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/,message:"INVALID_DATE_ISO",type:"regex"};break;case"dateIsoBetween":case"date_iso_between":case"betweenDateIso":case"between_date_iso":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_date_iso:1990-01-01,2000-12-31";r={condition:[">=","<="],dateType:"ISO",params:[_[0],_[1]],pattern:/^(19|20)\d\d([-])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/,message:"INVALID_DATE_ISO_BETWEEN",type:"conditionalDate"};break;case"dateIsoMax":case"date_iso_max":case"maxDateIso":case"max_date_iso":r={condition:"<=",dateType:"ISO",params:[n],pattern:/^(19|20)\d\d([-])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/,message:"INVALID_DATE_ISO_MAX",type:"conditionalDate"};break;case"dateIsoMin":case"date_iso_min":case"minDateIso":case"min_date_iso":r={condition:">=",dateType:"ISO",params:[n],pattern:/^(19|20)\d\d([-])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/,message:"INVALID_DATE_ISO_MIN",type:"conditionalDate"};break;case"dateUsLong":case"date_us_long":r={pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_US_LONG",type:"regex"};break;case"dateUsLongBetween":case"date_us_long_between":case"betweenDateUsLong":case"between_date_us_long":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_date_us_long:01/01/1990,12/31/2015";r={condition:[">=","<="],dateType:"US_LONG",params:[_[0],_[1]],pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_US_LONG_BETWEEN",type:"conditionalDate"};break;case"dateUsLongMax":case"date_us_long_max":case"maxDateUsLong":case"max_date_us_long":r={condition:"<=",dateType:"US_LONG",params:[n],pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_US_LONG_MAX",type:"conditionalDate"};break;case"dateUsLongMin":case"date_us_long_min":case"minDateUsLong":case"min_date_us_long":r={condition:">=",dateType:"US_LONG",params:[n],pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.](19|20)\d\d$/,message:"INVALID_DATE_US_LONG_MIN",type:"conditionalDate"};break;case"dateUsShort":case"date_us_short":r={pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.]\d\d$/,message:"INVALID_DATE_US_SHORT",type:"regex"};break;case"dateUsShortBetween":case"date_us_short_between":case"betweenDateUsShort":case"between_date_us_short":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: between_date_us_short:01/01/90,12/31/15";r={condition:[">=","<="],dateType:"US_SHORT",params:[_[0],_[1]],pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.]\d\d$/,message:"INVALID_DATE_US_SHORT_BETWEEN",type:"conditionalDate"};break;case"dateUsShortMax":case"date_us_short_max":case"maxDateUsShort":case"max_date_us_short":r={condition:"<=",dateType:"US_SHORT",params:[n],pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.]\d\d$/,message:"INVALID_DATE_US_SHORT_MAX",type:"conditionalDate"};break;case"dateUsShortMin":case"date_us_short_min":case"minDateUsShort":case"min_date_us_short":r={condition:">=",dateType:"US_SHORT",params:[n],pattern:/^(0[1-9]|1[012])[-\/\.](0[1-9]|[12][0-9]|3[01])[-\/\.]\d\d$/,message:"INVALID_DATE_US_SHORT_MIN",type:"conditionalDate"};break;case"different":case"differentInput":case"different_input":var e=n.split(",");r={condition:"!=",message:"INVALID_INPUT_DIFFERENT",params:e,type:"matching"};break;case"digits":r={pattern:"^\\d{"+n+"}$",message:"INVALID_DIGITS",params:[n],type:"regex"};break;case"digitsBetween":case"digits_between":var _=n.split(",");if(2!==_.length)throw"This validation must include exactly 2 params separated by a comma (,) ex.: digits_between:1,5";r={pattern:"^\\d{"+_[0]+","+_[1]+"}$",message:"INVALID_DIGITS_BETWEEN",params:[_[0],_[1]],type:"regex"};break;case"email":r={pattern:/^[-\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9#~!$%^&*_=+\/`\|}{\'?]+(\.[-\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9#~!$%^&*_=+\/`\|}{\'?]+)*@([\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9_][-\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9_]*(\.[-\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ0-9_]+)*([\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ]+)|(\.[\wа-яàáâãäåæçèéêëœìíïîðòóôõöøùúûñüýÿßÞďđ]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i,message:"INVALID_EMAIL",type:"regex"};break;case"exactLen":case"exact_len":r={pattern:"^(.|[\\r\\n]){"+n+"}$",message:"INVALID_EXACT_LEN",params:[n],type:"regex"};break;case"float":r={pattern:/^\d*\.{1}\d+$/,message:"INVALID_FLOAT",type:"regex"};break;case"floatSigned":case"float_signed":r={pattern:/^[-+]?\d*\.{1}\d+$/,message:"INVALID_FLOAT_SIGNED",type:"regex"};break;case"iban":r={pattern:/^[a-zA-Z]{2}\d{2}\s?([0-9a-zA-Z]{4}\s?){4}[0-9a-zA-Z]{2}$/i,message:"INVALID_IBAN",type:"regex"};break;case"in":case"inList":case"in_list":var c=n.replace(/,/g,"|");r={pattern:"^(\\b("+c+")\\b)$",patternFlag:"i",message:"INVALID_IN_LIST",params:[n],type:"regex"};break;case"int":case"integer":r={pattern:/^\d+$/,message:"INVALID_INTEGER",type:"regex"};break;case"intSigned":case"integerSigned":case"int_signed":case"integer_signed":r={pattern:/^[+-]?\d+$/,message:"INVALID_INTEGER_SIGNED",type:"regex"};break;case"ip":case"ipv4":r={pattern:/^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$/,message:"INVALID_IPV4",type:"regex"};break;case"ipv6":r={pattern:/^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/i,message:"INVALID_IPV6",type:"regex"};break;case"match":case"matchInput":case"match_input":case"same":var e=n.split(",");r={condition:"===",message:"INVALID_INPUT_MATCH",params:e,type:"matching"};break;case"max":r={patternLength:"^(.|[\\r\\n]){0,"+n+"}$",messageLength:"INVALID_MAX_CHAR",conditionNum:"<=",messageNum:"INVALID_MAX_NUM",params:[n],type:"autoDetect"};break;case"maxLen":case"max_len":r={pattern:"^(.|[\\r\\n]){0,"+n+"}$",message:"INVALID_MAX_CHAR",params:[n],type:"regex"};break;case"maxNum":case"max_num":r={condition:"<=",message:"INVALID_MAX_NUM",params:[n],type:"conditionalNumber"};break;case"min":r={patternLength:"^(.|[\\r\\n]){"+n+",}$",messageLength:"INVALID_MIN_CHAR",conditionNum:">=",messageNum:"INVALID_MIN_NUM",params:[n],type:"autoDetect"};break;case"minLen":case"min_len":r={pattern:"^(.|[\\r\\n]){"+n+",}$",message:"INVALID_MIN_CHAR",params:[n],type:"regex"};break;case"minNum":case"min_num":r={condition:">=",message:"INVALID_MIN_NUM",params:[n],type:"conditionalNumber"};break;case"notIn":case"not_in":case"notInList":case"not_in_list":var c=n.replace(/,/g,"|");r={pattern:"^((?!\\b("+c+")\\b).)+$",patternFlag:"i",message:"INVALID_NOT_IN_LIST",params:[n],type:"regex"};break;case"numeric":r={pattern:/^\d*\.?\d+$/,message:"INVALID_NUMERIC",type:"regex"};break;case"numericSigned":case"numeric_signed":r={pattern:/^[-+]?\d*\.?\d+$/,message:"INVALID_NUMERIC_SIGNED",type:"regex"};break;case"pattern":case"regex":r={pattern:t.pattern,message:"INVALID_PATTERN",params:[t.message],type:"regex"};break;case"remote":r={message:"",params:[n],type:"remote"};break;case"required":r={pattern:/\S+/,message:"INVALID_REQUIRED",type:"regex"};break;case"size":r={patternLength:"^(.|[\\r\\n]){"+n+"}$",messageLength:"INVALID_EXACT_LEN",conditionNum:"==",messageNum:"INVALID_EXACT_NUM",params:[n],type:"autoDetect"};break;case"url":r={pattern:/^(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:\/~\+#]*[\w\-\@?^=%&\/~\+#])?/i,message:"INVALID_URL",type:"regex"};break;case"time":r={pattern:/^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/,message:"INVALID_TIME",type:"regex"}}return r.altText=a,r}var a={getElementValidators:e};return a}]); angular.module("ghiscoding.validation").service("validationService",["$interpolate","$timeout","validationCommon",function(e,o,t){function n(t,n,a){var i=this,m={};if("string"==typeof t&&"string"==typeof n?(m.elmName=t,m.rules=n,m.friendlyName="string"==typeof a?a:""):m=t,"object"!=typeof m||!m.hasOwnProperty("elmName")||!m.hasOwnProperty("rules")||!m.hasOwnProperty("scope")&&"undefined"==typeof i.validationAttrs.scope)throw"Angular-Validation-Service requires at least the following 3 attributes: {elmName, rules, scope}";var r=m.scope?m.scope:i.validationAttrs.scope;if(m.elm=angular.element(document.querySelector('[name="'+m.elmName+'"]')),"object"!=typeof m.elm||0===m.elm.length)return i;if(new RegExp("{{(.*?)}}").test(m.elmName)&&(m.elmName=e(m.elmName)(r)),m.name=m.elmName,i.validationAttrs.isolatedScope){var l=r.$validationOptions||null;r=i.validationAttrs.isolatedScope,l&&(r.$validationOptions=l)}m.elm.bind("blur",p=function(e){var o=i.commonObj.getFormElementByName(m.elmName);o&&!o.isValidationCancelled&&(i.commonObj.initialize(r,m.elm,m,m.ctrl),d(i,e.target.value,10))}),m=i.commonObj.mergeObjects(i.validationAttrs,m),f(i,r,m),m.elm.on("$destroy",function(){var e=i.commonObj.getFormElementByName(i.commonObj.ctrl.$name);e&&(s(i,e),i.commonObj.removeFromValidationSummary(m.name))});var c=r.$watch(m.elmName,function(e,t){return void 0===e&&void 0!==t?(o.cancel(i.timer),void i.commonObj.ctrl.$setValidity("validation",i.commonObj.validate("",!0))):(m.ctrl=angular.element(m.elm).controller("ngModel"),m.value=e,i.commonObj.initialize(r,m.elm,m,m.ctrl),void d(i,e))},!0);return v.push({elmName:m.elmName,watcherHandler:c}),i}function a(e){var o=this,t="",n=!0;if("undefined"==typeof e||"undefined"==typeof e.$validationSummary)throw"checkFormValidity() requires a valid Angular Form or $scope/vm object passed as argument to work properly, for example:: fn($scope) OR fn($scope.form1) OR fn(vm) OR fn(vm.form1)";for(var a=0,i=e.$validationSummary.length;i>a;a++)if(n=!1,t=e.$validationSummary[a].field){var m=o.commonObj.getFormElementByName(t);m&&m.elm&&m.elm.length>0&&("function"==typeof m.ctrl.$setTouched&&m.ctrl.$setTouched(),o.commonObj.updateErrorMsg(e.$validationSummary[a].message,{isSubmitted:!0,isValid:m.isValid,obj:m}))}return n}function i(e){var o=this;if("undefined"==typeof e||"undefined"==typeof e.$validationSummary)throw"clearInvalidValidatorsInSummary() requires a valid Angular Form or $scope/vm object passed as argument to work properly, for example:: fn($scope) OR fn($scope.form1) OR fn(vm) OR fn(vm.form1)";for(var t=[],n=0,a=e.$validationSummary.length;a>n;n++)t.push(e.$validationSummary[n].field);for(n=0,a=t.length;a>n;n++)t[n]&&(o.commonObj.removeFromFormElementObjectList(t[n]),o.commonObj.removeFromValidationSummary(t[n],e.$validationSummary))}function m(e,o){var t,n=this;if("undefined"==typeof e||"undefined"==typeof e.$validationSummary)throw"removeValidator() only works with Validation that were defined by the Service (not by the Directive) and requires a valid Angular Form or $scope/vm object passed as argument to work properly, for example:: fn($scope) OR fn($scope.form1) OR fn(vm) OR fn(vm.form1)";if(o instanceof Array)for(var a=0,i=o.length;i>a;a++)t=n.commonObj.getFormElementByName(o[a]),t.elm.removeAttr("validation"),u(n,t,e.$validationSummary);else o instanceof Object&&o.formElmObj?(t=o.formElmObj,t.elm.removeAttr("validation"),u(o.self,t,e.$validationSummary)):(t=n.commonObj.getFormElementByName(o),t.elm.removeAttr("validation"),u(n,t,e.$validationSummary));return n}function r(e,o){var t,n=this,o=o||{},a="undefined"!=typeof o.removeAllValidators?o.removeAllValidators:!1,i="undefined"!=typeof o.emptyAllInputValues?o.emptyAllInputValues:!1;if("undefined"==typeof e||"undefined"==typeof e.$name)throw"resetForm() requires a valid Angular Form object passed as argument to work properly (ex.: $scope.form1).";var r=n.commonObj.getFormElements(e.$name);if(r instanceof Array)for(var l=0,c=r.length;c>l;l++)t=r[l],i&&t.elm.val(null),a?m(e,{self:n,formElmObj:t}):("function"==typeof t.ctrl.$setUntouched&&t.ctrl.$setUntouched(),t.ctrl.$setPristine(),n.commonObj.updateErrorMsg("",{isValid:!1,obj:t}))}function l(e){var o=this,t="boolean"==typeof e?e:!0;o.commonObj.setDisplayOnlyLastErrorMsg(t)}function c(e){var o=this;return o.validationAttrs=e,o.commonObj.setGlobalOptions(e),o}function d(e,t,n){var a="undefined"!=typeof n?n:e.commonObj.typingLimit,i=e.commonObj.getFormElementByName(e.commonObj.ctrl.$name);return e.commonObj.validate(t,!1),e.commonObj.isFieldRequired()||""!==t&&null!==t&&"undefined"!=typeof t?(i.isValidationCancelled=!1,(e.commonObj.isFieldRequired()||t)&&e.commonObj.ctrl.$setValidity("validation",!1),""!==t&&"undefined"!=typeof t||"NUMBER"!==e.commonObj.elm.prop("type").toUpperCase()?"SELECT"===e.commonObj.elm.prop("tagName").toUpperCase()?(e.commonObj.ctrl.$setValidity("validation",e.commonObj.validate(t,!0)),t):("undefined"!=typeof t&&(e.commonObj.updateErrorMsg(""),o.cancel(e.timer),e.timer=o(function(){e.commonObj.scope.$evalAsync(e.commonObj.ctrl.$setValidity("validation",e.commonObj.validate(t,!0)))},a)),t):(o.cancel(e.timer),e.commonObj.ctrl.$setValidity("validation",e.commonObj.validate(t,!0)),t)):(s(e,i),t)}function s(e,t){var n=t&&t.ctrl?t.ctrl:e.commonObj.ctrl;if(t&&(t.isValidationCancelled=!0),o.cancel(self.timer),n.$setValidity("validation",!0),e.commonObj.updateErrorMsg("",{isValid:!0,obj:t}),"function"==typeof p){var a=t&&t.elm?t.elm:e.commonObj.elm;a.unbind("blur",p)}}function u(e,o,t){var n=e.commonObj.scope?e.commonObj.scope:o.scope?o.scope:null;if("undefined"==typeof n)throw"removeValidator() requires a valid $scope object passed but unfortunately could not find it.";var a=e.commonObj.arrayFindObject(v,"elmName",o.fieldName);a&&a.watcherHandler(),o.isValidationCancelled=!0,o.isValid=!0,o.attrs.validation="",s(e,o),"function"==typeof o.ctrl.$setUntouched&&o.ctrl.$setUntouched(),e.commonObj.scope=n,o.ctrl.$setPristine(),e.commonObj.removeFromValidationSummary(o.fieldName,t)}function f(e,t,n){t.$watch(function(){return"undefined"==typeof n.elm.attr("ng-disabled")?null:t.$eval(n.elm.attr("ng-disabled"))},function(a){if("undefined"==typeof a||null===a)return null;n.ctrl=angular.element(n.elm).controller("ngModel"),e.commonObj.initialize(t,n.elm,n,n.ctrl);var i=e.commonObj.getFormElementByName(n.name);o(function(){if(a)n.ctrl.$setValidity("validation",!0),e.commonObj.updateErrorMsg("",{isValid:!0,obj:i}),e.commonObj.removeFromValidationSummary(n.name);else{var o=n.ctrl.$viewValue||"";e.commonObj.initialize(t,n.elm,n,n.ctrl),n.ctrl.$setValidity("validation",e.commonObj.validate(o,!1)),i&&(i.isValidationCancelled=!1),n.elm.bind("blur",p=function(o){i&&!i.isValidationCancelled&&d(e,o.target.value,10)})}},0,!1),a&&("function"==typeof n.ctrl.$setUntouched&&n.ctrl.$setUntouched(),n.ctrl.$setValidity("validation",!0),e.commonObj.removeFromValidationSummary(n.name))})}var p,v=[],y=function(e){this.isValidationCancelled=!1,this.timer=null,this.validationAttrs={},this.commonObj=new t,e&&this.setGlobalOptions(e)};return y.prototype.addValidator=n,y.prototype.checkFormValidity=a,y.prototype.removeValidator=m,y.prototype.resetForm=r,y.prototype.setDisplayOnlyLastErrorMsg=l,y.prototype.setGlobalOptions=c,y.prototype.clearInvalidValidatorsInSummary=i,y}]); \ No newline at end of file diff --git a/index.html b/index.html index 3e19ae0..c871113 100644 --- a/index.html +++ b/index.html @@ -32,6 +32,7 @@

Angular-Validation Directive|Service (ghiscoding)

+ 3rd Party Addon

diff --git a/locales/validation/fr.json b/locales/validation/fr.json index 4735445..89d9d17 100644 --- a/locales/validation/fr.json +++ b/locales/validation/fr.json @@ -10,26 +10,26 @@ "INVALID_BETWEEN_NUM": "Doit être une valeur numérique, entre {0} et {1}. ", "INVALID_BOOLEAN": "Doit contenir qu'une valeur vraie ou fausse. ", "INVALID_CREDIT_CARD": "Doit être un numéro de carte de crédit valide. ", - "INVALID_DATE_EURO_LONG": "Doit être un format de date valide (dd-mm-yyyy) OU (dd/mm/yyyy). ", - "INVALID_DATE_EURO_LONG_BETWEEN": "Doit être un format de date valide (dd-mm-yyyy) OU (dd/mm/yyyy) entre {0} et {1}. ", - "INVALID_DATE_EURO_LONG_MAX": "Doit être une date valide (dd-mm-yyyy) OU (dd/mm/yyyy), égale ou inférieure à {0}. ", - "INVALID_DATE_EURO_LONG_MIN": "Doit être une date valide (dd-mm-yyyy) OU (dd/mm/yyyy), égale ou supérieure à {0}. ", - "INVALID_DATE_EURO_SHORT": "Doit être un format de date valide (dd-mm-yy) OU (dd/mm/yy). ", - "INVALID_DATE_EURO_SHORT_BETWEEN": "Doit être un format de date valide (dd-mm-yy) OU (dd/mm/yy) entre {0} et {1}. ", - "INVALID_DATE_EURO_SHORT_MAX": "Doit être une date valide (dd-mm-yy) OU (dd/mm/yy), égale ou inférieure à {0}. ", - "INVALID_DATE_EURO_SHORT_MIN": "Doit être une date valide (dd-mm-yy) OU (dd/mm/yy), égale ou supérieure à {0}. ", - "INVALID_DATE_ISO": "Doit être un format de date valide (yyyy-mm-dd). ", - "INVALID_DATE_ISO_BETWEEN": "Doit être un format de date valide (yyyy-mm-dd) entre {0} et {1}. ", - "INVALID_DATE_ISO_MAX": "Doit être une date valide (yyyy-mm-dd), égale ou inférieure à {0}. ", - "INVALID_DATE_ISO_MIN": "Doit être une date valide (yyyy-mm-dd), égale ou supérieure à {0}. ", - "INVALID_DATE_US_LONG": "Doit être un format de date valide (mm/dd/yyyy) OU (mm-dd-yyyy). ", - "INVALID_DATE_US_LONG_BETWEEN": "Doit être un format de date valide (mm/dd/yyyy) OU (mm-dd-yyyy) entre {0} et {1}. ", - "INVALID_DATE_US_LONG_MAX": "Doit être une date valide (mm/dd/yyyy) OU (mm-dd-yyyy), égale ou inférieure à {0}. ", - "INVALID_DATE_US_LONG_MIN": "Doit être une date valide (mm/dd/yyyy) OU (mm-dd-yyyy), égale ou supérieure à {0}. ", - "INVALID_DATE_US_SHORT": "Doit être un format de date valide (mm/dd/yy) OU (mm-dd-yy). ", - "INVALID_DATE_US_SHORT_BETWEEN": "Doit être un format de date valide (mm/dd/yy) OU (mm-dd-yy) entre {0} et {1}. ", - "INVALID_DATE_US_SHORT_MAX": "Doit être une date valide (mm/dd/yy) OU (mm-dd-yy), égale ou inférieure à {0}. ", - "INVALID_DATE_US_SHORT_MIN": "Doit être une date valide (mm/dd/yy) OU (mm-dd-yy), égale ou supérieure à {0}. ", + "INVALID_DATE_EURO_LONG": "Doit être un format de date valide (jj-mm-aaaa) OU (jj/mm/aaaa). ", + "INVALID_DATE_EURO_LONG_BETWEEN": "Doit être un format de date valide (jj-mm-aaaa) OU (jj/mm/aaaa) entre {0} et {1}. ", + "INVALID_DATE_EURO_LONG_MAX": "Doit être une date valide (jj-mm-aaaa) OU (jj/mm/aaaa), égale ou inférieure à {0}. ", + "INVALID_DATE_EURO_LONG_MIN": "Doit être une date valide (jj-mm-aaaa) OU (jj/mm/aaaa), égale ou supérieure à {0}. ", + "INVALID_DATE_EURO_SHORT": "Doit être un format de date valide (jj-mm-aa) OU (jj/mm/aa). ", + "INVALID_DATE_EURO_SHORT_BETWEEN": "Doit être un format de date valide (jj-mm-aa) OU (jj/mm/aa) entre {0} et {1}. ", + "INVALID_DATE_EURO_SHORT_MAX": "Doit être une date valide (jj-mm-aa) OU (jj/mm/aa), égale ou inférieure à {0}. ", + "INVALID_DATE_EURO_SHORT_MIN": "Doit être une date valide (jj-mm-aa) OU (jj/mm/aa), égale ou supérieure à {0}. ", + "INVALID_DATE_ISO": "Doit être un format de date valide (aaaa-mm-jj). ", + "INVALID_DATE_ISO_BETWEEN": "Doit être un format de date valide (aaaa-mm-jj) entre {0} et {1}. ", + "INVALID_DATE_ISO_MAX": "Doit être une date valide (aaaa-mm-jj), égale ou inférieure à {0}. ", + "INVALID_DATE_ISO_MIN": "Doit être une date valide (aaaa-mm-jj), égale ou supérieure à {0}. ", + "INVALID_DATE_US_LONG": "Doit être un format de date valide (mm/jj/aaaa) OU (mm-jj-aaaa). ", + "INVALID_DATE_US_LONG_BETWEEN": "Doit être un format de date valide (mm/jj/aaaa) OU (mm-jj-aaaa) entre {0} et {1}. ", + "INVALID_DATE_US_LONG_MAX": "Doit être une date valide (mm/jj/aaaa) OU (mm-jj-aaaa), égale ou inférieure à {0}. ", + "INVALID_DATE_US_LONG_MIN": "Doit être une date valide (mm/jj/aaaa) OU (mm-jj-aaaa), égale ou supérieure à {0}. ", + "INVALID_DATE_US_SHORT": "Doit être un format de date valide (mm/jj/aa) OU (mm-jj-aa). ", + "INVALID_DATE_US_SHORT_BETWEEN": "Doit être un format de date valide (mm/jj/aa) OU (mm-jj-aa) entre {0} et {1}. ", + "INVALID_DATE_US_SHORT_MAX": "Doit être une date valide (mm/jj/aa) OU (mm-jj-aa), égale ou inférieure à {0}. ", + "INVALID_DATE_US_SHORT_MIN": "Doit être une date valide (mm/jj/aa) OU (mm-jj-aa), égale ou supérieure à {0}. ", "INVALID_DIGITS": "Doit être {0} chiffres. ", "INVALID_DIGITS_BETWEEN": "Doit être entre {0} et {1} chiffres. ", "INVALID_EMAIL": "Doit être une adresse courriel valide. ", @@ -74,8 +74,8 @@ "INPUT7": "IP (IPV4)", "INPUT8": "Carte de Crédit", "INPUT9": "Entre(2,6) Caractères", - "INPUT10": "Date ISO (yyyy-mm-dd)", - "INPUT11": "Date US LONG (mm/dd/yyyy)", + "INPUT10": "Date ISO (aaaa-mm-jj)", + "INPUT11": "Date US LONG (mm/jj/aaaa)", "INPUT12": "Time (hh:mm OU hh:mm:ss) -- NON Requis", "INPUT13": "AlphaDashSpaces + Requis + Minimum(5) Caractères -- DOIT UTILISER: validation-error-to=\" \"", "INPUT14": "Alphanumérique + Requis -- NG-DISABLED", @@ -83,8 +83,8 @@ "INPUT16": "Mot de Passe (Confirmation)", "INPUT17": "Different Mot de Passe", "INPUT18": "Alphanumérique + Exactement(3) + Requis -- debounce(3sec)", - "INPUT19": "Date ISO (yyyy-mm-dd ) -- condition minimal >= 2001-01-01 ", - "INPUT20": "Date US COURT (mm/dd/yy) -- entre les dates 12/01/99 et 12/31/15", + "INPUT19": "Date ISO (aaaa-mm-jj ) -- condition minimal >= 2001-01-01 ", + "INPUT20": "Date US COURT (mm/jj/aa) -- entre les dates 12/01/99 et 12/31/15", "INPUT21": "Choix dans cette liste (banana,orange,ice cream)", "FIRST_NAME": "Prénom", "LAST_NAME": "Nom de Famille", diff --git a/more-examples/addon-3rdParty/app.js b/more-examples/addon-3rdParty/app.js new file mode 100644 index 0000000..78cb346 --- /dev/null +++ b/more-examples/addon-3rdParty/app.js @@ -0,0 +1,47 @@ +'use strict'; + +var myApp = angular.module('myApp', ['ghiscoding.validation', 'pascalprecht.translate', 'ngTagsInput', 'angularjs-dropdown-multiselect', + 'hljs', 'isteven-multi-select']); + +myApp.config(['$compileProvider', function ($compileProvider) { + $compileProvider.debugInfoEnabled(false); + }]) + .config(['$translateProvider', function ($translateProvider) { + $translateProvider.useStaticFilesLoader({ + prefix: '../../locales/validation/', + suffix: '.json' + }); + // load English ('en') table on startup + $translateProvider.preferredLanguage('en').fallbackLanguage('en'); + }]); + +myApp.controller('Ctrl', ['validationService', function (validationService) { + var vm = this; + var myValidation = new validationService({ controllerAs: vm, formName: 'vm.test', preValidateFormElements: false }); + + vm.tags1 = [ + { id: 1, text: 'Tag1' }, + { id: 2, text: 'Tag2' }, + { id: 3, text: 'Tag3' } + ]; + vm.tags2 = [ + { id: 1, text: 'Tag7' }, + { id: 2, text: 'Tag8' }, + { id: 3, text: 'abc' } + ]; + + vm.select1model = []; + vm.select1data = [ + {id: 1, label: "Joe"}, + {id: 2, label: "John"}, + {id: 3, label: "Jane"} + ]; + + vm.modernBrowsers = [ + { name: "Opera", maker: "Opera Software", ticked: true, icon: "" }, + { name: "Internet Explorer", maker: "Microsoft", ticked: false, icon: "" }, + { name: "Firefox", maker: "Mozilla Foundation", ticked: false, icon: "" }, + { name: "Safari", maker: "Apple", ticked: false, icon: "" }, + { name: "Chrome", maker: "Google", ticked: false, icon: "" } + ]; +}]); diff --git a/more-examples/addon-3rdParty/index.html b/more-examples/addon-3rdParty/index.html new file mode 100644 index 0000000..a6c43dc --- /dev/null +++ b/more-examples/addon-3rdParty/index.html @@ -0,0 +1,139 @@ + + + + + Angular-Validation Example with Interpolation + + + + + + + +
+

Validation examples with external 3rd party addons

+ + + +
+ +

ERRORS!

+
    +
  • {{ item.friendlyName != '' ? item.friendlyName : item.field }}: {{item.message}}
  • +
+
+ +
+ +
+
+ +
+
+
+ +
+ +
+
+
+ +
+ + <valid-array-require-how-many="one"> + + +
+ +
+ + <valid-array-require-how-many="all"> + + +
+
+
+
+ +
+
+ +
+ We can validate an input array by 2 ways: +
    +
  1. <valid-array-require-how-many="one"> (default), if 1 value is found good, the complete input set is Valid.
  2. +
  3. <valid-array-require-how-many="all">. For the input to be Valid, we need "all" array values to be valid.
  4. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/more-examples/angular-ui-calendar/index.html b/more-examples/angular-ui-calendar/index.html index d325639..60d1eeb 100644 --- a/more-examples/angular-ui-calendar/index.html +++ b/more-examples/angular-ui-calendar/index.html @@ -11,8 +11,8 @@

Example of Angular-Validation Date validation error after select from ui datepicker.


- - + | +
@@ -49,7 +49,7 @@

Example of Angular-Validation Date validation error after select from ui dat

ERRORS!

    -
  • {{ item.friendlyName != '' ? item.friendlyName : item.field }}: {{item.message}}
  • +
  • {{ item.field }}: {{item.message}}
diff --git a/package.json b/package.json index 373b077..a47c597 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-validation-ghiscoding", - "version": "1.4.8", + "version": "1.4.9", "author": "Ghislain B.", "description": "Angular-Validation Directive and Service (ghiscoding)", "main": "app.js", diff --git a/protractor/angularUI_spec.js b/protractor/angularUI_spec.js new file mode 100644 index 0000000..0c55e34 --- /dev/null +++ b/protractor/angularUI_spec.js @@ -0,0 +1,56 @@ +describe('Angular-Validation with AngularUI Tests:', function () { + // global variables + var validDate = "01/10/2015"; + var invalidOverDate = "03/10/2015"; + var invalidTypoDate = "03/10/201"; + var invalidDateMsg = "dateOfChange: Needs to be a valid date format (dd-mm-yyyy) OR (dd/mm/yyyy) between 02/10/2005 and 02/10/2015."; + + describe('When choosing `more-examples` AngularUI', function () { + it('Should navigate to home page', function () { + browser.get('http://localhost/Github/angular-validation/more-examples/angular-ui-calendar/'); + + // Find the title element + var titleElement = element(by.css('h2')); + + // Assert that the text element has the expected value. + // Protractor patches 'expect' to understand promises. + expect(titleElement.getText()).toEqual('Example of Angular-Validation Date validation error after select from ui datepicker.'); + }); + + it('Should enter valid date expect no errors on input and validation summary', function () { + var elmInput = $('[name=dateOfChange]'); + elmInput.sendKeys(validDate); + elmInput.sendKeys(protractor.Key.TAB); + + // validation summary should become empty + var itemRows = element.all(by.binding('message')); + expect(itemRows.count()).toBe(0); + }); + + it('Should enter outside of range date and show dateOfChange error on input and ValidationSummary', function () { + var elmInput = $('[name=dateOfChange]'); + elmInput.sendKeys(invalidOverDate); + elmInput.sendKeys(protractor.Key.TAB); + + var itemRows = element.all(by.binding('message')); + var inputName; + + for (var i = 0, j = 0, ln = itemRows.length; i < ln; i++) { + expect(itemRows.get(i).getText()).toEqual(invalidDateMsg); + } + }); + + it('Should enter wrong date format and show dateOfChange error on input and ValidationSummary', function () { + var elmInput = $('[name=dateOfChange]'); + elmInput.sendKeys(invalidTypoDate); + elmInput.sendKeys(protractor.Key.TAB); + + var itemRows = element.all(by.binding('message')); + var inputName; + + for (var i = 0, j = 0, ln = itemRows.length; i < ln; i++) { + expect(itemRows.get(i).getText()).toEqual(invalidDateMsg); + } + }); + }); +}); \ No newline at end of file diff --git a/protractor/conf.js b/protractor/conf.js index 230ce6d..c31fcfd 100644 --- a/protractor/conf.js +++ b/protractor/conf.js @@ -22,17 +22,19 @@ // Spec patterns are relative to the current working directory when protractor is called specs: [ 'mixed_validation_spec.js', + 'angularUI_spec.js', 'dynamic_spec.js', 'controllerAsWithRoute_spec.js', 'interpolate_spec.js', 'ngIfDestroy_spec.js', + 'thirdParty_spec.js', 'full_tests_spec.js' ], jasmineNodeOpts: { showColors: true, - defaultTimeoutInterval: 650000 + defaultTimeoutInterval: 800000 }, - allScriptsTimeout: 650000, + allScriptsTimeout: 800000, seleniumAddress: 'http://localhost:4444/wd/hub', // format the output when tests are run with Team City diff --git a/protractor/thirdParty_spec.js b/protractor/thirdParty_spec.js new file mode 100644 index 0000000..78c3acd --- /dev/null +++ b/protractor/thirdParty_spec.js @@ -0,0 +1,125 @@ +describe('Angular-Validation 3rd Party Addons Tests:', function () { + // global variables + var defaultErrorMessages = [ + 'select1: Must be a choice inside this list: (John,Jane). Field is required.', + 'select2: Must be a choice inside this list: (Firefox,Chrome).', + 'input1: Must be a choice inside this list: (Tag4,Tag5).', + 'input2: [abc] :: Must be a Tag with a number.' + ]; + + describe('When choosing `more-examples` 3rd Party Addons', function () { + it('Should navigate to home page', function () { + browser.get('http://localhost/Github/angular-validation/more-examples/addon-3rdParty/'); + + // Find the title element + var titleElement = element(by.css('h2')); + expect(titleElement.getText()).toEqual('Validation examples with external 3rd party addons'); + }); + + it('Should have multiple errors in validation summary', function () { + var itemRows = element.all(by.binding('message')); + var inputName; + + for (var i = 0, j = 0, ln = itemRows.length; i < ln; i++) { + expect(itemRows.get(i).getText()).toEqual(defaultErrorMessages[i]); + } + }); + + it('Should click on select1 and choose 1st choice (invalid)', function() { + browser.sleep(500); + $('.multiselect-parent button').click(); + browser.waitForAngular(); + + element.all(by.repeater('option in options')).get(0).click(); + + var elmError = $('.validation-select1'); + expect(elmError.getText()).toEqual('Must be a choice inside this list: (John,Jane).'); + }); + + it('Should click on select1 and choose 2nd choice (valid)', function() { + element.all(by.repeater('option in options')).get(1).click(); + + var elmError = $('.validation-select1'); + expect(elmError.getText()).toEqual(''); + + // hide back dropdown multi-select + $('.multiselect-parent button').click(); + browser.waitForAngular(); + }); + + it('Should click on select2 and choose 2nd choice (invalid)', function() { + element.all(by.css('.multiSelect button')).get(0).click(); + browser.waitForAngular(); + + element.all(by.repeater('item in filteredModel')).get(1).click(); + + var elmError = $('.validation-select2'); + expect(elmError.getText()).toEqual('Must be a choice inside this list: (Firefox,Chrome).'); + }); + + it('Should click on select2 and choose 3rd choice (valid)', function() { + element.all(by.repeater('item in filteredModel')).get(2).click(); + + var elmError = $('.validation-select2'); + expect(elmError.getText()).toEqual(''); + + element.all(by.css('.multiSelect button')).get(0).click(); + browser.waitForAngular(); + }); + + it('Should enter valid text on input1', function() { + var elmError = $('.validation-input1'); + expect(elmError.getText()).toEqual('Must be a choice inside this list: (Tag4,Tag5).'); + + // add a new 'tag4' + var elmInput = $('[name=input1] input'); + elmInput.click(); + elmInput.sendKeys('tag4'); + elmInput.sendKeys(protractor.Key.ENTER); + elmInput.sendKeys(protractor.Key.TAB); + + var elmError = $('.validation-input1'); + expect(elmError.getText()).toEqual(''); + }); + + it('Should add an invalid tag in input2 and show 2 errors concatenated', function() { + // should already have a default error of 1st tag + var elmError = $('.validation-input2'); + expect(elmError.getText()).toEqual('[abc] :: Must be a Tag with a number.'); + + // add a new 'tag4' + var elmInput = $('[name=input2] input'); + elmInput.click(); + elmInput.sendKeys('xyz'); + elmInput.sendKeys(protractor.Key.ENTER); + elmInput.sendKeys(protractor.Key.TAB); + + // get 2 errors concatenated + expect(elmError.getText()).toEqual('[abc] :: Must be a Tag with a number. [xyz] :: Must be a Tag with a number.'); + }); + + it('Should remove last 2 invalid tags on input2 and make input valid', function() { + var container = element(by.css("div#tag2")); + var rows = container.all(by.repeater('tag in tagList.items')); + + // remove the second last, then check error message + rows.get(2).$('.remove-button').click(); + var elmError = $('.validation-input2'); + expect(elmError.getText()).toEqual('[xyz] :: Must be a Tag with a number.'); + + // remove the last one left, then error message should be empty + rows.get(2).$('.remove-button').click(); + expect(elmError.getText()).toEqual(''); + }); + + it('Should expect validation summary to be empty and save button enabled', function () { + // validation summary should become empty + var itemRows = element.all(by.binding('message')); + expect(itemRows.count()).toBe(0); + + var elmSubmit1 = $('[name=save_btn]'); + expect(elmSubmit1.isEnabled()).toBe(true); + }); + + }); +}); \ No newline at end of file diff --git a/readme.md b/readme.md index db780ca..78c017a 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,5 @@ #Angular Validation (Directive / Service) -`Version: 1.4.8` +`Version: 1.4.9` ### Form validation after user inactivity of default 1sec. (customizable timeout) Forms Validation with Angular made easy! Angular-Validation is an angular directive/service with locales (languages) with a very simple approach of defining your `validation=""` directly within your element to validate (input, textarea, etc) and...that's it!!! The directive/service will take care of the rest! @@ -71,6 +71,7 @@ All the documentation has been moved to the Wiki section, see the [github wiki]( * [Bower/NuGet Packages](https://github.com/ghiscoding/angular-validation/wiki/Download-and-Install-it) * [Locales (languages)](https://github.com/ghiscoding/angular-validation/wiki/Locales-(languages)) * Code Samples + * [3rd Party Addon Validation](https://github.com/ghiscoding/angular-validation/wiki/3rd-Party-Addons) * [Directive Examples](https://github.com/ghiscoding/angular-validation/wiki/Working-Directive-Examples) * [Service Examples](https://github.com/ghiscoding/angular-validation/wiki/Working-Service-Examples) * [Alternate Text on Validators](https://github.com/ghiscoding/angular-validation/wiki/Alternate-Text-on-Validators) diff --git a/src/validation-common.js b/src/validation-common.js index 8db2449..403fa95 100644 --- a/src/validation-common.js +++ b/src/validation-common.js @@ -68,6 +68,7 @@ angular }; // list of available published public functions of this object + validationCommon.prototype.addToValidationSummary = addToValidationSummary; // add an element to the $validationSummary validationCommon.prototype.arrayFindObject = arrayFindObject; // search an object inside an array of objects validationCommon.prototype.defineValidation = defineValidation; // define our validation object validationCommon.prototype.getFormElementByName = getFormElementByName; // get the form element custom object by it's name @@ -95,6 +96,65 @@ angular // Public functions declaration //---------------------------------- + /** Add the error to the validation summary + * @param object self + * @param string message: error message + */ + function addToValidationSummary(self, message) { + if (typeof self === "undefined" || self == null) { + return; + } + + // get the element name, whichever we find it + var elmName = (!!self.ctrl && !!self.ctrl.$name) + ? self.ctrl.$name + : (!!self.attrs && !!self.attrs.name) + ? self.attrs.name + : self.elm.attr('name'); + + var form = getElementParentForm(elmName, self); // find the parent form (only found if it has a name) + var index = arrayFindObjectIndex(_validationSummary, 'field', elmName); // find index of object in our array + + // if message is empty, remove it from the validation summary + if (index >= 0 && message === '') { + _validationSummary.splice(index, 1); + } else if (message !== '') { + var friendlyName = (!!self.attrs && !!self.friendlyName) ? $translate.instant(self.friendlyName) : ''; + var errorObj = { field: elmName, friendlyName: friendlyName, message: message, formName: (!!form) ? form.$name : null }; + + // if error already exist then refresh the error object inside the array, else push it to the array + if (index >= 0) { + _validationSummary[index] = errorObj; + } else { + _validationSummary.push(errorObj); + } + } + + // save validation summary into scope root + self.scope.$validationSummary = _validationSummary; + + // and also save it inside the current scope form (if found) + if (!!form) { + // since validationSummary contain errors of all forms + // we need to find only the errors of current form and them into the current scope form object + form.$validationSummary = arrayFindObjects(_validationSummary, 'formName', form.$name); + } + + // also save it inside the ControllerAs alias if it was passed in the global options + if (!!_globalOptions && !!_globalOptions.controllerAs) { + _globalOptions.controllerAs.$validationSummary = _validationSummary; + + // also save it inside controllerAs form (if found) + if (!!form) { + var formName = form.$name.indexOf('.') >= 0 ? form.$name.split('.')[1] : form.$name; + var ctrlForm = (!!_globalOptions.controllerAs[formName]) ? _globalOptions.controllerAs[formName] : self.elm.controller()[formName]; + ctrlForm.$validationSummary = arrayFindObjects(_validationSummary, 'formName', form.$name); + } + } + + return _validationSummary; + } + /** Define our validation object * @return object self */ @@ -614,12 +674,11 @@ angular isValid = true; } else { // before running Regex test, we'll make sure that an input of type="number" doesn't hold invalid keyboard chars, if true skip Regex - if (typeof strValue === "string" && strValue === "" && self.elm.prop('type').toUpperCase() === "NUMBER") { + if (typeof strValue === "string" && strValue === "" && !!self.elm.prop('type') && self.elm.prop('type').toUpperCase() === "NUMBER") { isValid = false; - //message = $translate.instant("INVALID_KEY_CHAR"); } else { // run the Regex test through each iteration, if required (\S+) and is null then it's invalid automatically - regex = new RegExp(validator.pattern); + regex = new RegExp(validator.pattern, validator.patternFlag); isValid = ((!validator.pattern || validator.pattern.toString() === "/\\S+/" || (!!rules && validator.pattern === "required")) && strValue === null) ? false : regex.test(strValue); } } @@ -640,7 +699,11 @@ angular msgToTranslate = validator.altText.replace("alt=", ""); } - $translate(msgToTranslate).then(function (translation) { + var trsltPromise = $translate(msgToTranslate); + formElmObj.translatePromise = trsltPromise; + formElmObj.validator = validator; + + trsltPromise.then(function (translation) { // if user is requesting to see only the last error message, we will use '=' instead of usually concatenating with '+=' // then if validator rules has 'params' filled, then replace them inside the translation message (foo{0} {1}...), same syntax as String.format() in C# if (message.length > 0 && _globalOptions.displayOnlyLastErrorMsg) { @@ -716,6 +779,11 @@ angular // trim any white space message = message.trim(); + // if validation is cancelled, then erase error message + if(!!formElmObj && formElmObj.isValidationCancelled === true) { + message = ''; + } + // log the invalid message in the $validationSummary addToValidationSummary(formElmObj, message); @@ -744,65 +812,6 @@ angular } } - /** Add the error to the validation summary - * @param object self - * @param string message: error message - */ - function addToValidationSummary(self, message) { - if (typeof self === "undefined" || self == null) { - return; - } - - // get the element name, whichever we find it - var elmName = (!!self.ctrl && !!self.ctrl.$name) - ? self.ctrl.$name - : (!!self.attrs && !!self.attrs.name) - ? self.attrs.name - : self.elm.attr('name'); - - var form = getElementParentForm(elmName, self); // find the parent form (only found if it has a name) - var index = arrayFindObjectIndex(_validationSummary, 'field', elmName); // find index of object in our array - - // if message is empty, remove it from the validation summary - if (index >= 0 && message === '') { - _validationSummary.splice(index, 1); - } else if (message !== '') { - var friendlyName = (!!self.attrs && !!self.friendlyName) ? $translate.instant(self.friendlyName) : ''; - var errorObj = { field: elmName, friendlyName: friendlyName, message: message, formName: (!!form) ? form.$name : null }; - - // if error already exist then refresh the error object inside the array, else push it to the array - if (index >= 0) { - _validationSummary[index] = errorObj; - } else { - _validationSummary.push(errorObj); - } - } - - // save validation summary into scope root - self.scope.$validationSummary = _validationSummary; - - // and also save it inside the current scope form (if found) - if (!!form) { - // since validationSummary contain errors of all forms - // we need to find only the errors of current form and them into the current scope form object - form.$validationSummary = arrayFindObjects(_validationSummary, 'formName', form.$name); - } - - // also save it inside the ControllerAs alias if it was passed in the global options - if (!!_globalOptions && !!_globalOptions.controllerAs) { - _globalOptions.controllerAs.$validationSummary = _validationSummary; - - // also save it inside controllerAs form (if found) - if (!!form) { - var formName = form.$name.indexOf('.') >= 0 ? form.$name.split('.')[1] : form.$name; - var ctrlForm = (!!_globalOptions.controllerAs[formName]) ? _globalOptions.controllerAs[formName] : self.elm.controller()[formName]; - ctrlForm.$validationSummary = arrayFindObjects(_validationSummary, 'formName', form.$name); - } - } - - return _validationSummary; - } - /** Quick function to find an object inside an array by it's given field name and value, return the object found or null * @param Array sourceArray * @param string searchId: search property id @@ -877,6 +886,13 @@ angular * @return object scope form */ function getElementParentForm(elmName, self) { + // get the parentForm directly by it's formName if it was passed in the global options + if(!!_globalOptions && !!_globalOptions.formName) { + var parentForm = document.querySelector('[name="'+_globalOptions.formName+'"]'); + parentForm.$name = _globalOptions.formName; // make sure it has a $name, since we use that variable later on + return parentForm; + } + // from the element passed, get his parent form var forms = document.getElementsByName(elmName); var parentForm = null; diff --git a/src/validation-directive.js b/src/validation-directive.js index 8092a10..5831fe7 100644 --- a/src/validation-directive.js +++ b/src/validation-directive.js @@ -12,16 +12,26 @@ */ angular .module('ghiscoding.validation', ['pascalprecht.translate']) - .directive('validation', ['$timeout', 'validationCommon', function($timeout, validationCommon) { + .directive('validation', ['$q', '$timeout', 'validationCommon', function($q, $timeout, validationCommon) { return { restrict: "A", require: "ngModel", link: function(scope, elm, attrs, ctrl) { // create an object of the common validation var commonObj = new validationCommon(scope, elm, attrs, ctrl); - var timer; - var blurHandler; - var isValidationCancelled = false; + var _arrayErrorMessage = ''; + var _promises = []; + var _timer; + var _watchers = []; + + // Possible element attributesW + var _elmName = attrs.name; + + //-- Possible validation-array attributes + // on a validation array, how many does it require to be valid? + // As soon as 'one' is valid, or does it need 'all' array values to make the input valid? + var _validArrayRequireHowMany = (attrs.hasOwnProperty('validArrayRequireHowMany')) ? attrs.validArrayRequireHowMany : "one"; + var _validationArrayObjprop = (attrs.hasOwnProperty('validationArrayObjprop')) ? attrs.validationArrayObjprop : null; // construct the functions, it's just to make the code cleaner and put the functions at bottom var construct = { @@ -29,12 +39,15 @@ cancelValidation : cancelValidation } - // attach the attemptToValidate function to the element - // wrap the calls into a $evalAsync so that if falls at the end of the $digest, because other tool like Bootstrap UI might interfere with our validation - scope.$evalAsync(function() { - ctrl.$formatters.unshift(attemptToValidate); - ctrl.$parsers.unshift(attemptToValidate); - }); + // watch the element for any value change, validate it once that happen + var validationWatcher = scope.$watch(function() { + return ctrl.$modelValue; + }, function(newValue, oldValue) { + attemptToValidate(newValue); + }, true); + + // save the watcher inside an array in case we want to deregister it when removing a validator + _watchers.push({ elmName: _elmName, watcherHandler: validationWatcher}); // watch the `disabled` attribute for changes // if it become disabled then skip validation else it becomes enable then we need to revalidate it @@ -42,7 +55,7 @@ if (disabled) { // Turn off validation when element is disabled & remove it from validation summary cancelValidation(); - commonObj.removeFromValidationSummary(attrs.name); + commonObj.removeFromValidationSummary(_elmName); } else { // revalidate & re-attach the onBlur event revalidateAndAttachOnBlur(); @@ -54,7 +67,7 @@ cancelAndUnbindValidation(); }); - // watch for a validation becoming empty, if that is the case, unbind everything from it + // watch for a validation attribute changing to empty, if that is the case, unbind everything from it scope.$watch(function() { return elm.attr('validation'); }, function(validation) { @@ -70,21 +83,9 @@ } }); - // onBlur make validation without waiting + // attach the onBlur event handler on the element elm.bind('blur', blurHandler); - function blurHandler(event) { - // get the form element custom object and use it after - var formElmObj = commonObj.getFormElementByName(ctrl.$name); - - if (!formElmObj.isValidationCancelled) { - // validate without delay - attemptToValidate(event.target.value, 10); - }else { - ctrl.$setValidity('validation', true); - } - } - //---- // Private functions declaration //---------------------------------- @@ -92,22 +93,52 @@ /** Validator function to attach to the element, this will get call whenever the input field is updated * and is also customizable through the (typing-limit) for which inactivity this.timer will trigger validation. * @param string value: value of the input field + * @param int typingLimit: when user stop typing, in how much time it will start validating */ function attemptToValidate(value, typingLimit) { + var deferred = $q.defer(); + var isValid = false; + // get the waiting delay time if passed as argument or get it from common Object var waitingLimit = (typeof typingLimit !== "undefined") ? typingLimit : commonObj.typingLimit; // get the form element custom object and use it after var formElmObj = commonObj.getFormElementByName(ctrl.$name); + // if the input value is an array (like a 3rd party addons) then attempt to validate + // by exploding the array into individual input values and then validate them one value by one + if(Array.isArray(value)) { + // reset the promises array + _promises = []; + _arrayErrorMessage = ''; + waitingLimit = 0; + + // If we get a filled array, we will explode the array and try to validate each input value independently and + // Else when array is or become empty, we still want to validate it but without waiting time, + // a "required" validation needs to be invalid right away. + // NOTE: because most 3rd party addons support AngularJS older than 1.3, the $touched property on the element is most often not implemented + // but is required for Angular-Validation to properly display error messages and I have to force a $setTouched and by doing so force to show error message instantly on screen. + // unless someone can figure out a better approach that is universal to all addons. I could in fact also use $dirty but even that is most often not implement by adons either. + if(value.length > 0) { + // make the element as it was touched for CSS, only works in AngularJS 1.3+ + if (typeof formElmObj.ctrl.$setTouched === "function") { + formElmObj.ctrl.$setTouched(); + } + return explodeArrayAndAttemptToValidate(value, typeof value); + }else { + waitingLimit = 0; + } + } + // pre-validate without any events just to pre-fill our validationSummary with all field errors - // passing false as 2nd argument for not showing any errors on screen + // passing False as the 2nd argument to hide errors from being displayed on screen commonObj.validate(value, false); // if field is not required and his value is empty, cancel validation and exit out if(!commonObj.isFieldRequired() && (value === "" || value === null || typeof value === "undefined")) { cancelValidation(); - return value; + deferred.resolve({ isFieldValid: true, formElmObj: formElmObj, value: value }); + return deferred.promise; }else if(!!formElmObj) { formElmObj.isValidationCancelled = false; } @@ -119,40 +150,159 @@ // if a field holds invalid characters which are not numbers inside an `input type="number"`, then it's automatically invalid // we will still call the `.validate()` function so that it shows also the possible other error messages - if((value === "" || typeof value === "undefined") && elm.prop('type').toUpperCase() === "NUMBER") { - $timeout.cancel(timer); - ctrl.$setValidity('validation', commonObj.validate(value, true)); - return value; + if((value === "" || typeof value === "undefined") && !!elm.prop('type') && elm.prop('type').toUpperCase() === "NUMBER") { + $timeout.cancel(_timer); + isValid = commonObj.validate(value, true); + ctrl.$setValidity('validation', isValid); + deferred.resolve({ isFieldValid: isValid, formElmObj: formElmObj, value: value }); + return deferred.promise; } // select(options) will be validated on the spot if(elm.prop('tagName').toUpperCase() === "SELECT") { - ctrl.$setValidity('validation', commonObj.validate(value, true)); - return value; + isValid = commonObj.validate(value, true); + ctrl.$setValidity('validation', isValid); + deferred.resolve({ isFieldValid: isValid, formElmObj: formElmObj, value: value }); + return deferred.promise; } // onKeyDown event is the default of Angular, no need to even bind it, it will fall under here anyway // in case the field is already pre-filled, we need to validate it without looking at the event binding if(typeof value !== "undefined") { - // Make the validation only after the user has stopped activity on a field - // everytime a new character is typed, it will cancel/restart the timer & we'll erase any error mmsg - commonObj.updateErrorMsg(''); - $timeout.cancel(timer); - timer = $timeout(function() { - scope.$evalAsync(ctrl.$setValidity('validation', commonObj.validate(value, true) )); - }, waitingLimit); + // when no timer, validate right away without a $timeout. This seems quite important on the array input value check + if(typingLimit === 0) { + isValid = commonObj.validate(value, true); + scope.$evalAsync(ctrl.$setValidity('validation', isValid )); + deferred.resolve({ isFieldValid: isValid, formElmObj: formElmObj, value: value }); + }else { + // Start validation only after the user has stopped typing in a field + // everytime a new character is typed, it will cancel/restart the timer & we'll erase any error mmsg + commonObj.updateErrorMsg(''); + $timeout.cancel(_timer); + _timer = $timeout(function() { + isValid = commonObj.validate(value, true); + scope.$evalAsync(ctrl.$setValidity('validation', isValid )); + deferred.resolve({ isFieldValid: isValid, formElmObj: formElmObj, value: value }); + }, waitingLimit); + } } - return value; + return deferred.promise; } // attemptToValidate() + /** Attempt to validate an input value that was previously exploded fromn the input array + * Each attempt will return a promise but only after reaching the last index, will we analyze the final validation. + * @param string: input value + * @param int: position index + * @param int: size of original array + */ + function attemptToValidateArrayInput(inputValue, index, arraySize) { + var promise = attemptToValidate(inputValue, 0); + if(!!promise && typeof promise.then === "function") { + _promises.push(promise); + + // if we reached the last index + // then loop through all promises to run validation on each array input values + if(parseInt(index) === (arraySize - 1)) { + _promises.forEach(function(promise) { + promise.then(function(result) { + // user requires how many values of the array to make the form input to be valid? + // If require "one", as soon as an array value changes is valid, the complete input becomes valid + // If require "all", as soon as an array value changes is invalid, the complete input becomes invalid + switch(_validArrayRequireHowMany) { + case "all" : + if(result.isFieldValid === false) { + var globalOptions = commonObj.getGlobalOptions(); + result.formElmObj.translatePromise.then(function(translation) { + // if user is requesting to see only the last error message, we will use '=' instead of usually concatenating with '+=' + // then if validator rules has 'params' filled, then replace them inside the translation message (foo{0} {1}...), same syntax as String.format() in C# + if (_arrayErrorMessage.length > 0 && globalOptions.displayOnlyLastErrorMsg) { + _arrayErrorMessage = '[' + result.value + '] :: ' + ((!!result.formElmObj.validator && !!result.formElmObj.validator.params) ? String.format(translation, result.formElmObj.validator.params) : translation); + } else { + _arrayErrorMessage += ' [' + result.value + '] :: ' + ((!!result.formElmObj.validator && !!result.formElmObj.validator.params) ? String.format(translation, result.formElmObj.validator.params) : translation); + } + commonObj.updateErrorMsg(_arrayErrorMessage, { isValid: false }); + commonObj.addToValidationSummary(result.formElmObj, _arrayErrorMessage); + }); + } + break; + case "one" : + default : + if(result.isFieldValid === true) { + ctrl.$setValidity('validation', true); + cancelValidation(); + } + } + }); + }); + } + } + } + + /** Definition of our blur event handler that will be used to attached on an element + * @param object event + */ + function blurHandler(event) { + // get the form element custom object and use it after + var formElmObj = commonObj.getFormElementByName(ctrl.$name); + var value = (typeof event.target.value !== "undefined") ? event.target.value : ctrl.$modelValue; + + if (!formElmObj.isValidationCancelled) { + attemptToValidate(value, 10); + }else { + ctrl.$setValidity('validation', true); + } + } + + /** Explode the input array and attempt to validate every single input values that comes out of it. + * Input array could be passed as 2 different types (array of string, array of objects) + * If we are dealing with an array of strings, we will consider the strings being the input value to validate + * But if instead it is an array of objects, we need to user to provide which object property name to use + * ex. : array of objects, var arrObj = [{ id: 1, label: 'tag1' }, { id: 2, label: 'tag2' }] + * --> we want the user to tell the directive that the input values are in the property name 'label' + * @param Array: input array + * @param string: array type + */ + function explodeArrayAndAttemptToValidate(inputArray, arrayType) { + var arraySize = inputArray.length; + + // array of strings, 1 for loop to get all input values + if(arrayType === "string") { + for (var key in inputArray) { + attemptToValidateArrayInput(inputArray[key], key, arraySize); + } + } + // array of objects, 2 for loops to get all input values via an object property name defined by the user + else if(arrayType === "object") { + for (var key in inputArray) { + if (inputArray.hasOwnProperty(key)) { + var obj = inputArray[key]; + for (var prop in obj) { + // check if there's a property on the object, compare it to what the user defined as the object property label + // then attempt to validate the array input value + if(obj.hasOwnProperty(prop)) { + if(!!_validationArrayObjprop && prop !== _validationArrayObjprop) { + continue; + } + attemptToValidateArrayInput(obj[prop], key, arraySize); + } + } + } + } + } + } + /** Cancel the validation, unbind onBlur and remove from $validationSummary */ function cancelAndUnbindValidation() { // unbind everything and cancel the validation - ctrl.$formatters.shift(); - ctrl.$parsers.shift(); cancelValidation(); - commonObj.removeFromValidationSummary(attrs.name); + commonObj.removeFromValidationSummary(_elmName); + + // deregister the $watch from the _watchers array we kept it + var foundWatcher = commonObj.arrayFindObject(_watchers, 'elmName', ctrl.$name); + if(!!foundWatcher) { + foundWatcher.watcherHandler(); // deregister the watch by calling his handler + } } /** Cancel current validation test and blank any leftover error message */ @@ -162,7 +312,7 @@ if(!!formElmObj) { formElmObj.isValidationCancelled = true; } - $timeout.cancel(timer); + $timeout.cancel(_timer); commonObj.updateErrorMsg(''); ctrl.$setValidity('validation', true); @@ -176,15 +326,20 @@ function revalidateAndAttachOnBlur() { // Revalidate the input when enabled (without displaying the error) var value = ctrl.$viewValue || ''; - ctrl.$setValidity('validation', commonObj.validate(value, false)); + if(!Array.isArray(value)) { + ctrl.$setValidity('validation', commonObj.validate(value, false)); + } // get the form element custom object and use it after var formElmObj = commonObj.getFormElementByName(ctrl.$name); if(!!formElmObj) { - formElmObj.isValidationCancelled = false; // make sure it's renable validation as well + formElmObj.isValidationCancelled = false; // make sure validation re-enabled as well } - // re-attach the onBlur handler + // unbind previous handler (if any) not to have double handlers and then re-attach just 1 handler + if(typeof blurHandler === "function") { + elm.unbind('blur', blurHandler); + } elm.bind('blur', blurHandler); } diff --git a/src/validation-rules.js b/src/validation-rules.js index a7def03..e7181db 100644 --- a/src/validation-rules.js +++ b/src/validation-rules.js @@ -6,7 +6,7 @@ * @desc: angular-validation rules definition * Each rule objects must have 3 properties {pattern, message, type} * and in some cases you could also define a 4th properties {params} to pass extras, for example: max_len will know his maximum length by this extra {params} - * Rule.type can be {autoDetect, conditionalDate, conditionalNumber, match, regex} + * Rule.type can be {autoDetect, conditionalDate, conditionalNumber, matching, regex} * * WARNING: Rule patterns are defined as String type so don't forget to escape your characters: \\ */ @@ -483,9 +483,10 @@ angular case "in" : case "inList" : case "in_list" : - var list = ruleParams.replace(/,/g, '|'); // replace ',' by '|' + var list = ruleParams.replace(/,/g, '|'); // replace comma (,) by pipe (|) validator = { pattern: "^(\\b(" + list + ")\\b)$", + patternFlag: 'i', message: "INVALID_IN_LIST", params: [ruleParams], type: "regex" @@ -596,9 +597,10 @@ angular case "not_in" : case "notInList" : case "not_in_list" : - var list = ruleParams.replace(/,/g, '|'); // replace ',' by '|' + var list = ruleParams.replace(/,/g, '|'); // replace comma (,) by pipe (|) validator = { pattern: "^((?!\\b(" + list + ")\\b).)+$", + patternFlag: 'i', message: "INVALID_NOT_IN_LIST", params: [ruleParams], type: "regex" diff --git a/src/validation-service.js b/src/validation-service.js index 3002f65..1a8c5ab 100644 --- a/src/validation-service.js +++ b/src/validation-service.js @@ -124,7 +124,7 @@ angular }); // watch the element for any value change, validate it once that happen - var watcherHandler = scope.$watch(attrs.elmName, function (newVal, oldVal) { + var validationWatcher = scope.$watch(attrs.elmName, function (newVal, oldVal) { // when previous value was set and new value is not, this is most probably an invalid character entered in a type input="text" // we will still call the `.validate()` function so that it shows also the possible other error messages if(newVal === undefined && oldVal !== undefined) { @@ -141,7 +141,7 @@ angular }, true); // $watch() // save the watcher inside an array in case we want to deregister it when removing a validator - _watchers.push({ elmName: attrs.elmName, watcherHandler: watcherHandler}); + _watchers.push({ elmName: attrs.elmName, watcherHandler: validationWatcher}); return self; } // addValidator()