[Common UI] Initial commonUI bundles

Bring in work on general-purpose and over-arching
user interface bundles from the sandbox transition
branch. WTD-574.
This commit is contained in:
Victor Woeltjen
2014-11-23 15:41:20 -08:00
parent 0cd331e8a5
commit 1b0303e517
73 changed files with 6035 additions and 0 deletions

View File

@ -0,0 +1,31 @@
/*global define,Promise*/
/**
* Module defining EditActionController. Created by vwoeltje on 11/17/14.
*/
define(
[],
function () {
"use strict";
/**
* Controller which supplies action instances for Save/Cancel.
* @constructor
*/
function EditActionController($scope) {
function updateActions() {
if (!$scope.action) {
$scope.editActions = [];
} else {
$scope.editActions = $scope.action.getActions({
category: 'conclude-editing'
});
}
}
$scope.$watch("action", updateActions);
}
return EditActionController;
}
);

View File

@ -0,0 +1,27 @@
/*global define,Promise*/
/**
* Module defining EditController. Created by vwoeltje on 11/14/14.
*/
define(
["./objects/EditableDomainObject"],
function (EditableDomainObject) {
"use strict";
/**
*
* @constructor
*/
function EditController($scope, navigationService) {
function setNavigation(domainObject) {
$scope.navigatedObject =
domainObject && new EditableDomainObject(domainObject);
}
setNavigation(navigationService.getNavigation());
navigationService.addListener(setNavigation);
}
return EditController;
}
);

View File

@ -0,0 +1,51 @@
/*global define*/
/**
* The "Save" action; the action triggered by clicking Save from
* Edit Mode. Exits the editing user interface and invokes object
* capabilities to persist the changes that have been made.
*/
define(
function () {
'use strict';
function CancelAction($location, context) {
var domainObject = context.domainObject;
// Look up the object's "editor.completion" capability;
// this is introduced by EditableDomainObject which is
// used to insulate underlying objects from changes made
// during editing.
function getEditorCapability() {
return domainObject.getCapability("editor");
}
// Invoke any save behavior introduced by the editor.completion
// capability.
function doCancel(editor) {
return editor.cancel();
}
// Discard the current root view (which will be the editing
// UI, which will have been pushed atop the Browise UI.)
function returnToBrowse() {
$location.path("/browse");
}
return {
perform: function () {
return doCancel(getEditorCapability())
.then(returnToBrowse);
}
};
}
CancelAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject;
return domainObject !== undefined &&
domainObject.hasCapability("editor");
};
return CancelAction;
}
);

View File

@ -0,0 +1,47 @@
/*global define,Promise*/
/**
* Module defining EditAction. Created by vwoeltje on 11/14/14.
*/
define(
[],
function () {
"use strict";
var NULL_ACTION = {
perform: function () {
return undefined;
}
};
/**
*
* @constructor
*/
function EditAction($location, navigationService, $log, context) {
var domainObject = (context || {}).domainObject;
if (!domainObject) {
$log.error([
"No domain object to edit; ",
"edit action is not valid."
].join(""));
return NULL_ACTION;
}
return {
perform: function () {
navigationService.setNavigation(domainObject);
$location.path("/edit");
}
};
}
EditAction.appliesTo = function (context) {
return (context || {}).domainObject !== undefined;
};
return EditAction;
}
);

View File

@ -0,0 +1,90 @@
/*global define*/
/**
* Module defining RemoveAction. Created by vwoeltje on 11/17/14.
*/
define(
[],
function () {
"use strict";
/**
* Construct an action which will remove the provided object manifestation.
* The object will be removed from its parent's composition; the parent
* is looked up via the "context" capability (so this will be the
* immediate ancestor by which this specific object was reached.)
*
* @param {DomainObject} object the object to be removed
* @param {ActionContext} context the context in which this action is performed
* @constructor
* @memberof module:editor/actions/remove-action
*/
function RemoveAction($q, context) {
var object = (context || {}).domainObject;
/**
* Check whether an object ID matches the ID of the object being
* removed (used to filter a parent's composition to handle the
* removal.)
*/
function isNotObject(otherObjectId) {
return otherObjectId !== object.getId();
}
/**
* Mutate a parent object such that it no longer contains the object
* which is being removed.
*/
function doMutate(model) {
model.composition = model.composition.filter(isNotObject);
}
/**
* Invoke persistence on a domain object. This will be called upon
* the removed object's parent (as its composition will have changed.)
*/
function doPersist(domainObject) {
var persistence = domainObject.getCapability('persistence');
return persistence && persistence.persist();
}
/**
* Remove the object from its parent, as identified by its context
* capability.
* @param {ContextCapability} contextCapability the "context" capability
* of the domain object being removed.
*/
function removeFromContext(contextCapability) {
var parent = contextCapability.getParent();
$q.when(
parent.useCapability('mutation', doMutate)
).then(function () {
return doPersist(parent);
});
}
return {
/**
* Perform this action.
* @return {module:core/promises.Promise} a promise which will be
* fulfilled when the action has completed.
*/
perform: function () {
return $q.when(object.getCapability('context'))
.then(removeFromContext);
}
};
}
// Object needs to have a parent for Remove to be applicable
/*RemoveAction.appliesTo = function (context) {
var object = context.domainObject,
contextCapability = object && object.getCapability("context"),
parent = contextCapability && contextCapability.getParent();
return parent !== undefined &&
Array.isArray(parent.getModel().composition);
};*/
return RemoveAction;
}
);

View File

@ -0,0 +1,50 @@
/*global define*/
/**
* The "Save" action; the action triggered by clicking Save from
* Edit Mode. Exits the editing user interface and invokes object
* capabilities to persist the changes that have been made.
*/
define(
function () {
'use strict';
function SaveAction($location, context) {
var domainObject = context.domainObject;
// Look up the object's "editor.completion" capability;
// this is introduced by EditableDomainObject which is
// used to insulate underlying objects from changes made
// during editing.
function getEditorCapability() {
return domainObject.getCapability("editor");
}
// Invoke any save behavior introduced by the editor.completion
// capability.
function doSave(editor) {
return editor.save();
}
// Discard the current root view (which will be the editing
// UI, which will have been pushed atop the Browise UI.)
function returnToBrowse() {
$location.path("/browse");
}
return {
perform: function () {
return doSave(getEditorCapability()).then(returnToBrowse);
}
};
}
SaveAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject;
return domainObject !== undefined &&
domainObject.hasCapability("editor");
};
return SaveAction;
}
);

View File

@ -0,0 +1,56 @@
/*global define*/
/**
* Wrapper for both "context" and "composition" capabilities;
* ensures that any domain objects reachable in Edit mode
* are also wrapped as EditableDomainObjects
*/
define(
[],
function () {
'use strict';
return function EditableContextCapability(
contextCapability,
editableObject,
domainObject,
factory
) {
var capability = Object.create(contextCapability);
function isDomainObject(obj) {
return typeof obj.getId === 'function' &&
typeof obj.getModel === 'function' &&
typeof obj.getCapability === 'function';
}
function makeEditableObject(obj) {
return isDomainObject(obj) ?
factory.getEditableObject(obj) :
obj;
}
function makeEditable(obj) {
return Array.isArray(obj) ?
obj.map(makeEditableObject) :
makeEditableObject(obj);
}
// Replace all methods; return only editable domain objects.
Object.keys(contextCapability).forEach(function (k) {
capability[k] = function () {
var result = contextCapability[k].apply(
capability,
arguments
);
return result.then ? // promise-like
result.then(makeEditable) :
makeEditable(result);
};
});
return capability;
};
}
);

View File

@ -0,0 +1,30 @@
/*global define*/
/**
* Editable Persistence Capability. Overrides the persistence capability
* normally exhibited by a domain object to ensure that changes made
* during edit mode are not immediately stored to the database or other
* backing storage.
*/
define(
function () {
'use strict';
return function EditablePersistenceCapability(
persistenceCapability,
editableObject,
domainObject,
cache
) {
var persistence = Object.create(persistenceCapability);
// Simply trigger refresh of in-view objects; do not
// write anything to database.
persistence.persist = function () {
cache.markDirty(editableObject);
};
return persistence;
};
}
);

View File

@ -0,0 +1,55 @@
/*global define*/
/**
* Implements "save" and "cancel" as capabilities of
* the object. In editing mode, user is seeing/using
* a copy of the object (an EditableDomainObject)
* which is disconnected from persistence; the Save
* and Cancel actions can use this capability to
* propagate changes from edit mode to the underlying
* actual persistable object.
*/
define(
[],
function () {
'use strict';
return function EditorCapability(
persistenceCapability,
editableObject,
domainObject,
cache
) {
function doMutate() {
return domainObject.useCapability('mutation', function () {
return editableObject.getModel();
});
}
function doPersist() {
return persistenceCapability.persist();
}
function saveOthers() {
return cache.saveAll();
}
function markClean() {
return cache.markClean(editableObject);
}
return {
save: function () {
return Promise.resolve(doMutate())
.then(doPersist)
.then(markClean)
.then(saveOthers);
},
cancel: function () {
return Promise.resolve(undefined);
}
};
};
}
);

View File

@ -0,0 +1,84 @@
/*global define*/
/**
* Defines EditableDomainObject, which wraps domain objects
* such that user code may work with and mutate a copy of the
* domain object model; these changes may then be propagated
* up to the real domain object (or not) by way of invoking
* save or cancel behaviors of the "editor.completion"
* capability (a capability intended as internal to edit
* mode; invoked by way of the Save and Cancel actions.)
*/
define(
[
'../capabilities/EditablePersistenceCapability',
'../capabilities/EditableContextCapability',
'../capabilities/EditorCapability',
'./EditableDomainObjectCache'
],
function (
EditablePersistenceCapability,
EditableContextCapability,
EditorCapability,
EditableDomainObjectCache
) {
"use strict";
var capabilityFactories = {
persistence: EditablePersistenceCapability,
context: EditableContextCapability,
composition: EditableContextCapability,
editor: EditorCapability
};
// Handle special case where "editor.completion" wraps persistence
// (other capability overrides wrap capabilities of the same type.)
function getDelegateArguments(name, args) {
return name === "editor" ? ['persistence'] : args;
}
/**
* An EditableDomainObject overrides capabilities
* which need to behave differently in edit mode,
* and provides a "working copy" of the object's
* model to allow changes to be easily cancelled.
*/
function EditableDomainObject(domainObject) {
// The cache will hold all domain objects reached from
// the initial EditableDomainObject; this ensures that
// different versions of the same editable domain object
// are not shown in different sections of the same Edit
// UI, which might thereby fall out of sync.
var cache;
// Constructor for EditableDomainObject, which adheres
// to the same shared cache.
function EditableDomainObjectImpl(domainObject) {
var model = JSON.parse(JSON.stringify(domainObject.getModel())),
editableObject = Object.create(domainObject);
// Only provide the cloned model.
editableObject.getModel = function () { return model; };
// Override certain capabilities
editableObject.getCapability = function (name) {
var delegateArguments = getDelegateArguments(name, arguments),
capability = domainObject.getCapability.apply(this, delegateArguments),
factory = capabilityFactories[name];
return (factory && capability) ?
factory(capability, editableObject, domainObject, cache) :
capability;
};
return editableObject;
}
cache = new EditableDomainObjectCache(EditableDomainObjectImpl);
return cache.getEditableObject(domainObject);
}
return EditableDomainObject;
}
);

View File

@ -0,0 +1,102 @@
/*global define*/
/**
* An editable domain object cache stores domain objects that have been
* made editable, in a group that can be saved all-at-once. This supports
* Edit mode, which is launched for a specific object but may contain
* changes across many objects.
*
* Editable domain objects have certain specific capabilities overridden
* to ensure that changes made while in edit mode do not propagate up
* to the objects used in browse mode (or to persistence) until the user
* initiates a Save.
*
* @module editor/object/editable-domain-object-cache
*/
define(
function () {
'use strict';
/**
* Construct a new cache for editable domain objects. This can be used
* to get-or-create editable objects, particularly to support wrapping
* of objects retrieved via composition or context capabilities as
* editable domain objects.
*
* @param {Constructor<EditableDomainObject>} EditableDomainObject a
* constructor function which takes a regular domain object as
* an argument, and returns an editable domain object as its
* result.
* @constructor
* @memberof module:editor/object/editable-domain-object-cache
*/
function EditableDomainObjectCache(EditableDomainObject) {
var cache = {},
dirty = {};
return {
/**
* Wrap this domain object in an editable form, or pull such
* an object from the cache if one already exists.
*
* @param {DomainObject} domainObject the regular domain object
* @returns {DomainObject} the domain object in an editable form
*/
getEditableObject: function (domainObject) {
var id = domainObject.getId();
return (cache[id] =
cache[id] || new EditableDomainObject(domainObject));
},
/**
* Mark an editable domain object (presumably already cached)
* as having received modifications during editing; it should be
* included in the bulk save invoked when editing completes.
*
* @param {DomainObject} domainObject the domain object
*/
markDirty: function (domainObject) {
dirty[domainObject.getId()] = domainObject;
},
/**
* Mark an object (presumably already cached) as having had its
* changes saved (and thus no longer needing to be subject to a
* save operation.)
*
* @param {DomainObject} domainObject the domain object
*/
markClean: function (domainObject) {
delete dirty[domainObject.getId()];
},
/**
* Initiate a save on all objects that have been cached.
*/
saveAll: function () {
var object;
// Most save logic is handled by the "editor.completion"
// capability, but this in turn will typically invoke
// Save All. An infinite loop is avoided by marking
// objects as clean as we go.
function doSave(editCapability) {
return editCapability.save();
}
while (Object.keys(dirty).length > 0) {
// Pick the first dirty object
object = dirty[Object.keys(dirty)[0]];
// Mark non-dirty to avoid successive invocations
this.markClean(object);
// Invoke its save behavior
object.getCapability('editor.completion').then(doSave);
}
}
};
}
return EditableDomainObjectCache;
}
);