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 a67f51bfed..2b2f36dd7d 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 @@ -19,7 +19,7 @@ }, bindings: { property: "=", - elementUdi: "@", + elementKey: "@", // optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/) propertyAlias: "@", showInherit: "<", @@ -44,20 +44,14 @@ vm.propertyActions = actions; }; - // returns the unique Id for the property to be used as the validation key for server side validation logic + // returns the validation path for the property to be used as the validation key for server side validation logic vm.getValidationPath = function () { - // the elementUdi will be empty when this is not a nested property + var parentValidationPath = vm.parentUmbProperty ? vm.parentUmbProperty.getValidationPath() : null; var propAlias = vm.propertyAlias ? vm.propertyAlias : vm.property.alias; - vm.elementUdi = ensureUdi(vm.elementUdi); - return serverValidationManager.createPropertyValidationKey(propAlias, vm.elementUdi); - } - - vm.getParentValidationPath = function () { - if (!vm.parentUmbProperty) { - return null; - } - return vm.parentUmbProperty.getValidationPath(); + // the elementKey will be empty when this is not a nested property + var valPath = vm.elementKey ? vm.elementKey + "/" + propAlias : propAlias; + return serverValidationManager.createPropertyValidationKey(valPath, parentValidationPath); } function onInit() { @@ -70,8 +64,6 @@ }); } - vm.elementUdi = ensureUdi(vm.elementUdi); - if (!vm.parentUmbProperty) { // not found, then fallback to searching the scope chain, this may be needed when DOM inheritance isn't maintained but scope // inheritance is (i.e.infinite editing) @@ -80,13 +72,6 @@ } } - // if only a guid is passed in, we'll ensure a correct udi structure - function ensureUdi(udi) { - if (udi && !udi.startsWith("umb://")) { - udi = udiService.build("element", udi); - } - return udi; - } } })(); 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 353ff2a137..fe3369f731 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 @@ -24,6 +24,7 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel var unsubscribe = []; var watcher = null; var hasError = false; + var hasServerError = false; // tracks if this validator has an explicit server validator key attached to it //create properties on our custom scope so we can use it in our template scope.errorMsg = ""; @@ -49,9 +50,6 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel var isMandatory = currentProperty.validation ? currentProperty.validation.mandatory : undefined; var labels = {}; - localizationService.localize("errors_propertyHasErrors").then(function (data) { - labels.propertyHasErrors = data; - }); if (umbVariantCtrl) { //if we are inside of an umbVariantContent directive @@ -69,7 +67,6 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel // if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language. currentCulture = currentCulture || "invariant"; - // Gets the error message to display function getErrorMsg() { //this can be null if no property was assigned @@ -96,11 +93,6 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel // the form. Of course normal client-side validators will continue to execute. function startWatch() { - // TODO: Can we watch on something other than the value?? This doesn't work for complex editors especially once that have a - // viewmodel/model setup the value that this is watching doesn't actually get updated by a sub-editor in all cases. - // we can probably watch the formCtrl view value? But then we also don't want this to watch complex values that have sub editors anyways - // since that might end up clearing the whole chain of valPropertyMsg when a sub value is changed (in some cases, not with the block editor). - //if there's not already a watch if (!watcher) { @@ -125,6 +117,25 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel if (errCount === 0 || (errCount === 1 && Utilities.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && Utilities.isArray(formCtrl.$error.valServer))) { + + console.log("RESETTING ERROR FROM WATCH " + propertyValidationKey + " - " + hasServerError); + + if (hasServerError) { + + // check if we can clear it based on child server errors, if we are the only explicit one remaining we can clear ourselves + var nestedErrs = serverValidationManager.getPropertyErrorsByValidationPath( + propertyValidationKey, + currentCulture, + "", + currentSegment, + true); + if (nestedErrs.length === 1 && nestedErrs[0].propertyAlias === propertyValidationKey) { + + console.log("CLEARING SERVER VAL FROM WATCH " + propertyValidationKey); + serverValidationManager.removePropertyError(propertyValidationKey, currentCulture, "", currentSegment); + } + } + resetError(); } else if (showValidation && scope.errorMsg === "") { @@ -144,104 +155,18 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel } function resetError() { - var hadError = hasError; hasError = false; formCtrl.$setValidity('valPropertyMsg', true, formCtrl); scope.errorMsg = ""; stopWatch(); - - // if we had an error, then check on the current valFormManager to see if it's - // now valid, if it is it means that the containing form (i.e. the form rendering) - // properties for an element/content type) is now valid which means we can clear - // the parent's valPropertyMsg if there is one. This will only occur with complex editors - // where we have nested umb-property components. - if (hadError) { - scope.$evalAsync(function () { - - //// TODO: This does not work :( :( :( - //// We cannot clear a val-property-msg because another nested child might have server validation errors too. - //// I 'think' we might be able to set the UI validation of this val-property-msg based on the child validators as well - //// as the server validator so it can 'just' unset itself if all child validators are cleared. Can it be done? - - //// Here we loop over the umbProperty hierarchy to see if we should clear the val-property-msg server validation key. - //// we will clear the key if the parent for is valid, or if the parent form is only invalid due to a single val-property-msg error. - //var currUmbProperty = umbPropCtrl; - //var parentValidationKey = currUmbProperty.getParentValidationPath(); - //while (currUmbProperty && parentValidationKey) { - - // if (!currUmbProperty.parentForm.$invalid || (_.keys(currUmbProperty.parentForm.$error).length === 1 && currUmbProperty.parentForm.$error.valPropertyMsg)) { - // serverValidationManager.removePropertyError(parentValidationKey, currentCulture, "", currentSegment); - - // // re-assign and loop - // if (currUmbProperty !== umbPropCtrl.parentUmbProperty) { - // currUmbProperty = umbPropCtrl.parentUmbProperty; - // parentValidationKey = currUmbProperty ? currUmbProperty.getParentValidationPath() : null; - // } - // else { - // break; - // } - // } - // else { - // break; - // } - //} - - //// we need to navigate the parentForm here, unfortunately there's no real alternative unless we create our own directive - //// of some sort but that would also get messy. This works though since in this case we're always going to be in the property - //// form and the parent form about this will contain any invalid flags for all the other sibling properties. So when that is - //// no longer invalid, we can check if we have a parent validation key (meaning we'd be nested inside of umb-property) and - //// we can clear that server error. - //// TODO: If there is another server error for this property though this might clear it inadvertently, at this time I'm unsure how to deal with that. - //var parentValidationKey = umbPropCtrl.getParentValidationPath(); - //if (parentValidationKey) { - // // TODO: Instead of using the parent form, can we 'just' use umbProperty again which itself can check if it's - // // parent form is valid? then below we can call in a loop each parent umb property check if it has a parent validation - // // path and check if it's form is valid, this will recursively perform this logic up the chain. - // var parentForm = formCtrl.$$parentForm; - // if (parentForm && !parentForm.$invalid) { - // // TODO: Though this works for one level, if you have errors at level 1 and 2, clear errors at level when - // // and then level 2, then only the val-property-msg is cleared at level 1 and not also at level 0. - // // So we still need to recurse up the chain to deal with this - // serverValidationManager.removePropertyError(parentValidationKey, currentCulture, "", currentSegment); - // } - //} - - }); - - - } } - //function checkFormValidation(f) { - - // if (!angularHelper.isForm(f)) { - // throw "The object is not an angular Form"; - // } - - // // if there's no value, the controls can be reset, which clears the error state on formCtrl - // for (let control of formCtrl.$getControls()) { - // control.$setValidity(); - // } - - //} - function checkValidationStatus() { if (formCtrl.$invalid) { //first we need to check if the valPropertyMsg validity is invalid if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) { //since we already have an error we'll just return since this means we've already set the - // hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe - - // TODO: This does not work! we cannot just clear if there are no errors in child controls because child controls - // won't even have been loaded yet so this will just instantly clear them - //// At this stage we might have an error assigned because it was assigned based on validation hierarchy from the server, - //// BUT one or ALL of the child server (and client) validations may be cleared at this point. We will know if we have an - //// explicitly assigned error based on the error message assigned, if it is a non-explicit error (meaning that the error - //// was assigned because it has child errors) then the message will just be: labels.propertyHasErrors - //if (scope.errorMsg === labels.propertyHasErrors && _.every(formCtrl.$getControls(), c => c.$valid)) { - // resetError(); - //} - + //hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe return; } //if there are any errors in the current property form that are not valPropertyMsg @@ -275,73 +200,92 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel } } - //if there's any remaining errors in the server validation service then we should show them. - var showValidation = serverValidationManager.items.length > 0; - if (!showValidation) { - //We can either get the form submitted status by the parent directive valFormManager (if we add a property to it) - //or we can just check upwards in the DOM for the css class (easier for now). - //The initial hidden state can't always be hidden because when we switch variants in the content editor we cannot - //reset the status. - showValidation = element.closest(".show-validation").length > 0; - } + function onInit() { + localizationService.localize("errors_propertyHasErrors").then(function (data) { - //listen for form validation changes. - //The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then - // subscribing to this single watch. - valFormManager.onValidationStatusChanged(function (evt, args) { - checkValidationStatus(); - }); + labels.propertyHasErrors = data; - //listen for the forms saving event - unsubscribe.push(scope.$on("formSubmitting", function (ev, args) { - showValidation = true; - if (hasError && scope.errorMsg === "") { - scope.errorMsg = getErrorMsg(); - startWatch(); - } - else if (!hasError) { - resetError(); - } - })); - - //listen for the forms saved event - unsubscribe.push(scope.$on("formSubmitted", function (ev, args) { - showValidation = false; - resetError(); - })); - - //listen for server validation changes - // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for - // validation changes to fields in the property this is because some server side validators may not - // return the field name for which the error belongs too, just the property for which it belongs. - // It's important to note that we need to subscribe to server validation changes here because we always must - // 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.currentProperty) { //this can be null if no property was assigned - - function serverValidationManagerCallback(isValid, propertyErrors, allErrors) { - hasError = !isValid; - if (hasError) { - //set the error message to the server message - scope.errorMsg = propertyErrors[0].errorMsg ? propertyErrors[0].errorMsg : labels.propertyHasErrors; - //flag that the current validator is invalid - formCtrl.$setValidity('valPropertyMsg', false, formCtrl); - startWatch(); + //if there's any remaining errors in the server validation service then we should show them. + var showValidation = serverValidationManager.items.length > 0; + if (!showValidation) { + //We can either get the form submitted status by the parent directive valFormManager (if we add a property to it) + //or we can just check upwards in the DOM for the css class (easier for now). + //The initial hidden state can't always be hidden because when we switch variants in the content editor we cannot + //reset the status. + showValidation = element.closest(".show-validation").length > 0; } - else { + + //listen for form validation changes. + //The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then + // subscribing to this single watch. + valFormManager.onValidationStatusChanged(function (evt, args) { + checkValidationStatus(); + }); + + //listen for the forms saving event + unsubscribe.push(scope.$on("formSubmitting", function (ev, args) { + showValidation = true; + if (hasError && scope.errorMsg === "") { + scope.errorMsg = getErrorMsg(); + startWatch(); + } + else if (!hasError) { + resetError(); + } + })); + + //listen for the forms saved event + unsubscribe.push(scope.$on("formSubmitted", function (ev, args) { + showValidation = false; resetError(); - } - } + })); - unsubscribe.push(serverValidationManager.subscribe( - propertyValidationKey, - currentCulture, - "", - serverValidationManagerCallback, - currentSegment - )); + if (scope.currentProperty) { //this can be null if no property was assigned + + //listen for server validation changes + // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for + // validation changes to fields in the property this is because some server side validators may not + // return the field name for which the error belongs too, just the property for which it belongs. + // It's important to note that we need to subscribe to server validation changes here because we always must + // 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. + + function serverValidationManagerCallback(isValid, propertyErrors, allErrors) { + var hadError = hasError; + hasError = !isValid; + if (hasError) { + + // check if one of the errors is explicitly assigned to our validation key + hasServerError = _.some(propertyErrors, x => x.propertyAlias === propertyValidationKey); + if (hadError && hasServerError && propertyErrors.length === 1) { + // we're the only error remaining in the server validation + console.log("ONLY ERROR REMAINING " + propertyValidationKey); + } + + //set the error message to the server message + scope.errorMsg = propertyErrors[0].errorMsg ? propertyErrors[0].errorMsg : labels.propertyHasErrors; + //flag that the current validator is invalid + console.log("valPropertyMsg invalid - " + propertyValidationKey); + formCtrl.$setValidity('valPropertyMsg', false, formCtrl); + startWatch(); + } + else { + resetError(); + } + } + + unsubscribe.push(serverValidationManager.subscribe( + propertyValidationKey, + currentCulture, + "", + serverValidationManagerCallback, + currentSegment, + true // match property validation path prefix + )); + } + + }); } //when the scope is disposed we need to unsubscribe @@ -351,6 +295,8 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel unsubscribe[u](); } }); + + onInit(); } 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 2846a9d196..84542bf184 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 @@ -82,6 +82,7 @@ function valServer(serverValidationManager) { //clear the server validation entry serverValidationManager.removePropertyError(propertyValidationPath, currentCulture, fieldName, currentSegment); + stopWatch(); } }, true); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js index ec232b4914..4920f2b911 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js @@ -375,7 +375,7 @@ * @name getBlockObject * @methodOf umbraco.services.blockEditorModelObject * @description Retrieve a Block Object for the given layout entry. - * The Block Object offers the nesecary data to display and edit a block. + * The Block Object offers the necessary data to display and edit a block. * The Block Object setups live syncronization of content and settings models back to the data of your Property Editor model. * The returned object, named ´BlockObject´, contains several usefull models to make editing of this block happen. * The ´BlockObject´ contains the following properties: @@ -448,6 +448,8 @@ // make basics from scaffold blockObject.content = Utilities.copy(contentScaffold); blockObject.content.udi = udi; + // Change the content.key to the GUID part of the udi, else it's just random which we don't want, it should be consistent + blockObject.content.key = udiService.getKey(udi); mapToElementModel(blockObject.content, dataModel); 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 8801c3aac8..8b900bc6ea 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 @@ -52,6 +52,9 @@ function serverValidationManager($timeout) { * Notifies all subscriptions again. Called when there are changes to subscriptions or errors. */ function notify() { + + console.log("NOTIFY!"); + $timeout(function () { console.log(`VAL-ERROR-COUNT: ${items.length}`); @@ -92,7 +95,8 @@ function serverValidationManager($timeout) { //find all errors for this property return _.filter(items, function (item) { - return ((matchPrefixValidationPath ? (item.propertyAlias === propertyAlias || propertyAlias.startsWith(item.propertyAlias + '/')) : item.propertyAlias === propertyAlias) + return ((matchPrefixValidationPath ? (item.propertyAlias === propertyAlias || (item.propertyAlias && item.propertyAlias.startsWith(propertyAlias + '/'))) : item.propertyAlias === propertyAlias) + //return ((matchPrefixValidationPath ? (item.propertyAlias === propertyAlias || propertyAlias.startsWith(item.propertyAlias + '/')) : item.propertyAlias === propertyAlias) && item.culture === culture && item.segment === segment // ignore field matching if @@ -115,29 +119,36 @@ function serverValidationManager($timeout) { }); } + function notifyCallback(cb) { + if (cb.propertyAlias === null && cb.fieldName !== null) { + //its a field error callback + const fieldErrors = getFieldErrors(cb.fieldName); + const valid = fieldErrors.length === 0; + executeCallback(fieldErrors, cb.callback, cb.culture, cb.segment, valid); + } + else if (cb.propertyAlias != null) { + //its a property error + const propErrors = getPropertyErrors(cb.propertyAlias, cb.culture, cb.segment, cb.fieldName, cb.matchPrefix); + const valid = propErrors.length === 0; + executeCallback(propErrors, cb.callback, cb.culture, cb.segment, valid); + } + else { + //its a variant error + const variantErrors = getVariantErrors(cb.culture, cb.segment); + const valid = variantErrors.length === 0; + executeCallback(variantErrors, cb.callback, cb.culture, cb.segment, valid); + } + } + /** Call all registered callbacks indicating if the data they are subscribed to is valid or invalid */ function notifyCallbacks() { - callbacks.forEach(cb => { - if (cb.propertyAlias === null && cb.fieldName !== null) { - //its a field error callback - const fieldErrors = getFieldErrors(cb.fieldName); - const valid = fieldErrors.length === 0; - executeCallback(fieldErrors, cb.callback, cb.culture, cb.segment, valid); - } - else if (cb.propertyAlias != null) { - //its a property error - const propErrors = getPropertyErrors(cb.propertyAlias, cb.culture, cb.segment, cb.fieldName, cb.matchPrefix); - const valid = propErrors.length === 0; - executeCallback(propErrors, cb.callback, cb.culture, cb.segment, valid); - } - else { - //its a variant error - const variantErrors = getVariantErrors(cb.culture, cb.segment); - const valid = variantErrors.length === 0; - executeCallback(variantErrors, cb.callback, cb.culture, cb.segment, valid); - } - }); + // nothing to call + if (items.length === 0) { + return; + } + + callbacks.forEach(cb => notifyCallback(cb)); } /** @@ -343,29 +354,7 @@ function serverValidationManager($timeout) { }); } - //find all errors for this item - var propertyErrors = getPropertyErrors(propertyAlias, culture, segment, fieldName); - var propertyPrefixErrors = getPropertyErrors(propertyAlias, culture, segment, fieldName, true); - - //now call all of the call backs registered for this error - var cbs = getPropertyCallbacks(propertyAlias, culture, fieldName, segment); - - //call each callback for this error - cbs.forEach(cb => { - if (cb.matchPrefix) { - executeCallback(propertyPrefixErrors, cb.callback, culture, segment, false); - } - else { - executeCallback(propertyErrors, cb.callback, culture, segment, false); - } - }); - - //execute variant specific callbacks here too when a propery error is added - var variantCbs = getVariantCallbacks(culture, segment); - //call each callback for this error - variantCbs.forEach(cb => { - executeCallback(propertyErrors, cb.callback, culture, segment, false); - }); + notifyCallbacks(); } /** @@ -602,30 +591,33 @@ function serverValidationManager($timeout) { segment = null; } - if (propertyAlias === null) { + let cb = null; - callbacks.push({ + if (propertyAlias === null) { + cb = { propertyAlias: null, culture: culture, segment: segment, fieldName: fieldName, callback: callback, id: id - }); + }; } else if (propertyAlias !== undefined) { - callbacks.push({ + cb = { propertyAlias: propertyAlias, - culture: culture, + culture: culture, segment: segment, fieldName: fieldName, callback: callback, id: id, matchPrefix: matchValidationPathPrefix - }); + }; } + callbacks.push(cb); + function unsubscribeId() { //remove all callbacks for the content field callbacks = _.reject(callbacks, function (item) { @@ -633,9 +625,8 @@ function serverValidationManager($timeout) { }); } - // Now notify the registrations for this callback if we've previously been notified and we're not cleared. - // This will happen for dynamically shown editors, like complex editors that load in sub element types. - notify(); + // Notify the new callback + notifyCallback(cb); //return a function to unsubscribe this subscription by uniqueId return unsubscribeId; @@ -733,7 +724,7 @@ function serverValidationManager($timeout) { if (items.length !== count) { // removal was successful, re-notify all subscribers - notify(); + notifyCallbacks(); } }, @@ -757,6 +748,10 @@ function serverValidationManager($timeout) { return undefined; }, + getPropertyErrorsByValidationPath: function (propertyAlias, culture, segment) { + return getPropertyErrors(propertyAlias, culture, segment, "", true); + }, + /** * @ngdoc function * @name getFieldError diff --git a/src/Umbraco.Web.UI.Client/src/common/services/udi.service.js b/src/Umbraco.Web.UI.Client/src/common/services/udi.service.js index 980d5ddc72..3d3db8d346 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/udi.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/udi.service.js @@ -27,6 +27,21 @@ build: function (entityType, guid) { return "umb://" + entityType + "/" + (guid.replace(/-/g, "")); + }, + + getKey: function (udi) { + if (!Utilities.isString(udi)) { + throw "udi is not a string"; + } + if (!udi.startsWith("umb://")) { + throw "udi does not start with umb://"; + } + var withoutScheme = udi.substr("umb://".length); + var withoutHost = withoutScheme.substr(withoutScheme.indexOf("/") + 1).trim(); + if (withoutHost.length !== 32) { + throw "udi is not 32 chars"; + } + return `${withoutHost.substr(0, 8)}-${withoutHost.substr(8, 4)}-${withoutHost.substr(12, 4)}-${withoutHost.substr(16, 4)}-${withoutHost.substr(20)}`; } } } 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 0c6ccbd22f..fae639562f 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 @@ -13,7 +13,7 @@ data-element="property-{{property.alias}}" ng-repeat="property in group.properties track by property.alias" property="property" - element-udi="{{vm.model.udi}}" + element-key="{{vm.model.key}}" show-inherit="vm.model.variants.length > 1 && !property.culture && !activeVariant.language.isDefault" inherits-from="defaultVariant.language.name"> 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 968dc63265..125e920fe6 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 @@ -3,7 +3,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 27258c96a4..0627063be5 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 @@ -1,9 +1,11 @@ describe('serverValidationManager tests', function () { - var serverValidationManager; + var $rootScope, serverValidationManager, $timeout; beforeEach(module('umbraco.services')); beforeEach(inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $timeout = $injector.get('$timeout'); serverValidationManager = $injector.get('serverValidationManager'); serverValidationManager.clear(); })); @@ -480,7 +482,9 @@ allErrors: allErrors }; }, null); + + //act serverValidationManager.addFieldError("Name", "Required"); serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); @@ -501,7 +505,9 @@ var cb2 = function () { }; serverValidationManager.subscribe(null, null, "Name", cb1, null); + serverValidationManager.subscribe(null, null, "Title", cb2, null); + //act serverValidationManager.addFieldError("Name", "Required"); @@ -537,6 +543,7 @@ }; }, null); + serverValidationManager.subscribe("myProperty", null, "", function (isValid, propertyErrors, allErrors) { numCalled++; args2 = { @@ -545,6 +552,9 @@ allErrors: allErrors }; }, null); + + + console.log("NOW ADDING ERRORS " + numCalled); //act serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); @@ -587,6 +597,7 @@ allErrors: allErrors }; }, null); + serverValidationManager.subscribe(null, "es-ES", null, function (isValid, propertyErrors, allErrors) { numCalled++; @@ -596,6 +607,7 @@ allErrors: allErrors }; }, null); + //act serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); @@ -604,28 +616,30 @@ serverValidationManager.addPropertyError("myProperty", "fr-FR", "", "Some value 3", null); //assert - expect(args1).not.toBeUndefined(); expect(args1.isValid).toBe(false); + expect(args2.isValid).toBe(true); // no errors registered for this callback - expect(args2).toBeUndefined(); - - expect(numCalled).toEqual(2); + expect(numCalled).toEqual(8); // both subscriptions will be called once per addPropertyError }); it('can subscribe to a property validation path prefix', function () { - var numCalled = 0; + var callbackA = []; + var callbackB = []; //arrange serverValidationManager.subscribe("myProperty", null, null, function (isValid, propertyErrors, allErrors) { - numCalled++; - // since this is matching on prefix, there should be as many property errors as numCalled - expect(propertyErrors.length).toEqual(numCalled); + callbackA.push(propertyErrors); + }, null, true); + + serverValidationManager.subscribe("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses", null, null, function (isValid, propertyErrors, allErrors) { + callbackB.push(propertyErrors); }, null, true); //act - // will match: + // will match A: serverValidationManager.addPropertyError("myProperty", null, null, "property error", null); serverValidationManager.addPropertyError("myProperty", null, "value1", "value error", null); + // will match A + B serverValidationManager.addPropertyError("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", null, null, "property error", null); serverValidationManager.addPropertyError("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", null, "value1", "value error", null); // won't match: @@ -635,7 +649,25 @@ serverValidationManager.addPropertyError("otherProperty", null, "value1", "value error", null); //assert - expect(numCalled).toEqual(4); + + // both will be called each time addPropertyError is called + expect(callbackA.length).toEqual(8); + expect(callbackB.length).toEqual(8); + expect(callbackA[callbackA.length - 1].length).toEqual(4); // 4 errors for A + expect(callbackB[callbackB.length - 1].length).toEqual(2); // 2 errors for B + + // clear the data and notify + callbackA = []; + callbackB = []; + + serverValidationManager.notify(); + $timeout.flush(); + + expect(callbackA.length).toEqual(1); + expect(callbackB.length).toEqual(1); + expect(callbackA[0].length).toEqual(4); // 4 errors for A + expect(callbackB[0].length).toEqual(2); // 2 errors for B + }); // TODO: Finish testing the rest!