[Layout] Add JSDoc to new scripts

Add in-line documentation to new scripts introduced to
support the transitioned Layout view, WTD-535.
This commit is contained in:
Victor Woeltjen
2014-12-05 18:01:38 -08:00
parent c01d253c8e
commit 6de973c11e
5 changed files with 236 additions and 15 deletions

View File

@ -5,25 +5,51 @@ define(
function () { function () {
"use strict"; "use strict";
/**
* The EditRepresenter is responsible for implementing
* representation-level behavior relevant to Edit mode.
* Specifically, this listens for changes to view configuration
* or to domain object models, and triggers persistence when
* these are detected.
*
* This is exposed as an extension of category `representers`,
* which mct-representation will utilize to add additional
* behavior to each representation.
*
* This will be called once per mct-representation directive,
* and may be reused for different domain objects and/or
* representations resulting from changes there.
*
* @constructor
*/
function EditRepresenter($q, scope) { function EditRepresenter($q, scope) {
var watches = [], var watches = [],
domainObject, domainObject,
key; key;
// Mutate and persist a new version of a domain object's model.
function doPersist(model) { function doPersist(model) {
// First, mutate; then, persist.
return $q.when(domainObject.useCapability("mutation", function () { return $q.when(domainObject.useCapability("mutation", function () {
return model; return model;
})).then(function (result) { })).then(function (result) {
// Only persist when mutation was successful
return result && return result &&
domainObject.getCapability("persistence").persist(); domainObject.getCapability("persistence").persist();
}); });
} }
// Handle changes to model and/or view configuration
function update() { function update() {
// Look up from scope; these will have been populated by
// mct-representation.
var model = scope.model, var model = scope.model,
configuration = scope.configuration; configuration = scope.configuration;
// Update the configuration stored in the model, and persist.
if (domainObject && domainObject.hasCapability("persistence")) { if (domainObject && domainObject.hasCapability("persistence")) {
// Configurations for specific views are stored by
// key in the "configuration" field of the model.
if (key && configuration) { if (key && configuration) {
model.configuration = model.configuration || {}; model.configuration = model.configuration || {};
model.configuration[key] = configuration; model.configuration[key] = configuration;
@ -32,18 +58,26 @@ define(
} }
} }
// Respond to the destruction of the current representation.
function destroy() { function destroy() {
// Stop watching for changes // Stop watching for changes
watches.forEach(function (deregister) { deregister(); }); watches.forEach(function (deregister) { deregister(); });
watches = []; watches = [];
} }
// Handle a specific representation of a specific domain object
function represent(representation, representedObject) { function represent(representation, representedObject) {
// Track the key, to know which view configuration to save to.
key = representation.key; key = representation.key;
// Track the represented object
domainObject = representedObject; domainObject = representedObject;
destroy(); // Ensure existing watches are released // Ensure existing watches are released
destroy();
// Watch for changes to model or configuration; keep the
// results, as $watch returns an de-registration function.
// Use the "editor" capability to check if we are in Edit mode.
watches = representedObject.hasCapability("editor") ? [ watches = representedObject.hasCapability("editor") ? [
scope.$watch("model", update, true), scope.$watch("model", update, true),
scope.$watch("configuration", update, true) scope.$watch("configuration", update, true)
@ -51,7 +85,19 @@ define(
} }
return { 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
*/
represent: represent, represent: represent,
/**
* Release any resources associated with this representer.
*/
destroy: destroy destroy: destroy
}; };
} }

View File

@ -5,29 +5,65 @@ define(
function () { function () {
"use strict"; "use strict";
/**
* The mct-drag directive allows drag functionality
* (in the mousedown-mousemove-mouseup sense, as opposed to
* the drag-and-drop sense) to be attached to specific
* elements. This takes the form of three attributes:
*
* * `mct-drag`: An Angular expression to evaluate during
* drag movement.
* * `mct-drag-down`: An Angular expression to evaluate
* when the drag begins.
* * `mct-drag-up`: An Angular expression to evaluate when
* dragging ends.
*
* In each case, a variable `delta` will be provided to the
* expression; this is a two-element array or the horizontal
* and vertical pixel offset of the current mouse position
* relative to the mouse position where dragging began.
*
* @constructor
*
*/
function MCTDrag($document) { function MCTDrag($document) {
// Link; install event handlers.
function link(scope, element, attrs) { function link(scope, element, attrs) {
// Keep a reference to the body, to attach/detach
// mouse event handlers; mousedown and mouseup cannot
// only be attached to the element being linked, as the
// mouse may leave this element during the drag.
var body = $document.find('body'), var body = $document.find('body'),
initialPosition, initialPosition,
currentPosition,
delta; delta;
// Utility function to cause evaluation of mctDrag,
// mctDragUp, etc
function fireListener(name) { function fireListener(name) {
// Evaluate the expression, with current delta
scope.$eval(attrs[name], { delta: delta }); scope.$eval(attrs[name], { delta: delta });
// Trigger prompt digestion // Trigger prompt digestion
scope.$apply(); scope.$apply();
} }
// Update positions (both actual and relative)
// based on a new mouse event object.
function updatePosition(event) { function updatePosition(event) {
currentPosition = [ event.pageX, event.pageY ]; // Get the current position, as an array
var currentPosition = [ event.pageX, event.pageY ];
// Track the initial position, if one hasn't been observed
initialPosition = initialPosition || currentPosition; initialPosition = initialPosition || currentPosition;
// Compute relative position
delta = currentPosition.map(function (v, i) { delta = currentPosition.map(function (v, i) {
return v - initialPosition[i]; return v - initialPosition[i];
}); });
} }
// Called during a drag, on mousemove
function continueDrag(event) { function continueDrag(event) {
updatePosition(event); updatePosition(event);
fireListener("mctDrag"); fireListener("mctDrag");
@ -37,10 +73,14 @@ define(
return false; return false;
} }
// Called only when the drag ends (on mouseup)
function endDrag(event) { function endDrag(event) {
// Detach event handlers
body.off("mouseup", endDrag); body.off("mouseup", endDrag);
body.off("mousemove", continueDrag); body.off("mousemove", continueDrag);
// Also call continueDrag, to fire mctDrag
// and do its usual position update
continueDrag(event); continueDrag(event);
fireListener("mctDragUp"); fireListener("mctDragUp");
@ -53,12 +93,18 @@ define(
return false; return false;
} }
// Called on mousedown on the element
function startDrag(event) { function startDrag(event) {
// Listen for mouse events at the body level,
// since the mouse may leave the element during
// the drag.
body.on("mouseup", endDrag); body.on("mouseup", endDrag);
body.on("mousemove", continueDrag); body.on("mousemove", continueDrag);
// Set an initial position
updatePosition(event); updatePosition(event);
// Fire listeners, including mctDrag
fireListener("mctDragDown"); fireListener("mctDragDown");
fireListener("mctDrag"); fireListener("mctDrag");
@ -67,11 +113,14 @@ define(
return false; return false;
} }
// Listen for mousedown on the element
element.on("mousedown", startDrag); element.on("mousedown", startDrag);
} }
return { return {
// mct-drag only makes sense as an attribute
restrict: "A", restrict: "A",
// Link function, to install event handlers
link: link link: link
}; };
} }

View File

@ -5,17 +5,27 @@ define(
function (LayoutDrag) { function (LayoutDrag) {
"use strict"; "use strict";
var DEFAULT_DIMENSIONS = [ 12, 8 ]; var DEFAULT_DIMENSIONS = [ 12, 8 ],
DEFAULT_GRID_SIZE = [32, 32];
/**
* The LayoutController is responsible for supporting the
* Layout view. It arranges frames according to saved configuration
* and provides methods for updating these based on mouse
* movement.
* @constructor
* @param {Scope} $scope the controller's Angular scope
*/
function LayoutController($scope) { function LayoutController($scope) {
var width = 32, var gridSize = DEFAULT_GRID_SIZE,
height = 32,
activeDrag, activeDrag,
activeDragId, activeDragId,
rawPositions = {}, rawPositions = {},
positions = {}; positions = {};
// Utility function to copy raw positions from configuration,
// without writing directly to configuration (to avoid triggering
// persistence from watchers during drags).
function shallowCopy(obj, keys) { function shallowCopy(obj, keys) {
var copy = {}; var copy = {};
keys.forEach(function (k) { keys.forEach(function (k) {
@ -24,15 +34,20 @@ define(
return copy; return copy;
} }
// Convert from { positions: ..., dimensions: ... } to an
// apropriate ng-style argument, to position frames.
function convertPosition(raw) { function convertPosition(raw) {
// Multiply position/dimensions by grid size
return { return {
left: (width * raw.position[0]) + 'px', left: (gridSize[0] * raw.position[0]) + 'px',
top: (width * raw.position[1]) + 'px', top: (gridSize[1] * raw.position[1]) + 'px',
width: (width * raw.dimensions[0]) + 'px', width: (gridSize[0] * raw.dimensions[0]) + 'px',
height: (height * raw.dimensions[1]) + 'px' height: (gridSize[1] * raw.dimensions[1]) + 'px'
}; };
} }
// Generate a default position (in its raw format) for a frame.
// Use an index to ensure that default positions are unique.
function defaultPosition(index) { function defaultPosition(index) {
return { return {
position: [index, index], position: [index, index],
@ -40,6 +55,9 @@ define(
}; };
} }
// Store a computed position for a contained frame by its
// domain object id. Called in a forEach loop, so arguments
// are as expected there.
function populatePosition(id, index) { function populatePosition(id, index) {
rawPositions[id] = rawPositions[id] =
rawPositions[id] || defaultPosition(index || 0); rawPositions[id] || defaultPosition(index || 0);
@ -47,37 +65,76 @@ define(
convertPosition(rawPositions[id]); convertPosition(rawPositions[id]);
} }
// Compute panel positions based on the layout's object model
function lookupPanels(model) { function lookupPanels(model) {
var configuration = $scope.configuration || {}, var configuration = $scope.configuration || {},
ids = (model || {}).composition || []; ids = (model || {}).composition || [];
// Clear prior positions // Pull panel positions from configuration
rawPositions = shallowCopy(configuration.panels || {}, ids); rawPositions = shallowCopy(configuration.panels || {}, ids);
// Clear prior computed positions
positions = {}; positions = {};
// Update width/height that we are tracking // Update width/height that we are tracking
gridSize = (model || {}).layoutGrid || DEFAULT_GRID_SIZE;
// Pull values from panels field to rawPositions
// Compute positions and add defaults where needed // Compute positions and add defaults where needed
ids.forEach(populatePosition); ids.forEach(populatePosition);
} }
// Position panes when the model field changes
$scope.$watch("model", lookupPanels); $scope.$watch("model", lookupPanels);
return { return {
/**
* Get a style object for a frame with the specified domain
* object identifier, suitable for use in an `ng-style`
* directive to position a frame as configured for this layout.
* @param {string} id the object identifier
* @returns {Object.<string, string>} an object with
* appropriate left, width, etc fields for positioning
*/
getFrameStyle: function (id) { getFrameStyle: function (id) {
// Called in a loop, so just look up; the "positions"
// object is kept up to date by a watch.
return positions[id]; return positions[id];
}, },
/**
* Start a drag gesture to move/resize a frame.
*
* The provided position and dimensions factors will determine
* whether this is a move or a resize, and what type it
* will be. For instance, a position factor of [1, 1]
* will move a frame along with the mouse as the drag
* proceeds, while a dimension factor of [0, 0] will leave
* dimensions unchanged. Combining these in different
* ways results in different handles; a position factor of
* [1, 0] and a dimensions factor of [-1, 0] will implement
* a left-edge resize, as the horizontal position will move
* with the mouse while the horizontal dimensions shrink in
* kind (and vertical properties remain unmodified.)
*
* @param {string} id the identifier of the domain object
* in the frame being manipulated
* @param {number[]} posFactor the position factor
* @param {number[]} dimFactor the dimensions factor
*/
startDrag: function (id, posFactor, dimFactor) { startDrag: function (id, posFactor, dimFactor) {
activeDragId = id; activeDragId = id;
activeDrag = new LayoutDrag( activeDrag = new LayoutDrag(
rawPositions[id], rawPositions[id],
posFactor, posFactor,
dimFactor, dimFactor,
[ width, height ] gridSize
); );
}, },
/**
* Continue an active drag gesture.
* @param {number[]} delta the offset, in pixels,
* of the current pointer position, relative
* to its position when the drag started
*/
continueDrag: function (delta) { continueDrag: function (delta) {
if (activeDrag) { if (activeDrag) {
rawPositions[activeDragId] = rawPositions[activeDragId] =
@ -85,13 +142,20 @@ define(
populatePosition(activeDragId); populatePosition(activeDragId);
} }
}, },
/**
* End the active drag gesture. This will update the
* view configuration.
*/
endDrag: function () { endDrag: function () {
// Write to configuration; this is watched and // Write to configuration; this is watched and
// saved by the EditRepresenter. // saved by the EditRepresenter.
$scope.configuration = $scope.configuration =
$scope.configuration || {}; $scope.configuration || {};
// Make sure there is a "panels" field in the
// view configuration.
$scope.configuration.panels = $scope.configuration.panels =
$scope.configuration.panels || {}; $scope.configuration.panels || {};
// Store the position of this panel.
$scope.configuration.panels[activeDragId] = $scope.configuration.panels[activeDragId] =
rawPositions[activeDragId]; rawPositions[activeDragId];
} }

View File

@ -5,25 +5,55 @@ define(
function () { function () {
"use strict"; "use strict";
/**
* Handles drag interactions on frames in layouts. This will
* provides new positions/dimensions for frames based on
* relative pixel positions provided; these will take into account
* the grid size (in a snap-to sense) and will enforce some minimums
* on both position and dimensions.
*
* The provided position and dimensions factors will determine
* whether this is a move or a resize, and what type of resize it
* will be. For instance, a position factor of [1, 1]
* will move a frame along with the mouse as the drag
* proceeds, while a dimension factor of [0, 0] will leave
* dimensions unchanged. Combining these in different
* ways results in different handles; a position factor of
* [1, 0] and a dimensions factor of [-1, 0] will implement
* a left-edge resize, as the horizontal position will move
* with the mouse while the horizontal dimensions shrink in
* kind (and vertical properties remain unmodified.)
*
* @param {object} rawPosition the initial position/dimensions
* of the frame being interacted with
* @param {number[]} posFactor the position factor
* @param {number[]} dimFactor the dimensions factor
* @param {number[]} the size of each grid element, in pixels
*/
function LayoutDrag(rawPosition, posFactor, dimFactor, gridSize) { function LayoutDrag(rawPosition, posFactor, dimFactor, gridSize) {
// Convert a delta from pixel coordinates to grid coordinates,
// rounding to whole-number grid coordinates.
function toGridDelta(pixelDelta) { function toGridDelta(pixelDelta) {
return pixelDelta.map(function (v, i) { return pixelDelta.map(function (v, i) {
return Math.round(v / gridSize[i]); return Math.round(v / gridSize[i]);
}); });
} }
// Utility function to perform element-by-element multiplication
function multiply(array, factors) { function multiply(array, factors) {
return array.map(function (v, i) { return array.map(function (v, i) {
return v * factors[i]; return v * factors[i];
}); });
} }
// Utility function to perform element-by-element addition
function add(array, other) { function add(array, other) {
return array.map(function (v, i) { return array.map(function (v, i) {
return v + other[i]; return v + other[i];
}); });
} }
// Utility function to perform element-by-element max-choosing
function max(array, other) { function max(array, other) {
return array.map(function (v, i) { return array.map(function (v, i) {
return Math.max(v, other[i]); return Math.max(v, other[i]);
@ -46,6 +76,13 @@ define(
} }
return { return {
/**
* Get a new position object in grid coordinates, with
* position and dimensions both offset appropriately
* according to the factors supplied in the constructor.
* @param {number[]} pixelDelta the offset from the
* original position, in pixels
*/
getAdjustedPosition: getAdjustedPosition getAdjustedPosition: getAdjustedPosition
}; };
} }

View File

@ -5,10 +5,23 @@ define(
function () { function () {
"use strict"; "use strict";
/**
* The GestureRepresenter is responsible for installing predefined
* gestures upon mct-representation instances.
* Gestures themselves are pulled from the gesture service; this
* simply wraps that behavior in a Representer interface, such that
* it may be included among other such Representers used to prepare
* specific representations.
* @param {GestureService} gestureService the service which provides
* gestures
* @param {Scope} scope the Angular scope for this representation
* @param element the JQLite-wrapped mct-representation element
*/
function GestureRepresenter(gestureService, scope, element) { function GestureRepresenter(gestureService, scope, element) {
var gestureHandle; var gestureHandle;
function destroy() { function destroy() {
// Release any resources associated with these gestures
if (gestureHandle) { if (gestureHandle) {
gestureHandle.destroy(); gestureHandle.destroy();
} }
@ -27,7 +40,19 @@ define(
} }
return { 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
*/
represent: represent, represent: represent,
/**
* Release any resources associated with this representer.
*/
destroy: destroy destroy: destroy
}; };
} }