1+ "use strict" ;
2+
3+ /**
4+ * @ngdoc directive
5+ * @name bsValidation
6+ * @requires $interpolate
7+ * @requires BsValidationService
8+ * @description
9+ * This directive must be applied to every input element on which we need to use custom validations like jQuery.
10+ * Those element must have ng-model attributes. This directive will automatically add the "has-error" class on the
11+ * parent element with class ".form-group" and will show/hide the validation message automatically.
12+ *
13+ * Custom supported validations are:
14+ *
15+ * 1. For number, add attribute number instead of type number in input class (for int, float & double),
16+ * 2. For digits, add attribute digits (only for int),
17+ * 3. For minimum length validation, use HTML5 minlength attribute (Will work for non HTML5 browsers),
18+ * 4. For maximum length validation, use HTML5 maxlength attribute (Will work for non HTML5 browsers),
19+ * 5. For max number validation, use HTML5 max attribute,
20+ * 6. For min number validation, use HTML5 min attribute,
21+ * 7. For required validation, use HTML5 required attribute.
22+ *
23+ * On any validation error, a default message will be shown to users, which can be customized dynamically with the
24+ * element itself. Hence this will work great for Angular UI validation.
25+ */
26+ angular . module ( "bootstrap.angular.validation" ) . directive ( "bsValidation" , [ "$interpolate" , "BsValidationService" , function ( $interpolate , bsValidationService ) {
27+ return {
28+ restrict : "A" ,
29+ require : "ngModel" ,
30+ link : function ( $scope , $element , $attr , ngModelController ) {
31+ // All classed needed to add to validation message
32+ var errorClasses = [ "validation-error" , "help-block" ] ;
33+ var searchableClasses = errorClasses . join ( "," ) ;
34+ var markupClasses = errorClasses . join ( " " ) ;
35+
36+ // Search parent element with class form-group to operate on.
37+ var formGroupElement = $element . parents ( ".form-group" ) ;
38+
39+ /*
40+ * Used to resolve the message for current validation failure. Will first search the title attribute
41+ * with the validation key and then fallback to use the default message.
42+ */
43+ function resolveMessage ( element , key ) {
44+ var message = element . attr ( "title-" + key ) ;
45+ if ( ! message ) {
46+ message = bsValidationService . getDefaultMessage ( key ) ;
47+
48+ if ( ! message ) {
49+ console . warn ( "Not found any message for the key" , key ) ;
50+ return "Please fix the error." ;
51+ }
52+ }
53+
54+ // Format the string if it contains any variable. Like <code>String.format()</code> in Java.
55+ return $interpolate ( message ) ( { validValue : $attr [ key ] } ) ;
56+ }
57+
58+ /**
59+ * Display or hide validation errors for a single element.
60+ */
61+ function displayOrHideError ( ) {
62+ /*
63+ * Do not show or hide error for current element don"t have any validation errors or has validation
64+ * error but user has not attempted to submit the form yet.
65+ */
66+ if ( ! $scope . formSubmissionAttempted || ! ngModelController . $invalid ) {
67+ formGroupElement . removeClass ( "has-error" ) ;
68+
69+ if ( formGroupElement . length > 0 ) {
70+ formGroupElement . findAll ( "span." + errorClasses . join ( "." ) ) . remove ( ) ;
71+ }
72+ return false ;
73+ }
74+
75+ var oneErrorDisplayed = false ;
76+
77+ /**
78+ * Iterate through each error for the current element & display only the first error.
79+ * For multiple validation, this $error object will be like this:
80+ *
81+ * $error = {
82+ * required: true, // When field is marked as required but not entered.
83+ * minlength: true,
84+ * number: false
85+ * }
86+ */
87+ angular . forEach ( ngModelController . $error , function ( value , key ) {
88+ if ( value && ! oneErrorDisplayed ) {
89+ // Add bootstrap error class
90+ formGroupElement . addClass ( "has-error" ) ;
91+
92+ var message = resolveMessage ( $element , key ) ;
93+ // Find if the parent class already have error message container.
94+ var errorElement = formGroupElement . findOne ( "span." + searchableClasses ) ;
95+ var iconMarkup = "<i class=\"fa fa-exclamation-triangle fa-fw\"></i>" ;
96+
97+ // If not, then append an error container
98+ if ( errorElement . length === 0 ) {
99+ var insertAfter = $element ;
100+ // Check if the container have any Bootstrap input group then append the error after it
101+ var groupElement = formGroupElement . findOne ( ".input-group" ) ;
102+ if ( groupElement . length > 0 ) {
103+ insertAfter = groupElement ;
104+ }
105+ var errorMarkup = "<span class=\"" + markupClasses + "\">" + iconMarkup + message + "</span>" ;
106+ insertAfter . after ( errorMarkup ) ;
107+ } else {
108+ // Else change the message.
109+ errorElement . html ( iconMarkup + message ) ;
110+ }
111+
112+ // Mark that, first error is displayed. TODO Can use a much cleaner solution.
113+ oneErrorDisplayed = true ;
114+ }
115+ } ) ;
116+ }
117+
118+ var validators = [ "equalTo" , "minlength" , "maxlength" , "min" , "max" , "number" , "digits" , "length" ] ;
119+ // Register generic custom validators if added to element
120+ angular . forEach ( validators , function ( key ) {
121+ var attrValue = $element . attr ( key ) ;
122+ if ( attrValue || ( typeof attrValue !== "undefined" && attrValue !== false ) || $attr [ key ] ) {
123+ bsValidationService . addValidator ( $scope , $attr , ngModelController , key ) ;
124+ }
125+ } ) ;
126+
127+ $scope . $watch ( $attr . ngModel , function ( ) {
128+ displayOrHideError ( ) ;
129+ } ) ;
130+
131+ // Look when user try to submit the form & display validation errors.
132+ $scope . $watch ( "formSubmissionAttempted" , function ( ) {
133+ displayOrHideError ( ) ;
134+ } ) ;
135+ }
136+ } ;
137+ } ] ) ;
0 commit comments