Skip to content

Commit 4ebeccd

Browse files
committed
First version of jQuery link Bootstrap + Angular validation functional.
1 parent 9f41120 commit 4ebeccd

File tree

7 files changed

+461
-1
lines changed

7 files changed

+461
-1
lines changed

bootstrap-angular-validation.iml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
<orderEntry type="inheritedJdk" />
1111
<orderEntry type="sourceFolder" forTests="false" />
1212
<orderEntry type="library" name="bootstrap-angular-validation node_modules" level="project" />
13+
<orderEntry type="library" name="angular.min" level="project" />
1314
</component>
1415
</module>

dist/bootstrap-angular-validation.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,40 @@
1+
/* global document */
2+
13
"use strict";
24

3-
angular.module("bootstrap.angular.validation", []);
5+
angular.module("bootstrap.angular.validation", []).config(["$httpProvider", function ($httpProvider) {
6+
var interceptor = ["$rootScope", "BsValidationService", function($rootScope, bsValidationService) {
7+
return {
8+
"response": function(response) {
9+
/**
10+
* Intercept all ng-included templates in a form and add validation-errors directive. See form
11+
* directive in this component itself.
12+
*/
13+
if (bsValidationService.checkNgIncludedURL(response.config.url)) {
14+
/**
15+
* Empty "div" is being used to temporarily store the received HTML from the server to find out
16+
* all the child form elements to add the "bs-validation" directive. And then later the innerHTML
17+
* i.e. the received HTML can easily be extracted using the html() and the temporary "div" will
18+
* be discarded.
19+
*
20+
* http://stackoverflow.com/a/652771/2405040
21+
*/
22+
var parsedJQLiteElements = angular.element(document.createElement("div"));
23+
parsedJQLiteElements.append(response.data);
24+
bsValidationService.addDirective(parsedJQLiteElements);
25+
26+
/*
27+
* Get the modified HTML template which is now having the "bs-validation" directive.
28+
* (Not iterating over the every jQlite element otherwise it will append a string "undefined"
29+
* for every new empty line in the HTML)
30+
*/
31+
response.data = parsedJQLiteElements.html();
32+
}
33+
34+
return response;
35+
}
36+
};
37+
}];
38+
39+
$httpProvider.interceptors.push(interceptor);
40+
}]);

src/directives/form.directive.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"use strict";
2+
3+
/**
4+
* @ngdoc directive
5+
* @name form
6+
* @requires $parse
7+
* @requires $rootScope
8+
*
9+
* @description
10+
* Using form element as directive, we don't require to put the "bs-validation" directive to every form element.
11+
* To add handler on submit, use <code>on-submit</code> instead of <code>ng-submit</code>,
12+
* since ng-submit directive doesn't cares about validation errors.
13+
*/
14+
angular.module("bootstrap.angular.validation").directive("form", ["$parse", "$rootScope", "BsValidationService",
15+
function($parse, $rootScope, bsValidationService) {
16+
17+
return {
18+
restrict: "E",
19+
require: "form",
20+
priority: 1000, // Setting a higher priority so that, this directive compiles first.
21+
compile: function($formElement) {
22+
// Disable HTML5 validation display
23+
$formElement.attr("novalidate", "novalidate");
24+
bsValidationService.addDirective($formElement);
25+
26+
/**
27+
* If there is an "ng-include" directive available inside a form then the "bs-validation" directive
28+
* won't get applied until Angular resolves the view sourced by "ng-include".
29+
*/
30+
var nestedNgIncludeElement = $formElement.findAll("[ng-include]");
31+
if (nestedNgIncludeElement.length > 0) {
32+
var src = $parse(nestedNgIncludeElement.attr("ng-include"))();
33+
34+
/**
35+
* Then add the source URL of ng-include to a list so that in the response interceptor, we can add
36+
* the "bs-validation" directive. We can do this by recompiling here the content after the content is
37+
* loaded but that leads to some problem and also increases the rendering time.
38+
*
39+
* @see response interceptor in app.js
40+
*/
41+
bsValidationService.addToNgIncludedURLs(src);
42+
}
43+
44+
var preLinkFunction = function($scope, formElement, $attr, formController) {
45+
// Expose a method to manually trigger the validation
46+
formController.$validate = function() {
47+
$scope.formSubmissionAttempted = true;
48+
};
49+
50+
formElement.on("submit", function(e) {
51+
$scope.$apply(function() {
52+
/*
53+
* Notify all "bs-validation" directive that user has tried submitting the form, now we can
54+
* display validation error messages (if any). This is required, since if we immediately
55+
* starts displaying the validation error messages as the form displayed or as the user starts
56+
* typing, that will not a good user experience.
57+
*/
58+
$scope.formSubmissionAttempted = true;
59+
});
60+
61+
// If any of the form element does not pass the validation
62+
if (formController.$invalid) {
63+
// Then focus the first invalid element
64+
formElement[0].querySelector(".ng-invalid").focus();
65+
return false;
66+
}
67+
68+
// Do not show error once the form gets submitted
69+
$scope.formSubmissionAttempted = false;
70+
71+
// Parse the handler of on-submit & execute it
72+
var submitHandler = $parse($attr.onSubmit);
73+
$scope.$apply(function() {
74+
submitHandler($scope, {$event: e});
75+
$scope.formSubmissionAttempted = false;
76+
});
77+
78+
return true;
79+
});
80+
};
81+
82+
return {
83+
pre: preLinkFunction
84+
};
85+
}
86+
};
87+
}]);
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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

Comments
 (0)