From ee4c0cbfc34f9f3ab41e87a4b9b16c90aac92d9c Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 11 Oct 2013 14:11:25 +1100 Subject: [PATCH] Streamlines how forms (editors) work with a new formHelper, this reduces much of the repetitive code and also ensures that the correct events fire, this will be the start of streamlining how all forms work - even simple ones. --- .../services/contenteditinghelper.service.js | 11 -- .../src/common/services/formhelper.service.js | 108 +++++++++++++ src/Umbraco.Web.UI.Client/src/less/forms.less | 14 -- .../views/content/content.edit.controller.js | 145 ++++++++---------- .../src/views/media/edit.html | 2 +- .../src/views/media/media.edit.controller.js | 56 +++---- .../src/views/member/edit.html | 2 +- .../views/member/member.edit.controller.js | 65 +++----- 8 files changed, 222 insertions(+), 181 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 9df21c6b24..c7567983c9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -198,21 +198,10 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser if (!args) { throw "args cannot be null"; } - if (!args.scope) { - throw "args.scope cannot be null"; - } - if (!args.scope.content) { - throw "args.scope.content cannot be null"; - } if (!args.newContent) { throw "args.newContent cannot be null"; } - for (var i = 0; i < args.newContent.notifications.length; i++) { - notificationsService.showNotification(args.newContent.notifications[i]); - } - - args.scope.$broadcast("formSubmitted", { scope: args.scope }); if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.newContent.id)) { //we are not redirecting because this is not new content, it is existing content. In this case diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js new file mode 100644 index 0000000000..7efc8bebaa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -0,0 +1,108 @@ +/** + * @ngdoc service + * @name umbraco.services.formHelper + * @function + * + * @description + * A utility class used to streamline how forms are developed, to ensure that validation is check and displayed consistently and to ensure that the correct events + * fire when they need to. + */ +function formHelper(angularHelper, serverValidationManager, $timeout, notificationsService) { + return { + + /** + * @ngdoc function + * @name umbraco.services.formHelper#submitForm + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * Called by controllers when submitting a form - this ensures that all client validation is checked, + * server validation is cleared, that the correct events execute and status messages are displayed. + * This returns true if the form is valid, otherwise false if form submission cannot continue. + * + * @param {object} args An object containing arguments for form submission + */ + submitForm: function (args) { + + var currentForm; + + if (!args) { + throw "args cannot be null"; + } + if (!args.scope) { + throw "args.scope cannot be null"; + } + if (!args.formCtrl) { + //try to get the closest form controller + currentForm = angularHelper.getRequiredCurrentForm(args.scope); + } + else { + currentForm = args.formCtrl; + } + //if no statusPropertyName is set we'll default to formStatus. + if (!args.statusPropertyName) { + args.statusPropertyName = "formStatus"; + } + //if no statusTimeout is set, we'll default to 2500 ms + if (!args.statusTimeout) { + args.statusTimeout = 2500; + } + + //the first thing any form must do is broadcast the formSubmitting event + args.scope.$broadcast("formSubmitting", { scope: args.scope }); + + //then check if the form is valid + if (!args.skipValidation) { + if (currentForm.$invalid) { + return false; + } + } + + //reset the server validations + serverValidationManager.reset(); + + //check if a form status should be set on the scope + if (args.statusMessage) { + args.scope[args.statusPropertyName] = args.statusMessage; + + //clear the message after the timeout + $timeout(function () { + args.scope[args.statusPropertyName] = undefined; + }, args.statusTimeout); + } + + return true; + }, + + /** + * @ngdoc function + * @name umbraco.services.formHelper#submitForm + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * Called by controllers when a form has been successfully submitted. the correct events execute + * and that the notifications are displayed if there are any. + * + * @param {object} args An object containing arguments for form submission + */ + resetForm: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.scope) { + throw "args.scope cannot be null"; + } + + if (angular.isArray(args.notifications)) { + for (var i = 0; i < args.notifications.length; i++) { + notificationsService.showNotification(args.notifications[i]); + } + } + + args.scope.$broadcast("formSubmitted", { scope: args.scope }); + } + }; +} +angular.module('umbraco.services').factory('formHelper', formHelper); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 3a35c06ad5..0fd960ee85 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -454,20 +454,6 @@ input[type="checkbox"][readonly] { } } -//non-html5 states, pp: added default ng-invalid class -input.highlight-error, -select.highlight-error, -textarea.highlight-error, -.ng-dirty input.ng-invalid, -.ng-dirty select.ng-invalid, -.ng-dirty textarea.ng-invalid{ - border-color: #953b39 !important; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; -} - - // FORM ACTIONS // ------------ diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index 88587fbfab..fcfd724214 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -6,7 +6,7 @@ * @description * The controller for the content editor */ -function ContentEditController($scope, $routeParams, $q, $timeout, $window, contentResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager) { +function ContentEditController($scope, $routeParams, $q, $timeout, $window, contentResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper) { if ($routeParams.create) { //we are creating so get an empty content item @@ -33,56 +33,55 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, cont }); } - //TODO: Need to figure out a way to share the saving and event broadcasting with all editors! - $scope.unPublish = function () { - $scope.setStatus("Unpublishing..."); + if (formHelper.submitForm({ scope: $scope, statusMessage: "Unpublishing...", skipValidation: true })) { - contentResource.unPublish($scope.content.id) - .then(function (data) { - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - newContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + contentResource.unPublish($scope.content.id) + .then(function (data) { + + formHelper.resetForm({ scope: $scope, notifications: data.notifications }); + + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + newContent: data, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + }); + + navigationService.syncPath(data.path.split(",")); }); - - navigationService.syncPath(data.path.split(",")); - }); + } + }; - $scope.saveAndPublish = function () { - - $scope.setStatus("Publishing..."); - $scope.$broadcast("formSubmitting", { scope: $scope }); - - var currentForm = angularHelper.getRequiredCurrentForm($scope); - - //don't continue if the form is invalid - if (currentForm.$invalid) return; + $scope.saveAndPublish = function() { - serverValidationManager.reset(); - - contentResource.publish($scope.content, $routeParams.create, fileManager.getFiles()) - .then(function (data) { - - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - newContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + if (formHelper.submitForm({ scope: $scope, statusMessage: "Publishing..." })) { + + contentResource.publish($scope.content, $routeParams.create, fileManager.getFiles()) + .then(function(data) { + + formHelper.resetForm({ scope: $scope, notifications: data.notifications }); + + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + newContent: data, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + }); + + navigationService.syncPath(data.path.split(",")); + + }, function(err) { + + contentEditingHelper.handleSaveError({ + err: err, + redirectOnFailure: true, + allNewProps: contentEditingHelper.getAllProps(err.data), + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) + }); }); - - navigationService.syncPath(data.path.split(",")); + } - }, function (err) { - - contentEditingHelper.handleSaveError({ - err: err, - redirectOnFailure: true, - allNewProps: contentEditingHelper.getAllProps(err.data), - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) - }); - }); }; $scope.preview = function(content){ @@ -95,48 +94,38 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, cont } }; - $scope.setStatus = function(status){ - //add localization - $scope.status = status; - $timeout(function(){ - $scope.status = undefined; - }, 2500); - }; - - $scope.save = function () { + $scope.save = function() { var deferred = $q.defer(); - $scope.setStatus("Saving..."); - $scope.$broadcast("formSubmitting", { scope: $scope }); - - var currentForm = angularHelper.getRequiredCurrentForm($scope); + if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { - //don't continue if the form is invalid - if (currentForm.$invalid) return; + contentResource.save($scope.content, $routeParams.create, fileManager.getFiles()) + .then(function(data) { - serverValidationManager.reset(); + formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - contentResource.save($scope.content, $routeParams.create, fileManager.getFiles()) - .then(function (data) { - - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - newContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + newContent: data, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + }); + + navigationService.syncPath(data.path.split(",")); + + deferred.resolve(data); + }, function(err) { + contentEditingHelper.handleSaveError({ + err: err, + allNewProps: contentEditingHelper.getAllProps(err.data), + allOrigProps: contentEditingHelper.getAllProps($scope.content) + }); + + deferred.reject(err); }); - - navigationService.syncPath(data.path.split(",")); - - deferred.resolve(data); - }, function (err) { - contentEditingHelper.handleSaveError({ - err: err, - allNewProps: contentEditingHelper.getAllProps(err.data), - allOrigProps: contentEditingHelper.getAllProps($scope.content) - }); - - deferred.reject(err); - }); + } + else { + deferred.reject(); + } return deferred.promise; }; diff --git a/src/Umbraco.Web.UI.Client/src/views/media/edit.html b/src/Umbraco.Web.UI.Client/src/views/media/edit.html index 62bb43c400..db60480be9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/edit.html @@ -15,7 +15,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index 7aa3772652..74a45e5c82 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -6,7 +6,7 @@ * @description * The controller for the media editor */ -function mediaEditController($scope, $routeParams, mediaResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, $timeout) { +function mediaEditController($scope, $routeParams, mediaResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper) { if ($routeParams.create) { @@ -32,44 +32,32 @@ function mediaEditController($scope, $routeParams, mediaResource, notificationsS }); } - $scope.setStatus = function(status){ - //add localization - $scope.status = status; - $timeout(function(){ - $scope.status = undefined; - }, 2500); - }; - $scope.save = function () { - - $scope.setStatus("Saving..."); - - $scope.$broadcast("formSubmitting", { scope: $scope }); - var currentForm = angularHelper.getRequiredCurrentForm($scope); - //don't continue if the form is invalid - if (currentForm.$invalid) return; - - serverValidationManager.reset(); + if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { - mediaResource.save($scope.content, $routeParams.create, fileManager.getFiles()) - .then(function (data) { + mediaResource.save($scope.content, $routeParams.create, fileManager.getFiles()) + .then(function(data) { - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - newContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + formHelper.resetForm({ scope: $scope, notifications: data.notifications }); + + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + newContent: data, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + }); + + }, function(err) { + + contentEditingHelper.handleSaveError({ + err: err, + redirectOnFailure: true, + allNewProps: contentEditingHelper.getAllProps(err.data), + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) + }); }); - - }, function (err) { - - contentEditingHelper.handleSaveError({ - err: err, - redirectOnFailure: true, - allNewProps: contentEditingHelper.getAllProps(err.data), - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) - }); - }); + } + }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/member/edit.html b/src/Umbraco.Web.UI.Client/src/views/member/edit.html index 5501ccb381..77d7fe707a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/member/edit.html @@ -15,7 +15,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index 2138a99872..84ada1e88c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -6,7 +6,7 @@ * @description * The controller for the member editor */ -function MemberEditController($scope, $routeParams, $location, $q, $timeout, $window, memberResource, entityResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager) { +function MemberEditController($scope, $routeParams, $location, $q, $window, memberResource, entityResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper) { if ($routeParams.create) { //we are creating so get an empty member item @@ -45,53 +45,34 @@ function MemberEditController($scope, $routeParams, $location, $q, $timeout, $wi } - //TODO: Need to figure out a way to share the saving and event broadcasting with all editors! - - $scope.setStatus = function(status){ - //add localization - $scope.status = status; - $timeout(function(){ - $scope.status = undefined; - }, 2500); - }; + $scope.save = function() { - $scope.save = function () { - var deferred = $q.defer(); - - $scope.setStatus("Saving..."); - $scope.$broadcast("formSubmitting", { scope: $scope }); + if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { - var currentForm = angularHelper.getRequiredCurrentForm($scope); + memberResource.save($scope.content, $routeParams.create, fileManager.getFiles()) + .then(function(data) { - //don't continue if the form is invalid - if (currentForm.$invalid) return; + formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - serverValidationManager.reset(); + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + newContent: data, + //specify a custom id to redirect to since we want to use the GUID + redirectId: data.key, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + }); + + }, function (err) { + + contentEditingHelper.handleSaveError({ + err: err, + allNewProps: contentEditingHelper.getAllProps(err.data), + allOrigProps: contentEditingHelper.getAllProps($scope.content) + }); - memberResource.save($scope.content, $routeParams.create, fileManager.getFiles()) - .then(function (data) { - - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - newContent: data, - //specify a custom id to redirect to since we want to use the GUID - redirectId: data.key, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) }); - - deferred.resolve(data); - - }, function (err) { - contentEditingHelper.handleSaveError({ - err: err, - allNewProps: contentEditingHelper.getAllProps(err.data), - allOrigProps: contentEditingHelper.getAllProps($scope.content) - }); - - deferred.reject(err); - }); - - return deferred.promise; + } + }; }