Gets contextual culture server validation working
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors.Validators
|
||||
{
|
||||
@@ -11,6 +13,7 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
/// </summary>
|
||||
internal sealed class RegexValidator : IValueFormatValidator, IManifestValueValidator
|
||||
{
|
||||
private readonly ILocalizedTextService _textService;
|
||||
private string _regex;
|
||||
|
||||
/// <inheritdoc cref="IManifestValueValidator.ValidationName"/>
|
||||
@@ -22,8 +25,8 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
/// <remarks>Use this constructor when the validator is used as an <see cref="IValueFormatValidator"/>,
|
||||
/// and the regular expression is supplied at validation time. This constructor is also used when
|
||||
/// the validator is used as an <see cref="IManifestValueValidator"/> and the regular expression
|
||||
/// is supplied via the <see cref="Configure"/> method.</remarks>
|
||||
public RegexValidator()
|
||||
/// is supplied via the <see cref="Configuration"/> method.</remarks>
|
||||
public RegexValidator() : this(Current.Services.TextService, null)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
@@ -31,10 +34,9 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
/// </summary>
|
||||
/// <remarks>Use this constructor when the validator is used as an <see cref="IValueValidator"/>,
|
||||
/// and the regular expression must be supplied when the validator is created.</remarks>
|
||||
public RegexValidator(string regex)
|
||||
public RegexValidator(ILocalizedTextService textService, string regex)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(regex))
|
||||
throw new ArgumentNullOrEmptyException(nameof(regex));
|
||||
_textService = textService;
|
||||
_regex = regex;
|
||||
}
|
||||
|
||||
@@ -66,7 +68,7 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(format)) throw new ArgumentNullOrEmptyException(nameof(format));
|
||||
if (value == null || !new Regex(format).IsMatch(value.ToString()))
|
||||
yield return new ValidationResult("Value is invalid, it does not match the correct pattern", new[] { "value" });
|
||||
yield return new ValidationResult(_textService.Localize("validation", "invalidPattern"), new[] { "value" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors.Validators
|
||||
{
|
||||
@@ -8,6 +9,17 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
/// </summary>
|
||||
internal sealed class RequiredValidator : IValueRequiredValidator, IManifestValueValidator
|
||||
{
|
||||
private readonly ILocalizedTextService _textService;
|
||||
|
||||
public RequiredValidator()
|
||||
{
|
||||
}
|
||||
|
||||
public RequiredValidator(ILocalizedTextService textService)
|
||||
{
|
||||
_textService = textService;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IManifestValueValidator.ValidationName"/>
|
||||
public string ValidationName => "Required";
|
||||
|
||||
@@ -22,20 +34,20 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
yield return new ValidationResult("Value cannot be null", new[] {"value"});
|
||||
yield return new ValidationResult(_textService.Localize("validation", "invalidNull"), new[] {"value"});
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (valueType.InvariantEquals(ValueTypes.Json))
|
||||
{
|
||||
if (value.ToString().DetectIsEmptyJson())
|
||||
yield return new ValidationResult("Value cannot be empty", new[] { "value" });
|
||||
yield return new ValidationResult(_textService.Localize("validation", "invalidEmpty"), new[] { "value" });
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (value.ToString().IsNullOrWhiteSpace())
|
||||
{
|
||||
yield return new ValidationResult("Value cannot be empty", new[] { "value" });
|
||||
yield return new ValidationResult(_textService.Localize("validation", "invalidEmpty"), new[] { "value" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function tabbedContentDirective() {
|
||||
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/content/tabbed-content.html',
|
||||
controller: function ($scope) {
|
||||
|
||||
//expose the property/methods for other directives to use
|
||||
this.content = $scope.content;
|
||||
|
||||
},
|
||||
link: function(scope) {
|
||||
|
||||
function onInit() {
|
||||
angular.forEach(scope.content.tabs, function (group) {
|
||||
group.open = true;
|
||||
});
|
||||
}
|
||||
|
||||
onInit();
|
||||
|
||||
},
|
||||
scope: {
|
||||
content: "="
|
||||
}
|
||||
};
|
||||
|
||||
return directive;
|
||||
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive('tabbedContent', tabbedContentDirective);
|
||||
|
||||
})();
|
||||
@@ -12,10 +12,7 @@
|
||||
function valPropertyMsg(serverValidationManager) {
|
||||
|
||||
return {
|
||||
scope: {
|
||||
property: "="
|
||||
},
|
||||
require: ['^^form', '^^valFormManager'],
|
||||
require: ['^^form', '^^valFormManager', '^^umbProperty', '^^tabbedContent'],
|
||||
replace: true,
|
||||
restrict: "E",
|
||||
template: "<div ng-show=\"errorMsg != ''\" class='alert alert-error property-error' >{{errorMsg}}</div>",
|
||||
@@ -24,25 +21,31 @@ function valPropertyMsg(serverValidationManager) {
|
||||
|
||||
//the property form controller api
|
||||
var formCtrl = ctrl[0];
|
||||
|
||||
//the valFormManager controller api
|
||||
var valFormManager = ctrl[1];
|
||||
//the property controller api
|
||||
var umbPropCtrl = ctrl[2];
|
||||
//the tabbed content controller api
|
||||
var tabbedContent = ctrl[3];
|
||||
|
||||
var currentProperty = umbPropCtrl.property;
|
||||
var currentCulture = tabbedContent.content.language.culture;
|
||||
|
||||
var watcher = null;
|
||||
|
||||
// Gets the error message to display
|
||||
function getErrorMsg() {
|
||||
//this can be null if no property was assigned
|
||||
if (scope.property) {
|
||||
if (currentProperty) {
|
||||
//first try to get the error msg from the server collection
|
||||
var err = serverValidationManager.getPropertyError(scope.property.alias, "");
|
||||
var err = serverValidationManager.getPropertyError(currentProperty.alias, null, "");
|
||||
//if there's an error message use it
|
||||
if (err && err.errorMsg) {
|
||||
return err.errorMsg;
|
||||
}
|
||||
else {
|
||||
//TODO: localize
|
||||
return scope.property.propertyErrorMessage ? scope.property.propertyErrorMessage : "Property has errors";
|
||||
return currentProperty.propertyErrorMessage ? currentProperty.propertyErrorMessage : "Property has errors";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,8 +62,10 @@ function valPropertyMsg(serverValidationManager) {
|
||||
function startWatch() {
|
||||
//if there's not already a watch
|
||||
if (!watcher) {
|
||||
watcher = scope.$watch("property.value", function (newValue, oldValue) {
|
||||
|
||||
watcher = scope.$watch(function () {
|
||||
return currentProperty.value;
|
||||
}, function (newValue, oldValue) {
|
||||
|
||||
if (!newValue || angular.equals(newValue, oldValue)) {
|
||||
return;
|
||||
}
|
||||
@@ -133,7 +138,7 @@ function valPropertyMsg(serverValidationManager) {
|
||||
});
|
||||
|
||||
//listen for the forms saving event
|
||||
unsubscribe.push(scope.$on("formSubmitting", function(ev, args) {
|
||||
unsubscribe.push(scope.$on("formSubmitting", function (ev, args) {
|
||||
showValidation = true;
|
||||
if (hasError && scope.errorMsg === "") {
|
||||
scope.errorMsg = getErrorMsg();
|
||||
@@ -145,7 +150,7 @@ function valPropertyMsg(serverValidationManager) {
|
||||
}));
|
||||
|
||||
//listen for the forms saved event
|
||||
unsubscribe.push(scope.$on("formSubmitted", function(ev, args) {
|
||||
unsubscribe.push(scope.$on("formSubmitted", function (ev, args) {
|
||||
showValidation = false;
|
||||
scope.errorMsg = "";
|
||||
formCtrl.$setValidity('valPropertyMsg', true);
|
||||
@@ -160,8 +165,8 @@ function valPropertyMsg(serverValidationManager) {
|
||||
// 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) {
|
||||
if (currentProperty) { //this can be null if no property was assigned
|
||||
serverValidationManager.subscribe(currentProperty.alias, currentCulture, "", function (isValid, propertyErrors, allErrors) {
|
||||
hasError = !isValid;
|
||||
if (hasError) {
|
||||
//set the error message to the server message
|
||||
@@ -183,7 +188,7 @@ function valPropertyMsg(serverValidationManager) {
|
||||
// but they are a different callback instance than the above.
|
||||
element.bind('$destroy', function () {
|
||||
stopWatch();
|
||||
serverValidationManager.unsubscribe(scope.property.alias, "");
|
||||
serverValidationManager.unsubscribe(currentProperty.alias, currentCulture, "");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
**/
|
||||
function valServer(serverValidationManager) {
|
||||
return {
|
||||
require: ['ngModel', '?^umbProperty'],
|
||||
require: ['ngModel', '?^^umbProperty', '?^^tabbedContent'],
|
||||
restrict: "A",
|
||||
link: function (scope, element, attr, ctrls) {
|
||||
|
||||
var modelCtrl = ctrls[0];
|
||||
var umbPropCtrl = ctrls.length > 1 ? ctrls[1] : null;
|
||||
if (!umbPropCtrl) {
|
||||
var tabbedContent = ctrls.length > 2 ? ctrls[2] : null;
|
||||
if (!umbPropCtrl || !tabbedContent) {
|
||||
//we cannot proceed, this validator will be disabled
|
||||
return;
|
||||
}
|
||||
@@ -54,6 +55,7 @@ function valServer(serverValidationManager) {
|
||||
}
|
||||
|
||||
var currentProperty = umbPropCtrl.property;
|
||||
var currentCulture = tabbedContent.content.language.culture;
|
||||
|
||||
//default to 'value' if nothing is set
|
||||
var fieldName = "value";
|
||||
@@ -66,7 +68,7 @@ function valServer(serverValidationManager) {
|
||||
}
|
||||
|
||||
//subscribe to the server validation changes
|
||||
serverValidationManager.subscribe(currentProperty.alias, fieldName, function (isValid, propertyErrors, allErrors) {
|
||||
serverValidationManager.subscribe(currentProperty.alias, currentCulture, fieldName, function (isValid, propertyErrors, allErrors) {
|
||||
if (!isValid) {
|
||||
modelCtrl.$setValidity('valServer', false);
|
||||
//assign an error msg property to the current validator
|
||||
@@ -86,9 +88,9 @@ function valServer(serverValidationManager) {
|
||||
// but they are a different callback instance than the above.
|
||||
element.bind('$destroy', function () {
|
||||
stopWatch();
|
||||
serverValidationManager.unsubscribe(currentProperty.alias, fieldName);
|
||||
serverValidationManager.unsubscribe(currentProperty.alias, currentCulture, fieldName);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
angular.module('umbraco.directives.validation').directive("valServer", valServer);
|
||||
angular.module('umbraco.directives.validation').directive("valServer", valServer);
|
||||
|
||||
@@ -33,7 +33,7 @@ function valServerField(serverValidationManager) {
|
||||
}));
|
||||
|
||||
//subscribe to the server validation changes
|
||||
serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) {
|
||||
serverValidationManager.subscribe(null, null, fieldName, function (isValid, fieldErrors, allErrors) {
|
||||
if (!isValid) {
|
||||
ngModel.$setValidity('valServerField', false);
|
||||
//assign an error msg property to the current validator
|
||||
@@ -50,7 +50,7 @@ function valServerField(serverValidationManager) {
|
||||
// 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);
|
||||
serverValidationManager.unsubscribe(null, null, fieldName);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -145,32 +145,33 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati
|
||||
// that each property is a User Developer property editor.
|
||||
// The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations
|
||||
// system.
|
||||
// So, to do this (since we need to support backwards compat), we need to hack a little bit. For Content Properties,
|
||||
// which are user defined, we know that they will exist with a prefixed ModelState of "_Properties.", so if we detect
|
||||
// this, then we know it's a Property.
|
||||
// So, to do this there's some special ModelState syntax we need to know about.
|
||||
// For Content Properties, which are user defined, we know that they will exist with a prefixed
|
||||
// ModelState of "_Properties.", so if we detect this, then we know it's for a content Property.
|
||||
|
||||
//the alias in model state can be in dot notation which indicates
|
||||
// * the first part is the content property alias
|
||||
// * the second part is the field to which the valiation msg is associated with
|
||||
//There will always be at least 2 parts for properties since all model errors for properties are prefixed with "Properties"
|
||||
//If it is not prefixed with "Properties" that means the error is for a field of the object directly.
|
||||
//There will always be at least 3 parts for content properties since all model errors for properties are prefixed with "_Properties"
|
||||
//If it is not prefixed with "_Properties" that means the error is for a field of the object directly.
|
||||
|
||||
var parts = e.split(".");
|
||||
|
||||
//Check if this is for content properties - specific to content/media/member editors because those are special
|
||||
// user defined properties with custom controls.
|
||||
if (parts.length > 1 && parts[0] === "_Properties") {
|
||||
if (parts.length > 2 && parts[0] === "_Properties") {
|
||||
|
||||
var propertyAlias = parts[1];
|
||||
var culture = parts[2];
|
||||
|
||||
//if it contains 2 '.' then we will wire it up to a property's field
|
||||
if (parts.length > 2) {
|
||||
//if it contains 3 '.' then we will wire it up to a property's html field
|
||||
if (parts.length > 3) {
|
||||
//add an error with a reference to the field for which the validation belongs too
|
||||
serverValidationManager.addPropertyError(propertyAlias, parts[2], modelState[e][0]);
|
||||
serverValidationManager.addPropertyError(propertyAlias, culture, parts[3], modelState[e][0]);
|
||||
}
|
||||
else {
|
||||
//add a generic error for the property, no reference to a specific field
|
||||
serverValidationManager.addPropertyError(propertyAlias, "", modelState[e][0]);
|
||||
//add a generic error for the property, no reference to a specific html field
|
||||
serverValidationManager.addPropertyError(propertyAlias, culture, "", modelState[e][0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ function serverValidationManager($timeout) {
|
||||
|
||||
//find errors for this field name
|
||||
return _.filter(self.items, function (item) {
|
||||
return (item.propertyAlias === null && item.fieldName === fieldName);
|
||||
return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName);
|
||||
});
|
||||
}
|
||||
|
||||
function getPropertyErrors(self, propertyAlias, fieldName) {
|
||||
function getPropertyErrors(self, propertyAlias, culture, fieldName) {
|
||||
if (!angular.isString(propertyAlias)) {
|
||||
throw "propertyAlias must be a string";
|
||||
}
|
||||
@@ -42,7 +42,7 @@ function serverValidationManager($timeout) {
|
||||
|
||||
//find all errors for this property
|
||||
return _.filter(self.items, function (item) {
|
||||
return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ function serverValidationManager($timeout) {
|
||||
* field alias to listen for.
|
||||
* If propertyAlias is null, then this subscription is for a field property (not a user defined property).
|
||||
*/
|
||||
subscribe: function (propertyAlias, fieldName, callback) {
|
||||
subscribe: function (propertyAlias, culture, fieldName, callback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
@@ -115,41 +115,46 @@ function serverValidationManager($timeout) {
|
||||
return item.propertyAlias === null && item.fieldName === fieldName;
|
||||
});
|
||||
if (!exists1) {
|
||||
callbacks.push({ propertyAlias: null, fieldName: fieldName, callback: callback });
|
||||
callbacks.push({ propertyAlias: null, culture: null, fieldName: fieldName, callback: callback });
|
||||
}
|
||||
}
|
||||
else if (propertyAlias !== undefined) {
|
||||
if (!culture) {
|
||||
culture = null; // if empty or null, always make null
|
||||
}
|
||||
//don't add it if it already exists
|
||||
var exists2 = _.find(callbacks, function (item) {
|
||||
return item.propertyAlias === propertyAlias && item.fieldName === fieldName;
|
||||
return item.propertyAlias === propertyAlias && item.culture === culture && item.fieldName === fieldName;
|
||||
});
|
||||
if (!exists2) {
|
||||
callbacks.push({ propertyAlias: propertyAlias, fieldName: fieldName, callback: callback });
|
||||
callbacks.push({ propertyAlias: propertyAlias, culture: culture, fieldName: fieldName, callback: callback });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
unsubscribe: function (propertyAlias, fieldName) {
|
||||
unsubscribe: function (propertyAlias, culture, fieldName) {
|
||||
|
||||
if (propertyAlias === null) {
|
||||
|
||||
//remove all callbacks for the content field
|
||||
callbacks = _.reject(callbacks, function (item) {
|
||||
return item.propertyAlias === null && item.fieldName === fieldName;
|
||||
return item.propertyAlias === null && item.culture === null && item.fieldName === fieldName;
|
||||
});
|
||||
|
||||
}
|
||||
else if (propertyAlias !== undefined) {
|
||||
|
||||
|
||||
if (!culture) {
|
||||
culture = null; // if empty or null, always make null
|
||||
}
|
||||
|
||||
//remove all callbacks for the content property
|
||||
callbacks = _.reject(callbacks, function (item) {
|
||||
return item.propertyAlias === propertyAlias &&
|
||||
return item.propertyAlias === propertyAlias && item.culture === culture &&
|
||||
(item.fieldName === fieldName ||
|
||||
((item.fieldName === undefined || item.fieldName === "") && (fieldName === undefined || fieldName === "")));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
@@ -164,10 +169,15 @@ function serverValidationManager($timeout) {
|
||||
* This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an
|
||||
* explicit field name set.
|
||||
*/
|
||||
getPropertyCallbacks: function (propertyAlias, fieldName) {
|
||||
getPropertyCallbacks: function (propertyAlias, culture, fieldName) {
|
||||
|
||||
if (!culture) {
|
||||
culture = null; // if empty or null, always make null
|
||||
}
|
||||
|
||||
var found = _.filter(callbacks, function (item) {
|
||||
//returns any callback that have been registered directly against the field and for only the property
|
||||
return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === "")));
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === "")));
|
||||
});
|
||||
return found;
|
||||
},
|
||||
@@ -184,7 +194,7 @@ function serverValidationManager($timeout) {
|
||||
getFieldCallbacks: function (fieldName) {
|
||||
var found = _.filter(callbacks, function (item) {
|
||||
//returns any callback that have been registered directly against the field
|
||||
return (item.propertyAlias === null && item.fieldName === fieldName);
|
||||
return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName);
|
||||
});
|
||||
return found;
|
||||
},
|
||||
@@ -207,6 +217,7 @@ function serverValidationManager($timeout) {
|
||||
if (!this.hasFieldError(fieldName)) {
|
||||
this.items.push({
|
||||
propertyAlias: null,
|
||||
culture: null,
|
||||
fieldName: fieldName,
|
||||
errorMsg: errorMsg
|
||||
});
|
||||
@@ -231,24 +242,29 @@ function serverValidationManager($timeout) {
|
||||
* @description
|
||||
* Adds an error message for the content property
|
||||
*/
|
||||
addPropertyError: function (propertyAlias, fieldName, errorMsg) {
|
||||
addPropertyError: function (propertyAlias, culture, fieldName, errorMsg) {
|
||||
if (!propertyAlias) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!culture) {
|
||||
culture = null; // if empty or null, always make null
|
||||
}
|
||||
|
||||
//only add the item if it doesn't exist
|
||||
if (!this.hasPropertyError(propertyAlias, fieldName)) {
|
||||
if (!this.hasPropertyError(propertyAlias, culture, fieldName)) {
|
||||
this.items.push({
|
||||
propertyAlias: propertyAlias,
|
||||
culture: culture,
|
||||
fieldName: fieldName,
|
||||
errorMsg: errorMsg
|
||||
});
|
||||
}
|
||||
|
||||
//find all errors for this item
|
||||
var errorsForCallback = getPropertyErrors(this, propertyAlias, fieldName);
|
||||
var errorsForCallback = getPropertyErrors(this, propertyAlias, culture, fieldName);
|
||||
//we should now call all of the call backs registered for this error
|
||||
var cbs = this.getPropertyCallbacks(propertyAlias, fieldName);
|
||||
var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName);
|
||||
//call each callback for this error
|
||||
for (var cb in cbs) {
|
||||
executeCallback(this, errorsForCallback, cbs[cb].callback);
|
||||
@@ -264,14 +280,14 @@ function serverValidationManager($timeout) {
|
||||
* @description
|
||||
* Removes an error message for the content property
|
||||
*/
|
||||
removePropertyError: function (propertyAlias, fieldName) {
|
||||
removePropertyError: function (propertyAlias, culture, fieldName) {
|
||||
|
||||
if (!propertyAlias) {
|
||||
return;
|
||||
}
|
||||
//remove the item
|
||||
this.items = _.reject(this.items, function (item) {
|
||||
return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
});
|
||||
},
|
||||
|
||||
@@ -316,10 +332,10 @@ function serverValidationManager($timeout) {
|
||||
* @description
|
||||
* Gets the error message for the content property
|
||||
*/
|
||||
getPropertyError: function (propertyAlias, fieldName) {
|
||||
getPropertyError: function (propertyAlias, culture, fieldName) {
|
||||
var err = _.find(this.items, function (item) {
|
||||
//return true if the property alias matches and if an empty field name is specified or the field name matches
|
||||
return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
});
|
||||
return err;
|
||||
},
|
||||
@@ -336,7 +352,7 @@ function serverValidationManager($timeout) {
|
||||
getFieldError: function (fieldName) {
|
||||
var err = _.find(this.items, function (item) {
|
||||
//return true if the property alias matches and if an empty field name is specified or the field name matches
|
||||
return (item.propertyAlias === null && item.fieldName === fieldName);
|
||||
return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName);
|
||||
});
|
||||
return err;
|
||||
},
|
||||
@@ -348,12 +364,12 @@ function serverValidationManager($timeout) {
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if the content property + field name combo has an error
|
||||
* Checks if the content property + culture + field name combo has an error
|
||||
*/
|
||||
hasPropertyError: function (propertyAlias, fieldName) {
|
||||
hasPropertyError: function (propertyAlias, culture, fieldName) {
|
||||
var err = _.find(this.items, function (item) {
|
||||
//return true if the property alias matches and if an empty field name is specified or the field name matches
|
||||
return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
});
|
||||
return err ? true : false;
|
||||
},
|
||||
@@ -370,7 +386,7 @@ function serverValidationManager($timeout) {
|
||||
hasFieldError: function (fieldName) {
|
||||
var err = _.find(this.items, function (item) {
|
||||
//return true if the property alias matches and if an empty field name is specified or the field name matches
|
||||
return (item.propertyAlias === null && item.fieldName === fieldName);
|
||||
return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName);
|
||||
});
|
||||
return err ? true : false;
|
||||
},
|
||||
@@ -380,4 +396,4 @@ function serverValidationManager($timeout) {
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('umbraco.services').factory('serverValidationManager', serverValidationManager);
|
||||
angular.module('umbraco.services').factory('serverValidationManager', serverValidationManager);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<div>
|
||||
<div class="umb-expansion-panel" ng-repeat="group in content.tabs | filter: { hide : '!' + true } track by group.label">
|
||||
|
||||
<div class="umb-expansion-panel__header" ng-click="group.open = !group.open">
|
||||
<div>{{ group.label }}</div>
|
||||
<ins class="umb-expansion-panel__expand icon-navigation-right" ng-class="{'icon-navigation-down': !group.open, 'icon-navigation-up': group.open}"> </ins>
|
||||
</div>
|
||||
|
||||
<div class="umb-expansion-panel__content" ng-show="group.open">
|
||||
|
||||
<umb-property data-element="property-{{group.alias}}" ng-repeat="property in group.properties track by property.alias" property="property">
|
||||
<umb-property-editor model="property"></umb-property-editor>
|
||||
</umb-property>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,7 +2,7 @@
|
||||
<ng-form name="propertyForm">
|
||||
<div class="control-group umb-control-group" ng-class="{hidelabel:property.hideLabel}">
|
||||
|
||||
<val-property-msg property="property"></val-property-msg>
|
||||
<val-property-msg></val-property-msg>
|
||||
|
||||
<div class="umb-el-wrap">
|
||||
|
||||
@@ -19,4 +19,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,14 +6,9 @@
|
||||
var vm = this;
|
||||
vm.loading = true;
|
||||
|
||||
//TODO: Figure out what we need to do to maintain validation states since this will re-init the editor
|
||||
function onInit() {
|
||||
|
||||
vm.content = $scope.model.viewModel;
|
||||
|
||||
angular.forEach(vm.content.tabs, function (group) {
|
||||
group.open = true;
|
||||
});
|
||||
|
||||
vm.loading = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
<div class="form-horizontal" ng-controller="Umbraco.Editors.Content.Apps.ContentController as vm">
|
||||
|
||||
<div class="umb-expansion-panel" ng-if="!vm.loading" ng-repeat="group in vm.content.tabs | filter: { hide : '!' + true } track by group.label">
|
||||
|
||||
<div class="umb-expansion-panel__header" ng-click="group.open = !group.open">
|
||||
<div>{{ group.label }}</div>
|
||||
<ins class="umb-expansion-panel__expand icon-navigation-right" ng-class="{'icon-navigation-down': !group.open, 'icon-navigation-up': group.open}"> </ins>
|
||||
</div>
|
||||
|
||||
<div class="umb-expansion-panel__content" ng-show="group.open">
|
||||
<umb-property data-element="property-{{group.alias}}" ng-repeat="property in group.properties track by property.alias" property="property">
|
||||
<umb-property-editor model="property"></umb-property-editor>
|
||||
</umb-property>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<tabbed-content ng-if="!vm.loading" content="vm.content"></tabbed-content>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -60,12 +60,12 @@
|
||||
it('can retrieve property validation errors for a sub field', function () {
|
||||
|
||||
//arrange
|
||||
serverValidationManager.addPropertyError("myProperty", "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", "value2", "Another value 2");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2");
|
||||
|
||||
//act
|
||||
var err1 = serverValidationManager.getPropertyError("myProperty", "value1");
|
||||
var err2 = serverValidationManager.getPropertyError("myProperty", "value2");
|
||||
var err1 = serverValidationManager.getPropertyError("myProperty", null, "value1");
|
||||
var err2 = serverValidationManager.getPropertyError("myProperty", null, "value2");
|
||||
|
||||
//assert
|
||||
expect(err1).not.toBeUndefined();
|
||||
@@ -82,8 +82,8 @@
|
||||
it('can add a property errors with multiple sub fields and it the first will be retreived with only the property alias', function () {
|
||||
|
||||
//arrange
|
||||
serverValidationManager.addPropertyError("myProperty", "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", "value2", "Another value 2");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2");
|
||||
|
||||
//act
|
||||
var err = serverValidationManager.getPropertyError("myProperty");
|
||||
@@ -98,10 +98,10 @@
|
||||
it('will return null for a non-existing property error', function () {
|
||||
|
||||
//arrage
|
||||
serverValidationManager.addPropertyError("myProperty", "value", "Required");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value", "Required");
|
||||
|
||||
//act
|
||||
var err = serverValidationManager.getPropertyError("DoesntExist", "value");
|
||||
var err = serverValidationManager.getPropertyError("DoesntExist", null, "value");
|
||||
|
||||
//assert
|
||||
expect(err).toBeUndefined();
|
||||
@@ -111,15 +111,15 @@
|
||||
it('detects if a property error exists', function () {
|
||||
|
||||
//arrange
|
||||
serverValidationManager.addPropertyError("myProperty", "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", "value2", "Another value 2");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2");
|
||||
|
||||
//act
|
||||
var err1 = serverValidationManager.hasPropertyError("myProperty");
|
||||
var err2 = serverValidationManager.hasPropertyError("myProperty", "value1");
|
||||
var err3 = serverValidationManager.hasPropertyError("myProperty", "value2");
|
||||
var err2 = serverValidationManager.hasPropertyError("myProperty", null, "value1");
|
||||
var err3 = serverValidationManager.hasPropertyError("myProperty", null, "value2");
|
||||
var err4 = serverValidationManager.hasPropertyError("notFound");
|
||||
var err5 = serverValidationManager.hasPropertyError("myProperty", "notFound");
|
||||
var err5 = serverValidationManager.hasPropertyError("myProperty", null, "notFound");
|
||||
|
||||
//assert
|
||||
expect(err1).toBe(true);
|
||||
@@ -133,30 +133,30 @@
|
||||
it('can remove a property error with a sub field specified', function () {
|
||||
|
||||
//arrage
|
||||
serverValidationManager.addPropertyError("myProperty", "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", "value2", "Another value 2");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2");
|
||||
|
||||
//act
|
||||
serverValidationManager.removePropertyError("myProperty", "value1");
|
||||
serverValidationManager.removePropertyError("myProperty", null, "value1");
|
||||
|
||||
//assert
|
||||
expect(serverValidationManager.hasPropertyError("myProperty", "value1")).toBe(false);
|
||||
expect(serverValidationManager.hasPropertyError("myProperty", "value2")).toBe(true);
|
||||
expect(serverValidationManager.hasPropertyError("myProperty", null, "value1")).toBe(false);
|
||||
expect(serverValidationManager.hasPropertyError("myProperty", null, "value2")).toBe(true);
|
||||
|
||||
});
|
||||
|
||||
it('can remove a property error and all sub field errors by specifying only the property', function () {
|
||||
|
||||
//arrage
|
||||
serverValidationManager.addPropertyError("myProperty", "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", "value2", "Another value 2");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2");
|
||||
|
||||
//act
|
||||
serverValidationManager.removePropertyError("myProperty");
|
||||
|
||||
//assert
|
||||
expect(serverValidationManager.hasPropertyError("myProperty", "value1")).toBe(false);
|
||||
expect(serverValidationManager.hasPropertyError("myProperty", "value2")).toBe(false);
|
||||
expect(serverValidationManager.hasPropertyError("myProperty", null, "value1")).toBe(false);
|
||||
expect(serverValidationManager.hasPropertyError("myProperty", null, "value2")).toBe(false);
|
||||
|
||||
});
|
||||
|
||||
@@ -168,7 +168,7 @@
|
||||
var args;
|
||||
|
||||
//arrange
|
||||
serverValidationManager.subscribe(null, "Name", function (isValid, propertyErrors, allErrors) {
|
||||
serverValidationManager.subscribe(null, null, "Name", function (isValid, propertyErrors, allErrors) {
|
||||
args = {
|
||||
isValid: isValid,
|
||||
propertyErrors: propertyErrors,
|
||||
@@ -178,7 +178,7 @@
|
||||
|
||||
//act
|
||||
serverValidationManager.addFieldError("Name", "Required");
|
||||
serverValidationManager.addPropertyError("myProperty", "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
|
||||
|
||||
//assert
|
||||
expect(args).not.toBeUndefined();
|
||||
@@ -195,8 +195,8 @@
|
||||
};
|
||||
var cb2 = function () {
|
||||
};
|
||||
serverValidationManager.subscribe(null, "Name", cb1);
|
||||
serverValidationManager.subscribe(null, "Title", cb2);
|
||||
serverValidationManager.subscribe(null, null, "Name", cb1);
|
||||
serverValidationManager.subscribe(null, null, "Title", cb2);
|
||||
|
||||
//act
|
||||
serverValidationManager.addFieldError("Name", "Required");
|
||||
@@ -224,7 +224,7 @@
|
||||
var numCalled = 0;
|
||||
|
||||
//arrange
|
||||
serverValidationManager.subscribe("myProperty", "value1", function (isValid, propertyErrors, allErrors) {
|
||||
serverValidationManager.subscribe("myProperty", null, "value1", function (isValid, propertyErrors, allErrors) {
|
||||
args1 = {
|
||||
isValid: isValid,
|
||||
propertyErrors: propertyErrors,
|
||||
@@ -232,7 +232,7 @@
|
||||
};
|
||||
});
|
||||
|
||||
serverValidationManager.subscribe("myProperty", "", function (isValid, propertyErrors, allErrors) {
|
||||
serverValidationManager.subscribe("myProperty", null, "", function (isValid, propertyErrors, allErrors) {
|
||||
numCalled++;
|
||||
args2 = {
|
||||
isValid: isValid,
|
||||
@@ -242,9 +242,9 @@
|
||||
});
|
||||
|
||||
//act
|
||||
serverValidationManager.addPropertyError("myProperty", "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", "value2", "Some value 2");
|
||||
serverValidationManager.addPropertyError("myProperty", "", "Some value 3");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value2", "Some value 2");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "", "Some value 3");
|
||||
|
||||
//assert
|
||||
expect(args1).not.toBeUndefined();
|
||||
@@ -272,4 +272,4 @@
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2063,6 +2063,9 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="invalidDate">Invalid date</key>
|
||||
<key alias="invalidNumber">Not a number</key>
|
||||
<key alias="invalidEmail">Invalid email</key>
|
||||
<key alias="invalidNull">Value cannot be null</key>
|
||||
<key alias="invalidEmpty">Value cannot be empty</key>
|
||||
<key alias="invalidPattern">Value is invalid, it does not match the correct pattern</key>
|
||||
</area>
|
||||
<area alias="healthcheck">
|
||||
<!-- The following keys get these tokens passed in:
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
// validate
|
||||
var valueEditor = editor.GetValueEditor(p.DataType.Configuration);
|
||||
foreach (var r in valueEditor.Validate(postedValue, p.IsRequired, p.ValidationRegExp))
|
||||
modelState.AddPropertyError(r, p.Alias);
|
||||
modelState.AddPropertyError(r, p.Alias, p.Culture);
|
||||
}
|
||||
|
||||
return modelState.IsValid;
|
||||
|
||||
@@ -57,24 +57,40 @@ namespace Umbraco.Web
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="propertyAlias"></param>
|
||||
internal static void AddPropertyError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, ValidationResult result, string propertyAlias)
|
||||
/// <param name="culture">The culture for the property, if the property is invariant than this is empty</param>
|
||||
internal static void AddPropertyError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState,
|
||||
ValidationResult result, string propertyAlias, string culture = "")
|
||||
{
|
||||
modelState.AddValidationError(result, "_Properties", propertyAlias);
|
||||
if (culture == null)
|
||||
culture = "";
|
||||
modelState.AddValidationError(result, "_Properties", propertyAlias, culture);
|
||||
}
|
||||
|
||||
internal static void AddValidationError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, ValidationResult result, string prefix, string owner)
|
||||
/// <summary>
|
||||
/// Adds the error to model state correctly for a property so we can use it on the client side.
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="parts">
|
||||
/// Each model state validation error has a name and in most cases this name is made up of parts which are delimited by a '.'
|
||||
/// </param>
|
||||
internal static void AddValidationError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState,
|
||||
ValidationResult result, params string[] parts)
|
||||
{
|
||||
// if there are assigned member names, we combine the member name with the owner name
|
||||
// so that we can try to match it up to a real field. otherwise, we assume that the
|
||||
// validation message is for the overall owner.
|
||||
// Owner = the component being validated, like a content property but could be just an html field on another editor
|
||||
|
||||
var withNames = false;
|
||||
var delimitedParts = string.Join(".", parts);
|
||||
foreach (var memberName in result.MemberNames)
|
||||
{
|
||||
modelState.AddModelError($"{prefix}.{owner}.{memberName}", result.ErrorMessage);
|
||||
modelState.AddModelError($"{delimitedParts}.{memberName}", result.ErrorMessage);
|
||||
withNames = true;
|
||||
}
|
||||
if (!withNames)
|
||||
modelState.AddModelError($"{prefix}.{owner}", result.ErrorMessage);
|
||||
modelState.AddModelError($"{delimitedParts}", result.ErrorMessage);
|
||||
}
|
||||
|
||||
public static IDictionary<string, object> ToErrorDictionary(this System.Web.Http.ModelBinding.ModelStateDictionary modelState)
|
||||
|
||||
@@ -15,5 +15,13 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
public string Description { get; set; }
|
||||
public bool IsRequired { get; set; }
|
||||
public string ValidationRegExp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The culture for the property which is only relevant for variant properties
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Since content currently is the only thing that can be variant this will only be relevant to content
|
||||
/// </remarks>
|
||||
public string Culture { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
|
||||
//a culture needs to be in the context for a property type that can vary
|
||||
if (culture == null && property.PropertyType.VariesByCulture())
|
||||
throw new InvalidOperationException($"No languageId found in mapping operation when one is required for the culture neutral property type {property.PropertyType.Alias}");
|
||||
throw new InvalidOperationException($"No culture found in mapping operation when one is required for the culture variant property type {property.PropertyType.Alias}");
|
||||
|
||||
//set the culture to null if it's an invariant property type
|
||||
culture = !property.PropertyType.VariesByCulture() ? null : culture;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
@@ -17,15 +18,24 @@ namespace Umbraco.Web.Models.Mapping
|
||||
: base(dataTypeService, logger, propertyEditors)
|
||||
{ }
|
||||
|
||||
public override ContentPropertyDto Convert(Property originalProperty, ContentPropertyDto dest, ResolutionContext context)
|
||||
public override ContentPropertyDto Convert(Property property, ContentPropertyDto dest, ResolutionContext context)
|
||||
{
|
||||
var propertyDto = base.Convert(originalProperty, dest, context);
|
||||
var propertyDto = base.Convert(property, dest, context);
|
||||
|
||||
propertyDto.IsRequired = originalProperty.PropertyType.Mandatory;
|
||||
propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp;
|
||||
propertyDto.Description = originalProperty.PropertyType.Description;
|
||||
propertyDto.Label = originalProperty.PropertyType.Name;
|
||||
propertyDto.DataType = DataTypeService.GetDataType(originalProperty.PropertyType.DataTypeId);
|
||||
propertyDto.IsRequired = property.PropertyType.Mandatory;
|
||||
propertyDto.ValidationRegExp = property.PropertyType.ValidationRegExp;
|
||||
propertyDto.Description = property.PropertyType.Description;
|
||||
propertyDto.Label = property.PropertyType.Name;
|
||||
propertyDto.DataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId);
|
||||
|
||||
//Get the culture from the context which will be set during the mapping operation for each property
|
||||
var culture = context.GetCulture();
|
||||
|
||||
//a culture needs to be in the context for a property type that can vary
|
||||
if (culture == null && property.PropertyType.VariesByCulture())
|
||||
throw new InvalidOperationException($"No culture found in mapping operation when one is required for the culture variant property type {property.PropertyType.Alias}");
|
||||
|
||||
propertyDto.Culture = culture;
|
||||
|
||||
return propertyDto;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user