diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 1015124a8b..ac3f6bd464 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -137,7 +137,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js index 059a4c6853..27fa67695b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js @@ -118,7 +118,8 @@ function valPropertyMsg(serverValidationManager, localizationService) { || (formCtrl.$invalid && Utilities.isArray(formCtrl.$error.valServer))) { scope.errorMsg = ""; formCtrl.$setValidity('valPropertyMsg', true); - } else if (showValidation && scope.errorMsg === "") { + } + else if (showValidation && scope.errorMsg === "") { formCtrl.$setValidity('valPropertyMsg', false); scope.errorMsg = getErrorMsg(); } @@ -229,7 +230,7 @@ function valPropertyMsg(serverValidationManager, localizationService) { hasError = !isValid; if (hasError) { //set the error message to the server message - scope.errorMsg = propertyErrors[0].errorMsg; + scope.errorMsg = propertyErrors[0].errorMsg ? propertyErrors[0].errorMsg : labels.propertyHasErrors; //flag that the current validator is invalid formCtrl.$setValidity('valPropertyMsg', false); startWatch(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js index a945c71dff..02bccc8dd2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js @@ -123,25 +123,52 @@ function serverValidationManager($timeout, udiService) { * Returns a dictionary of id (of the block) and it's corresponding validation ModelState * @param {any} errorMsg */ - function parseComplexEditorError(errorMsg) { + function parseComplexEditorError(errorMsg, culture, segment) { + + //normalize culture to "invariant" + if (!culture) { + culture = "invariant"; + } + //normalize segment to "null" (as a string, in this case it's used for creating a key) + if (!segment) { + segment = "null"; + } var json = JSON.parse(errorMsg); var result = {}; function extractModelState(validation) { - if (validation.$id && validation.ModelState) { - result[validation.$id] = validation.ModelState; + if (validation.$id && validation.ModelState && Object.keys(validation.ModelState).length > 0) { + result[validation.$id] = validation.ModelState; } + else { + // we'll still add the id in the dictionary with an empty result, this indicates that this element + // has nested errors but no errors itself + result[validation.$id] = {}; + } + return result[validation.$id]; } function iterateErrorBlocks(blocks) { for (var i = 0; i < blocks.length; i++) { var validation = blocks[i]; - extractModelState(validation); + var modelState = extractModelState(validation); + var hasModelState = Object.keys(modelState).length > 0; var nested = _.omit(validation, "$id", "$elementTypeAlias", "ModelState"); for (const [key, value] of Object.entries(nested)) { + if (Array.isArray(value)) { + + // The key here is the property type alias of the nested validation. + // If the extracted ModelState is empty it indicates that this element + // has nested errors but no errors itself. In that case we need to manually populate the + // ModelState properties. + if (!hasModelState) { + var propertyKey = "_Properties." + key + "." + culture + "." + segment; + modelState[propertyKey] = null; + } + iterateErrorBlocks(value); // recurse } } @@ -262,9 +289,6 @@ function serverValidationManager($timeout, udiService) { */ function addPropertyError(propertyAlias, culture, fieldName, errorMsg, segment) { - // TODO: We need to handle the errorMsg in a special way to check if this is a json structure. If it is we know we are dealing with - // a complex editor and in which case we'll need to adjust how everything works. - if (!propertyAlias) { return; } @@ -277,17 +301,22 @@ function serverValidationManager($timeout, udiService) { if (!segment) { segment = null; } + //normalize errorMsg to empty + if (!errorMsg) { + errorMsg = ""; + } // if the error message is json it's a complex editor validation response that we need to parse if (errorMsg.startsWith("[")) { - var idsToErrors = parseComplexEditorError(errorMsg); + var idsToErrors = parseComplexEditorError(errorMsg, culture, segment); for (const [key, value] of Object.entries(idsToErrors)) { - addErrorsForModelState(value, udiService.build("element", key)); + const elementUdi = udiService.build("element", key); + addErrorsForModelState(value, elementUdi); } - // TODO: Make this the generic "Property has errors" but need to find the lang key for that - errorMsg = "Hello!"; + // We need to clear the error message else it will show up as a giant json block against the property + errorMsg = ""; } //only add the item if it doesn't exist @@ -372,7 +401,7 @@ function serverValidationManager($timeout, udiService) { * This wires up all of the server validation model state so that valServer and valServerField directives work */ function addErrorsForModelState(modelState, elementUdi) { - for (var e in modelState) { + for (const [key, value] of Object.entries(modelState)) { //This is where things get interesting.... // We need to support validation for all editor types such as both the content and content type editors. @@ -403,14 +432,15 @@ function serverValidationManager($timeout, udiService) { // * it's for the mySegment segment // * it's for the myField html field (optional) - var parts = e.split("."); + var parts = key.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") { // create the validation key, might just be the prop alias but if it's nested will be a unique udi - var propertyAlias = createPropertyValidationKey(parts[1], elementUdi); + // like umb://element/GUID/propertyAlias + var propertyValidationKey = createPropertyValidationKey(parts[1], elementUdi); var culture = null; if (parts.length > 2) { @@ -436,16 +466,14 @@ function serverValidationManager($timeout, udiService) { } // add a generic error for the property - addPropertyError(propertyAlias, culture, htmlFieldReference, modelState[e][0], segment); - + addPropertyError(propertyValidationKey, culture, htmlFieldReference, value && Array.isArray(value) && value.length > 0 ? value[0] : null, segment); } else { //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: // Groups[0].Properties[2].Alias - addFieldError(e, modelState[e][0]); + addFieldError(key, value[0]); } - } }