Gets unique keys working per nested property
This commit is contained in:
-59
@@ -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: '<div><pre>{{vm.getValidationPath()}}</pre><div ng-transclude></div></div>',
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})();
|
||||
+11
-2
@@ -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;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
+1
-2
@@ -64,8 +64,7 @@
|
||||
templateUrl: Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/views/propertyeditors/nestedcontent/nestedcontent.editor.html",
|
||||
scope: {
|
||||
ngModel: '=',
|
||||
tabAlias: '=',
|
||||
itemIndex: '='
|
||||
tabAlias: '='
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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!";
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
<umb-element-editor-content model="model.settings" item-index="model.index"></umb-element-editor-content>
|
||||
<umb-element-editor-content model="model.settings"></umb-element-editor-content>
|
||||
|
||||
+10
-15
@@ -1,8 +1,8 @@
|
||||
<div class="form-horizontal">
|
||||
<ng-form name="elementTypeContentForm">
|
||||
<div class="umb-group-panel"
|
||||
data-element="group-{{group.alias}}"
|
||||
ng-repeat="group in vm.model.variants[0].tabs track by group.label">
|
||||
data-element="group-{{group.alias}}"
|
||||
ng-repeat="group in vm.model.variants[0].tabs track by group.label">
|
||||
|
||||
<div class="umb-group-panel__header">
|
||||
<div id="group-{{group.id}}">{{ group.label }}</div>
|
||||
@@ -13,28 +13,23 @@
|
||||
<umb-property data-element="property-{{property.alias}}"
|
||||
ng-repeat="property in group.properties track by property.alias"
|
||||
property="property"
|
||||
element-udi="{{vm.model.udi}}"
|
||||
show-inherit="vm.model.variants.length > 1 && !property.culture && !activeVariant.language.isDefault"
|
||||
inherits-from="defaultVariant.language.name">
|
||||
|
||||
<umb-nested-property property-type-alias="{{property.alias}}"
|
||||
element-type-index="vm.itemIndex">
|
||||
|
||||
<div ng-class="{'o-40 cursor-not-allowed': vm.model.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue}">
|
||||
<umb-property-editor model="property"
|
||||
preview="vm.model.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue">
|
||||
</umb-property-editor>
|
||||
</div>
|
||||
|
||||
</umb-nested-property>
|
||||
<div ng-class="{'o-40 cursor-not-allowed': vm.model.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue}">
|
||||
<umb-property-editor model="property"
|
||||
preview="vm.model.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue">
|
||||
</umb-property-editor>
|
||||
</div>
|
||||
|
||||
</umb-property>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<umb-empty-state
|
||||
ng-if="vm.model.tabs.length === 0"
|
||||
position="center">
|
||||
<umb-empty-state ng-if="vm.model.tabs.length === 0"
|
||||
position="center">
|
||||
<localize key="content_noProperties"></localize>
|
||||
</umb-empty-state>
|
||||
|
||||
|
||||
+1
-4
@@ -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: '='
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<ng-form name="propertyForm">
|
||||
<div class="control-group umb-control-group" ng-class="{hidelabel:property.hideLabel, 'umb-control-group__listview': property.alias === '_umb_containerView'}">
|
||||
|
||||
<pre>{{ getValidationPath() }}</pre>
|
||||
|
||||
<val-property-msg></val-property-msg>
|
||||
|
||||
<div class="umb-el-wrap">
|
||||
|
||||
+1
-2
@@ -5,7 +5,6 @@
|
||||
<span>{{block.label}}</span>
|
||||
</button>
|
||||
<div class="blockelement-inlineblock-editor__inner" ng-class="{'--singleGroup':block.content.variants[0].tabs.length === 1}" ng-if="block.active === true">
|
||||
<!-- NOTE: The 'index' is passed to this via it's scope which is set on the outer component: umbBlockListBlockContent -->
|
||||
<umb-element-editor-content model="block.content" item-index="index"></umb-element-editor-content>
|
||||
<umb-element-editor-content model="block.content"></umb-element-editor-content>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+4
-5
@@ -1,14 +1,13 @@
|
||||
<div class="umb-pane">
|
||||
<div ng-repeat="property in tab.properties" class="umb-nested-content-property-container">
|
||||
|
||||
<umb-property property="property"
|
||||
<umb-property property="property"
|
||||
property-alias="{{property.propertyAlias}}"
|
||||
element-udi="{{'umb://element/' + model.key}}"
|
||||
ng-class="{'umb-nested-content--not-supported': property.notSupported, 'umb-nested-content--mandatory': property.ncMandatory}"
|
||||
data-element="property-{{property.alias}}">
|
||||
|
||||
<umb-nested-property property-type-alias="{{property.propertyAlias}}"
|
||||
element-type-index="itemIndex">
|
||||
<umb-property-editor model="property"></umb-property-editor>
|
||||
</umb-nested-property>
|
||||
<umb-property-editor model="property"></umb-property-editor>
|
||||
|
||||
</umb-property>
|
||||
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
|
||||
<div class="umb-nested-content__content" ng-if="vm.currentNode.key === node.key && !vm.sorting">
|
||||
<umb-nested-content-editor ng-model="node" item-index="$index" tab-alias="ncTabAlias" />
|
||||
<umb-nested-content-editor ng-model="node" tab-alias="ncTabAlias" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+53
-8
@@ -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");
|
||||
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user