fixes name, refactors a lot of serverValidationManager so it's structure is maintainable, moves more logic there from directives so it can be tested more easily, finally got the first result working.
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* @restrict E
|
||||
**/
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbProperty', function (userService) {
|
||||
.directive('umbProperty', function (userService, serverValidationManager, udiService) {
|
||||
return {
|
||||
scope: {
|
||||
property: "=",
|
||||
@@ -28,6 +28,11 @@ angular.module("umbraco.directives")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (scope.elementUdi && !scope.elementUdi.startsWith("umb://")) {
|
||||
scope.elementUdi = udiService.build("element", scope.elementUdi);
|
||||
}
|
||||
|
||||
},
|
||||
//Define a controller for this directive to expose APIs to other directives
|
||||
controller: function ($scope) {
|
||||
@@ -48,9 +53,10 @@ angular.module("umbraco.directives")
|
||||
|
||||
// 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;
|
||||
return serverValidationManager.createPropertyValidationKey(propAlias, $scope.elementUdi);
|
||||
}
|
||||
$scope.getValidationPath = self.getValidationPath;
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ function valPropertyMsg(serverValidationManager, localizationService) {
|
||||
var currentProperty = umbPropCtrl.property;
|
||||
scope.currentProperty = currentProperty;
|
||||
|
||||
var propertyValidationKey = umbPropCtrl.getValidationPath();
|
||||
|
||||
var currentCulture = currentProperty.culture;
|
||||
var currentSegment = currentProperty.segment;
|
||||
|
||||
@@ -71,7 +73,7 @@ function valPropertyMsg(serverValidationManager, localizationService) {
|
||||
//this can be null if no property was assigned
|
||||
if (scope.currentProperty) {
|
||||
//first try to get the error msg from the server collection
|
||||
var err = serverValidationManager.getPropertyError(scope.currentProperty.alias, null, "", null);
|
||||
var err = serverValidationManager.getPropertyError(propertyValidationKey, null, "", null);
|
||||
//if there's an error message use it
|
||||
if (err && err.errorMsg) {
|
||||
return err.errorMsg;
|
||||
@@ -240,7 +242,7 @@ function valPropertyMsg(serverValidationManager, localizationService) {
|
||||
}
|
||||
}
|
||||
|
||||
unsubscribe.push(serverValidationManager.subscribe(scope.currentProperty.alias,
|
||||
unsubscribe.push(serverValidationManager.subscribe(propertyValidationKey,
|
||||
currentCulture,
|
||||
"",
|
||||
serverValidationManagerCallback,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
**/
|
||||
function valServer(serverValidationManager) {
|
||||
return {
|
||||
require: ['ngModel', '?^^umbProperty', '?^^umbVariantContent', '?^^umbNestedProperty'],
|
||||
require: ['ngModel', '?^^umbProperty', '?^^umbVariantContent'],
|
||||
restrict: "A",
|
||||
scope: {},
|
||||
link: function (scope, element, attr, ctrls) {
|
||||
@@ -21,7 +21,6 @@ function valServer(serverValidationManager) {
|
||||
|
||||
// optional reference to the varaint-content-controller, needed to avoid validation when the field is invariant on non-default languages.
|
||||
var umbVariantCtrl = ctrls.length > 2 ? ctrls[2] : null;
|
||||
var umbNestedPropertyCtrl = ctrls.length > 3 ? ctrls[3] : null;
|
||||
|
||||
var currentProperty = umbPropCtrl.property;
|
||||
var currentCulture = currentProperty.culture;
|
||||
@@ -56,6 +55,14 @@ function valServer(serverValidationManager) {
|
||||
}
|
||||
}
|
||||
|
||||
function getPropertyValidationKey() {
|
||||
// Get the property validation path if there is one, this is how wiring up any nested/virtual property validation works
|
||||
var propertyValidationPath = umbPropCtrl ? umbPropCtrl.getValidationPath() : null;
|
||||
// TODO: Is this going to break with nested content because it changes the alias?
|
||||
// Hrm, don't think so because NC will use the property validation path
|
||||
return propertyValidationPath ? propertyValidationPath : currentProperty.alias;
|
||||
}
|
||||
|
||||
//Need to watch the value model for it to change, previously we had subscribed to
|
||||
//modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that
|
||||
// doesn't specifically have a 2 way ng binding. This is required because when we
|
||||
@@ -78,9 +85,7 @@ function valServer(serverValidationManager) {
|
||||
modelCtrl.$setValidity('valServer', true);
|
||||
|
||||
//clear the server validation entry
|
||||
// TODO: We'll need to handle this differently since this will need to target the actual 'fieldName' or validation
|
||||
// path if there is one
|
||||
serverValidationManager.removePropertyError(currentProperty.alias, currentCulture, fieldName, currentSegment);
|
||||
serverValidationManager.removePropertyError(getPropertyValidationKey(), currentCulture, fieldName, currentSegment);
|
||||
stopWatch();
|
||||
}
|
||||
}, true);
|
||||
@@ -110,21 +115,12 @@ 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
|
||||
// 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;
|
||||
|
||||
|
||||
unsubscribe.push(serverValidationManager.subscribe(
|
||||
currentProperty.alias,
|
||||
getPropertyValidationKey(),
|
||||
currentCulture,
|
||||
// use the propertyValidationPath for the fieldName value if there is one since if there is one it means it's a complex
|
||||
// editor and as such the 'fieldName' will be empty. The serverValidationManager knows how to handle the jsonpath
|
||||
// string as the fieldName.
|
||||
// TODO: This isn't quite true! If there is a fieldName specified, then it will need to be added to the
|
||||
// validation path. We should pass in the fieldName to umbNestedPropertyCtrl.getValidationPath(); since this could very well be targeting a specific field
|
||||
|
||||
propertyValidationPath ? propertyValidationPath : fieldName,
|
||||
fieldName,
|
||||
serverValidationManagerCallback,
|
||||
currentSegment)
|
||||
);
|
||||
|
||||
@@ -147,79 +147,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
|
||||
* @param {object} err The error object returned from the http promise
|
||||
*/
|
||||
handleServerValidation: function (modelState) {
|
||||
for (var e in 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.
|
||||
// The Content editor ModelState is quite specific with the way that Properties are validated especially considering
|
||||
// that each property is a User Developer property editor.
|
||||
// The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations
|
||||
// system.
|
||||
// So, to do this there's some special ModelState syntax we need to know about.
|
||||
// For Content Properties, which are user defined, we know that they will exist with a prefixed
|
||||
// ModelState of "_Properties.", so if we detect this, then we know it's for a content Property.
|
||||
|
||||
//the alias in model state can be in dot notation which indicates
|
||||
// * the first part is the content property alias
|
||||
// * the second part is the field to which the valiation msg is associated with
|
||||
//There will always be at least 4 parts for content properties since all model errors for properties are prefixed with "_Properties"
|
||||
//If it is not prefixed with "_Properties" that means the error is for a field of the object directly.
|
||||
|
||||
// TODO: This 4 part dot notation isn't ideal and instead it would probably be nicer to have a json structure as the key (which could be converted
|
||||
// to base64 if we cannot do that since it's a 'key'). That way the key can be flexible and 'future proof' since I'm sure something in the future
|
||||
// will change for this. Another idea is to just have a single key for one property type and have the model error a json structure that handles
|
||||
// everything. This would probably be the 'nicest' way but would require quite a lot of work. We are part way there with how we are doing
|
||||
// validation for complex editors.
|
||||
|
||||
// Example: "_Properties.headerImage.en-US.mySegment.myField"
|
||||
// * it's for a property since it has a _Properties prefix
|
||||
// * it's for the headerImage property type
|
||||
// * it's for the en-US culture
|
||||
// * it's for the mySegment segment
|
||||
// * it's for the myField html field (optional)
|
||||
|
||||
var parts = e.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") {
|
||||
|
||||
var propertyAlias = parts[1];
|
||||
|
||||
var culture = null;
|
||||
if (parts.length > 2) {
|
||||
culture = parts[2];
|
||||
//special check in case the string is formatted this way
|
||||
if (culture === "null") {
|
||||
culture = null;
|
||||
}
|
||||
}
|
||||
|
||||
var segment = null;
|
||||
if (parts.length > 3) {
|
||||
segment = parts[3];
|
||||
//special check in case the string is formatted this way
|
||||
if (segment === "null") {
|
||||
segment = null;
|
||||
}
|
||||
}
|
||||
|
||||
var htmlFieldReference = "";
|
||||
if (parts.length > 4) {
|
||||
htmlFieldReference = parts[4] || "";
|
||||
}
|
||||
|
||||
// add a generic error for the property
|
||||
serverValidationManager.addPropertyError(propertyAlias, culture, htmlFieldReference, modelState[e][0], 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
|
||||
serverValidationManager.addFieldError(e, modelState[e][0]);
|
||||
}
|
||||
|
||||
}
|
||||
serverValidationManager.addErrorsForModelState(modelState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,35 +8,55 @@
|
||||
* is for user defined properties (called Properties) and the other is for field properties which are attached to the native
|
||||
* model objects (not user defined). The methods below are named according to these rules: Properties vs Fields.
|
||||
*/
|
||||
function serverValidationManager($timeout) {
|
||||
function serverValidationManager($timeout, udiService) {
|
||||
|
||||
var callbacks = [];
|
||||
|
||||
// The array of error messages
|
||||
var items = [];
|
||||
|
||||
/** calls the callback specified with the errors specified, used internally */
|
||||
function executeCallback(self, errorsForCallback, callback, culture, segment) {
|
||||
function executeCallback(errorsForCallback, callback, culture, segment) {
|
||||
|
||||
callback.apply(self, [
|
||||
callback.apply(instance, [
|
||||
false, // pass in a value indicating it is invalid
|
||||
errorsForCallback, // pass in the errors for this item
|
||||
self.items, // pass in all errors in total
|
||||
items, // pass in all errors in total
|
||||
culture, // pass the culture that we are listing for.
|
||||
segment // pass the segment that we are listing for.
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
function getFieldErrors(self, fieldName) {
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name notify
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* This method isn't used very often but can be used if all subscriptions need to be notified again. This can be
|
||||
* handy if a view needs to be reloaded/rebuild like when switching variants in the content editor. This is also used
|
||||
* when a new subscription occurs and there is already registered errors like dynamically created/shown editors.
|
||||
*/
|
||||
function notify() {
|
||||
$timeout(function () {
|
||||
notifyCallbacks();
|
||||
});
|
||||
}
|
||||
|
||||
function getFieldErrors(fieldName) {
|
||||
if (!Utilities.isString(fieldName)) {
|
||||
throw "fieldName must be a string";
|
||||
}
|
||||
|
||||
//find errors for this field name
|
||||
return _.filter(self.items, function (item) {
|
||||
return _.filter(items, function (item) {
|
||||
return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName);
|
||||
});
|
||||
}
|
||||
|
||||
function getPropertyErrors(self, propertyAlias, culture, segment, fieldName) {
|
||||
function getPropertyErrors(propertyAlias, culture, segment, fieldName) {
|
||||
if (!Utilities.isString(propertyAlias)) {
|
||||
throw "propertyAlias must be a string";
|
||||
}
|
||||
@@ -52,12 +72,12 @@ function serverValidationManager($timeout) {
|
||||
}
|
||||
|
||||
//find all errors for this property
|
||||
return _.filter(self.items, function (item) {
|
||||
return _.filter(items, function (item) {
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
});
|
||||
}
|
||||
|
||||
function getVariantErrors(self, culture, segment) {
|
||||
function getVariantErrors(culture, segment) {
|
||||
|
||||
if (!culture) {
|
||||
culture = "invariant";
|
||||
@@ -67,32 +87,33 @@ function serverValidationManager($timeout) {
|
||||
}
|
||||
|
||||
//find all errors for this property
|
||||
return _.filter(self.items, function (item) {
|
||||
return _.filter(items, function (item) {
|
||||
return (item.culture === culture && item.segment === segment);
|
||||
});
|
||||
}
|
||||
|
||||
function notifyCallbacks(self) {
|
||||
for (var cb in callbacks) {
|
||||
if (callbacks[cb].propertyAlias === null && callbacks[cb].fieldName !== null) {
|
||||
function notifyCallbacks() {
|
||||
for (var i = 0; i < callbacks.length; i++) {
|
||||
var cb = callbacks[i];
|
||||
if (cb.propertyAlias === null && cb.fieldName !== null) {
|
||||
//its a field error callback
|
||||
var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName);
|
||||
var fieldErrors = getFieldErrors(cb.fieldName);
|
||||
if (fieldErrors.length > 0) {
|
||||
executeCallback(self, fieldErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment);
|
||||
executeCallback(fieldErrors, cb.callback, cb.culture, cb.segment);
|
||||
}
|
||||
}
|
||||
else if (callbacks[cb].propertyAlias != null) {
|
||||
else if (cb.propertyAlias != null) {
|
||||
//its a property error
|
||||
var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].culture, callbacks[cb].segment, callbacks[cb].fieldName);
|
||||
var propErrors = getPropertyErrors(cb.propertyAlias, cb.culture, cb.segment, cb.fieldName);
|
||||
if (propErrors.length > 0) {
|
||||
executeCallback(self, propErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment);
|
||||
executeCallback(propErrors, cb.callback, cb.culture, cb.segment);
|
||||
}
|
||||
}
|
||||
else {
|
||||
//its a variant error
|
||||
var variantErrors = getVariantErrors(self, callbacks[cb].culture, callbacks[cb].segment);
|
||||
var variantErrors = getVariantErrors(cb.culture, cb.segment);
|
||||
if (variantErrors.length > 0) {
|
||||
executeCallback(self, variantErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment);
|
||||
executeCallback(variantErrors, cb.callback, cb.culture, cb.segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,9 +153,347 @@ function serverValidationManager($timeout) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name getPropertyCallbacks
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Gets all callbacks that has been registered using the subscribe method for the propertyAlias + fieldName combo.
|
||||
* This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an
|
||||
* explicit field name set.
|
||||
*/
|
||||
function getPropertyCallbacks(propertyAlias, culture, fieldName, segment) {
|
||||
|
||||
//normalize culture to "invariant"
|
||||
if (!culture) {
|
||||
culture = "invariant";
|
||||
}
|
||||
//normalize segment to null
|
||||
if (!segment) {
|
||||
segment = null;
|
||||
}
|
||||
|
||||
var found = _.filter(callbacks, function (item) {
|
||||
//returns any callback that have been registered directly against the field and for only the property
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === "")));
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name getFieldCallbacks
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Gets all callbacks that has been registered using the subscribe method for the field.
|
||||
*/
|
||||
function getFieldCallbacks(fieldName) {
|
||||
var found = _.filter(callbacks, function (item) {
|
||||
//returns any callback that have been registered directly against the field
|
||||
return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName);
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name getVariantCallbacks
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Gets all callbacks that has been registered using the subscribe method for the culture and segment.
|
||||
*/
|
||||
function getVariantCallbacks(culture, segment) {
|
||||
var found = _.filter(callbacks, function (item) {
|
||||
//returns any callback that have been registered directly against the given culture and given segment.
|
||||
return (item.culture === culture && item.segment === segment && item.propertyAlias === null && item.fieldName === null);
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name addFieldError
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Adds an error message for a native content item field (not a user defined property, for Example, 'Name')
|
||||
*/
|
||||
function addFieldError(fieldName, errorMsg) {
|
||||
if (!fieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
//only add the item if it doesn't exist
|
||||
if (!hasFieldError(fieldName)) {
|
||||
items.push({
|
||||
propertyAlias: null,
|
||||
culture: "invariant",
|
||||
segment: null,
|
||||
fieldName: fieldName,
|
||||
errorMsg: errorMsg
|
||||
});
|
||||
}
|
||||
|
||||
//find all errors for this item
|
||||
var errorsForCallback = getFieldErrors(fieldName);
|
||||
//we should now call all of the call backs registered for this error
|
||||
var cbs = getFieldCallbacks(fieldName);
|
||||
//call each callback for this error
|
||||
for (var cb in cbs) {
|
||||
executeCallback(errorsForCallback, cbs[cb].callback, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name addPropertyError
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Adds an error message for the content property
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
//normalize culture to "invariant"
|
||||
if (!culture) {
|
||||
culture = "invariant";
|
||||
}
|
||||
//normalize segment to null
|
||||
if (!segment) {
|
||||
segment = null;
|
||||
}
|
||||
|
||||
// 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);
|
||||
for (const [key, value] of Object.entries(idsToErrors)) {
|
||||
addErrorsForModelState(value, udiService.build("element", key));
|
||||
}
|
||||
|
||||
// TODO: Make this the generic "Property has errors" but need to find the lang key for that
|
||||
errorMsg = "Hello!";
|
||||
}
|
||||
|
||||
//only add the item if it doesn't exist
|
||||
if (!hasPropertyError(propertyAlias, culture, fieldName, segment)) {
|
||||
items.push({
|
||||
propertyAlias: propertyAlias,
|
||||
culture: culture,
|
||||
segment: segment,
|
||||
fieldName: fieldName,
|
||||
errorMsg: errorMsg
|
||||
});
|
||||
}
|
||||
|
||||
//find all errors for this item
|
||||
var errorsForCallback = getPropertyErrors(propertyAlias, culture, segment, fieldName);
|
||||
//we should now call all of the call backs registered for this error
|
||||
var cbs = getPropertyCallbacks(propertyAlias, culture, fieldName, segment);
|
||||
//call each callback for this error
|
||||
for (var cb in cbs) {
|
||||
executeCallback(errorsForCallback, cbs[cb].callback, culture, segment);
|
||||
}
|
||||
|
||||
//execute variant specific callbacks here too when a propery error is added
|
||||
var variantCbs = getVariantCallbacks(culture, segment);
|
||||
//call each callback for this error
|
||||
for (var cb in variantCbs) {
|
||||
executeCallback(errorsForCallback, variantCbs[cb].callback, culture, segment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name hasPropertyError
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if the content property + culture + field name combo has an error
|
||||
*/
|
||||
function hasPropertyError(propertyAlias, culture, fieldName, segment) {
|
||||
|
||||
//normalize culture to null
|
||||
if (!culture) {
|
||||
culture = "invariant";
|
||||
}
|
||||
//normalize segment to null
|
||||
if (!segment) {
|
||||
segment = null;
|
||||
}
|
||||
|
||||
var err = _.find(items, function (item) {
|
||||
//return true if the property alias matches and if an empty field name is specified or the field name matches
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
});
|
||||
return err ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name hasFieldError
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if a content field has an error
|
||||
*/
|
||||
function hasFieldError(fieldName) {
|
||||
var err = _.find(items, function (item) {
|
||||
//return true if the property alias matches and if an empty field name is specified or the field name matches
|
||||
return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName);
|
||||
});
|
||||
return err ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name addErrorsForModelState
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @param {any} modelState
|
||||
* @param {any} elementUdi optional parameter specifying a nested element's UDI for which this property belongs (for complex editors)
|
||||
* @description
|
||||
* 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) {
|
||||
|
||||
//This is where things get interesting....
|
||||
// We need to support validation for all editor types such as both the content and content type editors.
|
||||
// The Content editor ModelState is quite specific with the way that Properties are validated especially considering
|
||||
// that each property is a User Developer property editor.
|
||||
// The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations
|
||||
// system.
|
||||
// So, to do this there's some special ModelState syntax we need to know about.
|
||||
// For Content Properties, which are user defined, we know that they will exist with a prefixed
|
||||
// ModelState of "_Properties.", so if we detect this, then we know it's for a content Property.
|
||||
|
||||
//the alias in model state can be in dot notation which indicates
|
||||
// * the first part is the content property alias
|
||||
// * the second part is the field to which the valiation msg is associated with
|
||||
//There will always be at least 4 parts for content properties since all model errors for properties are prefixed with "_Properties"
|
||||
//If it is not prefixed with "_Properties" that means the error is for a field of the object directly.
|
||||
|
||||
// TODO: This 4 part dot notation isn't ideal and instead it would probably be nicer to have a json structure as the key (which could be converted
|
||||
// to base64 if we cannot do that since it's a 'key'). That way the key can be flexible and 'future proof' since I'm sure something in the future
|
||||
// will change for this. Another idea is to just have a single key for one property type and have the model error a json structure that handles
|
||||
// everything. This would probably be the 'nicest' way but would require quite a lot of work. We are part way there with how we are doing
|
||||
// validation for complex editors.
|
||||
|
||||
// Example: "_Properties.headerImage.en-US.mySegment.myField"
|
||||
// * it's for a property since it has a _Properties prefix
|
||||
// * it's for the headerImage property type
|
||||
// * it's for the en-US culture
|
||||
// * it's for the mySegment segment
|
||||
// * it's for the myField html field (optional)
|
||||
|
||||
var parts = e.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);
|
||||
|
||||
var culture = null;
|
||||
if (parts.length > 2) {
|
||||
culture = parts[2];
|
||||
//special check in case the string is formatted this way
|
||||
if (culture === "null") {
|
||||
culture = null;
|
||||
}
|
||||
}
|
||||
|
||||
var segment = null;
|
||||
if (parts.length > 3) {
|
||||
segment = parts[3];
|
||||
//special check in case the string is formatted this way
|
||||
if (segment === "null") {
|
||||
segment = null;
|
||||
}
|
||||
}
|
||||
|
||||
var htmlFieldReference = "";
|
||||
if (parts.length > 4) {
|
||||
htmlFieldReference = parts[4] || "";
|
||||
}
|
||||
|
||||
// add a generic error for the property
|
||||
addPropertyError(propertyAlias, culture, htmlFieldReference, modelState[e][0], 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]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Write a test or two for this and probs a bunch of other things here too!
|
||||
function createPropertyValidationKey(propertyAlias, elementUdi) {
|
||||
return elementUdi ? (elementUdi + "/" + propertyAlias) : propertyAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name reset
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Clears all errors and notifies all callbacks that all server errros are now valid - used when submitting a form
|
||||
*/
|
||||
function reset() {
|
||||
clear();
|
||||
for (var cb in callbacks) {
|
||||
callbacks[cb].callback.apply(instance, [
|
||||
true, //pass in a value indicating it is VALID
|
||||
[], //pass in empty collection
|
||||
[],
|
||||
null,
|
||||
null]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name clear
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Clears all errors
|
||||
*/
|
||||
function clear() {
|
||||
items = [];
|
||||
}
|
||||
|
||||
var instance = {
|
||||
|
||||
addErrorsForModelState: addErrorsForModelState,
|
||||
parseComplexEditorError: parseComplexEditorError,
|
||||
createPropertyValidationKey: createPropertyValidationKey,
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
@@ -143,42 +502,27 @@ function serverValidationManager($timeout) {
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* This method needs to be called once all field and property errors are wired up.
|
||||
* This method can be called once all field and property errors are wired up.
|
||||
*
|
||||
* In some scenarios where the error collection needs to be persisted over a route change
|
||||
* (i.e. when a content item (or any item) is created and the route redirects to the editor)
|
||||
* the controller should call this method once the data is bound to the scope
|
||||
* so that any persisted validation errors are re-bound to their controls. Once they are re-binded this then clears the validation
|
||||
* colleciton so that if another route change occurs, the previously persisted validation errors are not re-bound to the new item.
|
||||
*
|
||||
* In the case of content with complex editors, variants and different views, those editors don't call this method and instead
|
||||
* manage the server validation manually by calling notify when necessary and clear/reset when necessary.
|
||||
*/
|
||||
notifyAndClearAllSubscriptions: function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
$timeout(function () {
|
||||
notifyCallbacks(self);
|
||||
notifyCallbacks();
|
||||
//now that they are all executed, we're gonna clear all of the errors we have
|
||||
self.clear();
|
||||
clear();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name notify
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* This method isn't used very often but can be used if all subscriptions need to be notified again. This can be
|
||||
* handy if a view needs to be reloaded/rebuild like when switching variants in the content editor.
|
||||
*/
|
||||
notify: function() {
|
||||
var self = this;
|
||||
|
||||
$timeout(function () {
|
||||
notifyCallbacks(self);
|
||||
});
|
||||
},
|
||||
notify: notify,
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
@@ -209,14 +553,8 @@ function serverValidationManager($timeout) {
|
||||
segment = null;
|
||||
}
|
||||
|
||||
// TODO: Check if the fieldName is a jsonpath, we will know this if it starts with $.
|
||||
// in which case we need to handle this a little differently.
|
||||
if (fieldName && fieldName.startsWith("$.")) {
|
||||
// TODO: Or... Do we even need to deal with it differently? Maybe with some luck
|
||||
// we can just store that path and use it. Lets see how this goes.
|
||||
}
|
||||
|
||||
if (propertyAlias === null) {
|
||||
|
||||
callbacks.push({
|
||||
propertyAlias: null,
|
||||
culture: culture,
|
||||
@@ -227,8 +565,7 @@ function serverValidationManager($timeout) {
|
||||
});
|
||||
}
|
||||
else if (propertyAlias !== undefined) {
|
||||
//normalize culture to null
|
||||
|
||||
|
||||
callbacks.push({
|
||||
propertyAlias: propertyAlias,
|
||||
culture: culture,
|
||||
@@ -246,6 +583,11 @@ 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.
|
||||
// TODO: We need to see what the repercussions of this are in other editors!
|
||||
notify();
|
||||
|
||||
//return a function to unsubscribe this subscription by uniqueId
|
||||
return unsubscribeId;
|
||||
},
|
||||
@@ -286,52 +628,8 @@ function serverValidationManager($timeout) {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name getPropertyCallbacks
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Gets all callbacks that has been registered using the subscribe method for the propertyAlias + fieldName combo.
|
||||
* This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an
|
||||
* explicit field name set.
|
||||
*/
|
||||
getPropertyCallbacks: function (propertyAlias, culture, fieldName, segment) {
|
||||
|
||||
//normalize culture to "invariant"
|
||||
if (!culture) {
|
||||
culture = "invariant";
|
||||
}
|
||||
//normalize segment to null
|
||||
if (!segment) {
|
||||
segment = null;
|
||||
}
|
||||
|
||||
var found = _.filter(callbacks, function (item) {
|
||||
//returns any callback that have been registered directly against the field and for only the property
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === "")));
|
||||
});
|
||||
return found;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name getFieldCallbacks
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Gets all callbacks that has been registered using the subscribe method for the field.
|
||||
*/
|
||||
getFieldCallbacks: function (fieldName) {
|
||||
var found = _.filter(callbacks, function (item) {
|
||||
//returns any callback that have been registered directly against the field
|
||||
return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName);
|
||||
});
|
||||
return found;
|
||||
},
|
||||
getPropertyCallbacks: getPropertyCallbacks,
|
||||
getFieldCallbacks: getFieldCallbacks,
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
@@ -350,121 +648,9 @@ function serverValidationManager($timeout) {
|
||||
return found;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name getVariantCallbacks
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Gets all callbacks that has been registered using the subscribe method for the culture and segment.
|
||||
*/
|
||||
getVariantCallbacks: function (culture, segment) {
|
||||
var found = _.filter(callbacks, function (item) {
|
||||
//returns any callback that have been registered directly against the given culture and given segment.
|
||||
return (item.culture === culture && item.segment === segment && item.propertyAlias === null && item.fieldName === null);
|
||||
});
|
||||
return found;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name addFieldError
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Adds an error message for a native content item field (not a user defined property, for Example, 'Name')
|
||||
*/
|
||||
addFieldError: function(fieldName, errorMsg) {
|
||||
if (!fieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
//only add the item if it doesn't exist
|
||||
if (!this.hasFieldError(fieldName)) {
|
||||
this.items.push({
|
||||
propertyAlias: null,
|
||||
culture: "invariant",
|
||||
segment: null,
|
||||
fieldName: fieldName,
|
||||
errorMsg: errorMsg
|
||||
});
|
||||
}
|
||||
|
||||
//find all errors for this item
|
||||
var errorsForCallback = getFieldErrors(this, fieldName);
|
||||
//we should now call all of the call backs registered for this error
|
||||
var cbs = this.getFieldCallbacks(fieldName);
|
||||
//call each callback for this error
|
||||
for (var cb in cbs) {
|
||||
executeCallback(this, errorsForCallback, cbs[cb].callback, null, null);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name addPropertyError
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Adds an error message for the content property
|
||||
*/
|
||||
addPropertyError: function (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;
|
||||
}
|
||||
|
||||
//normalize culture to "invariant"
|
||||
if (!culture) {
|
||||
culture = "invariant";
|
||||
}
|
||||
//normalize segment to null
|
||||
if (!segment) {
|
||||
segment = null;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// TODO: Make this the generic "Property has errors" but need to find the lang key for that
|
||||
errorMsg = "Hello!";
|
||||
}
|
||||
|
||||
//only add the item if it doesn't exist
|
||||
if (!this.hasPropertyError(propertyAlias, culture, fieldName, segment)) {
|
||||
this.items.push({
|
||||
propertyAlias: propertyAlias,
|
||||
culture: culture,
|
||||
segment: segment,
|
||||
fieldName: fieldName,
|
||||
errorMsg: errorMsg
|
||||
});
|
||||
}
|
||||
|
||||
//find all errors for this item
|
||||
var errorsForCallback = getPropertyErrors(this, propertyAlias, culture, segment, fieldName);
|
||||
//we should now call all of the call backs registered for this error
|
||||
var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName, segment);
|
||||
//call each callback for this error
|
||||
for (var cb in cbs) {
|
||||
executeCallback(this, errorsForCallback, cbs[cb].callback, culture, segment);
|
||||
}
|
||||
|
||||
//execute variant specific callbacks here too when a propery error is added
|
||||
var variantCbs = this.getVariantCallbacks(culture, segment);
|
||||
//call each callback for this error
|
||||
for (var cb in variantCbs) {
|
||||
executeCallback(this, errorsForCallback, variantCbs[cb].callback, culture, segment);
|
||||
}
|
||||
},
|
||||
getVariantCallbacks: getVariantCallbacks,
|
||||
addFieldError: addFieldError,
|
||||
addPropertyError: addPropertyError,
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
@@ -491,45 +677,13 @@ function serverValidationManager($timeout) {
|
||||
}
|
||||
|
||||
//remove the item
|
||||
this.items = _.reject(this.items, function (item) {
|
||||
items = _.reject(items, function (item) {
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name reset
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Clears all errors and notifies all callbacks that all server errros are now valid - used when submitting a form
|
||||
*/
|
||||
reset: function () {
|
||||
this.clear();
|
||||
for (var cb in callbacks) {
|
||||
callbacks[cb].callback.apply(this, [
|
||||
true, //pass in a value indicating it is VALID
|
||||
[], //pass in empty collection
|
||||
[],
|
||||
null,
|
||||
null]
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name clear
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Clears all errors
|
||||
*/
|
||||
clear: function() {
|
||||
this.items = [];
|
||||
},
|
||||
reset: reset,
|
||||
clear: clear,
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
@@ -551,7 +705,7 @@ function serverValidationManager($timeout) {
|
||||
segment = null;
|
||||
}
|
||||
|
||||
var err = _.find(this.items, function (item) {
|
||||
var err = _.find(items, function (item) {
|
||||
//return true if the property alias matches and if an empty field name is specified or the field name matches
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
});
|
||||
@@ -568,56 +722,15 @@ function serverValidationManager($timeout) {
|
||||
* Gets the error message for a content field
|
||||
*/
|
||||
getFieldError: function (fieldName) {
|
||||
var err = _.find(this.items, function (item) {
|
||||
var err = _.find(items, function (item) {
|
||||
//return true if the property alias matches and if an empty field name is specified or the field name matches
|
||||
return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName);
|
||||
});
|
||||
return err;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name hasPropertyError
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if the content property + culture + field name combo has an error
|
||||
*/
|
||||
hasPropertyError: function (propertyAlias, culture, fieldName, segment) {
|
||||
|
||||
//normalize culture to null
|
||||
if (!culture) {
|
||||
culture = "invariant";
|
||||
}
|
||||
//normalize segment to null
|
||||
if (!segment) {
|
||||
segment = null;
|
||||
}
|
||||
|
||||
var err = _.find(this.items, function (item) {
|
||||
//return true if the property alias matches and if an empty field name is specified or the field name matches
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
});
|
||||
return err ? true : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name hasFieldError
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if a content field has an error
|
||||
*/
|
||||
hasFieldError: function (fieldName) {
|
||||
var err = _.find(this.items, function (item) {
|
||||
//return true if the property alias matches and if an empty field name is specified or the field name matches
|
||||
return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName);
|
||||
});
|
||||
return err ? true : false;
|
||||
},
|
||||
hasPropertyError: hasPropertyError,
|
||||
hasFieldError: hasFieldError,
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
@@ -635,7 +748,7 @@ function serverValidationManager($timeout) {
|
||||
culture = "invariant";
|
||||
}
|
||||
|
||||
var err = _.find(this.items, function (item) {
|
||||
var err = _.find(items, function (item) {
|
||||
return (item.culture === culture && item.segment === null);
|
||||
});
|
||||
return err ? true : false;
|
||||
@@ -661,14 +774,25 @@ function serverValidationManager($timeout) {
|
||||
segment = null;
|
||||
}
|
||||
|
||||
var err = _.find(this.items, function (item) {
|
||||
var err = _.find(items, function (item) {
|
||||
return (item.culture === culture && item.segment === segment);
|
||||
});
|
||||
return err ? true : false;
|
||||
},
|
||||
/** The array of error messages */
|
||||
items: []
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Used to return the 'items' array as a reference/getter
|
||||
Object.defineProperty(instance, "items", {
|
||||
get: function () {
|
||||
return items;
|
||||
},
|
||||
set: function (value) {
|
||||
throw "Cannot set the items array";
|
||||
}
|
||||
});
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
angular.module('umbraco.services').factory('serverValidationManager', serverValidationManager);
|
||||
|
||||
@@ -16,13 +16,17 @@
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Generates a Udi string.
|
||||
* Generates a Udi string with a new ID
|
||||
*
|
||||
* @param {string} entityType The entityType as a string.
|
||||
* @returns {string} The generated UDI
|
||||
*/
|
||||
create: function(entityType) {
|
||||
return "umb://" + entityType + "/" + (String.CreateGuid().replace(/-/g, ""));
|
||||
return this.create(entityType, String.CreateGuid());
|
||||
},
|
||||
|
||||
build: function (entityType, guid) {
|
||||
return "umb://" + entityType + "/" + (guid.replace(/-/g, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<umb-property property="property"
|
||||
property-alias="{{property.propertyAlias}}"
|
||||
element-udi="{{'umb://element/' + model.key}}"
|
||||
element-udi="{{model.key}}"
|
||||
ng-class="{'umb-nested-content--not-supported': property.notSupported, 'umb-nested-content--mandatory': property.ncMandatory}"
|
||||
data-element="property-{{property.alias}}">
|
||||
|
||||
|
||||
Reference in New Issue
Block a user