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 a85a9c38fe..a34c4aa0cc 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 @@ -288,7 +288,7 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel "", serverValidationManagerCallback, currentSegment, - true // match property validation path prefix + { matchPrefix: true } // match property validation path prefix )); } 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 6ad8ff1e9b..b9b9b98174 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 @@ -111,8 +111,6 @@ function valServer(serverValidationManager) { } } - - unsubscribe.push(serverValidationManager.subscribe( propertyValidationPath, currentCulture, 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 657c4e75fe..378eb79dc7 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 @@ -17,7 +17,7 @@ function serverValidationManager($timeout) { // - segment // - callback (function) // - id (unique identifier, auto-generated, used internally for unsubscribing the callback) - // - matchPrefix (used for complex properties, default is false, if set to true the callback will fire for any item with this propertyAlias prefix) + // - options (used for complex properties, can contain options.matchPrefix options.matchSuffix if either are set to true the callback will fire for any item with this propertyAlias prefix or suffix) var callbacks = []; // The array of error message objects, each object 'key' is: @@ -28,7 +28,12 @@ function serverValidationManager($timeout) { // The object also contains: // - errorMsg var items = []; - + + var defaultMatchOptions = { + matchPrefix: false, + matchSuffix: false + } + /** calls the callback specified with the errors specified, used internally */ function executeCallback(errorsForCallback, callback, culture, segment, isValid) { @@ -72,7 +77,7 @@ function serverValidationManager($timeout) { }); } - function getPropertyErrors(propertyAlias, culture, segment, fieldName, matchPrefixValidationPath) { + function getPropertyErrors(propertyAlias, culture, segment, fieldName, options) { if (!Utilities.isString(propertyAlias)) { throw "propertyAlias must be a string"; } @@ -87,14 +92,26 @@ function serverValidationManager($timeout) { segment = null; } + if (!options) { + options = defaultMatchOptions; + } + //find all errors for this property return _.filter(items, function (item) { - 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) + + var matchProp = options.matchPrefix + ? (item.propertyAlias === propertyAlias || (item.propertyAlias && item.propertyAlias.startsWith(propertyAlias + '/'))) + : options.matchSuffix + ? (item.propertyAlias === propertyAlias || (item.propertyAlias && item.propertyAlias.endsWith('/' + propertyAlias))) + : item.propertyAlias === propertyAlias; + + var ignoreField = options.matchPrefix || options.matchSuffix; + + return matchProp && item.culture === culture && item.segment === segment - // ignore field matching if - && (matchPrefixValidationPath || (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")))); + // ignore field matching if match options are used + && (ignoreField || (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); } @@ -122,7 +139,7 @@ function serverValidationManager($timeout) { } else if (cb.propertyAlias != null) { //its a property error - const propErrors = getPropertyErrors(cb.propertyAlias, cb.culture, cb.segment, cb.fieldName, cb.matchPrefix); + const propErrors = getPropertyErrors(cb.propertyAlias, cb.culture, cb.segment, cb.fieldName, cb.options); const valid = propErrors.length === 0; executeCallback(propErrors, cb.callback, cb.culture, cb.segment, valid); } @@ -213,12 +230,24 @@ function serverValidationManager($timeout) { var found = _.filter(callbacks, function (cb) { + if (!cb.options) { + cb.options = defaultMatchOptions; + } + + var matchProp = cb.options.matchPrefix + ? (cb.propertyAlias === propertyAlias || propertyAlias.startsWith(cb.propertyAlias + '/')) + : cb.options.matchSuffix + ? (cb.propertyAlias === propertyAlias || propertyAlias.endsWith(cb.propertyAlias + '/')) + : cb.propertyAlias === propertyAlias; + + var ignoreField = cb.options.matchPrefix || cb.options.matchSuffix; + //returns any callback that have been registered directly against the field and for only the property - return ((cb.matchPrefix ? (cb.propertyAlias === propertyAlias || propertyAlias.startsWith(cb.propertyAlias + '/')) : cb.propertyAlias === propertyAlias) + return matchProp && cb.culture === culture && cb.segment === segment // if the callback is configured to patch prefix then we ignore the field value - && (cb.matchPrefix || (cb.fieldName === fieldName || (cb.fieldName === undefined || cb.fieldName === "")))); + && (ignoreField || (cb.fieldName === fieldName || (cb.fieldName === undefined || cb.fieldName === ""))); }); return found; } @@ -567,7 +596,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, culture, fieldName, callback, segment, matchValidationPathPrefix) { + subscribe: function (propertyAlias, culture, fieldName, callback, segment, options) { if (!callback) { return; } @@ -604,7 +633,7 @@ function serverValidationManager($timeout) { fieldName: fieldName, callback: callback, id: id, - matchPrefix: matchValidationPathPrefix + options: options }; } @@ -740,8 +769,8 @@ function serverValidationManager($timeout) { return undefined; }, - getPropertyErrorsByValidationPath: function (propertyAlias, culture, segment) { - return getPropertyErrors(propertyAlias, culture, segment, "", true); + getPropertyErrorsByValidationPath: function (propertyAlias, culture, segment, options) { + return getPropertyErrors(propertyAlias, culture, segment, "", options); }, /** 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 0627063be5..c5c6177444 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 @@ -533,6 +533,7 @@ var args1; var args2; var numCalled = 0; + var numCalledWithErrors = 0; //arrange serverValidationManager.subscribe("myProperty", null, "value1", function (isValid, propertyErrors, allErrors) { @@ -546,6 +547,9 @@ serverValidationManager.subscribe("myProperty", null, "", function (isValid, propertyErrors, allErrors) { numCalled++; + if (propertyErrors.length > 0) { + numCalledWithErrors++; + } args2 = { isValid: isValid, propertyErrors: propertyErrors, @@ -553,9 +557,6 @@ }; }, null); - - console.log("NOW ADDING ERRORS " + numCalled); - //act serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); serverValidationManager.addPropertyError("myProperty", null, "value2", "Some value 2", null); @@ -578,19 +579,23 @@ expect(args2.propertyErrors[0].errorMsg).toEqual("Some value 1"); expect(args2.propertyErrors[1].errorMsg).toEqual("Some value 2"); expect(args2.allErrors.length).toEqual(2); - //Even though only 2 errors are added, the callback is called 3 times because any call to addPropertyError will invoke the callback - // if the property has errors existing. - expect(numCalled).toEqual(3); + //3 errors are added but a call to subscribe also calls the callback + expect(numCalled).toEqual(4); + expect(numCalledWithErrors).toEqual(3); }); it('can subscribe to a culture error for both a property and its sub field', function () { var args1; var args2; var numCalled = 0; + var numCalledWithErrors = 0; //arrange serverValidationManager.subscribe(null, "en-US", null, function (isValid, propertyErrors, allErrors) { numCalled++; + if (propertyErrors.length > 0) { + numCalledWithErrors++; + } args1 = { isValid: isValid, propertyErrors: propertyErrors, @@ -601,25 +606,28 @@ serverValidationManager.subscribe(null, "es-ES", null, function (isValid, propertyErrors, allErrors) { numCalled++; + if (propertyErrors.length > 0) { + numCalledWithErrors++; + } args2 = { isValid: isValid, propertyErrors: propertyErrors, allErrors: allErrors }; }, null); - //act - serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); - serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1", null); - serverValidationManager.addPropertyError("myProperty", "en-US", "value2", "Some value 2", null); - serverValidationManager.addPropertyError("myProperty", "fr-FR", "", "Some value 3", null); + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); // doesn't match + serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1", null); // matches callback 1 + serverValidationManager.addPropertyError("myProperty", "en-US", "value2", "Some value 2", null); // matches callback 1 + serverValidationManager.addPropertyError("myProperty", "fr-FR", "", "Some value 3", null); // doesn't match - but all callbacks still execute //assert expect(args1.isValid).toBe(false); expect(args2.isValid).toBe(true); // no errors registered for this callback - expect(numCalled).toEqual(8); // both subscriptions will be called once per addPropertyError + expect(numCalled).toEqual(10); // both subscriptions will be called once per addPropertyError and also called on subscribe + expect(numCalledWithErrors).toEqual(3); // the first subscription is called 3 times with errors because the 4th time we call addPropertyError all callbacks still execute }); it('can subscribe to a property validation path prefix', function () { @@ -628,12 +636,16 @@ //arrange serverValidationManager.subscribe("myProperty", null, null, function (isValid, propertyErrors, allErrors) { - callbackA.push(propertyErrors); - }, null, true); + if (propertyErrors.length > 0) { + callbackA.push(propertyErrors); + } + }, null, { matchPrefix: true }); serverValidationManager.subscribe("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses", null, null, function (isValid, propertyErrors, allErrors) { - callbackB.push(propertyErrors); - }, null, true); + if (propertyErrors.length > 0) { + callbackB.push(propertyErrors); + } + }, null, { matchPrefix: true }); //act // will match A: @@ -652,7 +664,7 @@ // both will be called each time addPropertyError is called expect(callbackA.length).toEqual(8); - expect(callbackB.length).toEqual(8); + expect(callbackB.length).toEqual(6); // B - will only be called 6 times with errors because the first 2 calls to addPropertyError haven't added errors for B yet expect(callbackA[callbackA.length - 1].length).toEqual(4); // 4 errors for A expect(callbackB[callbackB.length - 1].length).toEqual(2); // 2 errors for B