Yay! gets it all working for the block editor, refactored counting form errors recursively to actually work and be readable, updates valPropertyMsg with all of the odd edge cases.

This commit is contained in:
Shannon
2020-07-16 17:31:46 +10:00
parent 1291545908
commit b7e25eed21
5 changed files with 116 additions and 95 deletions

View File

@@ -516,6 +516,12 @@
}
}
function handleHttpException(err) {
if (!err.status) {
$exceptionHandler(err);
}
}
/** Just shows a simple notification that there are client side validation issues to be fixed */
function showValidationNotification() {
//TODO: We need to make the validation UI much better, there's a lot of inconsistencies in v8 including colors, issues with the property groups and validation errors between variants
@@ -576,6 +582,7 @@
overlayService.close();
}, function (err) {
$scope.page.buttonGroupState = 'error';
handleHttpException(err);
});
@@ -621,7 +628,7 @@
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
$exceptionHandler(err);
handleHttpException(err);
});
},
close: function () {
@@ -644,7 +651,7 @@
$scope.page.buttonGroupState = "success";
}, function (err) {
$scope.page.buttonGroupState = "error";
$exceptionHandler(err);
handleHttpException(err);
});;
}
};
@@ -680,7 +687,7 @@
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
$exceptionHandler(err);
handleHttpException(err);
});
},
close: function () {
@@ -705,7 +712,7 @@
$scope.page.buttonGroupState = "success";
}, function (err) {
$scope.page.buttonGroupState = "error";
$exceptionHandler(err);
handleHttpException(err);
});
}
};
@@ -743,7 +750,7 @@
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
$exceptionHandler(err);
handleHttpException(err);
});
},
close: function (oldModel) {
@@ -768,7 +775,7 @@
$scope.page.saveButtonState = "success";
}, function (err) {
$scope.page.saveButtonState = "error";
$exceptionHandler(err);
handleHttpException(err);
});
}
@@ -821,7 +828,7 @@
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = Utilities.copy($scope.content.variants);
$exceptionHandler(err);
handleHttpException(err);
});
},
@@ -880,7 +887,7 @@
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
$exceptionHandler(err);
handleHttpException(err);
});
},

View File

@@ -112,46 +112,22 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
});
//watch the list of validation errors to notify the application of any validation changes
scope.$watch(function () {
//the validators are in the $error collection: https://docs.angularjs.org/api/ng/type/form.FormController#$error
//since each key is the validator name (i.e. 'required') we can't just watch the number of keys, we need to watch
//the sum of the items inside of each key
scope.$watch(() => angularHelper.countAllFormErrors(formCtrl),
function (e) {
notify(scope);
notifySubView();
//find all invalid elements' .control-group's and apply the error class
var inError = element.find(".control-group .ng-invalid").closest(".control-group");
inError.addClass("error");
//find all control group's that have no error and ensure the class is removed
var noInError = element.find(".control-group .ng-valid").closest(".control-group").not(inError);
noInError.removeClass("error");
//get the lengths of each array for each key in the $error collection
var validatorLengths = _.map(formCtrl.$error, function (val, key) {
// if there are child ng-forms, include the $error collections in those as well
var innerErrorCount = _.reduce(
_.map(val, v =>
_.reduce(
_.map(v.$error, e => e.length),
(m, n) => m + n
)
),
(memo, num) => memo + num
);
return val.length + innerErrorCount;
});
//sum up all numbers in the resulting array
var sum = _.reduce(validatorLengths, function (memo, num) {
return memo + num;
}, 0);
//this is the value we watch to notify of any validation changes on the form
return sum;
}, function (e) {
notify(scope);
notifySubView();
//find all invalid elements' .control-group's and apply the error class
var inError = element.find(".control-group .ng-invalid").closest(".control-group");
inError.addClass("error");
//find all control group's that have no error and ensure the class is removed
var noInError = element.find(".control-group .ng-valid").closest(".control-group").not(inError);
noInError.removeClass("error");
});
//This tracks if the user is currently saving a new item, we use this to determine
// if we should display the warning dialog that they are leaving the page - if a new item

View File

@@ -84,26 +84,36 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
}
// return true if there is only a single error left on the property form of either valPropertyMsg or valServer
function shouldClearError() {
var errCount = 0;
function checkAndClearError() {
for (var e in formCtrl.$error) {
if (Utilities.isArray(formCtrl.$error[e])) {
errCount++;
}
var errCount = angularHelper.countAllFormErrors(formCtrl);
if (errCount === 0) {
resetError();
return true;
}
//we are explicitly checking for valServer errors here, since we shouldn't auto clear
// based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg
// is the only one, then we'll clear.
if (errCount === 0
|| (errCount === 1 && hasExplicitError())
|| (formCtrl.$invalid && Utilities.isArray(formCtrl.$error.valServer))) {
if (errCount > 2) {
return false;
}
var hasValServer = Utilities.isArray(formCtrl.$error.valServer);
if (errCount === 1 && hasValServer) {
return true;
}
var hasOwnErr = hasExplicitError();
if ((errCount === 1 && hasOwnErr) || (errCount === 2 && hasOwnErr && hasValServer)) {
var propertyValidationPath = umbPropCtrl.getValidationPath();
// check if we can clear it based on child server errors, if we are the only explicit one remaining we can clear ourselves
if (isLastServerError(propertyValidationPath)) {
serverValidationManager.removePropertyError(propertyValidationPath, currentCulture, "", currentSegment);
return true;
}
return false;
}
return false;
}
@@ -113,14 +123,13 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
}
// returns true if there is only a single server validation error for this property validation key in it's validation path
function isLastServerError(propertyValidationKey) {
function isLastServerError(propertyValidationPath) {
var nestedErrs = serverValidationManager.getPropertyErrorsByValidationPath(
propertyValidationKey,
propertyValidationPath,
currentCulture,
"",
currentSegment,
true);
if (nestedErrs.length === 1 && nestedErrs[0].propertyAlias === propertyValidationKey) {
{ matchType: "prefix" });
if (nestedErrs.length === 0 || (nestedErrs.length === 1 && nestedErrs[0].propertyAlias === propertyValidationPath)) {
return true;
}
@@ -144,13 +153,7 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
return;
}
if (shouldClearError()) {
var propertyValidationKey = umbPropCtrl.getValidationPath();
// check if we can clear it based on child server errors, if we are the only explicit one remaining we can clear ourselves
if (isLastServerError(propertyValidationKey)) {
serverValidationManager.removePropertyError(propertyValidationKey, currentCulture, "", currentSegment);
}
if (checkAndClearError()) {
resetError();
}
else if (showValidation && scope.errorMsg === "") {
@@ -170,10 +173,11 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
}
function resetError() {
stopWatch();
hasError = false;
formCtrl.$setValidity('valPropertyMsg', true, formCtrl);
scope.errorMsg = "";
stopWatch();
}
function checkValidationStatus() {
@@ -270,7 +274,7 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
hasError = !isValid;
if (hasError) {
//set the error message to the server message
scope.errorMsg = propertyErrors.length > 0 ? labels.propertyHasErrors : propertyErrors[0].errorMsg || labels.propertyHasErrors;
scope.errorMsg = propertyErrors.length > 1 ? labels.propertyHasErrors : propertyErrors[0].errorMsg || labels.propertyHasErrors;
//flag that the current validator is invalid
formCtrl.$setValidity('valPropertyMsg', false, formCtrl);
startWatch();

View File

@@ -10,8 +10,61 @@ function angularHelper($q) {
var requiredFormProps = ["$error", "$name", "$dirty", "$pristine", "$valid", "$submitted", "$pending"];
function collectAllFormErrorsRecursively(formCtrl, allErrors) {
// loop over the error dictionary (see https://docs.angularjs.org/api/ng/type/form.FormController#$error)
var keys = Object.keys(formCtrl.$error);
if (keys.length === 0) {
return;
}
keys.forEach(validationKey => {
var ctrls = formCtrl.$error[validationKey];
ctrls.forEach(ctrl => {
if (isForm(ctrl)) {
// sometimes the control in error is the same form so we cannot recurse else we'll cause an infinite loop
// and in this case it means the error is assigned directly to the form, not a control
if (ctrl === formCtrl) {
allErrors.push(ctrl); // add the error
return;
}
// recurse with the sub form
collectAllFormErrorsRecursively(ctrl, allErrors);
}
else {
// it's a normal control
allErrors.push(ctrl); // add the error
}
});
});
}
function isForm(obj) {
// a method to check that the collection of object prop names contains the property name expected
function allPropertiesExist(objectPropNames) {
//ensure that every required property name exists on the current object
return _.every(requiredFormProps, function (item) {
return _.contains(objectPropNames, item);
});
}
//get the keys of the property names for the current object
var props = _.keys(obj);
//if the length isn't correct, try the next prop
if (props.length < requiredFormProps.length) {
return false;
}
//ensure that every required property name exists on the current scope property
return allPropertiesExist(props);
}
return {
countAllFormErrors: function (formCtrl) {
var allErrors = [];
collectAllFormErrorsRecursively(formCtrl, allErrors);
return allErrors.length;
},
/**
* Will traverse up the $scope chain to all ancestors until the predicate matches for the current scope or until it's at the root.
* @param {any} scope
@@ -104,26 +157,7 @@ function angularHelper($q) {
},
isForm: function (obj) {
// a method to check that the collection of object prop names contains the property name expected
function allPropertiesExist(objectPropNames) {
//ensure that every required property name exists on the current object
return _.every(requiredFormProps, function (item) {
return _.contains(objectPropNames, item);
});
}
//get the keys of the property names for the current object
var props = _.keys(obj);
//if the length isn't correct, try the next prop
if (props.length < requiredFormProps.length) {
return false;
}
//ensure that every required property name exists on the current scope property
return allPropertiesExist(props);
},
isForm: isForm,
/**
* @ngdoc function

View File

@@ -776,8 +776,8 @@ function serverValidationManager($timeout) {
return undefined;
},
getPropertyErrorsByValidationPath: function (propertyAlias, culture, segment, options) {
return getPropertyErrors(propertyAlias, culture, segment, "", options);
getPropertyErrorsByValidationPath: function (propertyValidationPath, culture, segment, options) {
return getPropertyErrors(propertyValidationPath, culture, segment, "", options);
},
/**