222 lines
10 KiB
JavaScript
222 lines
10 KiB
JavaScript
/**
|
|
* @ngdoc directive
|
|
* @name umbraco.directives.directive:valFormManager
|
|
* @restrict A
|
|
* @require formController
|
|
* @description Used to broadcast an event to all elements inside this one to notify that form validation has
|
|
* changed. If we don't use this that means you have to put a watch for each directive on a form's validation
|
|
* changing which would result in much higher processing. We need to actually watch the whole $error collection of a form
|
|
* because just watching $valid or $invalid doesn't acurrately trigger form validation changing.
|
|
* This also sets the show-validation (or a custom) css class on the element when the form is invalid - this lets
|
|
* us css target elements to be displayed when the form is submitting/submitted.
|
|
* Another thing this directive does is to ensure that any .control-group that contains form elements that are invalid will
|
|
* be marked with the 'error' css class. This ensures that labels included in that control group are styled correctly.
|
|
**/
|
|
function valFormManager(serverValidationManager, $rootScope, $timeout, $location, overlayService, eventsService, $routeParams, navigationService, editorService, localizationService) {
|
|
|
|
var SHOW_VALIDATION_CLASS_NAME = "show-validation";
|
|
var SAVING_EVENT_NAME = "formSubmitting";
|
|
var SAVED_EVENT_NAME = "formSubmitted";
|
|
|
|
return {
|
|
require: ["form", "^^?valFormManager", "^^?valSubView"],
|
|
restrict: "A",
|
|
controller: function($scope) {
|
|
//This exposes an API for direct use with this directive
|
|
|
|
var unsubscribe = [];
|
|
var self = this;
|
|
|
|
//This is basically the same as a directive subscribing to an event but maybe a little
|
|
// nicer since the other directive can use this directive's API instead of a magical event
|
|
this.onValidationStatusChanged = function (cb) {
|
|
unsubscribe.push($scope.$on("valStatusChanged", function(evt, args) {
|
|
cb.apply(self, [evt, args]);
|
|
}));
|
|
};
|
|
|
|
this.showValidation = $scope.showValidation === true;
|
|
|
|
//Ensure to remove the event handlers when this instance is destroyted
|
|
$scope.$on('$destroy', function () {
|
|
for (var u in unsubscribe) {
|
|
unsubscribe[u]();
|
|
}
|
|
});
|
|
},
|
|
link: function (scope, element, attr, ctrls) {
|
|
|
|
function notifySubView() {
|
|
if (subView){
|
|
subView.valStatusChanged({ form: formCtrl, showValidation: scope.showValidation });
|
|
}
|
|
}
|
|
|
|
var formCtrl = ctrls[0];
|
|
var parentFormMgr = ctrls.length > 0 ? ctrls[1] : null;
|
|
var subView = ctrls.length > 1 ? ctrls[2] : null;
|
|
var labels = {};
|
|
|
|
var labelKeys = [
|
|
"prompt_unsavedChanges",
|
|
"prompt_unsavedChangesWarning",
|
|
"prompt_discardChanges",
|
|
"prompt_stay"
|
|
];
|
|
|
|
localizationService.localizeMany(labelKeys).then(function (values) {
|
|
labels.unsavedChangesTitle = values[0];
|
|
labels.unsavedChangesContent = values[1];
|
|
labels.discardChangesButton = values[2];
|
|
labels.stayButton = values[3];
|
|
});
|
|
|
|
//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
|
|
|
|
//get the lengths of each array for each key in the $error collection
|
|
var validatorLengths = _.map(formCtrl.$error, function (val, key) {
|
|
return val.length;
|
|
});
|
|
//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) {
|
|
scope.$broadcast("valStatusChanged", { form: formCtrl });
|
|
|
|
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
|
|
// is being saved we never want to display that dialog, this will also cause problems when there
|
|
// are server side validation issues.
|
|
var isSavingNewItem = false;
|
|
|
|
//we should show validation if there are any msgs in the server validation collection
|
|
if (serverValidationManager.items.length > 0 || (parentFormMgr && parentFormMgr.showValidation)) {
|
|
element.addClass(SHOW_VALIDATION_CLASS_NAME);
|
|
scope.showValidation = true;
|
|
notifySubView();
|
|
}
|
|
|
|
var unsubscribe = [];
|
|
|
|
//listen for the forms saving event
|
|
unsubscribe.push(scope.$on(SAVING_EVENT_NAME, function(ev, args) {
|
|
element.addClass(SHOW_VALIDATION_CLASS_NAME);
|
|
scope.showValidation = true;
|
|
notifySubView();
|
|
//set the flag so we can check to see if we should display the error.
|
|
isSavingNewItem = $routeParams.create;
|
|
}));
|
|
|
|
//listen for the forms saved event
|
|
unsubscribe.push(scope.$on(SAVED_EVENT_NAME, function(ev, args) {
|
|
//remove validation class
|
|
element.removeClass(SHOW_VALIDATION_CLASS_NAME);
|
|
scope.showValidation = false;
|
|
notifySubView();
|
|
//clear form state as at this point we retrieve new data from the server
|
|
//and all validation will have cleared at this point
|
|
formCtrl.$setPristine();
|
|
}));
|
|
|
|
var confirmed = false;
|
|
|
|
//This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but
|
|
// the form has pending changes
|
|
var locationEvent = $rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) {
|
|
|
|
var infiniteEditors = editorService.getEditors();
|
|
|
|
if (!formCtrl.$dirty && infiniteEditors.length === 0 || isSavingNewItem && infiniteEditors.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var nextPath = nextLocation.split("#")[1];
|
|
|
|
if (nextPath && !confirmed) {
|
|
|
|
if (navigationService.isRouteChangingNavigation(currentLocation, nextLocation)) {
|
|
|
|
if (nextPath.indexOf("%253") || nextPath.indexOf("%252")) {
|
|
nextPath = decodeURIComponent(nextPath);
|
|
}
|
|
|
|
// Open discard changes overlay
|
|
var overlay = {
|
|
"view": "default",
|
|
"title": labels.unsavedChangesTitle,
|
|
"content": labels.unsavedChangesContent,
|
|
"disableBackdropClick": true,
|
|
"disableEscKey": true,
|
|
"submitButtonLabel": labels.stayButton,
|
|
"closeButtonLabel": labels.discardChangesButton,
|
|
submit: function() {
|
|
overlayService.close();
|
|
},
|
|
close: function() {
|
|
// close all editors
|
|
editorService.closeAll();
|
|
// allow redirection
|
|
navigationService.clearSearch();
|
|
//we need to break the path up into path and query
|
|
var parts = nextPath.split("?");
|
|
var query = {};
|
|
if (parts.length > 1) {
|
|
_.each(parts[1].split("&"), function(q) {
|
|
var keyVal = q.split("=");
|
|
query[keyVal[0]] = keyVal[1];
|
|
});
|
|
}
|
|
$location.path(parts[0]).search(query);
|
|
overlayService.close();
|
|
confirmed = true;
|
|
}
|
|
};
|
|
|
|
overlayService.open(overlay);
|
|
|
|
//prevent the route!
|
|
event.preventDefault();
|
|
|
|
//raise an event
|
|
eventsService.emit("valFormManager.pendingChanges", true);
|
|
}
|
|
}
|
|
|
|
});
|
|
unsubscribe.push(locationEvent);
|
|
|
|
//Ensure to remove the event handler when this instance is destroyted
|
|
scope.$on('$destroy', function() {
|
|
for (var u in unsubscribe) {
|
|
unsubscribe[u]();
|
|
}
|
|
});
|
|
|
|
$timeout(function(){
|
|
formCtrl.$setPristine();
|
|
}, 1000);
|
|
|
|
}
|
|
};
|
|
}
|
|
angular.module('umbraco.directives.validation').directive("valFormManager", valFormManager);
|