U4-8445 Validation on the Email field for Members will not allow the entry of root domains greater than four characters.

This commit is contained in:
Shannon
2016-05-24 15:04:46 +02:00
parent c902649768
commit 3ea01b7dab
15 changed files with 748 additions and 741 deletions

View File

@@ -1,28 +1,28 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:valHighlight
* @restrict A
* @description Used on input fields when you want to signal that they are in error, this will highlight the item for 1 second
**/
function valHighlight($timeout) {
return {
restrict: "A",
link: function (scope, element, attrs, ctrl) {
attrs.$observe("valHighlight", function (newVal) {
if (newVal === "true") {
element.addClass("highlight-error");
$timeout(function () {
//set the bound scope property to false
scope[attrs.valHighlight] = false;
}, 1000);
}
else {
element.removeClass("highlight-error");
}
});
}
};
}
angular.module('umbraco.directives.validation').directive("valHighlight", valHighlight);
/**
* @ngdoc directive
* @name umbraco.directives.directive:valHighlight
* @restrict A
* @description Used on input fields when you want to signal that they are in error, this will highlight the item for 1 second
**/
function valHighlight($timeout) {
return {
restrict: "A",
link: function (scope, element, attrs, ctrl) {
attrs.$observe("valHighlight", function (newVal) {
if (newVal === "true") {
element.addClass("highlight-error");
$timeout(function () {
//set the bound scope property to false
scope[attrs.valHighlight] = false;
}, 1000);
}
else {
element.removeClass("highlight-error");
}
});
}
};
}
angular.module('umbraco.directives.validation').directive("valHighlight", valHighlight);

View File

@@ -1,24 +1,24 @@
angular.module('umbraco.directives.validation')
.directive('valCompare',function () {
return {
require: "ngModel",
link: function (scope, elem, attrs, ctrl) {
//TODO: Pretty sure this should be done using a requires ^form in the directive declaration
var otherInput = elem.inheritedData("$formController")[attrs.valCompare];
ctrl.$parsers.push(function(value) {
if(value === otherInput.$viewValue) {
ctrl.$setValidity("valCompare", true);
return value;
}
ctrl.$setValidity("valCompare", false);
});
otherInput.$parsers.push(function(value) {
ctrl.$setValidity("valCompare", value === ctrl.$viewValue);
return value;
});
}
};
angular.module('umbraco.directives.validation')
.directive('valCompare',function () {
return {
require: "ngModel",
link: function (scope, elem, attrs, ctrl) {
//TODO: Pretty sure this should be done using a requires ^form in the directive declaration
var otherInput = elem.inheritedData("$formController")[attrs.valCompare];
ctrl.$parsers.push(function(value) {
if(value === otherInput.$viewValue) {
ctrl.$setValidity("valCompare", true);
return value;
}
ctrl.$setValidity("valCompare", false);
});
otherInput.$parsers.push(function(value) {
ctrl.$setValidity("valCompare", value === ctrl.$viewValue);
return value;
});
}
};
});

View File

@@ -29,6 +29,12 @@ function valEmail(valEmailExpression) {
}
};
//we need to remove the existing parsers = the default angular one which is created by
// type="email", but this has a regex issue, so we'll remove that and add our custom one
ctrl.$parsers.pop();
//we also need to remove the existing formatter - the default angular one will not render
// what it thinks is an invalid email address, so it will just be blank
ctrl.$formatters.pop();
ctrl.$parsers.push(patternValidator);
}
};
@@ -36,7 +42,8 @@ function valEmail(valEmailExpression) {
angular.module('umbraco.directives.validation')
.directive("valEmail", valEmail)
.factory('valEmailExpression', function() {
.factory('valEmailExpression', function () {
//NOTE: This is the fixed regex which is part of the newer angular
return {
EMAIL_REGEXP: /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i
};

View File

@@ -1,133 +1,133 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:valFormManager
* @restrict A
* @require formController
* @description Used to broadcast an event to all elements inside this one to notify that form validation has
* changed. If we don't use this that means you have to put a watch for each directive on a form's validation
* changing which would result in much higher processing. We need to actually watch the whole $error collection of a form
* because just watching $valid or $invalid doesn't acurrately trigger form validation changing.
* This also sets the show-validation (or a custom) css class on the element when the form is invalid - this lets
* us css target elements to be displayed when the form is submitting/submitted.
* Another thing this directive does is to ensure that any .control-group that contains form elements that are invalid will
* be marked with the 'error' css class. This ensures that labels included in that control group are styled correctly.
**/
function valFormManager(serverValidationManager, $rootScope, $log, $timeout, notificationsService, eventsService, $routeParams) {
return {
require: "form",
restrict: "A",
controller: function($scope) {
//This exposes an API for direct use with this directive
var unsubscribe = [];
var self = this;
//This is basically the same as a directive subscribing to an event but maybe a little
// nicer since the other directive can use this directive's API instead of a magical event
this.onValidationStatusChanged = function (cb) {
unsubscribe.push($scope.$on("valStatusChanged", function(evt, args) {
cb.apply(self, [evt, args]);
}));
};
//Ensure to remove the event handlers when this instance is destroyted
$scope.$on('$destroy', function () {
for (var u in unsubscribe) {
unsubscribe[u]();
}
});
},
link: function (scope, element, attr, formCtrl) {
scope.$watch(function () {
return formCtrl.$error;
}, function (e) {
scope.$broadcast("valStatusChanged", { form: formCtrl });
//find all invalid elements' .control-group's and apply the error class
var inError = element.find(".control-group .ng-invalid").closest(".control-group");
inError.addClass("error");
//find all control group's that have no error and ensure the class is removed
var noInError = element.find(".control-group .ng-valid").closest(".control-group").not(inError);
noInError.removeClass("error");
}, true);
var className = attr.valShowValidation ? attr.valShowValidation : "show-validation";
var savingEventName = attr.savingEvent ? attr.savingEvent : "formSubmitting";
var savedEvent = attr.savedEvent ? attr.savingEvent : "formSubmitted";
//This tracks if the user is currently saving a new item, we use this to determine
// if we should display the warning dialog that they are leaving the page - if a new item
// is being saved we never want to display that dialog, this will also cause problems when there
// are server side validation issues.
var isSavingNewItem = false;
//we should show validation if there are any msgs in the server validation collection
if (serverValidationManager.items.length > 0) {
element.addClass(className);
}
var unsubscribe = [];
//listen for the forms saving event
unsubscribe.push(scope.$on(savingEventName, function(ev, args) {
element.addClass(className);
//set the flag so we can check to see if we should display the error.
isSavingNewItem = $routeParams.create;
}));
//listen for the forms saved event
unsubscribe.push(scope.$on(savedEvent, function(ev, args) {
//remove validation class
element.removeClass(className);
//clear form state as at this point we retrieve new data from the server
//and all validation will have cleared at this point
formCtrl.$setPristine();
}));
//This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but
// the form has pending changes
var locationEvent = $rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) {
if (!formCtrl.$dirty || isSavingNewItem) {
return;
}
var path = nextLocation.split("#")[1];
if (path) {
if (path.indexOf("%253") || path.indexOf("%252")) {
path = decodeURIComponent(path);
}
if (!notificationsService.hasView()) {
var msg = { view: "confirmroutechange", args: { path: path, listener: locationEvent } };
notificationsService.add(msg);
}
//prevent the route!
event.preventDefault();
//raise an event
eventsService.emit("valFormManager.pendingChanges", true);
}
});
unsubscribe.push(locationEvent);
//Ensure to remove the event handler when this instance is destroyted
scope.$on('$destroy', function() {
for (var u in unsubscribe) {
unsubscribe[u]();
}
});
$timeout(function(){
formCtrl.$setPristine();
}, 1000);
}
};
}
/**
* @ngdoc directive
* @name umbraco.directives.directive:valFormManager
* @restrict A
* @require formController
* @description Used to broadcast an event to all elements inside this one to notify that form validation has
* changed. If we don't use this that means you have to put a watch for each directive on a form's validation
* changing which would result in much higher processing. We need to actually watch the whole $error collection of a form
* because just watching $valid or $invalid doesn't acurrately trigger form validation changing.
* This also sets the show-validation (or a custom) css class on the element when the form is invalid - this lets
* us css target elements to be displayed when the form is submitting/submitted.
* Another thing this directive does is to ensure that any .control-group that contains form elements that are invalid will
* be marked with the 'error' css class. This ensures that labels included in that control group are styled correctly.
**/
function valFormManager(serverValidationManager, $rootScope, $log, $timeout, notificationsService, eventsService, $routeParams) {
return {
require: "form",
restrict: "A",
controller: function($scope) {
//This exposes an API for direct use with this directive
var unsubscribe = [];
var self = this;
//This is basically the same as a directive subscribing to an event but maybe a little
// nicer since the other directive can use this directive's API instead of a magical event
this.onValidationStatusChanged = function (cb) {
unsubscribe.push($scope.$on("valStatusChanged", function(evt, args) {
cb.apply(self, [evt, args]);
}));
};
//Ensure to remove the event handlers when this instance is destroyted
$scope.$on('$destroy', function () {
for (var u in unsubscribe) {
unsubscribe[u]();
}
});
},
link: function (scope, element, attr, formCtrl) {
scope.$watch(function () {
return formCtrl.$error;
}, function (e) {
scope.$broadcast("valStatusChanged", { form: formCtrl });
//find all invalid elements' .control-group's and apply the error class
var inError = element.find(".control-group .ng-invalid").closest(".control-group");
inError.addClass("error");
//find all control group's that have no error and ensure the class is removed
var noInError = element.find(".control-group .ng-valid").closest(".control-group").not(inError);
noInError.removeClass("error");
}, true);
var className = attr.valShowValidation ? attr.valShowValidation : "show-validation";
var savingEventName = attr.savingEvent ? attr.savingEvent : "formSubmitting";
var savedEvent = attr.savedEvent ? attr.savingEvent : "formSubmitted";
//This tracks if the user is currently saving a new item, we use this to determine
// if we should display the warning dialog that they are leaving the page - if a new item
// is being saved we never want to display that dialog, this will also cause problems when there
// are server side validation issues.
var isSavingNewItem = false;
//we should show validation if there are any msgs in the server validation collection
if (serverValidationManager.items.length > 0) {
element.addClass(className);
}
var unsubscribe = [];
//listen for the forms saving event
unsubscribe.push(scope.$on(savingEventName, function(ev, args) {
element.addClass(className);
//set the flag so we can check to see if we should display the error.
isSavingNewItem = $routeParams.create;
}));
//listen for the forms saved event
unsubscribe.push(scope.$on(savedEvent, function(ev, args) {
//remove validation class
element.removeClass(className);
//clear form state as at this point we retrieve new data from the server
//and all validation will have cleared at this point
formCtrl.$setPristine();
}));
//This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but
// the form has pending changes
var locationEvent = $rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) {
if (!formCtrl.$dirty || isSavingNewItem) {
return;
}
var path = nextLocation.split("#")[1];
if (path) {
if (path.indexOf("%253") || path.indexOf("%252")) {
path = decodeURIComponent(path);
}
if (!notificationsService.hasView()) {
var msg = { view: "confirmroutechange", args: { path: path, listener: locationEvent } };
notificationsService.add(msg);
}
//prevent the route!
event.preventDefault();
//raise an event
eventsService.emit("valFormManager.pendingChanges", true);
}
});
unsubscribe.push(locationEvent);
//Ensure to remove the event handler when this instance is destroyted
scope.$on('$destroy', function() {
for (var u in unsubscribe) {
unsubscribe[u]();
}
});
$timeout(function(){
formCtrl.$setPristine();
}, 1000);
}
};
}
angular.module('umbraco.directives.validation').directive("valFormManager", valFormManager);

View File

@@ -1,196 +1,196 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:valPropertyMsg
* @restrict A
* @element textarea
* @requires formController
* @description This directive is used to control the display of the property level validation message.
* We will listen for server side validation changes
* and when an error is detected for this property we'll show the error message.
* In order for this directive to work, the valStatusChanged directive must be placed on the containing form.
**/
function valPropertyMsg(serverValidationManager) {
return {
scope: {
property: "="
},
require: "^form", //require that this directive is contained within an ngForm
replace: true, //replace the element with the template
restrict: "E", //restrict to element
template: "<div ng-show=\"errorMsg != ''\" class='alert alert-error property-error' >{{errorMsg}}</div>",
/**
Our directive requries a reference to a form controller
which gets passed in to this parameter
*/
link: function (scope, element, attrs, formCtrl) {
var watcher = null;
// Gets the error message to display
function getErrorMsg() {
//this can be null if no property was assigned
if (scope.property) {
//first try to get the error msg from the server collection
var err = serverValidationManager.getPropertyError(scope.property.alias, "");
//if there's an error message use it
if (err && err.errorMsg) {
return err.errorMsg;
}
else {
return scope.property.propertyErrorMessage ? scope.property.propertyErrorMessage : "Property has errors";
}
}
return "Property has errors";
}
// We need to subscribe to any changes to our model (based on user input)
// This is required because when we have a server error we actually invalidate
// the form which means it cannot be resubmitted.
// So once a field is changed that has a server error assigned to it
// we need to re-validate it for the server side validator so the user can resubmit
// the form. Of course normal client-side validators will continue to execute.
function startWatch() {
//if there's not already a watch
if (!watcher) {
watcher = scope.$watch("property.value", function (newValue, oldValue) {
if (!newValue || angular.equals(newValue, oldValue)) {
return;
}
var errCount = 0;
for (var e in formCtrl.$error) {
if (angular.isArray(formCtrl.$error[e])) {
errCount++;
}
}
//we are explicitly checking for valServer errors here, since we shouldn't auto clear
// based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg
// is the only one, then we'll clear.
if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
scope.errorMsg = "";
formCtrl.$setValidity('valPropertyMsg', true);
stopWatch();
}
}, true);
}
}
//clear the watch when the property validator is valid again
function stopWatch() {
if (watcher) {
watcher();
watcher = null;
}
}
//if there's any remaining errors in the server validation service then we should show them.
var showValidation = serverValidationManager.items.length > 0;
var hasError = false;
//create properties on our custom scope so we can use it in our template
scope.errorMsg = "";
var unsubscribe = [];
//listen for form error changes
unsubscribe.push(scope.$on("valStatusChanged", function(evt, args) {
if (args.form.$invalid) {
//first we need to check if the valPropertyMsg validity is invalid
if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) {
//since we already have an error we'll just return since this means we've already set the
// hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe
return;
}
else if (element.closest(".umb-control-group").find(".ng-invalid").length > 0) {
//check if it's one of the properties that is invalid in the current content property
hasError = true;
//update the validation message if we don't already have one assigned.
if (showValidation && scope.errorMsg === "") {
scope.errorMsg = getErrorMsg();
}
}
else {
hasError = false;
scope.errorMsg = "";
}
}
else {
hasError = false;
scope.errorMsg = "";
}
}, true));
//listen for the forms saving event
unsubscribe.push(scope.$on("formSubmitting", function(ev, args) {
showValidation = true;
if (hasError && scope.errorMsg === "") {
scope.errorMsg = getErrorMsg();
}
else if (!hasError) {
scope.errorMsg = "";
stopWatch();
}
}));
//listen for the forms saved event
unsubscribe.push(scope.$on("formSubmitted", function(ev, args) {
showValidation = false;
scope.errorMsg = "";
formCtrl.$setValidity('valPropertyMsg', true);
stopWatch();
}));
//listen for server validation changes
// NOTE: we pass in "" in order to listen for all validation changes to the content property, not for
// validation changes to fields in the property this is because some server side validators may not
// return the field name for which the error belongs too, just the property for which it belongs.
// It's important to note that we need to subscribe to server validation changes here because we always must
// indicate that a content property is invalid at the property level since developers may not actually implement
// the correct field validation in their property editors.
if (scope.property) { //this can be null if no property was assigned
serverValidationManager.subscribe(scope.property.alias, "", function (isValid, propertyErrors, allErrors) {
hasError = !isValid;
if (hasError) {
//set the error message to the server message
scope.errorMsg = propertyErrors[0].errorMsg;
//flag that the current validator is invalid
formCtrl.$setValidity('valPropertyMsg', false);
startWatch();
}
else {
scope.errorMsg = "";
//flag that the current validator is valid
formCtrl.$setValidity('valPropertyMsg', true);
stopWatch();
}
});
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
// but they are a different callback instance than the above.
element.bind('$destroy', function () {
stopWatch();
serverValidationManager.unsubscribe(scope.property.alias, "");
});
}
//when the scope is disposed we need to unsubscribe
scope.$on('$destroy', function () {
for (var u in unsubscribe) {
unsubscribe[u]();
}
});
}
};
}
/**
* @ngdoc directive
* @name umbraco.directives.directive:valPropertyMsg
* @restrict A
* @element textarea
* @requires formController
* @description This directive is used to control the display of the property level validation message.
* We will listen for server side validation changes
* and when an error is detected for this property we'll show the error message.
* In order for this directive to work, the valStatusChanged directive must be placed on the containing form.
**/
function valPropertyMsg(serverValidationManager) {
return {
scope: {
property: "="
},
require: "^form", //require that this directive is contained within an ngForm
replace: true, //replace the element with the template
restrict: "E", //restrict to element
template: "<div ng-show=\"errorMsg != ''\" class='alert alert-error property-error' >{{errorMsg}}</div>",
/**
Our directive requries a reference to a form controller
which gets passed in to this parameter
*/
link: function (scope, element, attrs, formCtrl) {
var watcher = null;
// Gets the error message to display
function getErrorMsg() {
//this can be null if no property was assigned
if (scope.property) {
//first try to get the error msg from the server collection
var err = serverValidationManager.getPropertyError(scope.property.alias, "");
//if there's an error message use it
if (err && err.errorMsg) {
return err.errorMsg;
}
else {
return scope.property.propertyErrorMessage ? scope.property.propertyErrorMessage : "Property has errors";
}
}
return "Property has errors";
}
// We need to subscribe to any changes to our model (based on user input)
// This is required because when we have a server error we actually invalidate
// the form which means it cannot be resubmitted.
// So once a field is changed that has a server error assigned to it
// we need to re-validate it for the server side validator so the user can resubmit
// the form. Of course normal client-side validators will continue to execute.
function startWatch() {
//if there's not already a watch
if (!watcher) {
watcher = scope.$watch("property.value", function (newValue, oldValue) {
if (!newValue || angular.equals(newValue, oldValue)) {
return;
}
var errCount = 0;
for (var e in formCtrl.$error) {
if (angular.isArray(formCtrl.$error[e])) {
errCount++;
}
}
//we are explicitly checking for valServer errors here, since we shouldn't auto clear
// based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg
// is the only one, then we'll clear.
if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
scope.errorMsg = "";
formCtrl.$setValidity('valPropertyMsg', true);
stopWatch();
}
}, true);
}
}
//clear the watch when the property validator is valid again
function stopWatch() {
if (watcher) {
watcher();
watcher = null;
}
}
//if there's any remaining errors in the server validation service then we should show them.
var showValidation = serverValidationManager.items.length > 0;
var hasError = false;
//create properties on our custom scope so we can use it in our template
scope.errorMsg = "";
var unsubscribe = [];
//listen for form error changes
unsubscribe.push(scope.$on("valStatusChanged", function(evt, args) {
if (args.form.$invalid) {
//first we need to check if the valPropertyMsg validity is invalid
if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) {
//since we already have an error we'll just return since this means we've already set the
// hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe
return;
}
else if (element.closest(".umb-control-group").find(".ng-invalid").length > 0) {
//check if it's one of the properties that is invalid in the current content property
hasError = true;
//update the validation message if we don't already have one assigned.
if (showValidation && scope.errorMsg === "") {
scope.errorMsg = getErrorMsg();
}
}
else {
hasError = false;
scope.errorMsg = "";
}
}
else {
hasError = false;
scope.errorMsg = "";
}
}, true));
//listen for the forms saving event
unsubscribe.push(scope.$on("formSubmitting", function(ev, args) {
showValidation = true;
if (hasError && scope.errorMsg === "") {
scope.errorMsg = getErrorMsg();
}
else if (!hasError) {
scope.errorMsg = "";
stopWatch();
}
}));
//listen for the forms saved event
unsubscribe.push(scope.$on("formSubmitted", function(ev, args) {
showValidation = false;
scope.errorMsg = "";
formCtrl.$setValidity('valPropertyMsg', true);
stopWatch();
}));
//listen for server validation changes
// NOTE: we pass in "" in order to listen for all validation changes to the content property, not for
// validation changes to fields in the property this is because some server side validators may not
// return the field name for which the error belongs too, just the property for which it belongs.
// It's important to note that we need to subscribe to server validation changes here because we always must
// indicate that a content property is invalid at the property level since developers may not actually implement
// the correct field validation in their property editors.
if (scope.property) { //this can be null if no property was assigned
serverValidationManager.subscribe(scope.property.alias, "", function (isValid, propertyErrors, allErrors) {
hasError = !isValid;
if (hasError) {
//set the error message to the server message
scope.errorMsg = propertyErrors[0].errorMsg;
//flag that the current validator is invalid
formCtrl.$setValidity('valPropertyMsg', false);
startWatch();
}
else {
scope.errorMsg = "";
//flag that the current validator is valid
formCtrl.$setValidity('valPropertyMsg', true);
stopWatch();
}
});
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
// but they are a different callback instance than the above.
element.bind('$destroy', function () {
stopWatch();
serverValidationManager.unsubscribe(scope.property.alias, "");
});
}
//when the scope is disposed we need to unsubscribe
scope.$on('$destroy', function () {
for (var u in unsubscribe) {
unsubscribe[u]();
}
});
}
};
}
angular.module('umbraco.directives.validation').directive("valPropertyMsg", valPropertyMsg);

View File

@@ -1,78 +1,78 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:valRegex
* @restrict A
* @description A custom directive to allow for matching a value against a regex string.
* NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string
**/
function valRegex() {
return {
require: 'ngModel',
restrict: "A",
link: function (scope, elm, attrs, ctrl) {
var flags = "";
var regex;
var eventBindings = [];
attrs.$observe("valRegexFlags", function (newVal) {
if (newVal) {
flags = newVal;
}
});
attrs.$observe("valRegex", function (newVal) {
if (newVal) {
try {
var resolved = newVal;
if (resolved) {
regex = new RegExp(resolved, flags);
}
else {
regex = new RegExp(attrs.valRegex, flags);
}
}
catch (e) {
regex = new RegExp(attrs.valRegex, flags);
}
}
});
eventBindings.push(scope.$watch('ngModel', function(newValue, oldValue){
if(newValue && newValue !== oldValue) {
patternValidator(newValue);
}
}));
var patternValidator = function (viewValue) {
if (regex) {
//NOTE: we don't validate on empty values, use required validator for that
if (!viewValue || regex.test(viewValue.toString())) {
// it is valid
ctrl.$setValidity('valRegex', true);
//assign a message to the validator
ctrl.errorMsg = "";
return viewValue;
}
else {
// it is invalid, return undefined (no model update)
ctrl.$setValidity('valRegex', false);
//assign a message to the validator
ctrl.errorMsg = "Value is invalid, it does not match the correct pattern";
return undefined;
}
}
};
scope.$on('$destroy', function(){
// unbind watchers
for(var e in eventBindings) {
eventBindings[e]();
}
});
}
};
}
angular.module('umbraco.directives.validation').directive("valRegex", valRegex);
/**
* @ngdoc directive
* @name umbraco.directives.directive:valRegex
* @restrict A
* @description A custom directive to allow for matching a value against a regex string.
* NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string
**/
function valRegex() {
return {
require: 'ngModel',
restrict: "A",
link: function (scope, elm, attrs, ctrl) {
var flags = "";
var regex;
var eventBindings = [];
attrs.$observe("valRegexFlags", function (newVal) {
if (newVal) {
flags = newVal;
}
});
attrs.$observe("valRegex", function (newVal) {
if (newVal) {
try {
var resolved = newVal;
if (resolved) {
regex = new RegExp(resolved, flags);
}
else {
regex = new RegExp(attrs.valRegex, flags);
}
}
catch (e) {
regex = new RegExp(attrs.valRegex, flags);
}
}
});
eventBindings.push(scope.$watch('ngModel', function(newValue, oldValue){
if(newValue && newValue !== oldValue) {
patternValidator(newValue);
}
}));
var patternValidator = function (viewValue) {
if (regex) {
//NOTE: we don't validate on empty values, use required validator for that
if (!viewValue || regex.test(viewValue.toString())) {
// it is valid
ctrl.$setValidity('valRegex', true);
//assign a message to the validator
ctrl.errorMsg = "";
return viewValue;
}
else {
// it is invalid, return undefined (no model update)
ctrl.$setValidity('valRegex', false);
//assign a message to the validator
ctrl.errorMsg = "Value is invalid, it does not match the correct pattern";
return undefined;
}
}
};
scope.$on('$destroy', function(){
// unbind watchers
for(var e in eventBindings) {
eventBindings[e]();
}
});
}
};
}
angular.module('umbraco.directives.validation').directive("valRegex", valRegex);

View File

@@ -1,94 +1,94 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:valServer
* @restrict A
* @description This directive is used to associate a content property with a server-side validation response
* so that the validators in angular are updated based on server-side feedback.
**/
function valServer(serverValidationManager) {
return {
require: ['ngModel', '?^umbProperty'],
restrict: "A",
link: function (scope, element, attr, ctrls) {
var modelCtrl = ctrls[0];
var umbPropCtrl = ctrls.length > 1 ? ctrls[1] : null;
if (!umbPropCtrl) {
//we cannot proceed, this validator will be disabled
return;
}
var watcher = null;
//Need to watch the value model for it to change, previously we had subscribed to
//modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that
// doesn't specifically have a 2 way ng binding. This is required because when we
// have a server error we actually invalidate the form which means it cannot be
// resubmitted. So once a field is changed that has a server error assigned to it
// we need to re-validate it for the server side validator so the user can resubmit
// the form. Of course normal client-side validators will continue to execute.
function startWatch() {
//if there's not already a watch
if (!watcher) {
watcher = scope.$watch(function () {
return modelCtrl.$modelValue;
}, function (newValue, oldValue) {
if (!newValue || angular.equals(newValue, oldValue)) {
return;
}
if (modelCtrl.$invalid) {
modelCtrl.$setValidity('valServer', true);
stopWatch();
}
}, true);
}
}
function stopWatch() {
if (watcher) {
watcher();
watcher = null;
}
}
var currentProperty = umbPropCtrl.property;
//default to 'value' if nothing is set
var fieldName = "value";
if (attr.valServer) {
fieldName = scope.$eval(attr.valServer);
if (!fieldName) {
//eval returned nothing so just use the string
fieldName = attr.valServer;
}
}
//subscribe to the server validation changes
serverValidationManager.subscribe(currentProperty.alias, fieldName, function (isValid, propertyErrors, allErrors) {
if (!isValid) {
modelCtrl.$setValidity('valServer', false);
//assign an error msg property to the current validator
modelCtrl.errorMsg = propertyErrors[0].errorMsg;
startWatch();
}
else {
modelCtrl.$setValidity('valServer', true);
//reset the error message
modelCtrl.errorMsg = "";
stopWatch();
}
});
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
// but they are a different callback instance than the above.
element.bind('$destroy', function () {
stopWatch();
serverValidationManager.unsubscribe(currentProperty.alias, fieldName);
});
}
};
}
/**
* @ngdoc directive
* @name umbraco.directives.directive:valServer
* @restrict A
* @description This directive is used to associate a content property with a server-side validation response
* so that the validators in angular are updated based on server-side feedback.
**/
function valServer(serverValidationManager) {
return {
require: ['ngModel', '?^umbProperty'],
restrict: "A",
link: function (scope, element, attr, ctrls) {
var modelCtrl = ctrls[0];
var umbPropCtrl = ctrls.length > 1 ? ctrls[1] : null;
if (!umbPropCtrl) {
//we cannot proceed, this validator will be disabled
return;
}
var watcher = null;
//Need to watch the value model for it to change, previously we had subscribed to
//modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that
// doesn't specifically have a 2 way ng binding. This is required because when we
// have a server error we actually invalidate the form which means it cannot be
// resubmitted. So once a field is changed that has a server error assigned to it
// we need to re-validate it for the server side validator so the user can resubmit
// the form. Of course normal client-side validators will continue to execute.
function startWatch() {
//if there's not already a watch
if (!watcher) {
watcher = scope.$watch(function () {
return modelCtrl.$modelValue;
}, function (newValue, oldValue) {
if (!newValue || angular.equals(newValue, oldValue)) {
return;
}
if (modelCtrl.$invalid) {
modelCtrl.$setValidity('valServer', true);
stopWatch();
}
}, true);
}
}
function stopWatch() {
if (watcher) {
watcher();
watcher = null;
}
}
var currentProperty = umbPropCtrl.property;
//default to 'value' if nothing is set
var fieldName = "value";
if (attr.valServer) {
fieldName = scope.$eval(attr.valServer);
if (!fieldName) {
//eval returned nothing so just use the string
fieldName = attr.valServer;
}
}
//subscribe to the server validation changes
serverValidationManager.subscribe(currentProperty.alias, fieldName, function (isValid, propertyErrors, allErrors) {
if (!isValid) {
modelCtrl.$setValidity('valServer', false);
//assign an error msg property to the current validator
modelCtrl.errorMsg = propertyErrors[0].errorMsg;
startWatch();
}
else {
modelCtrl.$setValidity('valServer', true);
//reset the error message
modelCtrl.errorMsg = "";
stopWatch();
}
});
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
// but they are a different callback instance than the above.
element.bind('$destroy', function () {
stopWatch();
serverValidationManager.unsubscribe(currentProperty.alias, fieldName);
});
}
};
}
angular.module('umbraco.directives.validation').directive("valServer", valServer);

View File

@@ -1,65 +1,65 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:valServerField
* @restrict A
* @description This directive is used to associate a content field (not user defined) with a server-side validation response
* so that the validators in angular are updated based on server-side feedback.
**/
function valServerField(serverValidationManager) {
return {
require: 'ngModel',
restrict: "A",
link: function (scope, element, attr, ctrl) {
var fieldName = null;
var eventBindings = [];
attr.$observe("valServerField", function (newVal) {
if (newVal && fieldName === null) {
fieldName = newVal;
//subscribe to the changed event of the view model. This is required because when we
// have a server error we actually invalidate the form which means it cannot be
// resubmitted. So once a field is changed that has a server error assigned to it
// we need to re-validate it for the server side validator so the user can resubmit
// the form. Of course normal client-side validators will continue to execute.
eventBindings.push(scope.$watch('ngModel', function(newValue){
if (ctrl.$invalid) {
ctrl.$setValidity('valServerField', true);
}
}));
//subscribe to the server validation changes
serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) {
if (!isValid) {
ctrl.$setValidity('valServerField', false);
//assign an error msg property to the current validator
ctrl.errorMsg = fieldErrors[0].errorMsg;
}
else {
ctrl.$setValidity('valServerField', true);
//reset the error message
ctrl.errorMsg = "";
}
});
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
// but they are a different callback instance than the above.
element.bind('$destroy', function () {
serverValidationManager.unsubscribe(null, fieldName);
});
}
});
scope.$on('$destroy', function(){
// unbind watchers
for(var e in eventBindings) {
eventBindings[e]();
}
});
}
};
}
angular.module('umbraco.directives.validation').directive("valServerField", valServerField);
/**
* @ngdoc directive
* @name umbraco.directives.directive:valServerField
* @restrict A
* @description This directive is used to associate a content field (not user defined) with a server-side validation response
* so that the validators in angular are updated based on server-side feedback.
**/
function valServerField(serverValidationManager) {
return {
require: 'ngModel',
restrict: "A",
link: function (scope, element, attr, ctrl) {
var fieldName = null;
var eventBindings = [];
attr.$observe("valServerField", function (newVal) {
if (newVal && fieldName === null) {
fieldName = newVal;
//subscribe to the changed event of the view model. This is required because when we
// have a server error we actually invalidate the form which means it cannot be
// resubmitted. So once a field is changed that has a server error assigned to it
// we need to re-validate it for the server side validator so the user can resubmit
// the form. Of course normal client-side validators will continue to execute.
eventBindings.push(scope.$watch('ngModel', function(newValue){
if (ctrl.$invalid) {
ctrl.$setValidity('valServerField', true);
}
}));
//subscribe to the server validation changes
serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) {
if (!isValid) {
ctrl.$setValidity('valServerField', false);
//assign an error msg property to the current validator
ctrl.errorMsg = fieldErrors[0].errorMsg;
}
else {
ctrl.$setValidity('valServerField', true);
//reset the error message
ctrl.errorMsg = "";
}
});
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
// but they are a different callback instance than the above.
element.bind('$destroy', function () {
serverValidationManager.unsubscribe(null, fieldName);
});
}
});
scope.$on('$destroy', function(){
// unbind watchers
for(var e in eventBindings) {
eventBindings[e]();
}
});
}
};
}
angular.module('umbraco.directives.validation').directive("valServerField", valServerField);

View File

@@ -1,38 +1,38 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:valTab
* @restrict A
* @description Used to show validation warnings for a tab to indicate that the tab content has validations errors in its data.
* In order for this directive to work, the valFormManager directive must be placed on the containing form.
**/
function valTab() {
return {
require: ['^form', '^valFormManager'],
restrict: "A",
link: function (scope, element, attr, ctrs) {
var valFormManager = ctrs[1];
var tabId = "tab" + scope.tab.id;
scope.tabHasError = false;
//listen for form validation changes
valFormManager.onValidationStatusChanged(function (evt, args) {
if (!args.form.$valid) {
var tabContent = element.closest(".umb-panel").find("#" + tabId);
//check if the validation messages are contained inside of this tabs
if (tabContent.find(".ng-invalid").length > 0) {
scope.tabHasError = true;
} else {
scope.tabHasError = false;
}
}
else {
scope.tabHasError = false;
}
});
}
};
}
/**
* @ngdoc directive
* @name umbraco.directives.directive:valTab
* @restrict A
* @description Used to show validation warnings for a tab to indicate that the tab content has validations errors in its data.
* In order for this directive to work, the valFormManager directive must be placed on the containing form.
**/
function valTab() {
return {
require: ['^form', '^valFormManager'],
restrict: "A",
link: function (scope, element, attr, ctrs) {
var valFormManager = ctrs[1];
var tabId = "tab" + scope.tab.id;
scope.tabHasError = false;
//listen for form validation changes
valFormManager.onValidationStatusChanged(function (evt, args) {
if (!args.form.$valid) {
var tabContent = element.closest(".umb-panel").find("#" + tabId);
//check if the validation messages are contained inside of this tabs
if (tabContent.find(".ng-invalid").length > 0) {
scope.tabHasError = true;
} else {
scope.tabHasError = false;
}
}
else {
scope.tabHasError = false;
}
});
}
};
}
angular.module('umbraco.directives.validation').directive("valTab", valTab);

View File

@@ -1,90 +1,90 @@
function valToggleMsg(serverValidationManager) {
return {
require: "^form",
restrict: "A",
/**
Our directive requries a reference to a form controller which gets passed in to this parameter
*/
link: function (scope, element, attr, formCtrl) {
if (!attr.valToggleMsg){
throw "valToggleMsg requires that a reference to a validator is specified";
}
if (!attr.valMsgFor){
throw "valToggleMsg requires that the attribute valMsgFor exists on the element";
}
if (!formCtrl[attr.valMsgFor]) {
throw "valToggleMsg cannot find field " + attr.valMsgFor + " on form " + formCtrl.$name;
}
//if there's any remaining errors in the server validation service then we should show them.
var showValidation = serverValidationManager.items.length > 0;
var hasCustomMsg = element.contents().length > 0;
//add a watch to the validator for the value (i.e. myForm.value.$error.required )
scope.$watch(function () {
//sometimes if a dialog closes in the middle of digest we can get null references here
return (formCtrl && formCtrl[attr.valMsgFor]) ? formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] : null;
}, function () {
//sometimes if a dialog closes in the middle of digest we can get null references here
if ((formCtrl && formCtrl[attr.valMsgFor])) {
if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] && showValidation) {
element.show();
//display the error message if this element has no contents
if (!hasCustomMsg) {
element.html(formCtrl[attr.valMsgFor].errorMsg);
}
}
else {
element.hide();
}
}
});
var unsubscribe = [];
//listen for the saving event (the result is a callback method which is called to unsubscribe)
unsubscribe.push(scope.$on("formSubmitting", function(ev, args) {
showValidation = true;
if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg]) {
element.show();
//display the error message if this element has no contents
if (!hasCustomMsg) {
element.html(formCtrl[attr.valMsgFor].errorMsg);
}
}
else {
element.hide();
}
}));
//listen for the saved event (the result is a callback method which is called to unsubscribe)
unsubscribe.push(scope.$on("formSubmitted", function(ev, args) {
showValidation = false;
element.hide();
}));
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise if this directive is part of a modal, the listener still exists because the dom
// element might still be there even after the modal has been hidden.
element.bind('$destroy', function () {
for (var u in unsubscribe) {
unsubscribe[u]();
}
});
}
};
}
/**
* @ngdoc directive
* @name umbraco.directives.directive:valToggleMsg
* @restrict A
* @element input
* @requires formController
* @description This directive will show/hide an error based on: is the value + the given validator invalid? AND, has the form been submitted ?
**/
function valToggleMsg(serverValidationManager) {
return {
require: "^form",
restrict: "A",
/**
Our directive requries a reference to a form controller which gets passed in to this parameter
*/
link: function (scope, element, attr, formCtrl) {
if (!attr.valToggleMsg){
throw "valToggleMsg requires that a reference to a validator is specified";
}
if (!attr.valMsgFor){
throw "valToggleMsg requires that the attribute valMsgFor exists on the element";
}
if (!formCtrl[attr.valMsgFor]) {
throw "valToggleMsg cannot find field " + attr.valMsgFor + " on form " + formCtrl.$name;
}
//if there's any remaining errors in the server validation service then we should show them.
var showValidation = serverValidationManager.items.length > 0;
var hasCustomMsg = element.contents().length > 0;
//add a watch to the validator for the value (i.e. myForm.value.$error.required )
scope.$watch(function () {
//sometimes if a dialog closes in the middle of digest we can get null references here
return (formCtrl && formCtrl[attr.valMsgFor]) ? formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] : null;
}, function () {
//sometimes if a dialog closes in the middle of digest we can get null references here
if ((formCtrl && formCtrl[attr.valMsgFor])) {
if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] && showValidation) {
element.show();
//display the error message if this element has no contents
if (!hasCustomMsg) {
element.html(formCtrl[attr.valMsgFor].errorMsg);
}
}
else {
element.hide();
}
}
});
var unsubscribe = [];
//listen for the saving event (the result is a callback method which is called to unsubscribe)
unsubscribe.push(scope.$on("formSubmitting", function(ev, args) {
showValidation = true;
if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg]) {
element.show();
//display the error message if this element has no contents
if (!hasCustomMsg) {
element.html(formCtrl[attr.valMsgFor].errorMsg);
}
}
else {
element.hide();
}
}));
//listen for the saved event (the result is a callback method which is called to unsubscribe)
unsubscribe.push(scope.$on("formSubmitted", function(ev, args) {
showValidation = false;
element.hide();
}));
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise if this directive is part of a modal, the listener still exists because the dom
// element might still be there even after the modal has been hidden.
element.bind('$destroy', function () {
for (var u in unsubscribe) {
unsubscribe[u]();
}
});
}
};
}
/**
* @ngdoc directive
* @name umbraco.directives.directive:valToggleMsg
* @restrict A
* @element input
* @requires formController
* @description This directive will show/hide an error based on: is the value + the given validator invalid? AND, has the form been submitted ?
**/
angular.module('umbraco.directives.validation').directive("valToggleMsg", valToggleMsg);