From 459904ffdd99fc3516b8eb81ca13ac1c6ba44544 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 30 Jun 2020 10:49:31 +1000 Subject: [PATCH] Gets unique keys working per nested property --- .../property/umbnestedproperty.component.js | 59 ----------------- .../property/umbproperty.directive.js | 13 +++- .../components/umbnestedcontent.directive.js | 3 +- .../validation/valserver.directive.js | 17 +---- .../services/servervalidationmgr.service.js | 65 +++++++++---------- .../blockeditor/blockeditor.settings.html | 2 +- .../umb-element-editor-content.component.html | 25 +++---- .../umbelementeditorcontent.component.js | 5 +- .../components/property/umb-property.html | 2 + .../inlineblock/inlineblock.editor.html | 3 +- .../nestedcontent/nestedcontent.editor.html | 9 ++- .../nestedcontent.propertyeditor.html | 2 +- .../server-validation-manager.spec.js | 61 ++++++++++++++--- 13 files changed, 117 insertions(+), 149 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbnestedproperty.component.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbnestedproperty.component.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbnestedproperty.component.js deleted file mode 100644 index 2679ce92a0..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbnestedproperty.component.js +++ /dev/null @@ -1,59 +0,0 @@ -(function () { - "use strict"; - - - /** - * @ngdoc component - * @name umbraco.umbNestedProperty - * @function - * - * @description - * Used to have nested property editors within complex editors in order to generate jsonpath for them to be used in validation - */ - angular - .module("umbraco") - .component("umbNestedProperty", { - transclude: true, - template: '
{{vm.getValidationPath()}}
', - controller: NestedPropertyController, - controllerAs: 'vm', - bindings: { - propertyTypeAlias: "@", - elementTypeIndex: "<", - findInScopeChain: "<" // TODO: Use/enable this - }, - require: { - umbNestedProperty: "?^^" - } - }); - - function NestedPropertyController($scope, angularHelper) { - var vm = this; - vm.$onInit = function () { - if (!vm.propertyTypeAlias) { - throw "no propertyTypeAlias specified for umbNestedProperty"; - } - }; - - vm.$postLink = function () { - // if directive inheritance (DOM) doesn't find one, then check scope inheritance - if (!vm.umbNestedProperty/* && findInScopeChain*/) { - var found = angularHelper.traverseScopeChain($scope, s => s.vm && s.vm.constructor.name == "NestedPropertyController"); - if (found) { - vm.umbNestedProperty = found.vm; - } - } - } - - // returns a jsonpath for where this property is located in a hierarchy - // this will call into all hierarchical parents - vm.getValidationPath = function () { - - var path = vm.umbNestedProperty ? vm.umbNestedProperty.getValidationPath() : "$"; - path += ".[nestedValidation].[" + vm.elementTypeIndex + "].[" + vm.propertyTypeAlias + "]"; - return path; - } - } - - -})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index d83b066a89..3f37d06d2b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -7,7 +7,10 @@ angular.module("umbraco.directives") .directive('umbProperty', function (userService) { return { scope: { - property: "=", + property: "=", + elementUdi: "@", + // optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/) + propertyAlias: "@", showInherit: "<", inheritsFrom: "<" }, @@ -43,7 +46,13 @@ angular.module("umbraco.directives") $scope.propertyActions = actions; }; - + // returns the unique Id for the property to be used as the validation key for server side validation logic + self.getValidationPath = function () { + // the elementUdi will be empty when this is not a nested property + var propAlias = $scope.propertyAlias ? $scope.propertyAlias : $scope.property.alias; + return $scope.elementUdi ? ($scope.elementUdi + "/" + propAlias) : propAlias; + } + $scope.getValidationPath = self.getValidationPath; } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js index 8b51c384b0..f9b26c81a5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js @@ -64,8 +64,7 @@ templateUrl: Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/views/propertyeditors/nestedcontent/nestedcontent.editor.html", scope: { ngModel: '=', - tabAlias: '=', - itemIndex: '=' + tabAlias: '=' }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js index cea4cbfde6..d3a7035cac 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js @@ -110,21 +110,8 @@ function valServer(serverValidationManager) { } } - // TODO: If this is a property/field within a complex editor which means it could be a nested/nested/nested property/field - // we need to figure out a way to get it's "Path" (or jsonpath) which can be represented by something like: - // $.[nestedValidation].[0].[prop1].[nestedValidation].[0].[prop2] - // Or ... if we have names instead of indexes (which is seems like we do) - // $.nestedValidation.[type1].[prop1].[nestedValidation].[type2].[prop2] - // This would mean: - // - the first row/item in a complex editor - // - within the property 'prop1' - // - the first row/item in a complex editor - // - within the property 'prop2' - // So how can we figure out this path? The only way is really by looking up our current hierarchy of items - // TODO: OK, so we thought we had it with umb-property being able to know the content type BUT this doesn't work - // because the validation results could have a many rows for the same content type, we need to have the index available - // so the firest example above works much better. - // ... OK ... looks like we have an index to work with, but we'll need to update the block editor to support this too. + // TODO: If this is a property/field within a complex editor which means it could be a nested/nested/nested property/field + // TODO: We have a block $id to work with now so that is what we should be looking to use for the 'key' var propertyValidationPath = umbNestedPropertyCtrl ? umbNestedPropertyCtrl.getValidationPath() : null; 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 d3f798c35f..92e879ecac 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 @@ -98,48 +98,44 @@ function serverValidationManager($timeout) { } } + /** + * Returns a dictionary of id (of the block) and it's corresponding validation ModelState + * @param {any} errorMsg + */ function parseComplexEditorError(errorMsg) { + var json = JSON.parse(errorMsg); - var nestedValidation = json["nestedValidation"]; - if (!nestedValidation) { - throw "Invalid JSON structure for complex property, missing 'nestedValidation'"; + var result = {}; + + function extractModelState(validation) { + if (validation.$id && validation.ModelState) { + result[validation.$id] = validation.ModelState; + } } - nestedValidation.forEach((item, index) => { - - var elementType = Object.keys(item)[0]; - - - var key = "nestedValidation.[" + index + "]."; - - - - }); - - for (var i = 0; i < nestedValidation.length; i++) { - + function iterateErrorBlocks(blocks) { + for (var i = 0; i < blocks.length; i++) { + var validation = blocks[i]; + extractModelState(validation); + var nested = _.omit(validation, "$id", "$elementTypeAlias", "ModelState"); + for (const [key, value] of Object.entries(nested)) { + if (Array.isArray(value)) { + iterateErrorBlocks(value); // recurse + } + } + } } - // each key represents an element type, the key is it's alias - var keys = Object.keys(nestedValidation); - - var asdf = keys; - - // TODO: Could we use an individual instance of serverValidationManager for each element type? It could/should work the way - // it does today since it currently manages all callbacks for all simple properties on a content item based on a content type. - // Hrmmm... only thing is then how to dispose/cleanup of these instances? - - // TODO: ... actually, because we are registering a JSONPath into the 'fieldName' for when complex editors subscribe, perhaps - // the only thing we need to do is build up all of the different JSONPath's and their errors here based on this object and then - // execute callbacks for each? So I think we need to make a function recursively return all possible keys! ... we can even have tests - // for that :) - + iterateErrorBlocks(json); + return result; } return { - + + parseComplexEditorError: parseComplexEditorError, + /** * @ngdoc function * @name notifyAndClearAllSubscriptions @@ -434,12 +430,11 @@ function serverValidationManager($timeout) { } // if the error message is json it's a complex editor validation response that we need to parse - if (errorMsg.startsWith("{")) { + if (errorMsg.startsWith("[")) { - var parsed = parseComplexEditorError(errorMsg); + var idsToErrors = parseComplexEditorError(errorMsg); - // reset to nothing because we will not display the json message but we still want to inform the - // root properties of the error. + // TODO: Make this the generic "Property has errors" but need to find the lang key for that errorMsg = "Hello!"; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.settings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.settings.html index 63222a5cab..df69e2e648 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.settings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.settings.html @@ -1 +1 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umb-element-editor-content.component.html b/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umb-element-editor-content.component.html index aa1a8c5afa..fea0880195 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umb-element-editor-content.component.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umb-element-editor-content.component.html @@ -1,8 +1,8 @@
+ data-element="group-{{group.alias}}" + ng-repeat="group in vm.model.variants[0].tabs track by group.label">
{{ group.label }}
@@ -13,28 +13,23 @@ - - -
- - -
- -
+
+ + +
- + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umbelementeditorcontent.component.js b/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umbelementeditorcontent.component.js index 9658628dfd..cc692c47e1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umbelementeditorcontent.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umbelementeditorcontent.component.js @@ -10,10 +10,7 @@ controller: ElementEditorContentComponentController, controllerAs: 'vm', bindings: { - model: '=', - // As this component is used for creating nested editors based on an element type, we need to know the index of this nested - // editor so that validation works. For example, if this is used in the block editor, this is the index of the block being rendered. - itemIndex: '<' + model: '=' } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 4c757f0951..be8495c30a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -2,6 +2,8 @@
+
{{ getValidationPath() }}
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html index 2840aa87d2..60b3542d6c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html @@ -5,7 +5,6 @@ {{block.label}}
- - +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html index 156f6caed3..a6085cd8c3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html @@ -1,14 +1,13 @@ 
- - - - + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html index 74a821e520..da6e466b50 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html @@ -28,7 +28,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/server-validation-manager.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/server-validation-manager.spec.js index dcc662ff22..be30f2b92d 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/server-validation-manager.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/server-validation-manager.spec.js @@ -316,19 +316,64 @@ describe('managing complex editor validation errors', function () { - it('can retrieve validation errors for the property', function () { + it('create json paths for complex validation error', function () { //arrange - var complexValidationMsg = '{"nestedValidation":[{"textPage":{"title":[{"errorMessage":"WRONG!","memberNames":["innerFieldId"]}]}}]}'; - serverValidationManager.addPropertyError("myProperty", null, null, complexValidationMsg, null); + var complexValidationMsg = `[ + { + "$elementTypeAlias": "addressBook", + "$id": "34E3A26C-103D-4A05-AB9D-7E14032309C3", + "addresses": + [ + { + "$elementTypeAlias": "addressInfo", + "$id": "FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1", + "ModelState": + { + "_Properties.city.invariant.null.country": [ + "City is not in Australia" + ], + "_Properties.city.invariant.null.capital": [ + "Not a capital city" + ] + } + }, + { + "$elementTypeAlias": "addressInfo", + "$id": "7170A4DD-2441-4B1B-A8D3-437D75C4CBC9", + "ModelState": + { + "_Properties.city.invariant.null.country": [ + "City is not in Australia" + ], + "_Properties.city.invariant.null.capital": [ + "Not a capital city" + ] + } + } + ], + "ModelState": + { + "_Properties.addresses.invariant.null.counter": [ + "Must have at least 3 addresses" + ], + "_Properties.bookName.invariant.null.book": [ + "Invalid address book name" + ] + } + } +]`; //act - var err1 = serverValidationManager.getPropertyError("myProperty", null, null, null); + var ids = serverValidationManager.parseComplexEditorError(complexValidationMsg); - //assert - expect(err1).not.toBeUndefined(); - expect(err1.propertyAlias).toEqual("myProperty"); - expect(err1.errorMsg).toEqual(complexValidationMsg); + //assert + var keys = Object.keys(ids); + + expect(keys.length).toEqual(3); + expect(keys[0]).toEqual("34E3A26C-103D-4A05-AB9D-7E14032309C3"); + expect(keys[1]).toEqual("FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1"); + expect(keys[2]).toEqual("7170A4DD-2441-4B1B-A8D3-437D75C4CBC9"); });