11/**
2- * angular-validation - v1.0 - 2014-02-07
2+ * angular-validation - v1.1 - 2014-05-02
33 * https://github.com/ghiscoding/angular-validation
44 * @author : Ghislain B.
55 *
66 * @desc : If a field becomes invalid, the text inside the error <span> or <div> will show up because the error string gets filled
77 * Though when the field becomes valid then the error message becomes an empty string,
88 * it will be transparent to the user even though the <span> still exist but becomes invisible since the text is empty.
9+ *
10+ * Version 1.1: only start validating after user inactivity,
11+ * it could also be passed as an argument for more customization of the inactivity timeout (typing-limit)
912 */
1013 angular . module ( 'ghiscoding.validation' , [ 'pascalprecht.translate' ] )
11- . directive ( 'validation' , function ( $translate ) {
14+ . directive ( 'validation' , function ( $translate , $timeout ) {
1215 return {
1316 require : "ngModel" ,
1417 link : function ( scope , elm , attrs , ctrl ) {
15- // default validation event that triggers the validation error to be displayed
16- var DEFAULT_EVENT = "keyup" ; // keyup, blur, ...
18+ // constant of default typing limit in ms
19+ var TYPING_LIMIT = 1000 ;
1720
18- // get the validation attribute
21+ var timer ;
22+ var typingLimit = ( attrs . hasOwnProperty ( 'typingLimit' ) ) ? parseInt ( attrs . typingLimit , 10 ) : TYPING_LIMIT ;
23+
24+ // get the validation attributes which are the list of validators
1925 var validationAttr = attrs . validation ;
2026
2127 // define the variables we'll use
113119 condition : [ ">=" , "<=" ] ,
114120 message : "INVALID_BETWEEN_NUM" ,
115121 params : [ range [ 0 ] , range [ 1 ] ] ,
116- type : "condition "
122+ type : "condition_num "
117123 } ;
118124 break ;
119125 case "creditCard" :
217223 type : "regex"
218224 } ;
219225 break ;
226+ case "ipv4" :
227+ validators [ i ] = {
228+ pattern : "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$" ,
229+ message : "INVALID_IPV4" ,
230+ type : "regex"
231+ } ;
232+ break ;
233+ case "ipv6" :
234+ validators [ i ] = {
235+ pattern : "^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$" ,
236+ message : "INVALID_IPV6" ,
237+ type : "regex"
238+ } ;
239+ break ;
240+ case "ipv6_hex" :
241+ validators [ i ] = {
242+ pattern : "^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$" ,
243+ message : "INVALID_IPV6_HEX" ,
244+ type : "regex"
245+ } ;
246+ break ;
220247 case "maxLen" :
221248 case "max_len" :
222249 validators [ i ] = {
231258 condition : "<=" ,
232259 message : "INVALID_MAX_NUM" ,
233260 params : [ params [ 1 ] ] ,
234- type : "condition "
261+ type : "condition_num "
235262 } ;
236263 break ;
237264 case "minLen" :
248275 condition : ">=" ,
249276 message : "INVALID_MIN_NUM" ,
250277 params : [ params [ 1 ] ] ,
251- type : "condition "
278+ type : "condition_num "
252279 } ;
253280 break ;
254281 case "numeric" :
289316 type : "regex"
290317 } ;
291318 break ;
292- }
293- }
294- }
319+ case "time" :
320+ validators [ i ] = {
321+ pattern : "^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$" ,
322+ message : "INVALID_TIME" ,
323+ type : "regex"
324+ } ;
325+ break ;
326+
327+ } // end of switch()
328+ } // end of for()
329+ } // end of if()
295330
296331 /** Validate function, from the input value it will go through all validators (separated by pipe)
297332 * that were passed to the input element and will validate it. If field is invalid it will update
306341
307342 // loop through all validations (could be multiple)
308343 for ( var j = 0 , jln = validators . length ; j < jln ; j ++ ) {
309- if ( validators [ j ] . type === "condition " ) {
344+ if ( validators [ j ] . type === "condition_num " ) {
310345 // a condition type
311346 if ( validators [ j ] . params . length == 2 ) {
312- // typically a between condition, a range of number >= and <=
347+ // typically a " between" condition, a range of number >= and <=
313348 var isValid1 = testCondition ( validators [ j ] . condition [ 0 ] , parseFloat ( strValue ) , parseFloat ( validators [ j ] . params [ 0 ] ) ) ;
314349 var isValid2 = testCondition ( validators [ j ] . condition [ 1 ] , parseFloat ( strValue ) , parseFloat ( validators [ j ] . params [ 1 ] ) ) ;
315350 isValid = ( isValid1 && isValid2 ) ? true : false ;
331366 message = message . replace ( ( ':param' ) , validators [ j ] . params [ k ] ) ;
332367 }
333368 }
334-
335369 } // end !isValid
336- } // end for loop
370+ } // end for() loop
337371
338372 // -- Error Display --//
339- updateErrorMsg ( isFieldValid , message ) ;
340-
373+ updateErrorMsg ( message , isFieldValid ) ;
341374
342375 return isFieldValid ;
343376 }
375408 * @param bool isFieldValid: is the field valid?
376409 * @param string message: error message to display
377410 */
378- var updateErrorMsg = function ( isFieldValid , message ) {
379- var errorElm = ( typeof attrs . validationErrorTo !== "undefined" )
411+ var updateErrorMsg = function ( message , isFieldValid ) {
412+ var hasValidation = ( typeof isFieldValid === "undefined" ) ? false : true ;
413+ var errorElm = ( attrs . hasOwnProperty ( 'validationErrorTo' ) )
380414 ? angular . element ( document . querySelector ( '#' + attrs . validationErrorTo ) )
381415 : elm . next ( ) ;
382416
383417 // Re-Render Error display element inside the <span> or <div>
384418 if ( typeof errorElm !== "undefined" ) {
385- if ( ! isFieldValid && ctrl . $dirty ) {
419+ if ( hasValidation && ! isFieldValid && ctrl . $dirty ) {
386420 // Not valid & dirty, display the message
387421 errorElm . text ( message ) ;
388422 } else {
389- // element is prestine, error message has to be blank
423+ // element is prestine or there's no validation applied , error message has to be blank
390424 errorElm . text ( "" ) ;
391425 }
392426 }
393427 }
394428
395429 /** Validator function to attach to the element, this will get call whenever the input field is updated
396- * and is also customizable through the (validation-event) which can be (onblur).
397- * If no event is specified, it will validate (onkeyup) as a default action.
430+ * and is also customizable through the (typing-limit) for which inactivity timer will trigger validation.
398431 * @param string value: value of the input field
399432 */
400433 var validator = function ( value ) {
401434 // if field is not required and his value is empty
402- // then no need to validate & return it valid
435+ // then no need to validate & return it has valid
436+ // make sure to unbind any events else it will try to continue validating
403437 if ( ! isFieldRequired && ( value === "" || typeof value === "undefined" ) ) {
404- var isFieldValid = true ;
405- ctrl . $setValidity ( 'validation' , isFieldValid ) ;
406- updateErrorMsg ( isFieldValid , "" ) ;
407- elm . unbind ( 'keyup' ) . unbind ( 'keydown' ) ;
438+ $timeout . cancel ( timer ) ;
439+ updateErrorMsg ( "" ) ;
440+ ctrl . $setValidity ( 'validation' , true ) ;
441+ elm . unbind ( 'keyup' ) . unbind ( 'keydown' ) . unbind ( 'blur' ) ;
408442 return value ;
409443 }
410444
411- // analyze which event we'll use, if nothing was defined then use default
412- // also remove prefix substring of 'on' since we don't need it on the 'on' method
413- var evnt = ( typeof attrs . validationEvent === "undefined" ) ? DEFAULT_EVENT : attrs . validationEvent ;
414- evnt = evnt . replace ( 'on' , '' ) ; // remove possible 'on' prefix
415-
416445 // get some properties of the inspected element
417446 var elmTagName = elm . prop ( 'tagName' ) . toUpperCase ( ) ;
418447 var elmType = elm . prop ( 'type' ) . toUpperCase ( ) ;
437466 } ) ;
438467 }
439468
440- // Also make sure that if user has a select dropdown
441- // then we'll validate it has if it was a onBlur event
442- // since onKeyUp would fail has there would never be any keyup
443- if ( elmTagName === "SELECT" ) {
444- if ( isFieldRequired && ( value === "" || typeof value === "undefined" ) ) {
445- // if select option is null or empty string we already know it's invalid
446- // but we'll still run validation() to display proper error message
447- ctrl . $setValidity ( 'validation' , validate ( value ) ) ;
448- elm . unbind ( 'blur' ) ;
449- return value ;
450- }
451- // else we'll make sure we use an onBlur event to check validation
452- evnt = "blur" ;
453- }
454-
455- // invalidate field before doing validation
469+ // invalidate field before doing any validation
456470 ctrl . $setValidity ( 'validation' , false ) ;
457471
472+ // onBlur make validation without waiting
473+ elm . bind ( 'blur' , function ( ) {
474+ // make the regular validation of the field value
475+ var isValid = validate ( value ) ;
476+ scope . $apply ( ctrl . $setValidity ( 'validation' , isValid ) ) ;
477+ return value ;
478+ } ) ;
479+
480+ // onKeyDown event is the default of Angular, no need to even bind it, it will fall under here anyway
458481 // in case the field is already pre-filled
459482 // we need to validate it without looking at the event binding
460483 if ( value !== "" && typeof value !== "undefined" ) {
461- var isValid = validate ( value ) ;
462- ctrl . $setValidity ( 'validation' , isValid ) ;
484+ // Make the validation only after the user has stopped activity on a field
485+ // everytime a new character is typed, it will cancel/restart the timer & we<ll erase any error mmsg
486+ updateErrorMsg ( "" ) ;
487+ $timeout . cancel ( timer ) ;
488+ timer = $timeout ( function ( ) {
489+ var isValid = validate ( value ) ;
490+ ctrl . $setValidity ( 'validation' , isValid ) ;
491+ } , typingLimit ) ;
463492 }
464493
465- // run the validate method on the event
466- // update the validation on both the field & form element
467- elm . unbind ( 'keyup' ) . unbind ( evnt ) . bind ( evnt , function ( ) {
468- // make the regular validation of the field value
469- var isValid = validate ( value ) ;
470- scope . $apply ( ctrl . $setValidity ( 'validation' , isValid ) ) ;
471- } ) ;
472-
473494 return value ;
474- } ;
495+ } ; // end of validator()
475496
476497 // attach the Validator object to the element
477498 ctrl . $parsers . unshift ( validator ) ;
488509 ctrl . $setValidity ( 'validation' , validate ( ctrl . $viewValue ) ) ;
489510 }
490511 } ) ;
491- }
492- } ;
493- } ) ;
512+
513+ } // end of link: function()
514+ } ; // end of return;
515+ } ) ; // end of directive
0 commit comments