[Code Style] Use prototypes in Browse bundle

WTD-1482.
This commit is contained in:
Victor Woeltjen 2015-08-10 11:52:23 -07:00
parent 78146d97f8
commit a77920bd18
10 changed files with 418 additions and 365 deletions

View File

@ -37,16 +37,25 @@ define(
* @constructor * @constructor
*/ */
function MenuArrowController($scope) { function MenuArrowController($scope) {
function showMenu(event) { this.$scope = $scope;
var actionContext = {key: 'menu', domainObject: $scope.domainObject, event: event};
$scope.domainObject.getCapability('action').perform(actionContext);
}
return {
showMenu: showMenu
};
} }
/**
* Show a context menu for the domain object in this scope.
*
* @param event the browser event which caused this (used to
* position the menu)
*/
MenuArrowController.prototype.showMenu = function (event) {
var actionContext = {
key: 'menu',
domainObject: this.$scope.domainObject,
event: event
};
this.$scope.domainObject.getCapability('action').perform(actionContext);
};
return MenuArrowController; return MenuArrowController;
} }
); );

View File

@ -35,7 +35,9 @@ define(
* is performed when a user uses the Create menu. * is performed when a user uses the Create menu.
* *
* @memberof platform/commonUI/browse * @memberof platform/commonUI/browse
* @implements {Action}
* @constructor * @constructor
*
* @param {Type} type the type of domain object to create * @param {Type} type the type of domain object to create
* @param {DomainObject} parent the domain object that should * @param {DomainObject} parent the domain object that should
* act as a container for the newly-created object * act as a container for the newly-created object
@ -50,79 +52,83 @@ define(
* of the newly-created domain object * of the newly-created domain object
*/ */
function CreateAction(type, parent, context, dialogService, creationService, policyService) { function CreateAction(type, parent, context, dialogService, creationService, policyService) {
this.metadata = {
key: 'create',
glyph: type.getGlyph(),
name: type.getName(),
type: type.getKey(),
description: type.getDescription(),
context: context
};
this.type = type;
this.parent = parent;
this.policyService = policyService;
this.dialogService = dialogService;
this.creationService = creationService;
}
/**
* Create a new object of the given type.
* This will prompt for user input first.
*/
CreateAction.prototype.perform = function () {
/* /*
Overview of steps in object creation: Overview of steps in object creation:
1. Show dialog 1. Show dialog
a. Prepare dialog contents a. Prepare dialog contents
b. Invoke dialogService b. Invoke dialogService
2. Create new object in persistence service 2. Create new object in persistence service
a. Generate UUID a. Generate UUID
b. Store model b. Store model
3. Mutate destination container 3. Mutate destination container
a. Get mutation capability a. Get mutation capability
b. Add new id to composition b. Add new id to composition
4. Persist destination container 4. Persist destination container
a. ...use persistence capability. a. ...use persistence capability.
*/ */
function perform() { // The wizard will handle creating the form model based
// The wizard will handle creating the form model based // on the type...
// on the type... var wizard =
var wizard = new CreateWizard(type, parent, policyService); new CreateWizard(this.type, this.parent, this.policyService),
self = this;
// Create and persist the new object, based on user // Create and persist the new object, based on user
// input. // input.
function persistResult(formValue) { function persistResult(formValue) {
var parent = wizard.getLocation(formValue), var parent = wizard.getLocation(formValue),
newModel = wizard.createModel(formValue); newModel = wizard.createModel(formValue);
return creationService.createObject(newModel, parent); return self.creationService.createObject(newModel, parent);
}
function doNothing() {
// Create cancelled, do nothing
return false;
}
return dialogService.getUserInput(
wizard.getFormStructure(),
wizard.getInitialFormValue()
).then(persistResult, doNothing);
} }
return { function doNothing() {
/** // Create cancelled, do nothing
* Create a new object of the given type. return false;
* This will prompt for user input first. }
* @method
* @memberof CreateAction
* @memberof platform/commonUI/browse.CreateAction#
*/
perform: perform,
/** return this.dialogService.getUserInput(
* Get metadata about this action. This includes fields: wizard.getFormStructure(),
* * `name`: Human-readable name wizard.getInitialFormValue()
* * `key`: Machine-readable identifier ("create") ).then(persistResult, doNothing);
* * `glyph`: Glyph to use as an icon for this action };
* * `description`: Human-readable description
* * `context`: The context in which this action will be performed.
* /**
* @return {object} metadata about the create action * Metadata associated with a Create action.
* @memberof platform/commonUI/browse.CreateAction# * @typedef {ActionMetadata} CreateActionMetadata
*/ * @property {string} type the key for the type of domain object
getMetadata: function () { * to be created
return { */
key: 'create',
glyph: type.getGlyph(), /**
name: type.getName(), * Get metadata about this action.
type: type.getKey(), * @returns {CreateActionMetadata} metadata about this action
description: type.getDescription(), */
context: context CreateAction.prototype.getMetadata = function () {
}; return this.metadata;
} };
};
}
return CreateAction; return CreateAction;
} }

View File

@ -35,6 +35,8 @@ define(
* *
* @memberof platform/commonUI/browse * @memberof platform/commonUI/browse
* @constructor * @constructor
* @implements {ActionService}
*
* @param {TypeService} typeService the type service, used to discover * @param {TypeService} typeService the type service, used to discover
* available types * available types
* @param {DialogService} dialogService the dialog service, used by * @param {DialogService} dialogService the dialog service, used by
@ -45,45 +47,41 @@ define(
* object creation. * object creation.
*/ */
function CreateActionProvider(typeService, dialogService, creationService, policyService) { function CreateActionProvider(typeService, dialogService, creationService, policyService) {
return { this.typeService = typeService;
/** this.dialogService = dialogService;
* Get all Create actions which are applicable in the provided this.creationService = creationService;
* context. this.policyService = policyService;
* @memberof CreateActionProvider
* @method
* @returns {CreateAction[]}
* @memberof platform/commonUI/browse.CreateActionProvider#
*/
getActions: function (actionContext) {
var context = actionContext || {},
key = context.key,
destination = context.domainObject;
// We only provide Create actions, and we need a
// domain object to serve as the container for the
// newly-created object (although the user may later
// make a different selection)
if (key !== 'create' || !destination) {
return [];
}
// Introduce one create action per type
return typeService.listTypes().filter(function (type) {
return type.hasFeature("creation");
}).map(function (type) {
return new CreateAction(
type,
destination,
context,
dialogService,
creationService,
policyService
);
});
}
};
} }
CreateActionProvider.prototype.getActions = function (actionContext) {
var context = actionContext || {},
key = context.key,
destination = context.domainObject,
self = this;
// We only provide Create actions, and we need a
// domain object to serve as the container for the
// newly-created object (although the user may later
// make a different selection)
if (key !== 'create' || !destination) {
return [];
}
// Introduce one create action per type
return this.typeService.listTypes().filter(function (type) {
return type.hasFeature("creation");
}).map(function (type) {
return new CreateAction(
type,
destination,
context,
self.dialogService,
self.creationService,
self.policyService
);
});
};
return CreateActionProvider; return CreateActionProvider;
} }
); );

View File

@ -35,112 +35,113 @@ define(
* @constructor * @constructor
*/ */
function CreateWizard(type, parent, policyService) { function CreateWizard(type, parent, policyService) {
var model = type.getInitialModel(), this.type = type;
properties = type.getProperties(); this.model = type.getInitialModel();
this.properties = type.getProperties();
this.parent = parent;
this.policyService = policyService;
}
/**
* Get the form model for this wizard; this is a description
* that will be rendered to an HTML form. See the
* platform/forms bundle
*
* @return {FormModel} formModel the form model to
* show in the create dialog
*/
CreateWizard.prototype.getFormStructure = function () {
var sections = [],
type = this.type,
policyService = this.policyService;
function validateLocation(locatingObject) { function validateLocation(locatingObject) {
var locatingType = locatingObject && var locatingType = locatingObject &&
locatingObject.getCapability('type'); locatingObject.getCapability('type');
return locatingType && policyService.allow( return locatingType && policyService.allow(
"composition", "composition",
locatingType, locatingType,
type type
); );
} }
sections.push({
name: "Properties",
rows: this.properties.map(function (property, index) {
// Property definition is same as form row definition
var row = Object.create(property.getDefinition());
// Use index as the key into the formValue;
// this correlates to the indexing provided by
// getInitialFormValue
row.key = index;
return row;
})
});
// Ensure there is always a "save in" section
sections.push({ name: 'Location', rows: [{
name: "Save In",
control: "locator",
validate: validateLocation,
key: "createParent"
}]});
return { return {
/** sections: sections,
* Get the form model for this wizard; this is a description name: "Create a New " + this.type.getName()
* that will be rendered to an HTML form. See the
* platform/forms bundle
*
* @return {FormModel} formModel the form model to
* show in the create dialog
* @memberof platform/commonUI/browse.CreateWizard#
*/
getFormStructure: function () {
var sections = [];
sections.push({
name: "Properties",
rows: properties.map(function (property, index) {
// Property definition is same as form row definition
var row = Object.create(property.getDefinition());
// Use index as the key into the formValue;
// this correlates to the indexing provided by
// getInitialFormValue
row.key = index;
return row;
})
});
// Ensure there is always a "save in" section
sections.push({ name: 'Location', rows: [{
name: "Save In",
control: "locator",
validate: validateLocation,
key: "createParent"
}]});
return {
sections: sections,
name: "Create a New " + type.getName()
};
},
/**
* Get the initial value for the form being described.
* This will include the values for all properties described
* in the structure.
*
* @returns {object} the initial value of the form
* @memberof platform/commonUI/browse.CreateWizard#
*/
getInitialFormValue: function () {
// Start with initial values for properties
var formValue = properties.map(function (property) {
return property.getValue(model);
});
// Include the createParent
formValue.createParent = parent;
return formValue;
},
/**
* Based on a populated form, get the domain object which
* should be used as a parent for the newly-created object.
* @return {DomainObject}
* @memberof platform/commonUI/browse.CreateWizard#
*/
getLocation: function (formValue) {
return formValue.createParent || parent;
},
/**
* Create the domain object model for a newly-created object,
* based on user input read from a formModel.
* @return {object} the domain object' model
* @memberof platform/commonUI/browse.CreateWizard#
*/
createModel: function (formValue) {
// Clone
var newModel = JSON.parse(JSON.stringify(model));
// Always use the type from the type definition
newModel.type = type.getKey();
// Update all properties
properties.forEach(function (property, index) {
property.setValue(newModel, formValue[index]);
});
return newModel;
}
}; };
};
/**
* Get the initial value for the form being described.
* This will include the values for all properties described
* in the structure.
*
* @returns {object} the initial value of the form
*/
CreateWizard.prototype.getInitialFormValue = function () {
// Start with initial values for properties
var model = this.model,
formValue = this.properties.map(function (property) {
return property.getValue(model);
});
} // Include the createParent
formValue.createParent = this.parent;
return formValue;
};
/**
* Based on a populated form, get the domain object which
* should be used as a parent for the newly-created object.
* @return {DomainObject}
*/
CreateWizard.prototype.getLocation = function (formValue) {
return formValue.createParent || this.parent;
};
/**
* Create the domain object model for a newly-created object,
* based on user input read from a formModel.
* @return {object} the domain object model
*/
CreateWizard.prototype.createModel = function (formValue) {
// Clone
var newModel = JSON.parse(JSON.stringify(this.model));
// Always use the type from the type definition
newModel.type = this.type.getKey();
// Update all properties
this.properties.forEach(function (property, index) {
property.setValue(newModel, formValue[index]);
});
return newModel;
};
return CreateWizard; return CreateWizard;
} }

View File

@ -43,12 +43,35 @@ define(
* @constructor * @constructor
*/ */
function CreationService(persistenceService, $q, $log) { function CreationService(persistenceService, $q, $log) {
this.persistenceService = persistenceService;
this.$q = $q;
this.$log = $log;
}
/**
* Create a new domain object with the provided model, as
* a member of the provided parent domain object's composition.
* This parent will additionally determine which persistence
* space an object is created within (as it is possible to
* have multiple persistence spaces attached.)
*
* @param {object} model the model for the newly-created
* domain object
* @param {DomainObject} parent the domain object which
* should contain the newly-created domain object
* in its composition
* @return {Promise} a promise that will resolve when the domain
* object has been created
*/
CreationService.prototype.createObject = function (model, parent) {
var persistence = parent.getCapability("persistence"),
self = this;
// Persist the new domain object's model; it will be fully // Persist the new domain object's model; it will be fully
// constituted as a domain object when loaded back, as all // constituted as a domain object when loaded back, as all
// domain object models are. // domain object models are.
function doPersist(space, id, model) { function doPersist(space, id, model) {
return persistenceService.createObject( return self.persistenceService.createObject(
space, space,
id, id,
model model
@ -67,14 +90,14 @@ define(
} }
} else { } else {
// This is abnormal; composition should be an array // This is abnormal; composition should be an array
$log.warn(NO_COMPOSITION_WARNING + parent.getId()); self.$log.warn(NO_COMPOSITION_WARNING + parent.getId());
return false; // Cancel mutation return false; // Cancel mutation
} }
}); });
return $q.when(mutatationResult).then(function (result) { return self.$q.when(mutatationResult).then(function (result) {
if (!result) { if (!result) {
$log.error("Could not mutate " + parent.getId()); self.$log.error("Could not mutate " + parent.getId());
return undefined; return undefined;
} }
@ -94,49 +117,25 @@ define(
}); });
} }
// Create a new domain object with the provided model as a // We need the parent's persistence capability to determine
// member of the specified parent's composition // what space to create the new object's model in.
function createObject(model, parent) { if (!persistence) {
var persistence = parent.getCapability("persistence"); self.$log.warn(NON_PERSISTENT_WARNING);
return self.$q.reject(new Error(NON_PERSISTENT_WARNING));
}
// We need the parent's persistence capability to determine // We create a new domain object in three sequential steps:
// what space to create the new object's model in. // 1. Get a new UUID for the object
if (!persistence) { // 2. Create a model with that ID in the persistence space
$log.warn(NON_PERSISTENT_WARNING); // 3. Add that ID to
return $q.reject(new Error(NON_PERSISTENT_WARNING)); return self.$q.when(uuid()).then(function (id) {
}
// We create a new domain object in three sequential steps:
// 1. Get a new UUID for the object
// 2. Create a model with that ID in the persistence space
// 3. Add that ID to
return $q.when(
uuid()
).then(function (id) {
return doPersist(persistence.getSpace(), id, model); return doPersist(persistence.getSpace(), id, model);
}).then(function (id) { }).then(function (id) {
return addToComposition(id, parent, persistence); return addToComposition(id, parent, persistence);
}); });
} };
return {
/**
* Create a new domain object with the provided model, as
* a member of the provided parent domain object's composition.
* This parent will additionally determine which persistence
* space an object is created within (as it is possible to
* have multiple persistence spaces attached.)
*
* @param {object} model the model for the newly-created
* domain object
* @param {DomainObject} parent the domain object which
* should contain the newly-created domain object
* in its composition
* @memberof platform/commonUI/browse.CreationService#
*/
createObject: createObject
};
}
return CreationService; return CreationService;
} }

View File

@ -33,24 +33,24 @@ define(
* The navigate action navigates to a specific domain object. * The navigate action navigates to a specific domain object.
* @memberof platform/commonUI/browse * @memberof platform/commonUI/browse
* @constructor * @constructor
* @implements {Action}
*/ */
function NavigateAction(navigationService, $q, context) { function NavigateAction(navigationService, $q, context) {
var domainObject = context.domainObject; this.domainObject = context.domainObject;
this.$q = $q;
this.navigationService = navigationService;
}
function perform() { /**
// Set navigation, and wrap like a promise * Navigate to the object described in the context.
return $q.when(navigationService.setNavigation(domainObject)); * @returns {Promise} a promise that is resolved once the
} * navigation has been updated
*/
return { NavigateAction.prototype.perform = function () {
/** // Set navigation, and wrap like a promise
* Navigate to the object described in the context. return this.$q.when(
* @returns {Promise} a promise that is resolved once the this.navigationService.setNavigation(this.domainObject)
* navigation has been updated );
* @memberof platform/commonUI/browse.NavigateAction#
*/
perform: perform
};
} }
/** /**

View File

@ -36,67 +36,52 @@ define(
* @constructor * @constructor
*/ */
function NavigationService() { function NavigationService() {
var navigated, this.navigated = undefined;
callbacks = []; this.callbacks = [];
}
// Getter for current navigation /**
function getNavigation() { * Get the current navigation state.
return navigated; * @returns {DomainObject} the object that is navigated-to
} */
NavigationService.prototype.getNavigation = function () {
return this.navigated;
};
// Setter for navigation; invokes callbacks /**
function setNavigation(value) { * Set the current navigation state. This will invoke listeners.
if (navigated !== value) { * @param {DomainObject} domainObject the domain object to navigate to
navigated = value; */
callbacks.forEach(function (callback) { NavigationService.prototype.setNavigation = function (value) {
callback(value); if (this.navigated !== value) {
}); this.navigated = value;
} this.callbacks.forEach(function (callback) {
} callback(value);
// Adds a callback
function addListener(callback) {
callbacks.push(callback);
}
// Filters out a callback
function removeListener(callback) {
callbacks = callbacks.filter(function (cb) {
return cb !== callback;
}); });
} }
};
return { /**
/** * Listen for changes in navigation. The passed callback will
* Get the current navigation state. * be invoked with the new domain object of navigation when
* @memberof platform/commonUI/browse.NavigationService# * this changes.
*/ * @param {function} callback the callback to invoke when
getNavigation: getNavigation, * navigation state changes
/** */
* Set the current navigation state. Thiswill invoke listeners. NavigationService.prototype.addListener = function (callback) {
* @param {DomainObject} value the domain object to navigate this.callbacks.push(callback);
* to }
* @memberof platform/commonUI/browse.NavigationService#
*/ /**
setNavigation: setNavigation, * Stop listening for changes in navigation state.
/** * @param {function} callback the callback which should
* Listen for changes in navigation. The passed callback will * no longer be invoked when navigation state
* be invoked with the new domain object of navigation when * changes
* this changes. */
* @param {function} callback the callback to invoke when NavigationService.prototype.removeListener = function (callback) {
* navigation state changes this.callbacks = this.callbacks.filter(function (cb) {
* @memberof platform/commonUI/browse.NavigationService# return cb !== callback;
*/ });
addListener: addListener,
/**
* Stop listening for changes in navigation state.
* @param {function} callback the callback which should
* no longer be invoked when navigation state
* changes
* @memberof platform/commonUI/browse.NavigationService#
*/
removeListener: removeListener
};
} }
return NavigationService; return NavigationService;

View File

@ -37,37 +37,30 @@ define(
* and regular in-window display. * and regular in-window display.
* @memberof platform/commonUI/browse * @memberof platform/commonUI/browse
* @constructor * @constructor
* @implements {Action}
*/ */
function FullscreenAction(context) { function FullscreenAction(context) {
return { this.context = context;
/**
* Toggle full screen state
* @memberof platform/commonUI/browse.FullscreenAction#
*/
perform: function () {
screenfull.toggle();
},
/**
* Get metadata about this action, including the
* applicable glyph to display.
* @memberof platform/commonUI/browse.FullscreenAction#
*/
getMetadata: function () {
// We override getMetadata, because the glyph and
// description need to be determined at run-time
// based on whether or not we are currently
// full screen.
var metadata = Object.create(FullscreenAction);
metadata.glyph = screenfull.isFullscreen ? "_" : "z";
metadata.description = screenfull.isFullscreen ?
EXIT_FULLSCREEN : ENTER_FULLSCREEN;
metadata.group = "windowing";
metadata.context = context;
return metadata;
}
};
} }
FullscreenAction.prototype.perform = function () {
screenfull.toggle();
};
FullscreenAction.prototype.getMetadata = function () {
// We override getMetadata, because the glyph and
// description need to be determined at run-time
// based on whether or not we are currently
// full screen.
var metadata = Object.create(FullscreenAction);
metadata.glyph = screenfull.isFullscreen ? "_" : "z";
metadata.description = screenfull.isFullscreen ?
EXIT_FULLSCREEN : ENTER_FULLSCREEN;
metadata.group = "windowing";
metadata.context = this.context;
return metadata;
};
return FullscreenAction; return FullscreenAction;
} }
); );

View File

@ -35,34 +35,25 @@ define(
* into a new browser tab. * into a new browser tab.
* @memberof platform/commonUI/browse * @memberof platform/commonUI/browse
* @constructor * @constructor
* @implements {Action}
*/ */
function NewTabAction(urlService, $window, context) { function NewTabAction(urlService, $window, context) {
// Returns the selected domain object context = context || {};
// when using the context menu or the top right button
// based on the context and the existance of the object this.urlService = urlService;
// It is set to object an returned this.$window = $window;
function getSelectedObject() {
var object; // Choose the object to be opened into a new tab
if (context.selectedObject) { this.domainObject = context.selectedObject || context.domainObject;
object = context.selectedObject;
} else {
object = context.domainObject;
}
return object;
}
return {
// Performs the open in new tab function
// By calling the url service, the mode needed
// (browse) and the domainObject is passed in and
// the path is returned and opened in a new tab
perform: function () {
$window.open(urlService.urlForNewTab("browse", getSelectedObject()),
"_blank");
}
};
} }
NewTabAction.prototype.perform = function () {
this.$window.open(
this.urlService.urlForNewTab("browse", this.domainObject),
"_blank"
);
};
return NewTabAction; return NewTabAction;
} }
); );

View File

@ -25,6 +25,76 @@ define(
function () { function () {
"use strict"; "use strict";
/**
* Actions are reusable processes/behaviors performed by users within
* the system, typically upon domain objects. Actions are commonly
* exposed to users as menu items or buttons.
*
* Actions are usually registered via the `actions` extension
* category, or (in advanced cases) via an `actionService`
* implementation.
*
* @interface Action
*/
/**
* Perform the behavior associated with this action. The return type
* may vary depending on which action has been performed; in general,
* no return value should be expected.
*
* @method Action#perform
*/
/**
* Get metadata associated with this action.
*
* @method Action#getMetadata
* @returns {ActionMetadata}
*/
/**
* Metadata associated with an Action. Actions of specific types may
* extend this with additional properties.
*
* @typedef {Object} ActionMetadata
* @property {string} key machine-readable identifier for this action
* @property {string} name human-readable name for this action
* @property {string} description human-readable description
* @property {string} glyph character to display as icon
* @property {ActionContext} context the context in which the action
* will be performed.
*/
/**
* Provides actions that can be performed within specific contexts.
*
* @interface ActionService
*/
/**
* Get actions which can be performed within a certain context.
*
* @method ActionService#getActions
* @param {ActionContext} context the context in which the action will
* be performed
* @return {Action[]} relevant actions
*/
/**
* A description of the context in which an action may occur.
*
* @typedef ActionContext
* @property {DomainObject} [domainObject] the domain object being
* acted upon.
* @property {DomainObject} [selectedObject] the selection at the
* time of action (e.g. the dragged object in a
* drag-and-drop operation.)
* @property {string} [key] the machine-readable identifier of
* the relevant action
* @property {string} [category] a string identifying the category
* of action being performed
*/
/** /**
* The ActionAggregator makes several actionService * The ActionAggregator makes several actionService
* instances act as those they were one. When requesting * instances act as those they were one. When requesting
@ -33,7 +103,8 @@ define(
* *
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @param {ActionProvider[]} actionProviders an array * @implements {ActionService}
* @param {ActionService[]} actionProviders an array
* of action services * of action services
*/ */
function ActionAggregator(actionProviders) { function ActionAggregator(actionProviders) {