diff --git a/platform/commonUI/edit/src/actions/CancelAction.js b/platform/commonUI/edit/src/actions/CancelAction.js index 7b2f28538d..a9e6effe9f 100644 --- a/platform/commonUI/edit/src/actions/CancelAction.js +++ b/platform/commonUI/edit/src/actions/CancelAction.js @@ -31,9 +31,24 @@ define( * capabilities to persist the changes that have been made. * @constructor * @memberof platform/commonUI/edit + * @implements {Action} */ function CancelAction($location, urlService, context) { - var domainObject = context.domainObject; + this.domainObject = context.domainObject; + this.$location = $location; + this.urlService = urlService; + } + + /** + * Cancel editing. + * + * @returns {Promise} a promise that will be fulfilled when + * cancellation has completed + */ + CancelAction.prototype.perform = function () { + var domainObject = this.domainObject, + $location = this.$location, + urlService = this.urlService; // Look up the object's "editor.completion" capability; // this is introduced by EditableDomainObject which is @@ -58,26 +73,15 @@ define( ))); } - return { - /** - * Cancel editing. - * - * @returns {Promise} a promise that will be fulfilled when - * cancellation has completed - * @memberof platform/commonUI/edit.CancelAction# - */ - perform: function () { - return doCancel(getEditorCapability()) - .then(returnToBrowse); - } - }; - } + return doCancel(getEditorCapability()) + .then(returnToBrowse); + }; /** * Check if this action is applicable in a given context. * This will ensure that a domain object is present in the context, * and that this domain object is in Edit mode. - * @returns true if applicable + * @returns {boolean} true if applicable */ CancelAction.appliesTo = function (context) { var domainObject = (context || {}).domainObject; diff --git a/platform/commonUI/edit/src/actions/EditAction.js b/platform/commonUI/edit/src/actions/EditAction.js index 2559d0488d..86a8a75540 100644 --- a/platform/commonUI/edit/src/actions/EditAction.js +++ b/platform/commonUI/edit/src/actions/EditAction.js @@ -44,6 +44,7 @@ define( * route) * @memberof platform/commonUI/edit * @constructor + * @implements {Action} */ function EditAction($location, navigationService, $log, context) { var domainObject = (context || {}).domainObject; @@ -61,18 +62,19 @@ define( return NULL_ACTION; } - return { - /** - * Enter edit mode. - * @memberof platform/commonUI/edit.EditAction# - */ - perform: function () { - navigationService.setNavigation(domainObject); - $location.path("/edit"); - } - }; + this.domainObject = domainObject; + this.$location = $location; + this.navigationService = navigationService; } + /** + * Enter edit mode. + */ + EditAction.prototype.perform = function () { + this.navigationService.setNavigation(this.domainObject); + this.$location.path("/edit"); + }; + /** * Check for applicability; verify that a domain object is present * for this action to be performed upon. diff --git a/platform/commonUI/edit/src/actions/LinkAction.js b/platform/commonUI/edit/src/actions/LinkAction.js index b0c2e35e31..74abd2a93c 100644 --- a/platform/commonUI/edit/src/actions/LinkAction.js +++ b/platform/commonUI/edit/src/actions/LinkAction.js @@ -31,42 +31,40 @@ define( * Add one domain object to another's composition. * @constructor * @memberof platform/commonUI/edit + * @implements {Action} */ function LinkAction(context) { - var domainObject = (context || {}).domainObject, - selectedObject = (context || {}).selectedObject, - selectedId = selectedObject && selectedObject.getId(); + this.domainObject = (context || {}).domainObject; + this.selectedObject = (context || {}).selectedObject; + this.selectedId = this.selectedObject && this.selectedObject.getId(); + } + + LinkAction.prototype.perform = function () { + var self = this; // Add this domain object's identifier function addId(model) { if (Array.isArray(model.composition) && - model.composition.indexOf(selectedId) < 0) { - model.composition.push(selectedId); + model.composition.indexOf(self.selectedId) < 0) { + model.composition.push(self.selectedId); } } // Persist changes to the domain object function doPersist() { - var persistence = domainObject.getCapability('persistence'); + var persistence = + self.domainObject.getCapability('persistence'); return persistence.persist(); } // Link these objects function doLink() { - return domainObject.useCapability("mutation", addId) + return self.domainObject.useCapability("mutation", addId) .then(doPersist); } - return { - /** - * Perform this action. - * @memberof platform/commonUI/edit.LinkAction# - */ - perform: function () { - return selectedId && doLink(); - } - }; - } + return this.selectedId && doLink(); + }; return LinkAction; } diff --git a/platform/commonUI/edit/src/actions/PropertiesAction.js b/platform/commonUI/edit/src/actions/PropertiesAction.js index 0cab34a879..1134c23190 100644 --- a/platform/commonUI/edit/src/actions/PropertiesAction.js +++ b/platform/commonUI/edit/src/actions/PropertiesAction.js @@ -32,60 +32,58 @@ define( 'use strict'; /** - * Construct an action which will allow an object's metadata to be - * edited. + * Implements the "Edit Properties" action, which prompts the user + * to modify a domain object's properties. * * @param {DialogService} dialogService a service which will show the dialog * @param {DomainObject} object the object to be edited * @param {ActionContext} context the context in which this action is performed * @memberof platform/commonUI/edit + * @implements {Action} * @constructor */ function PropertiesAction(dialogService, context) { - var object = context.domainObject; + this.domainObject = (context || {}).domainObject; + this.dialogService = dialogService; + } + + PropertiesAction.prototype.perform = function () { + var type = this.domainObject.getCapability('type'), + domainObject = this.domainObject, + dialogService = this.dialogService; // Persist modifications to this domain object function doPersist() { - var persistence = object.getCapability('persistence'); + var persistence = domainObject.getCapability('persistence'); return persistence && persistence.persist(); } // Update the domain object model based on user input function updateModel(userInput, dialog) { - return object.useCapability('mutation', function (model) { + return domainObject.useCapability('mutation', function (model) { dialog.updateModel(model, userInput); }); } function showDialog(type) { // Create a dialog object to generate the form structure, etc. - var dialog = new PropertiesDialog(type, object.getModel()); + var dialog = + new PropertiesDialog(type, domainObject.getModel()); // Show the dialog return dialogService.getUserInput( dialog.getFormStructure(), dialog.getInitialFormValue() ).then(function (userInput) { - // Update the model, if user input was provided - return userInput && updateModel(userInput, dialog); - }).then(function (result) { - return result && doPersist(); - }); + // Update the model, if user input was provided + return userInput && updateModel(userInput, dialog); + }).then(function (result) { + return result && doPersist(); + }); } - return { - /** - * Perform this action. - * @return {Promise} a promise which will be - * fulfilled when the action has completed. - * @memberof platform/commonUI/edit.PropertiesAction# - */ - perform: function () { - var type = object.getCapability('type'); - return type && showDialog(type); - } - }; - } + return type && showDialog(type); + }; /** * Filter this action for applicability against a given context. diff --git a/platform/commonUI/edit/src/actions/PropertiesDialog.js b/platform/commonUI/edit/src/actions/PropertiesDialog.js index a4e3ba1f9f..97ee1f5c0a 100644 --- a/platform/commonUI/edit/src/actions/PropertiesDialog.js +++ b/platform/commonUI/edit/src/actions/PropertiesDialog.js @@ -35,57 +35,56 @@ define( * @constructor */ function PropertiesDialog(type, model) { - var properties = type.getProperties(); - - return { - /** - * Get sections provided by this dialog. - * @return {FormStructure} the structure of this form - * @memberof platform/commonUI/edit.PropertiesDialog# - */ - getFormStructure: function () { - return { - name: "Edit " + model.name, - sections: [{ - name: "Properties", - rows: properties.map(function (property, index) { - // Property definition is same as form row definition - var row = Object.create(property.getDefinition()); - row.key = index; - return row; - }) - }] - }; - }, - /** - * Get the initial state of the form shown by this dialog - * (based on the object model) - * @returns {object} initial state of the form - * @memberof platform/commonUI/edit.PropertiesDialog# - */ - getInitialFormValue: function () { - // Start with initial values for properties - // Note that index needs to correlate to row.key - // from getFormStructure - return properties.map(function (property) { - return property.getValue(model); - }); - }, - /** - * Update a domain object model based on the value of a form. - * @memberof platform/commonUI/edit.PropertiesDialog# - */ - updateModel: function (model, formValue) { - // Update all properties - properties.forEach(function (property, index) { - property.setValue(model, formValue[index]); - }); - } - }; - - + this.type = type; + this.model = model; + this.properties = type.getProperties(); } + /** + * Get sections provided by this dialog. + * @return {FormStructure} the structure of this form + */ + PropertiesDialog.prototype.getFormStructure = function () { + return { + name: "Edit " + this.model.name, + sections: [{ + name: "Properties", + rows: this.properties.map(function (property, index) { + // Property definition is same as form row definition + var row = Object.create(property.getDefinition()); + row.key = index; + return row; + }) + }] + }; + }; + + /** + * Get the initial state of the form shown by this dialog + * (based on the object model) + * @returns {object} initial state of the form + */ + PropertiesDialog.prototype.getInitialFormValue = function () { + var model = this.model; + + // Start with initial values for properties + // Note that index needs to correlate to row.key + // from getFormStructure + return this.properties.map(function (property) { + return property.getValue(model); + }); + }; + + /** + * Update a domain object model based on the value of a form. + */ + PropertiesDialog.prototype.updateModel = function (model, formValue) { + // Update all properties + this.properties.forEach(function (property, index) { + property.setValue(model, formValue[index]); + }); + }; + return PropertiesDialog; } ); diff --git a/platform/commonUI/edit/src/actions/RemoveAction.js b/platform/commonUI/edit/src/actions/RemoveAction.js index 9c64da9b7a..da1c81b486 100644 --- a/platform/commonUI/edit/src/actions/RemoveAction.js +++ b/platform/commonUI/edit/src/actions/RemoveAction.js @@ -39,68 +39,64 @@ define( * @param {ActionContext} context the context in which this action is performed * @memberof platform/commonUI/edit * @constructor + * @implements {Action} */ function RemoveAction($q, context) { - var object = (context || {}).domainObject; + this.domainObject = (context || {}).domainObject; + this.$q = $q; + } - /** + /** + * Perform this action. + * @return {Promise} a promise which will be + * fulfilled when the action has completed. + */ + RemoveAction.prototype.perform = function () { + var $q = this.$q, + domainObject = this.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.) - * @memberof platform/commonUI/edit.RemoveAction# */ function isNotObject(otherObjectId) { - return otherObjectId !== object.getId(); + return otherObjectId !== domainObject.getId(); } - /** + /* * Mutate a parent object such that it no longer contains the object * which is being removed. - * @memberof platform/commonUI/edit.RemoveAction# */ 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.) - * @memberof platform/commonUI/edit.RemoveAction# */ 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. - * @memberof platform/commonUI/edit.RemoveAction# */ function removeFromContext(contextCapability) { var parent = contextCapability.getParent(); - $q.when( - parent.useCapability('mutation', doMutate) - ).then(function () { - return doPersist(parent); - }); + return $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. - * @memberof platform/commonUI/edit.RemoveAction# - */ - perform: function () { - return $q.when(object.getCapability('context')) - .then(removeFromContext); - } - }; - } + return $q.when(this.domainObject.getCapability('context')) + .then(removeFromContext); + }; // Object needs to have a parent for Remove to be applicable RemoveAction.appliesTo = function (context) { diff --git a/platform/commonUI/edit/src/actions/SaveAction.js b/platform/commonUI/edit/src/actions/SaveAction.js index 428f825837..fa276bba4b 100644 --- a/platform/commonUI/edit/src/actions/SaveAction.js +++ b/platform/commonUI/edit/src/actions/SaveAction.js @@ -31,10 +31,26 @@ define( * Edit Mode. Exits the editing user interface and invokes object * capabilities to persist the changes that have been made. * @constructor + * @implements {Action} * @memberof platform/commonUI/edit */ function SaveAction($location, urlService, context) { - var domainObject = context.domainObject; + this.domainObject = (context || {}).domainObject; + this.$location = $location; + this.urlService = urlService; + } + + /** + * Save changes and conclude editing. + * + * @returns {Promise} a promise that will be fulfilled when + * cancellation has completed + * @memberof platform/commonUI/edit.SaveAction# + */ + SaveAction.prototype.perform = function () { + var domainObject = this.domainObject, + $location = this.$location, + urlService = this.urlService; // Invoke any save behavior introduced by the editor capability; // this is introduced by EditableDomainObject which is @@ -53,19 +69,8 @@ define( )); } - return { - /** - * Save changes and conclude editing. - * - * @returns {Promise} a promise that will be fulfilled when - * cancellation has completed - * @memberof platform/commonUI/edit.SaveAction# - */ - perform: function () { - return doSave().then(returnToBrowse); - } - }; - } + return doSave().then(returnToBrowse); + }; /** * Check if this action is applicable in a given context. diff --git a/platform/commonUI/edit/src/capabilities/EditableCompositionCapability.js b/platform/commonUI/edit/src/capabilities/EditableCompositionCapability.js index 462a95ec18..17dff58c0d 100644 --- a/platform/commonUI/edit/src/capabilities/EditableCompositionCapability.js +++ b/platform/commonUI/edit/src/capabilities/EditableCompositionCapability.js @@ -37,6 +37,7 @@ define( * to a pattern used there and may contain unused arguments. * @constructor * @memberof platform/commonUI/edit + * @implements {CompositionCapability} */ return function EditableCompositionCapability( contextCapability, diff --git a/platform/commonUI/edit/src/capabilities/EditableContextCapability.js b/platform/commonUI/edit/src/capabilities/EditableContextCapability.js index 3d61bdd8a3..d0df90afc4 100644 --- a/platform/commonUI/edit/src/capabilities/EditableContextCapability.js +++ b/platform/commonUI/edit/src/capabilities/EditableContextCapability.js @@ -37,6 +37,7 @@ define( * to a pattern used there and may contain unused arguments. * @constructor * @memberof platform/commonUI/edit + * @implements {ContextCapability} */ return function EditableContextCapability( contextCapability, diff --git a/platform/commonUI/edit/src/capabilities/EditablePersistenceCapability.js b/platform/commonUI/edit/src/capabilities/EditablePersistenceCapability.js index 5f702d71b8..42b08c72b1 100644 --- a/platform/commonUI/edit/src/capabilities/EditablePersistenceCapability.js +++ b/platform/commonUI/edit/src/capabilities/EditablePersistenceCapability.js @@ -37,6 +37,7 @@ define( * to a pattern used there and may contain unused arguments. * @constructor * @memberof platform/commonUI/edit + * @implements {PersistenceCapability} */ function EditablePersistenceCapability( persistenceCapability, diff --git a/platform/commonUI/edit/src/capabilities/EditableRelationshipCapability.js b/platform/commonUI/edit/src/capabilities/EditableRelationshipCapability.js index 7567143436..3034301502 100644 --- a/platform/commonUI/edit/src/capabilities/EditableRelationshipCapability.js +++ b/platform/commonUI/edit/src/capabilities/EditableRelationshipCapability.js @@ -37,6 +37,7 @@ define( * to a pattern used there and may contain unused arguments. * @constructor * @memberof platform/commonUI/edit + * @implements {RelationshipCapability} */ return function EditableRelationshipCapability( relationshipCapability, diff --git a/platform/commonUI/edit/src/capabilities/EditorCapability.js b/platform/commonUI/edit/src/capabilities/EditorCapability.js index 70dfa44dde..34b3044c17 100644 --- a/platform/commonUI/edit/src/capabilities/EditorCapability.js +++ b/platform/commonUI/edit/src/capabilities/EditorCapability.js @@ -42,26 +42,45 @@ define( * @constructor * @memberof platform/commonUI/edit */ - return function EditorCapability( + function EditorCapability( persistenceCapability, editableObject, domainObject, cache ) { + this.editableObject = editableObject; + this.domainObject = domainObject; + this.cache = cache; + } - // Simulate Promise.resolve (or $q.when); the former - // causes a delayed reaction from Angular (since it - // does not trigger a digest) and the latter is not - // readily accessible, since we're a few classes - // removed from the layer which gets dependency - // injection. - function resolvePromise(value) { - return (value && value.then) ? value : { - then: function (callback) { - return resolvePromise(callback(value)); - } - }; - } + // Simulate Promise.resolve (or $q.when); the former + // causes a delayed reaction from Angular (since it + // does not trigger a digest) and the latter is not + // readily accessible, since we're a few classes + // removed from the layer which gets dependency + // injection. + function resolvePromise(value) { + return (value && value.then) ? value : { + then: function (callback) { + return resolvePromise(callback(value)); + } + }; + } + + /** + * Save any changes that have been made to this domain object + * (as well as to others that might have been retrieved and + * modified during the editing session) + * @param {boolean} nonrecursive if true, save only this + * object (and not other objects with associated changes) + * @returns {Promise} a promise that will be fulfilled after + * persistence has completed. + * @memberof platform/commonUI/edit.EditorCapability# + */ + EditorCapability.prototype.save = function (nonrecursive) { + var domainObject = this.domainObject, + editableObject = this.editableObject, + cache = this.cache; // Update the underlying, "real" domain object's model // with changes made to the copy used for editing. @@ -76,42 +95,32 @@ define( return domainObject.getCapability('persistence').persist(); } - return { - /** - * Save any changes that have been made to this domain object - * (as well as to others that might have been retrieved and - * modified during the editing session) - * @param {boolean} nonrecursive if true, save only this - * object (and not other objects with associated changes) - * @returns {Promise} a promise that will be fulfilled after - * persistence has completed. - * @memberof platform/commonUI/edit.EditorCapability# - */ - save: function (nonrecursive) { - return nonrecursive ? - resolvePromise(doMutate()).then(doPersist) : - resolvePromise(cache.saveAll()); - }, - /** - * Cancel editing; Discard any changes that have been made to - * this domain object (as well as to others that might have - * been retrieved and modified during the editing session) - * @returns {Promise} a promise that will be fulfilled after - * cancellation has completed. - * @memberof platform/commonUI/edit.EditorCapability# - */ - cancel: function () { - return resolvePromise(undefined); - }, - /** - * Check if there are any unsaved changes. - * @returns {boolean} true if there are unsaved changes - * @memberof platform/commonUI/edit.EditorCapability# - */ - dirty: function () { - return cache.dirty(); - } - }; + return nonrecursive ? + resolvePromise(doMutate()).then(doPersist) : + resolvePromise(cache.saveAll()); }; + + /** + * Cancel editing; Discard any changes that have been made to + * this domain object (as well as to others that might have + * been retrieved and modified during the editing session) + * @returns {Promise} a promise that will be fulfilled after + * cancellation has completed. + * @memberof platform/commonUI/edit.EditorCapability# + */ + EditorCapability.prototype.cancel = function () { + return resolvePromise(undefined); + }; + + /** + * Check if there are any unsaved changes. + * @returns {boolean} true if there are unsaved changes + * @memberof platform/commonUI/edit.EditorCapability# + */ + EditorCapability.prototype.dirty = function () { + return cache.dirty(); + }; + + return EditorCapability; } ); diff --git a/platform/commonUI/edit/src/controllers/EditController.js b/platform/commonUI/edit/src/controllers/EditController.js index 9b0c228ac3..eaffe02186 100644 --- a/platform/commonUI/edit/src/controllers/EditController.js +++ b/platform/commonUI/edit/src/controllers/EditController.js @@ -38,12 +38,12 @@ define( * @constructor */ function EditController($scope, $q, navigationService) { - var navigatedObject; + var self = this; function setNavigation(domainObject) { // Wrap the domain object such that all mutation is // confined to edit mode (until Save) - navigatedObject = + self.navigatedDomainObject = domainObject && new EditableDomainObject(domainObject, $q); } @@ -52,35 +52,33 @@ define( $scope.$on("$destroy", function () { navigationService.removeListener(setNavigation); }); - - return { - /** - * Get the domain object which is navigated-to. - * @returns {DomainObject} the domain object that is navigated-to - * @memberof platform/commonUI/edit.EditController# - */ - navigatedObject: function () { - return navigatedObject; - }, - /** - * Get the warning to show if the user attempts to navigate - * away from Edit mode while unsaved changes are present. - * @returns {string} the warning to show, or undefined if - * there are no unsaved changes - * @memberof platform/commonUI/edit.EditController# - */ - getUnloadWarning: function () { - var editorCapability = navigatedObject && - navigatedObject.getCapability("editor"), - hasChanges = editorCapability && editorCapability.dirty(); - - return hasChanges ? - "Unsaved changes will be lost if you leave this page." : - undefined; - } - }; } + /** + * Get the domain object which is navigated-to. + * @returns {DomainObject} the domain object that is navigated-to + */ + EditController.prototype.navigatedObject = function () { + return this.navigatedDomainObject; + }; + + /** + * Get the warning to show if the user attempts to navigate + * away from Edit mode while unsaved changes are present. + * @returns {string} the warning to show, or undefined if + * there are no unsaved changes + */ + EditController.prototype.getUnloadWarning = function () { + var navigatedObject = this.navigatedDomainObject, + editorCapability = navigatedObject && + navigatedObject.getCapability("editor"), + hasChanges = editorCapability && editorCapability.dirty(); + + return hasChanges ? + "Unsaved changes will be lost if you leave this page." : + undefined; + }; + return EditController; } ); diff --git a/platform/commonUI/edit/src/controllers/EditPanesController.js b/platform/commonUI/edit/src/controllers/EditPanesController.js index 258286216f..7dedc251ec 100644 --- a/platform/commonUI/edit/src/controllers/EditPanesController.js +++ b/platform/commonUI/edit/src/controllers/EditPanesController.js @@ -32,12 +32,13 @@ define( * @constructor */ function EditPanesController($scope) { - var root; + var self = this; // Update root object based on represented object function updateRoot(domainObject) { - var context = domainObject && - domainObject.getCapability('context'), + var root = self.rootDomainObject, + context = domainObject && + domainObject.getCapability('context'), newRoot = context && context.getTrueRoot(), oldId = root && root.getId(), newId = newRoot && newRoot.getId(); @@ -45,25 +46,21 @@ define( // Only update if this has actually changed, // to avoid excessive refreshing. if (oldId !== newId) { - root = newRoot; + self.rootDomainObject = newRoot; } } // Update root when represented object changes $scope.$watch('domainObject', updateRoot); - - return { - /** - * Get the root-level domain object, as reported by the - * represented domain object. - * @returns {DomainObject} the root object - * @memberof platform/commonUI/edit.EditPanesController# - */ - getRoot: function () { - return root; - } - }; } + /** + * Get the root-level domain object, as reported by the + * represented domain object. + * @returns {DomainObject} the root object + */ + EditPanesController.prototype.getRoot = function () { + return this.rootDomainObject; + }; return EditPanesController; } diff --git a/platform/commonUI/edit/src/objects/EditableDomainObject.js b/platform/commonUI/edit/src/objects/EditableDomainObject.js index 47e10488ea..bbbc0ae512 100644 --- a/platform/commonUI/edit/src/objects/EditableDomainObject.js +++ b/platform/commonUI/edit/src/objects/EditableDomainObject.js @@ -70,6 +70,7 @@ define( * model to allow changes to be easily cancelled. * @constructor * @memberof platform/commonUI/edit + * @implements {DomainObject} */ function EditableDomainObject(domainObject, $q) { // The cache will hold all domain objects reached from @@ -94,10 +95,10 @@ define( this, delegateArguments ), - factory = capabilityFactories[name]; + Factory = capabilityFactories[name]; - return (factory && capability) ? - factory(capability, editableObject, domainObject, cache) : + return (Factory && capability) ? + new Factory(capability, editableObject, domainObject, cache) : capability; }; diff --git a/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js b/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js index 9fc47ef790..88a154d79b 100644 --- a/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js +++ b/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js @@ -44,7 +44,7 @@ define( * of objects retrieved via composition or context capabilities as * editable domain objects. * - * @param {Constructor} EditableDomainObject a + * @param {Constructor} EditableDomainObject a * constructor function which takes a regular domain object as * an argument, and returns an editable domain object as its * result. @@ -53,104 +53,108 @@ define( * @constructor */ function EditableDomainObjectCache(EditableDomainObject, $q) { - var cache = new EditableModelCache(), - dirty = {}, - root; - - 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 - * @memberof platform/commonUI/edit.EditableDomainObjectCache# - */ - getEditableObject: function (domainObject) { - var type = domainObject.getCapability('type'); - - // Track the top-level domain object; this will have - // some special behavior for its context capability. - root = root || domainObject; - - // Avoid double-wrapping (WTD-1017) - if (domainObject.hasCapability('editor')) { - return domainObject; - } - - // Don't bother wrapping non-editable objects - if (!type || !type.hasFeature('creation')) { - return domainObject; - } - - // Provide an editable form of the object - return new EditableDomainObject( - domainObject, - cache.getCachedModel(domainObject) - ); - }, - /** - * Check if a domain object is (effectively) the top-level - * object in this editable subgraph. - * @returns {boolean} true if it is the root - * @memberof platform/commonUI/edit.EditableDomainObjectCache# - */ - isRoot: function (domainObject) { - return domainObject === root; - }, - /** - * 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 - * @memberof platform/commonUI/edit.EditableDomainObjectCache# - */ - 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 - * @memberof platform/commonUI/edit.EditableDomainObjectCache# - */ - markClean: function (domainObject) { - delete dirty[domainObject.getId()]; - }, - /** - * Initiate a save on all objects that have been cached. - * @memberof platform/commonUI/edit.EditableDomainObjectCache# - */ - saveAll: function () { - // Get a list of all dirty objects - var objects = Object.keys(dirty).map(function (k) { - return dirty[k]; - }); - - // Clear dirty set, since we're about to save. - dirty = {}; - - // Most save logic is handled by the "editor.completion" - // capability, so that is delegated here. - return $q.all(objects.map(function (object) { - // Save; pass a nonrecursive flag to avoid looping - return object.getCapability('editor').save(true); - })); - }, - /** - * Check if any objects have been marked dirty in this cache. - * @returns {boolean} true if objects are dirty - * @memberof platform/commonUI/edit.EditableDomainObjectCache# - */ - dirty: function () { - return Object.keys(dirty).length > 0; - } - }; + this.cache = new EditableModelCache(); + this.dirtyObjects = {}; + this.root = undefined; + this.$q = $q; + this.EditableDomainObject = EditableDomainObject; } + /** + * 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 + */ + EditableDomainObjectCache.prototype.getEditableObject = function (domainObject) { + var type = domainObject.getCapability('type'), + EditableDomainObject = this.EditableDomainObject; + + // Track the top-level domain object; this will have + // some special behavior for its context capability. + this.root = this.root || domainObject; + + // Avoid double-wrapping (WTD-1017) + if (domainObject.hasCapability('editor')) { + return domainObject; + } + + // Don't bother wrapping non-editable objects + if (!type || !type.hasFeature('creation')) { + return domainObject; + } + + // Provide an editable form of the object + return new EditableDomainObject( + domainObject, + this.cache.getCachedModel(domainObject) + ); + }; + + /** + * Check if a domain object is (effectively) the top-level + * object in this editable subgraph. + * @returns {boolean} true if it is the root + */ + EditableDomainObjectCache.prototype.isRoot = function (domainObject) { + return domainObject === this.root; + }; + + /** + * 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 + * @memberof platform/commonUI/edit.EditableDomainObjectCache# + */ + EditableDomainObjectCache.prototype.markDirty = function (domainObject) { + this.dirtyObjects[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 + */ + EditableDomainObjectCache.prototype.markClean = function (domainObject) { + delete this.dirtyObjects[domainObject.getId()]; + }; + + /** + * Initiate a save on all objects that have been cached. + * @return {Promise} A promise which will resolve when all objects are + * persisted. + */ + EditableDomainObjectCache.prototype.saveAll = function () { + // Get a list of all dirty objects + var dirty = this.dirtyObjects, + objects = Object.keys(dirty).map(function (k) { + return dirty[k]; + }); + + // Clear dirty set, since we're about to save. + this.dirtyObjects = {}; + + // Most save logic is handled by the "editor.completion" + // capability, so that is delegated here. + return this.$q.all(objects.map(function (object) { + // Save; pass a nonrecursive flag to avoid looping + return object.getCapability('editor').save(true); + })); + }; + + /** + * Check if any objects have been marked dirty in this cache. + * @returns {boolean} true if objects are dirty + */ + EditableDomainObjectCache.prototype.dirty = function () { + return Object.keys(this.dirtyObjects).length > 0; + }; + return EditableDomainObjectCache; } ); diff --git a/platform/commonUI/edit/src/objects/EditableModelCache.js b/platform/commonUI/edit/src/objects/EditableModelCache.js index b20ba98c8a..30ca3d774a 100644 --- a/platform/commonUI/edit/src/objects/EditableModelCache.js +++ b/platform/commonUI/edit/src/objects/EditableModelCache.js @@ -35,31 +35,28 @@ define( * @constructor */ function EditableModelCache() { - var cache = {}; - - // Deep-copy a model. Models are JSONifiable, so this can be - // done by stringification then destringification - function clone(model) { - return JSON.parse(JSON.stringify(model)); - } - - return { - /** - * Get this domain object's model from the cache (or - * place it in the cache if it isn't in the cache yet) - * @returns a clone of the domain object's model - * @memberof platform/commonUI/edit.EditableModelCache# - */ - getCachedModel: function (domainObject) { - var id = domainObject.getId(); - - return (cache[id] = - cache[id] || clone(domainObject.getModel())); - } - }; - + this.cache = {}; } + // Deep-copy a model. Models are JSONifiable, so this can be + // done by stringification then destringification + function clone(model) { + return JSON.parse(JSON.stringify(model)); + } + + /** + * Get this domain object's model from the cache (or + * place it in the cache if it isn't in the cache yet) + * @returns a clone of the domain object's model + */ + EditableModelCache.prototype.getCachedModel = function (domainObject) { + var id = domainObject.getId(), + cache = this.cache; + + return (cache[id] = + cache[id] || clone(domainObject.getModel())); + }; + return EditableModelCache; } ); diff --git a/platform/commonUI/edit/src/policies/EditActionPolicy.js b/platform/commonUI/edit/src/policies/EditActionPolicy.js index 825224317a..bec2fc423d 100644 --- a/platform/commonUI/edit/src/policies/EditActionPolicy.js +++ b/platform/commonUI/edit/src/policies/EditActionPolicy.js @@ -32,52 +32,44 @@ define( * (shown as buttons in the top-right of browse mode.) * @memberof platform/commonUI/edit * @constructor + * @implements {Policy.} */ function EditActionPolicy() { - // Get a count of views which are not flagged as non-editable. - function countEditableViews(context) { - var domainObject = (context || {}).domainObject, - views = domainObject && domainObject.useCapability('view'), - count = 0; + } - // A view is editable unless explicitly flagged as not - (views || []).forEach(function (view) { - count += (view.editable !== false) ? 1 : 0; - }); + // Get a count of views which are not flagged as non-editable. + function countEditableViews(context) { + var domainObject = (context || {}).domainObject, + views = domainObject && domainObject.useCapability('view'), + count = 0; - return count; + // A view is editable unless explicitly flagged as not + (views || []).forEach(function (view) { + count += (view.editable !== false) ? 1 : 0; + }); + + return count; + } + + EditActionPolicy.prototype.allow = function (action, context) { + var key = action.getMetadata().key, + category = (context || {}).category; + + // Only worry about actions in the view-control category + if (category === 'view-control') { + // Restrict 'edit' to cases where there are editable + // views (similarly, restrict 'properties' to when + // the converse is true) + if (key === 'edit') { + return countEditableViews(context) > 0; + } else if (key === 'properties') { + return countEditableViews(context) < 1; + } } - return { - /** - * Check whether or not a given action is allowed by this - * policy. - * @param {Action} action the action - * @param context the context - * @returns {boolean} true if not disallowed - * @memberof platform/commonUI/edit.EditActionPolicy# - */ - allow: function (action, context) { - var key = action.getMetadata().key, - category = (context || {}).category; - - // Only worry about actions in the view-control category - if (category === 'view-control') { - // Restrict 'edit' to cases where there are editable - // views (similarly, restrict 'properties' to when - // the converse is true) - if (key === 'edit') { - return countEditableViews(context) > 0; - } else if (key === 'properties') { - return countEditableViews(context) < 1; - } - } - - // Like all policies, allow by default. - return true; - } - }; - } + // Like all policies, allow by default. + return true; + }; return EditActionPolicy; } diff --git a/platform/commonUI/edit/src/policies/EditableViewPolicy.js b/platform/commonUI/edit/src/policies/EditableViewPolicy.js index 92fa0b5256..17194064b0 100644 --- a/platform/commonUI/edit/src/policies/EditableViewPolicy.js +++ b/platform/commonUI/edit/src/policies/EditableViewPolicy.js @@ -30,30 +30,22 @@ define( * Policy controlling which views should be visible in Edit mode. * @memberof platform/commonUI/edit * @constructor + * @implements {Policy.} */ function EditableViewPolicy() { - return { - /** - * Check whether or not a given action is allowed by this - * policy. - * @param {Action} action the action - * @param domainObject the domain object which will be viewed - * @returns {boolean} true if not disallowed - * @memberof platform/commonUI/edit.EditableViewPolicy# - */ - allow: function (view, domainObject) { - // If a view is flagged as non-editable, only allow it - // while we're not in Edit mode. - if ((view || {}).editable === false) { - return !domainObject.hasCapability('editor'); - } - - // Like all policies, allow by default. - return true; - } - }; } + EditableViewPolicy.prototype.allow = function (view, domainObject) { + // If a view is flagged as non-editable, only allow it + // while we're not in Edit mode. + if ((view || {}).editable === false) { + return !domainObject.hasCapability('editor'); + } + + // Like all policies, allow by default. + return true; + }; + return EditableViewPolicy; } ); diff --git a/platform/commonUI/edit/src/representers/EditRepresenter.js b/platform/commonUI/edit/src/representers/EditRepresenter.js index 2cca2ee59f..17a0f634b2 100644 --- a/platform/commonUI/edit/src/representers/EditRepresenter.js +++ b/platform/commonUI/edit/src/representers/EditRepresenter.js @@ -42,14 +42,16 @@ define( * representations resulting from changes there. * * @memberof platform/commonUI/edit + * @implements {Representer} * @constructor */ function EditRepresenter($q, $log, scope) { - var domainObject, - key; + var self = this; // Mutate and persist a new version of a domain object's model. function doPersist(model) { + var domainObject = self.domainObject; + // First, mutate; then, persist. return $q.when(domainObject.useCapability("mutation", function () { return model; @@ -65,7 +67,8 @@ define( // Look up from scope; these will have been populated by // mct-representation. var model = scope.model, - configuration = scope.configuration; + configuration = scope.configuration, + domainObject = self.domainObject; // Log the commit message $log.debug([ @@ -79,52 +82,33 @@ define( if (domainObject && domainObject.hasCapability("persistence")) { // Configurations for specific views are stored by // key in the "configuration" field of the model. - if (key && configuration) { + if (self.key && configuration) { model.configuration = model.configuration || {}; - model.configuration[key] = configuration; + model.configuration[self.key] = configuration; } doPersist(model); } } - // Respond to the destruction of the current representation. - function destroy() { - // Nothing to clean up - } - - // Handle a specific representation of a specific domain object - function represent(representation, representedObject) { - // Track the key, to know which view configuration to save to. - key = (representation || {}).key; - // Track the represented object - domainObject = representedObject; - // Ensure existing watches are released - destroy(); - } - // Place the "commit" method in the scope scope.commit = commit; - - return { - /** - * Set the current representation in use, and the domain - * object being represented. - * - * @param {RepresentationDefinition} representation the - * definition of the representation in use - * @param {DomainObject} domainObject the domain object - * being represented - * @memberof platform/commonUI/edit.EditRepresenter# - */ - represent: represent, - /** - * Release any resources associated with this representer. - * @memberof platform/commonUI/edit.EditRepresenter# - */ - destroy: destroy - }; } + // Handle a specific representation of a specific domain object + EditRepresenter.prototype.represent = function represent(representation, representedObject) { + // Track the key, to know which view configuration to save to. + this.key = (representation || {}).key; + // Track the represented object + this.domainObject = representedObject; + // Ensure existing watches are released + this.destroy(); + }; + + // Respond to the destruction of the current representation. + EditRepresenter.prototype.destroy = function destroy() { + // Nothing to clean up + }; + return EditRepresenter; } ); diff --git a/platform/commonUI/edit/src/representers/EditToolbar.js b/platform/commonUI/edit/src/representers/EditToolbar.js index 81d95582aa..367eaf1705 100644 --- a/platform/commonUI/edit/src/representers/EditToolbar.js +++ b/platform/commonUI/edit/src/representers/EditToolbar.js @@ -42,122 +42,19 @@ define( * @constructor */ function EditToolbar(structure, commit) { - var toolbarStructure = Object.create(structure || {}), - toolbarState, - selection, - properties = []; + var self = this; // Generate a new key for an item's property function addKey(property) { - properties.push(property); - return properties.length - 1; // Return index of property - } - - // Update value for this property in all elements of the - // selection which have this property. - function updateProperties(property, value) { - var changed = false; - - // Update property in a selected element - function updateProperty(selected) { - // Ignore selected elements which don't have this property - if (selected[property] !== undefined) { - // Check if this is a setter, or just assignable - if (typeof selected[property] === 'function') { - changed = - changed || (selected[property]() !== value); - selected[property](value); - } else { - changed = - changed || (selected[property] !== value); - selected[property] = value; - } - } - } - - // Update property in all selected elements - selection.forEach(updateProperty); - - // Return whether or not anything changed - return changed; - } - - // Look up the current value associated with a property - // in selection i - function lookupState(property, selected) { - var value = selected[property]; - return (typeof value === 'function') ? value() : value; - } - - // Get initial value for a given property - function initializeState(property) { - var result; - // Look through all selections for this property; - // values should all match by the time we perform - // this lookup anyway. - selection.forEach(function (selected) { - result = (selected[property] !== undefined) ? - lookupState(property, selected) : - result; - }); - return result; - } - - // Check if all elements of the selection which have this - // property have the same value for this property. - function isConsistent(property) { - var consistent = true, - observed = false, - state; - - // Check if a given element of the selection is consistent - // with previously-observed elements for this property. - function checkConsistency(selected) { - var next; - // Ignore selections which don't have this property - if (selected[property] !== undefined) { - // Look up state of this element in the selection - next = lookupState(property, selected); - // Detect inconsistency - if (observed) { - consistent = consistent && (next === state); - } - // Track state for next iteration - state = next; - observed = true; - } - } - - // Iterate through selections - selection.forEach(checkConsistency); - - return consistent; - } - - // Used to filter out items which are applicable (or not) - // to the current selection. - function isApplicable(item) { - var property = (item || {}).property, - method = (item || {}).method, - exclusive = !!(item || {}).exclusive; - - // Check if a selected item defines this property - function hasProperty(selected) { - return (property && (selected[property] !== undefined)) || - (method && (typeof selected[method] === 'function')); - } - - return selection.map(hasProperty).reduce( - exclusive ? and : or, - exclusive - ) && isConsistent(property); + self.properties.push(property); + return self.properties.length - 1; // Return index of property } // Invoke all functions in selections with the given name function invoke(method, value) { if (method) { // Make the change in the selection - selection.forEach(function (selected) { + self.selection.forEach(function (selected) { if (typeof selected[method] === 'function') { selected[method](value); } @@ -190,75 +87,169 @@ define( return converted; } + this.toolbarState = []; + this.selection = undefined; + this.properties = []; + this.toolbarStructure = Object.create(structure || {}); + this.toolbarStructure.sections = + ((structure || {}).sections || []).map(convertSection); + } + + // Check if all elements of the selection which have this + // property have the same value for this property. + EditToolbar.prototype.isConsistent = function (property) { + var self = this, + consistent = true, + observed = false, + state; + + // Check if a given element of the selection is consistent + // with previously-observed elements for this property. + function checkConsistency(selected) { + var next; + // Ignore selections which don't have this property + if (selected[property] !== undefined) { + // Look up state of this element in the selection + next = self.lookupState(property, selected); + // Detect inconsistency + if (observed) { + consistent = consistent && (next === state); + } + // Track state for next iteration + state = next; + observed = true; + } + } + + // Iterate through selections + self.selection.forEach(checkConsistency); + + return consistent; + }; + + // Used to filter out items which are applicable (or not) + // to the current selection. + EditToolbar.prototype.isApplicable = function (item) { + var property = (item || {}).property, + method = (item || {}).method, + exclusive = !!(item || {}).exclusive; + + // Check if a selected item defines this property + function hasProperty(selected) { + return (property && (selected[property] !== undefined)) || + (method && (typeof selected[method] === 'function')); + } + + return this.selection.map(hasProperty).reduce( + exclusive ? and : or, + exclusive + ) && this.isConsistent(property); + }; + + + // Look up the current value associated with a property + EditToolbar.prototype.lookupState = function (property, selected) { + var value = selected[property]; + return (typeof value === 'function') ? value() : value; + }; + + /** + * Set the current selection. Visibility of sections + * and items in the toolbar will be updated to match this. + * @param {Array} s the new selection + */ + EditToolbar.prototype.setSelection = function (s) { + var self = this; + // Show/hide controls in this section per applicability function refreshSectionApplicability(section) { var count = 0; // Show/hide each item (section.items || []).forEach(function (item) { - item.hidden = !isApplicable(item); + item.hidden = !self.isApplicable(item); count += item.hidden ? 0 : 1; }); // Hide this section if there are no applicable items section.hidden = !count; } - // Show/hide controls if they are applicable - function refreshApplicability() { - toolbarStructure.sections.forEach(refreshSectionApplicability); + // Get initial value for a given property + function initializeState(property) { + var result; + // Look through all selections for this property; + // values should all match by the time we perform + // this lookup anyway. + self.selection.forEach(function (selected) { + result = (selected[property] !== undefined) ? + self.lookupState(property, selected) : + result; + }); + return result; } - // Refresh toolbar state to match selection - function refreshState() { - toolbarState = properties.map(initializeState); - } + this.selection = s; + this.toolbarStructure.sections.forEach(refreshSectionApplicability); + this.toolbarState = this.properties.map(initializeState); + }; - toolbarStructure.sections = - ((structure || {}).sections || []).map(convertSection); + /** + * Get the structure of the toolbar, as appropriate to + * pass to `mct-toolbar`. + * @returns the toolbar structure + */ + EditToolbar.prototype.getStructure = function () { + return this.toolbarStructure; + }; - toolbarState = []; + /** + * Get the current state of the toolbar, as appropriate + * to two-way bind to the state handled by `mct-toolbar`. + * @returns {Array} state of the toolbar + */ + EditToolbar.prototype.getState = function () { + return this.toolbarState; + }; - return { - /** - * Set the current selection. Visisbility of sections - * and items in the toolbar will be updated to match this. - * @param {Array} s the new selection - * @memberof platform/commonUI/edit.EditToolbar# - */ - setSelection: function (s) { - selection = s; - refreshApplicability(); - refreshState(); - }, - /** - * Get the structure of the toolbar, as appropriate to - * pass to `mct-toolbar`. - * @returns the toolbar structure - * @memberof platform/commonUI/edit.EditToolbar# - */ - getStructure: function () { - return toolbarStructure; - }, - /** - * Get the current state of the toolbar, as appropriate - * to two-way bind to the state handled by `mct-toolbar`. - * @returns {Array} state of the toolbar - * @memberof platform/commonUI/edit.EditToolbar# - */ - getState: function () { - return toolbarState; - }, - /** - * Update state within the current selection. - * @param {number} index the index of the corresponding - * element in the state array - * @param value the new value to convey to the selection - * @memberof platform/commonUI/edit.EditToolbar# - */ - updateState: function (index, value) { - return updateProperties(properties[index], value); + /** + * Update state within the current selection. + * @param {number} index the index of the corresponding + * element in the state array + * @param value the new value to convey to the selection + */ + EditToolbar.prototype.updateState = function (index, value) { + var self = this; + + // Update value for this property in all elements of the + // selection which have this property. + function updateProperties(property, value) { + var changed = false; + + // Update property in a selected element + function updateProperty(selected) { + // Ignore selected elements which don't have this property + if (selected[property] !== undefined) { + // Check if this is a setter, or just assignable + if (typeof selected[property] === 'function') { + changed = + changed || (selected[property]() !== value); + selected[property](value); + } else { + changed = + changed || (selected[property] !== value); + selected[property] = value; + } + } } - }; - } + + // Update property in all selected elements + self.selection.forEach(updateProperty); + + // Return whether or not anything changed + return changed; + } + + return updateProperties(this.properties[index], value); + }; return EditToolbar; } diff --git a/platform/commonUI/edit/src/representers/EditToolbarRepresenter.js b/platform/commonUI/edit/src/representers/EditToolbarRepresenter.js index a574fffc77..059b5e13f8 100644 --- a/platform/commonUI/edit/src/representers/EditToolbarRepresenter.js +++ b/platform/commonUI/edit/src/representers/EditToolbarRepresenter.js @@ -27,7 +27,10 @@ define( "use strict"; // No operation - function noop() {} + var NOOP_REPRESENTER = { + represent: function () {}, + destroy: function () {} + }; /** * The EditToolbarRepresenter populates the toolbar in Edit mode @@ -35,10 +38,10 @@ define( * @param {Scope} scope the Angular scope of the representation * @memberof platform/commonUI/edit * @constructor + * @implements {Representer} */ function EditToolbarRepresenter(scope, element, attrs) { - var toolbar, - toolbarObject = {}; + var self = this; // Mark changes as ready to persist function commit(message) { @@ -50,31 +53,33 @@ define( // Handle changes to the current selection function updateSelection(selection) { // Only update if there is a toolbar to update - if (toolbar) { + if (self.toolbar) { // Make sure selection is array-like selection = Array.isArray(selection) ? selection : (selection ? [selection] : []); // Update the toolbar's selection - toolbar.setSelection(selection); + self.toolbar.setSelection(selection); // ...and expose its structure/state - toolbarObject.structure = toolbar.getStructure(); - toolbarObject.state = toolbar.getState(); + self.toolbarObject.structure = + self.toolbar.getStructure(); + self.toolbarObject.state = + self.toolbar.getState(); } } // Get state (to watch it) function getState() { - return toolbarObject.state; + return self.toolbarObject.state; } // Update selection models to match changed toolbar state function updateState(state) { // Update underlying state based on toolbar changes var changed = (state || []).map(function (value, index) { - return toolbar.updateState(index, value); + return self.toolbar.updateState(index, value); }).reduce(function (a, b) { return a || b; }, false); @@ -86,68 +91,62 @@ define( } } - // Initialize toolbar (expose object to parent scope) - function initialize(definition) { - // If we have been asked to expose toolbar state... - if (attrs.toolbar) { - // Initialize toolbar object - toolbar = new EditToolbar(definition, commit); - // Ensure toolbar state is exposed - scope.$parent[attrs.toolbar] = toolbarObject; - } - } - - // Represent a domain object using this definition - function represent(representation) { - // Get the newest toolbar definition from the view - var definition = (representation || {}).toolbar || {}; - // Expose the toolbar object to the parent scope - initialize(definition); - // Create a selection scope - scope.selection = new EditToolbarSelection(); - // Initialize toolbar to an empty selection - updateSelection([]); - } - - // Destroy; remove toolbar object from parent scope - function destroy() { - // Clear exposed toolbar state (if any) - if (attrs.toolbar) { - delete scope.$parent[attrs.toolbar]; - } - } + this.commit = commit; + this.scope = scope; + this.attrs = attrs; + this.updateSelection = updateSelection; + this.toolbar = undefined; + this.toolbarObject = {}; // If this representation exposes a toolbar, set up watches // to synchronize with it. - if (attrs.toolbar) { + if (attrs && attrs.toolbar) { // Detect and handle changes to state from the toolbar scope.$watchCollection(getState, updateState); // Watch for changes in the current selection state scope.$watchCollection("selection.all()", updateSelection); // Expose toolbar state under that name - scope.$parent[attrs.toolbar] = toolbarObject; + scope.$parent[attrs.toolbar] = this.toolbarObject; + } else { + // No toolbar declared, so do nothing. + return NOOP_REPRESENTER; } - return { - /** - * Set the current representation in use, and the domain - * object being represented. - * - * @param {RepresentationDefinition} representation the - * definition of the representation in use - * @param {DomainObject} domainObject the domain object - * being represented - * @memberof platform/commonUI/edit.EditToolbarRepresenter# - */ - represent: (attrs || {}).toolbar ? represent : noop, - /** - * Release any resources associated with this representer. - * @memberof platform/commonUI/edit.EditToolbarRepresenter# - */ - destroy: (attrs || {}).toolbar ? destroy : noop - }; } + // Represent a domain object using this definition + EditToolbarRepresenter.prototype.represent = function (representation) { + // Get the newest toolbar definition from the view + var definition = (representation || {}).toolbar || {}, + self = this; + + // Initialize toolbar (expose object to parent scope) + function initialize(definition) { + // If we have been asked to expose toolbar state... + if (self.attrs.toolbar) { + // Initialize toolbar object + self.toolbar = new EditToolbar(definition, self.commit); + // Ensure toolbar state is exposed + self.scope.$parent[self.attrs.toolbar] = self.toolbarObject; + } + } + + // Expose the toolbar object to the parent scope + initialize(definition); + // Create a selection scope + this.scope.selection = new EditToolbarSelection(); + // Initialize toolbar to an empty selection + this.updateSelection([]); + }; + + // Destroy; remove toolbar object from parent scope + EditToolbarRepresenter.prototype.destroy = function () { + // Clear exposed toolbar state (if any) + if (this.attrs.toolbar) { + delete this.scope.$parent[this.attrs.toolbar]; + } + }; + return EditToolbarRepresenter; } ); diff --git a/platform/commonUI/edit/src/representers/EditToolbarSelection.js b/platform/commonUI/edit/src/representers/EditToolbarSelection.js index 87483a4bbb..318ae935b5 100644 --- a/platform/commonUI/edit/src/representers/EditToolbarSelection.js +++ b/platform/commonUI/edit/src/representers/EditToolbarSelection.js @@ -41,112 +41,91 @@ define( * @constructor */ function EditToolbarSelection() { - var selection = [ {} ], - selecting = false, - selected; + this.selection = [{}]; + this.selecting = false; + this.selectedObj = undefined; + } - // Remove the currently-selected object - function deselect() { - // Nothing to do if we don't have a selected object - if (selecting) { - // Clear state tracking - selecting = false; - selected = undefined; + /** + * Check if an object is currently selected. + * @param {*} obj the object to check for selection + * @returns {boolean} true if selected, otherwise false + */ + EditToolbarSelection.prototype.selected = function (obj) { + return (obj === this.selectedObj) || (obj === this.selection[0]); + }; - // Remove the selection - selection.pop(); - - return true; - } + /** + * Select an object. + * @param obj the object to select + * @returns {boolean} true if selection changed + */ + EditToolbarSelection.prototype.select = function (obj) { + // Proxy is always selected + if (obj === this.selection[0]) { return false; } - // Select an object - function select(obj) { - // Proxy is always selected - if (obj === selection[0]) { - return false; - } + // Clear any existing selection + this.deselect(); - // Clear any existing selection - deselect(); + // Note the current selection state + this.selectedObj = obj; + this.selecting = true; - // Note the current selection state - selected = obj; - selecting = true; + // Add the selection + this.selection.push(obj); + }; - // Add the selection - selection.push(obj); + /** + * Clear the current selection. + * @returns {boolean} true if selection changed + */ + EditToolbarSelection.prototype.deselect = function () { + // Nothing to do if we don't have a selected object + if (this.selecting) { + // Clear state tracking + this.selecting = false; + this.selectedObj = undefined; + + // Remove the selection + this.selection.pop(); + + return true; } + return false; + }; + /** + * Get the currently-selected object. + * @returns the currently selected object + */ + EditToolbarSelection.prototype.get = function () { + return this.selectedObj; + }; - // Check if an object is selected - function isSelected(obj) { - return (obj === selected) || (obj === selection[0]); + /** + * Get/set the view proxy (for toolbar actions taken upon + * the view itself.) + * @param [proxy] the view proxy (if setting) + * @returns the current view proxy + */ + EditToolbarSelection.prototype.proxy = function (p) { + if (arguments.length > 0) { + this.selection[0] = p; } + return this.selection[0]; + }; - // Getter for current selection - function get() { - return selected; - } - - // Getter/setter for view proxy - function proxy(p) { - if (arguments.length > 0) { - selection[0] = p; - } - return selection[0]; - } - - // Getter for the full array of selected objects (incl. view proxy) - function all() { - return selection; - } - - return { - /** - * Check if an object is currently selected. - * @returns true if selected, otherwise false - * @memberof platform/commonUI/edit.EditToolbarSelection# - */ - selected: isSelected, - /** - * Select an object. - * @param obj the object to select - * @returns {boolean} true if selection changed - * @memberof platform/commonUI/edit.EditToolbarSelection# - */ - select: select, - /** - * Clear the current selection. - * @returns {boolean} true if selection changed - * @memberof platform/commonUI/edit.EditToolbarSelection# - */ - deselect: deselect, - /** - * Get the currently-selected object. - * @returns the currently selected object - * @memberof platform/commonUI/edit.EditToolbarSelection# - */ - get: get, - /** - * Get/set the view proxy (for toolbar actions taken upon - * the view itself.) - * @param [proxy] the view proxy (if setting) - * @returns the current view proxy - * @memberof platform/commonUI/edit.EditToolbarSelection# - */ - proxy: proxy, - /** - * Get an array containing all selections, including the - * selection proxy. It is generally not advisable to - * mutate this array directly. - * @returns {Array} all selections - * @memberof platform/commonUI/edit.EditToolbarSelection# - */ - all: all - }; - } + /** + * Get an array containing all selections, including the + * selection proxy. It is generally not advisable to + * mutate this array directly. + * @returns {Array} all selections + */ + EditToolbarSelection.prototype.all = function () { + return this.selection; + }; return EditToolbarSelection; } diff --git a/platform/commonUI/edit/test/objects/EditableDomainObjectCacheSpec.js b/platform/commonUI/edit/test/objects/EditableDomainObjectCacheSpec.js index 39cac56d9f..4eea727e26 100644 --- a/platform/commonUI/edit/test/objects/EditableDomainObjectCacheSpec.js +++ b/platform/commonUI/edit/test/objects/EditableDomainObjectCacheSpec.js @@ -112,7 +112,9 @@ define( }); it("saves objects that have been marked dirty", function () { - var objects = ['a', 'b', 'c'].map(TestObject).map(cache.getEditableObject); + var objects = ['a', 'b', 'c'].map(TestObject).map(function (domainObject) { + return cache.getEditableObject(domainObject); + }); cache.markDirty(objects[0]); cache.markDirty(objects[2]); @@ -123,7 +125,9 @@ define( }); it("does not save objects that have been marked clean", function () { - var objects = ['a', 'b', 'c'].map(TestObject).map(cache.getEditableObject); + var objects = ['a', 'b', 'c'].map(TestObject).map(function (domainObject) { + return cache.getEditableObject(domainObject); + }); cache.markDirty(objects[0]); cache.markDirty(objects[2]); diff --git a/platform/policy/src/PolicyProvider.js b/platform/policy/src/PolicyProvider.js index 9c63d154f2..a15b296b1b 100644 --- a/platform/policy/src/PolicyProvider.js +++ b/platform/policy/src/PolicyProvider.js @@ -30,6 +30,29 @@ define( function () { "use strict"; + /** + * A policy is a participant in decision-making policies. Policies + * are divided into categories (identified symbolically by strings); + * within a given category, every given policy-driven decision will + * occur by consulting all available policies and requiring their + * collective consent (that is, every individual policy has the + * power to reject the decision entirely.) + * + * @interface Policy + * @template C, X + */ + + /** + * Check if this policy allows the described decision. The types + * of the arguments expected here vary depending on policy category. + * + * @method Policy#allow + * @template C, X + * @param {C} candidate the thing to allow or disallow + * @param {X} context the context in which the decision occurs + * @returns {boolean} false if disallowed; otherwise, true + */ + /** * Provides an implementation of `policyService` which consults * various policy extensions to determine whether or not a specific