[UI] Progress indicator for pending operations - Refactoring for code clarity

This commit is contained in:
Henry 2015-11-05 17:32:39 -08:00
parent e1c6c76612
commit 793ed7ebe6
3 changed files with 134 additions and 98 deletions

View File

@ -94,41 +94,42 @@ define(
CopyAction.prototype.perform = function() {
var self = this;
return AbstractComposeAction.prototype.perform.call(this)
.then(
function success(){
self.notification.dismiss();
self.notificationService.info("Copying complete.");
},
function error(errorDetails){
var errorMessage = {
title: "Error copying objects.",
severity: "error",
hint: errorDetails.message,
minimized: true, // want the notification to be minimized initially (don't show banner)
options: [{
label: "OK",
callback: function() {
self.dialogService.dismiss();
}
}]
};
self.dialogService.dismiss();
if (self.notification) {
self.notification.dismiss(); // Clear the progress notification
function success(){
self.notification.dismiss();
self.notificationService.info("Copying complete.");
}
function error(errorDetails){
var errorMessage = {
title: "Error copying objects.",
severity: "error",
hint: errorDetails.message,
minimized: true, // want the notification to be minimized initially (don't show banner)
options: [{
label: "OK",
callback: function() {
self.dialogService.dismiss();
}
self.$log.error("Error copying objects. ", errorDetails);
//Show a minimized notification of error for posterity
self.notificationService.notify(errorMessage);
//Display a blocking message
self.dialogService.showBlockingMessage(errorMessage);
},
function notification(details){
self.progress(details.phase, details.totalObjects, details.processed);
}
);
}]
};
self.dialogService.dismiss();
if (self.notification) {
self.notification.dismiss(); // Clear the progress notification
}
self.$log.error("Error copying objects. ", errorDetails);
//Show a minimized notification of error for posterity
self.notificationService.notify(errorMessage);
//Display a blocking message
self.dialogService.showBlockingMessage(errorMessage);
}
function notification(details){
self.progress(details.phase, details.totalObjects, details.processed);
}
return AbstractComposeAction.prototype.perform.call(this)
.then(success, error, notification);
};
return CopyAction;
}

View File

@ -27,6 +27,17 @@ define(
function (uuid) {
"use strict";
/**
* This class encapsulates the process of copying a domain object
* and all of its children.
*
* @param domainObject The object to copy
* @param parent The new location of the cloned object tree
* @param persistenceService
* @param $q
* @param now
* @constructor
*/
function CopyTask (domainObject, parent, persistenceService, $q, now){
this.domainObject = domainObject;
this.parent = parent;
@ -35,6 +46,25 @@ define(
this.persistenceService = persistenceService;
this.persisted = 0;
this.now = now;
this.clones = [];
}
function composeChild(child, parent) {
//Once copied, associate each cloned
// composee with its parent clone
child.model.location = parent.id;
parent.model.composition = parent.model.composition || [];
return parent.model.composition.push(child.id);
}
function cloneObjectModel(objectModel) {
var clone = JSON.parse(JSON.stringify(objectModel));
delete clone.composition;
delete clone.persisted;
delete clone.modified;
return clone;
}
/**
@ -43,26 +73,24 @@ define(
* result in automatic request batching by the browser.
* @private
*/
CopyTask.prototype.persistObjects = function(objectClones) {
CopyTask.prototype.persistObjects = function() {
var self = this;
return this.$q.all(objectClones.map(function(clone){
return this.$q.all(this.clones.map(function(clone){
clone.model.persisted = self.now();
return self.persistenceService.createObject(clone.persistenceSpace, clone.id, clone.model)
.then(function(){
return self.deferred.notify({phase: "copying", totalObjects: objectClones.length, processed: ++self.persisted});
return self.deferred.notify({phase: "copying", totalObjects: self.clones.length, processed: ++self.persisted});
});
})).then(function(){
return objectClones;
});
}));
};
/**
* Will add a list of clones to the specified parent's composition
* @private
*/
CopyTask.prototype.addClonesToParent = function(clones) {
var parentClone = clones[clones.length-1],
CopyTask.prototype.addClonesToParent = function() {
var parentClone = this.clones[this.clones.length-1],
self = this;
if (!this.parent.hasCapability('composition')){
@ -77,6 +105,61 @@ define(
// Ensure the clone of the original domainObject is returned
};
/**
* Given an array of objects composed by a parent, clone them, then
* add them to the parent.
* @private
* @returns {*}
*/
CopyTask.prototype.copyComposees = function(composees, clonedParent, originalParent){
var self = this;
return (composees || []).reduce(function(promise, composee){
//If the composee is composed of other
// objects, chain a promise..
return promise.then(function(){
// ...to recursively copy it (and its children)
return self.copy(composee, originalParent).then(function(composee){
composeChild(composee, clonedParent)
});
});}, self.$q.when(undefined)
)
}
/**
* A recursive function that will perform a bottom-up copy of
* the object tree with originalObject at the root. Recurses to
* the farthest leaf, then works its way back up again,
* cloning objects, and composing them with their child clones
* as it goes
* @private
* @param originalObject
* @param originalParent
* @returns {*}
*/
CopyTask.prototype.copy = function(originalObject, originalParent) {
var self = this;
//Make a clone of the model of the object to be copied
var modelClone = {
id: uuid(),
model: cloneObjectModel(originalObject.getModel()),
persistenceSpace: originalParent.hasCapability('persistence') && originalParent.getCapability('persistence').getSpace()
};
return this.$q.when(originalObject.useCapability('composition')).then(function(composees){
self.deferred.notify({phase: "preparing"});
//Duplicate the object's children, and their children, and
// so on down to the leaf nodes of the tree.
return self.copyComposees(composees, modelClone, originalObject).then(function (){
//Add the clone to the list of clones that will
//be returned by this function
self.clones.push(modelClone);
return modelClone;
});
});
}
/**
* Will build a graph of an object and all of its child objects in
* memory
@ -90,66 +173,18 @@ define(
* references to their own children.
*/
CopyTask.prototype.buildCopyPlan = function() {
var clones = [],
$q = this.$q,
self = this;
var self = this;
function makeClone(object) {
return JSON.parse(JSON.stringify(object));
}
/**
* A recursive function that will perform a bottom-up copy of
* the object tree with originalObject at the root. Recurses to
* the farthest leaf, then works its way back up again,
* cloning objects, and composing them with their child clones
* as it goes
* @param originalObject
* @param originalParent
* @returns {*}
*/
function copy(originalObject, originalParent) {
//Make a clone of the model of the object to be copied
var modelClone = {
id: uuid(),
model: makeClone(originalObject.getModel()),
persistenceSpace: originalParent.hasCapability('persistence') && originalParent.getCapability('persistence').getSpace()
};
delete modelClone.model.composition;
delete modelClone.model.persisted;
delete modelClone.model.modified;
return $q.when(originalObject.useCapability('composition')).then(function(composees){
self.deferred.notify({phase: "preparing"});
return (composees || []).reduce(function(promise, composee){
//If the object is composed of other
// objects, chain a promise..
return promise.then(function(){
// ...to recursively copy it (and its children)
return copy(composee, originalObject).then(function(composeeClone){
//Once copied, associate each cloned
// composee with its parent clone
composeeClone.model.location = modelClone.id;
modelClone.model.composition = modelClone.model.composition || [];
return modelClone.model.composition.push(composeeClone.id);
});
});}, $q.when(undefined)
).then(function (){
//Add the clone to the list of clones that will
//be returned by this function
clones.push(modelClone);
return modelClone;
});
});
}
return copy(self.domainObject, self.parent).then(function(domainObjectClone){
return this.copy(self.domainObject, self.parent).then(function(domainObjectClone){
domainObjectClone.model.location = self.parent.getId();
return clones;
});
};
/**
* Execute the copy task with the objects provided in the constructor.
* @returns {promise} Which will resolve with a clone of the object
* once complete.
*/
CopyTask.prototype.perform = function(){
var persistObjects = this.persistObjects.bind(this),
addClonesToParent = this.addClonesToParent.bind(this);

View File

@ -342,7 +342,7 @@ define(
expect(copyFinished.mostRecentCall.args[0].model.modified).toBeUndefined();
expect(copyFinished.mostRecentCall.args[0].model.persisted).toBe(mockNow.now());
});
it ("correctly locates cloned objects", function() {
expect(mockPersistenceService.createObject.calls[0].args[2].location).toEqual(mockPersistenceService.createObject.calls[1].args[1]);
});