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.

This commit is contained in:
Shannon
2013-10-11 14:11:25 +11:00
parent 3ba9f87d93
commit ee4c0cbfc3
8 changed files with 222 additions and 181 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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
// ------------

View File

@@ -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;
};

View File

@@ -15,7 +15,7 @@
<div class="span8">
<div class="btn-toolbar pull-right umb-btn-toolbar">
<div class="btn-group" ng-animate="'fade'" ng-show="status">
<p class="btn btn-link umb-status-label">{{status}}</p>
<p class="btn btn-link umb-status-label">{{formStatus}}</p>
</div>
<div class="btn-group">

View File

@@ -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)
});
});
}
};
}

View File

@@ -15,7 +15,7 @@
<div class="span8">
<div class="btn-toolbar pull-right umb-btn-toolbar">
<div class="btn-group" ng-animate="'fade'" ng-show="status">
<p class="btn btn-link umb-status-label">{{status}}</p>
<p class="btn btn-link umb-status-label">{{formStatus}}</p>
</div>
<div class="btn-group">

View File

@@ -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;
}
};
}