diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js
index bd4279a161..63a4dece6e 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js
@@ -11,7 +11,7 @@
function LockedFieldDirective($timeout, localizationService) {
- function link(scope, el, attr, ngModel) {
+ function link(scope, el, attr, ngModel) {
var input = el.children('.umb-locked-field__input');
@@ -22,6 +22,16 @@
scope.locked = true;
}
+ // if regex validation is not defined as an attr set default state
+ // if this is set to an empty string then regex validation can be ignored.
+ if (scope.regexValidation === undefined || scope.regexValidation === null) {
+ scope.regexValidation = "^[a-zA-Z]\\w.*$";
+ }
+
+ if (scope.serverValidationField === undefined || scope.serverValidationField === null) {
+ scope.serverValidationField = "";
+ }
+
// if locked state is not defined as an attr set default state
if (scope.placeholderText === undefined || scope.placeholderText === null) {
scope.placeholderText = "Enter value...";
@@ -70,7 +80,9 @@
scope: {
model: '=ngModel',
locked: "=?",
- placeholderText: "=?"
+ placeholderText: "=?",
+ regexValidation: "=?",
+ serverValidationField: "@"
},
link: link
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js
index a402065708..dac010a97f 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js
@@ -57,7 +57,7 @@ angular.module('umbraco.directives.validation')
}
};
validators[key] = validateFn;
- ctrl.$formatters.push(validateFn);
+
ctrl.$parsers.push(validateFn);
});
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js
index fdcf768947..599cda766c 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js
@@ -9,9 +9,7 @@ function valHighlight($timeout) {
restrict: "A",
link: function (scope, element, attrs, ctrl) {
- scope.$watch(function() {
- return scope.$eval(attrs.valHighlight);
- }, function(newVal, oldVal) {
+ attrs.$observe("valHighlight", function (newVal) {
if (newVal === true) {
element.addClass("highlight-error");
$timeout(function () {
@@ -23,7 +21,7 @@ function valHighlight($timeout) {
element.removeClass("highlight-error");
}
});
-
+
}
};
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js
index 88ffd6f0fa..1e81d8edec 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js
@@ -29,7 +29,6 @@ function valEmail(valEmailExpression) {
}
};
- ctrl.$formatters.push(patternValidator);
ctrl.$parsers.push(patternValidator);
}
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js
index 77652d7f69..53a1ea67b2 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js
@@ -59,9 +59,6 @@ function valPropertyValidator(serverValidationManager) {
}
};
- // Formatters are invoked when the model is modified in the code.
- modelCtrl.$formatters.push(validate);
-
// Parsers are called as soon as the value in the form input is modified
modelCtrl.$parsers.push(validate);
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js
index 651c0a54c7..45a35c5257 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js
@@ -13,50 +13,51 @@ function valRegex() {
link: function (scope, elm, attrs, ctrl) {
var flags = "";
- if (attrs.valRegexFlags) {
- try {
- flags = scope.$eval(attrs.valRegexFlags);
- if (!flags) {
- flags = attrs.valRegexFlags;
+ var regex;
+
+ 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);
}
}
- catch (e) {
- flags = attrs.valRegexFlags;
- }
- }
- var regex;
- try {
- var resolved = scope.$eval(attrs.valRegex);
- if (resolved) {
- regex = new RegExp(resolved, flags);
- }
- else {
- regex = new RegExp(attrs.valRegex, flags);
- }
- }
- catch(e) {
- regex = new RegExp(attrs.valRegex, flags);
- }
+ });
var patternValidator = function (viewValue) {
- //NOTE: we don't validate on empty values, use required validator for that
- if (!viewValue || regex.test(viewValue)) {
- // 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;
+ if (regex) {
+ //NOTE: we don't validate on empty values, use required validator for that
+ if (!viewValue || regex.test(viewValue)) {
+ // 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;
+ }
}
};
- ctrl.$formatters.push(patternValidator);
ctrl.$parsers.push(patternValidator);
}
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js
index c07ee26dec..6fe2dfdf08 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js
@@ -11,47 +11,46 @@ function valServerField(serverValidationManager) {
restrict: "A",
link: function (scope, element, attr, ctrl) {
- if (!attr.valServerField) {
- throw "valServerField must have a field name for referencing server errors";
- }
+ var fieldName = null;
- var fieldName = attr.valServerField;
- var evalfieldName = scope.$eval(attr.valServerField);
- if (evalfieldName) {
- fieldName = evalfieldName;
- }
+ 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.
+ ctrl.$viewChangeListeners.push(function () {
+ 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);
+ });
- //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.
- ctrl.$viewChangeListeners.push(function () {
- 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);
- });
}
};
}
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html
index 3d02980e97..c7fbcb9308 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html
@@ -42,6 +42,8 @@
umb-auto-resize
required
val-server-field="{{'Groups[' + $index + '].Name'}}" />
+
+
@@ -93,13 +95,21 @@
-
{{ property.alias }}
+
+
+
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html
index 8a70b25c80..a57f7cc1ef 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html
@@ -8,20 +8,21 @@
-
-
+
+
- Invalid alias
+ Invalid alias
+
diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs
index 15cc1d6785..17eb6bb35e 100644
--- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs
+++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs
@@ -60,11 +60,32 @@ namespace Umbraco.Web.Models.ContentEditing
if (CompositeContentTypes.Any(x => x.IsNullOrWhiteSpace()))
yield return new ValidationResult("Composite Content Type value cannot be null", new[] { "CompositeContentTypes" });
- if (Groups.GroupBy(x => x.Name).Any(x => x.Count() > 1))
- yield return new ValidationResult("Duplicate group names not allowed", new[] { "Groups" });
+ var duplicateGroups = Groups.GroupBy(x => x.Name).Where(x => x.Count() > 1).ToArray();
+ if (duplicateGroups.Any())
+ {
+ //we need to return the field name with an index so it's wired up correctly
+ var firstIndex = Groups.IndexOf(duplicateGroups.First().First());
+ yield return new ValidationResult("Duplicate group names not allowed", new[]
+ {
+ string.Format("Groups[{0}].Name", firstIndex)
+ });
+ }
+
+ var duplicateProperties = Groups.SelectMany(x => x.Properties).Where(x => x.Inherited == false).GroupBy(x => x.Alias).Where(x => x.Count() > 1).ToArray();
+ if (duplicateProperties.Any())
+ {
+ //we need to return the field name with an index so it's wired up correctly
+ var firstProperty = duplicateProperties.First().First();
+ var propertyGroup = Groups.Single(x => x.Properties.Contains(firstProperty));
+ var groupIndex = Groups.IndexOf(propertyGroup);
+ var propertyIndex = propertyGroup.Properties.IndexOf(firstProperty);
- if (Groups.SelectMany(x => x.Properties).Where(x => x.Inherited == false).GroupBy(x => x.Alias).Any(x => x.Count() > 1))
- yield return new ValidationResult("Duplicate property aliases not allowed", new[] { "Groups" });
+ yield return new ValidationResult("Duplicate property aliases not allowed", new[]
+ {
+ string.Format("Groups[{0}].Properties[{1}].Alias", groupIndex, propertyIndex)
+ });
+ }
+
}
}
}
\ No newline at end of file