' +
//NOTE: This ins element is used to display the search icon if the node is a container/listview and the tree is currently in dialog
//'
' +
@@ -72,14 +72,13 @@ angular.module("umbraco.directives")
//set the padding
.css("padding-left", (node.level * 20) + "px");
- //remove first 'ins' if there is no children
- //show/hide last 'ins' depending on children
+ //toggle visibility of last 'ins' depending on children
+ //visibility still ensure the space is "reserved", so both nodes with and without children are aligned.
if (!node.hasChildren) {
- element.find("ins:first").remove();
- element.find("ins").last().hide();
+ element.find("ins").last().css("visibility", "hidden");
}
else {
- element.find("ins").last().show();
+ element.find("ins").last().css("visibility", "visible");
}
var icon = element.find("i:first");
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/nodirtycheck.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js
similarity index 100%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/nodirtycheck.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/umbsetdirtyonchange.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js
similarity index 100%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/umbsetdirtyonchange.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valCustom.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js
similarity index 100%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valCustom.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valHighlight.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js
similarity index 97%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valHighlight.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js
index 2afd75eb29..9182441f8b 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valHighlight.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js
@@ -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);
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valcompare.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js
similarity index 97%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valcompare.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js
index 31595273de..1a36dcc24f 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valcompare.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js
@@ -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;
+ });
+ }
+ };
});
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valemail.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js
similarity index 66%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valemail.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js
index 1e81d8edec..8574d01f5a 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valemail.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js
@@ -29,6 +29,16 @@ function valEmail(valEmailExpression) {
}
};
+ //if there is an attribute: type="email" then we need to remove those formatters and parsers
+ if (attrs.type === "email") {
+ //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 +46,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
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valformmanager.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js
similarity index 97%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valformmanager.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js
index 37c0313c45..9a00d5718c 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valformmanager.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js
@@ -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);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js
similarity index 97%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertymsg.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js
index eba308d830..be5da51702 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertymsg.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js
@@ -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: "
{{errorMsg}}
",
-
- /**
- 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: "
{{errorMsg}}
",
+
+ /**
+ 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);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertyvalidator.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js
similarity index 100%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertyvalidator.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js
similarity index 97%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js
index 7bc3c6b877..6406583e77 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js
@@ -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);
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js
similarity index 97%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserver.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js
index 6225485073..1432a713c0 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserver.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js
@@ -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);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserverfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js
similarity index 97%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserverfield.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js
index 46fbaf93dd..1e0d2d8ba5 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserverfield.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js
@@ -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);
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtab.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js
similarity index 97%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtab.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js
index fbca0cd233..8d1fc60083 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtab.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js
@@ -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);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtogglemsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js
similarity index 97%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtogglemsg.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js
index 43792a708a..304f151274 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtogglemsg.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js
@@ -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);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtriggerchange.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtriggerchange.directive.js
similarity index 100%
rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtriggerchange.directive.js
rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valtriggerchange.directive.js
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js
index 6d83c1b907..04194838ab 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js
@@ -109,11 +109,16 @@ function iconHelper($q, $timeout) {
},
formatContentTypeIcons: function (contentTypes) {
for (var i = 0; i < contentTypes.length; i++) {
- contentTypes[i].icon = this.convertFromLegacyIcon(contentTypes[i].icon);
+ if (!contentTypes[i].icon) {
+ //just to be safe (e.g. when focus was on close link and hitting save)
+ contentTypes[i].icon = "icon-document"; // default icon
+ } else {
+ contentTypes[i].icon = this.convertFromLegacyIcon(contentTypes[i].icon);
+ }
//couldnt find replacement
if(contentTypes[i].icon.indexOf(".") > 0){
- contentTypes[i].icon = "icon-document-dashed-line";
+ contentTypes[i].icon = "icon-document-dashed-line";
}
}
return contentTypes;
@@ -128,6 +133,10 @@ function iconHelper($q, $timeout) {
},
/** If the icon is legacy */
isLegacyIcon: function (icon) {
+ if(!icon) {
+ return false;
+ }
+
if(icon.startsWith('..')){
return false;
}
diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js
index 42d50b1ce7..0d3b56991e 100644
--- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js
@@ -19,7 +19,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
{ value: "assets/img/application/logo@3x.png" }
];
$scope.touchDevice = appState.getGlobalState("touchDevice");
-
+
$scope.removeNotification = function (index) {
notificationsService.remove(index);
@@ -28,12 +28,12 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
$scope.closeDialogs = function (event) {
//only close dialogs if non-link and non-buttons are clicked
var el = event.target.nodeName;
- var els = ["INPUT","A","BUTTON"];
+ var els = ["INPUT", "A", "BUTTON"];
- if(els.indexOf(el) >= 0){return;}
+ if (els.indexOf(el) >= 0) { return; }
var parents = $(event.target).parents("a,button");
- if(parents.length > 0){
+ if (parents.length > 0) {
return;
}
@@ -49,31 +49,31 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
var evts = [];
//when a user logs out or timesout
- evts.push(eventsService.on("app.notAuthenticated", function() {
+ evts.push(eventsService.on("app.notAuthenticated", function () {
$scope.authenticated = null;
$scope.user = null;
}));
-
+
//when the app is read/user is logged in, setup the data
evts.push(eventsService.on("app.ready", function (evt, data) {
-
+
$scope.authenticated = data.authenticated;
$scope.user = data.user;
- updateChecker.check().then(function(update){
- if(update && update !== "null"){
- if(update.type !== "None"){
+ updateChecker.check().then(function(update) {
+ if (update && update !== "null") {
+ if (update.type !== "None") {
var notification = {
- headline: "Update available",
- message: "Click to download",
- sticky: true,
- type: "info",
- url: update.url
+ headline: "Update available",
+ message: "Click to download",
+ sticky: true,
+ type: "info",
+ url: update.url
};
notificationsService.add(notification);
}
}
- })
+ });
//if the user has changed we need to redirect to the root so they don't try to continue editing the
//last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true)
@@ -91,38 +91,40 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
if ($scope.user.emailHash) {
//let's attempt to load the avatar, it might not exist or we might not have
- // internet access so we'll detect it first
- $http.get("https://www.gravatar.com/avatar/" + $scope.user.emailHash + ".jpg?s=64&d=404")
+ // internet access, well get an empty string back
+ $http.get(umbRequestHelper.getApiUrl("gravatarApiBaseUrl", "GetCurrentUserGravatarUrl"))
.then(
- function successCallback(response) {
- $("#avatar-img").fadeTo(1000, 0, function () {
- $scope.$apply(function () {
- //this can be null if they time out
- if ($scope.user && $scope.user.emailHash) {
- var avatarBaseUrl = "https://www.gravatar.com/avatar/",
- hash = $scope.user.emailHash;
+ function successCallback(response) {
+ // if we can't download the gravatar for some reason, an null gets returned, we cannot do anything
+ if (response.data !== "null") {
+ $("#avatar-img").fadeTo(1000, 0, function () {
+ $scope.$apply(function () {
+ //this can be null if they time out
+ if ($scope.user && $scope.user.emailHash) {
+ var avatarBaseUrl = "https://www.gravatar.com/avatar/",
+ hash = $scope.user.emailHash;
- $scope.avatar = [
- { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" },
- { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" },
- { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" }
- ];
- }
+ $scope.avatar = [
+ { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" },
+ { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" },
+ { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" }
+ ];
+ }
+ });
+ $("#avatar-img").fadeTo(1000, 1);
});
- $("#avatar-img").fadeTo(1000, 1);
- });
+ }
}, function errorCallback(response) {
//cannot load it from the server so we cannot do anything
});
}
-
}));
- evts.push(eventsService.on("app.ysod", function(name, error) {
+ evts.push(eventsService.on("app.ysod", function (name, error) {
$scope.ysodOverlay = {
view: "ysod",
error: error,
- show: true
+ show: true
};
}));
diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less
index 0d41eed050..fb809af015 100644
--- a/src/Umbraco.Web.UI.Client/src/less/hacks.less
+++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less
@@ -63,9 +63,13 @@ iframe, .content-column-body {
/*tree legacy icon*/
-.legacy-custom-file{
- width: 16px; height: 16px; margin-right: 11px; display: inline-block;
+.legacy-custom-file {
+ width: 16px;
+ height: 16px;
+ min-width: 20px; /* this ensure the icon takes up same space as font-icon (20px) */
+ display: inline-block;
background-position: center center;
+ background-repeat: no-repeat;
}
/*
diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less
index 6805783a86..892977180e 100644
--- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less
+++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less
@@ -13,7 +13,9 @@
left: 0;
margin: 0 !important;
padding: 0;
+ border: none;
border-radius: 0;
+ overflow-y: auto;
}
@@ -50,7 +52,7 @@
}
.login-overlay .form {
- position:fixed;
+ position:relative;
display: block;
top: 100px;
left: 165px;
@@ -78,11 +80,30 @@
margin-top: 10px;
}
+@media (max-width: 767px) and (max-height: 420px) and (orientation: landscape) {
+ // Move form closer to top on narrow screen sizes
+ .login-overlay .form {
+ top: 50px;
+ }
+}
+
@media (max-width: 565px) {
// Remove padding on login-form on smaller devices
.login-overlay .form {
+ top: 60px;
+ right: 25px;
left: inherit;
- right:25px;
+ padding-left: 25px;
+ padding-right:25px;
+ width: auto;
+ }
+}
+
+@media (max-width: 339px) {
+ .login-overlay .form {
+ input[type="text"], input[type="password"] {
+ width: 250px;
+ }
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less
index a06484b8a0..85a1d9e36c 100644
--- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less
+++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less
@@ -201,6 +201,15 @@ ul.color-picker li a {
margin: 5px;
background: white;
border: 1px solid #f8f8f8;
+
+ max-width: 100%;
+}
+
+
+.umb-mediapicker .umb-sortable-thumbnails li {
+ flex-direction: column;
+ margin: 0;
+ padding: 5px;
}
@@ -289,7 +298,7 @@ ul.color-picker li a {
}
.umb-cropper img {
- max-width: initial;
+ max-width: none;
}
.umb-cropper .overlay, .umb-cropper-gravity .overlay {
@@ -384,6 +393,7 @@ ul.color-picker li a {
}
.umb-cropper-gravity .viewport, .umb-cropper-gravity, .umb-cropper-imageholder {
display: inline-block;
+ max-width: 100%;
}
.umb-cropper-imageholder {
diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less
index c2db58a99e..c89be83a10 100644
--- a/src/Umbraco.Web.UI.Client/src/less/sections.less
+++ b/src/Umbraco.Web.UI.Client/src/less/sections.less
@@ -22,6 +22,8 @@ ul.sections li [class^="icon-"]:before,
ul.sections li [class*=" icon-"]:before,
ul.sections li img.icon-section {
font-size: 30px;
+ line-height: 20px; /* set line-height to ensure all icons use same line-height */
+ display: inline-block;
margin: 1px 0 0 0;
opacity: 0.4;
-webkit-transition: all .3s linear;
diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less
index 35de441137..d809418f2c 100644
--- a/src/Umbraco.Web.UI.Client/src/less/tree.less
+++ b/src/Umbraco.Web.UI.Client/src/less/tree.less
@@ -155,6 +155,11 @@
display: flex;
}
+.umb-tree li > div:hover a:not(.umb-options) {
+ overflow: hidden;
+ margin-right: 6px;
+}
+
.umb-tree .icon {
vertical-align: middle;
margin: 0 13px 0 0;
@@ -203,7 +208,9 @@
content: "\e165";
}
-.umb-tree .umb-tree-node-checked i {
+.umb-tree .umb-tree-node-checked i[class^="icon-"],
+.umb-tree .umb-tree-node-checked i[class*=" icon-"] {
+ font-family: 'icomoon' !important;
color:@blue !important;
}
.umb-tree .umb-tree-node-checked i:before {
@@ -480,7 +487,6 @@ div.locked:before{
width:100%;
height:1px;
overflow:hidden;
-
position: absolute;
left: 0;
bottom: 0;
@@ -498,15 +504,18 @@ div.locked:before{
/*body.touch .umb-tree .icon{font-size: 19px;}*/
body.touch .umb-tree ins{font-size: 14px; visibility: visible; padding: 7px;}
-body.touch .umb-tree li div {
+body.touch .umb-tree li > div {
padding-top: 8px;
padding-bottom: 8px;
font-size: 110%;
}
-body.touch .umb-actions a{
- padding: 7px 25px 7px 20px;
- font-size: 110%;
+// change height of this if touch devices should have a different height of preloader.
+body.touch .umb-tree li div.l div {
+ padding: 0;
}
-body.touch a.umb-options i {margin-top: 20px;}
+body.touch .umb-actions a {
+ padding: 7px 25px 7px 20px;
+ font-size: 110%;
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.controller.js
index de71977ebe..ec1ad6e663 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.controller.js
@@ -7,13 +7,14 @@ angular.module("umbraco")
$scope.icons = icons;
});
- $scope.submitClass = function(icon){
- if($scope.color)
- {
+ $scope.submitClass = function (icon) {
+ if($scope.color) {
$scope.submit(icon + " " + $scope.color);
- }else{
- $scope.submit(icon);
+ }
+ else {
+ $scope.submit(icon);
}
};
+
}
);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html
index 260d8ff85c..96ab990447 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html
@@ -4,10 +4,11 @@
+ placeholder="Filter..."
+ no-dirty-check>
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/notifications/confirmroutechange.html b/src/Umbraco.Web.UI.Client/src/views/common/notifications/confirmroutechange.html
index 80942baaeb..0b2926307c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/notifications/confirmroutechange.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/notifications/confirmroutechange.html
@@ -1,7 +1,7 @@