[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 () {
"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) {
var watches = [],
domainObject,
key;
// Mutate and persist a new version of a domain object's model.
function doPersist(model) {
// First, mutate; then, persist.
return $q.when(domainObject.useCapability("mutation", function () {
return model;
})).then(function (result) {
// Only persist when mutation was successful
return result &&
domainObject.getCapability("persistence").persist();
});
}
// Handle changes to model and/or view configuration
function update() {
// Look up from scope; these will have been populated by
// mct-representation.
var model = scope.model,
configuration = scope.configuration;
// Update the configuration stored in the model, and persist.
if (domainObject && domainObject.hasCapability("persistence")) {
// Configurations for specific views are stored by
// key in the "configuration" field of the model.
if (key && configuration) {
model.configuration = model.configuration || {};
model.configuration[key] = configuration;
@ -32,18 +58,26 @@ define(
}
}
// Respond to the destruction of the current representation.
function destroy() {
// Stop watching for changes
watches.forEach(function (deregister) { deregister(); });
watches = [];
}
// 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;
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") ? [
scope.$watch("model", update, true),
scope.$watch("configuration", update, true)
@ -51,7 +85,19 @@ define(
}
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,
/**
* Release any resources associated with this representer.
*/
destroy: destroy
};
}

View File

@ -5,29 +5,65 @@ define(
function () {
"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) {
// Link; install event handlers.
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'),
initialPosition,
currentPosition,
delta;
// Utility function to cause evaluation of mctDrag,
// mctDragUp, etc
function fireListener(name) {
// Evaluate the expression, with current delta
scope.$eval(attrs[name], { delta: delta });
// Trigger prompt digestion
scope.$apply();
}
// Update positions (both actual and relative)
// based on a new mouse event object.
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;
// Compute relative position
delta = currentPosition.map(function (v, i) {
return v - initialPosition[i];
});
}
// Called during a drag, on mousemove
function continueDrag(event) {
updatePosition(event);
fireListener("mctDrag");
@ -37,10 +73,14 @@ define(
return false;
}
// Called only when the drag ends (on mouseup)
function endDrag(event) {
// Detach event handlers
body.off("mouseup", endDrag);
body.off("mousemove", continueDrag);
// Also call continueDrag, to fire mctDrag
// and do its usual position update
continueDrag(event);
fireListener("mctDragUp");
@ -53,12 +93,18 @@ define(
return false;
}
// Called on mousedown on the element
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("mousemove", continueDrag);
// Set an initial position
updatePosition(event);
// Fire listeners, including mctDrag
fireListener("mctDragDown");
fireListener("mctDrag");
@ -67,11 +113,14 @@ define(
return false;
}
// Listen for mousedown on the element
element.on("mousedown", startDrag);
}
return {
// mct-drag only makes sense as an attribute
restrict: "A",
// Link function, to install event handlers
link: link
};
}

View File

@ -5,17 +5,27 @@ define(
function (LayoutDrag) {
"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) {
var width = 32,
height = 32,
var gridSize = DEFAULT_GRID_SIZE,
activeDrag,
activeDragId,
rawPositions = {},
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) {
var copy = {};
keys.forEach(function (k) {
@ -24,15 +34,20 @@ define(
return copy;
}
// Convert from { positions: ..., dimensions: ... } to an
// apropriate ng-style argument, to position frames.
function convertPosition(raw) {
// Multiply position/dimensions by grid size
return {
left: (width * raw.position[0]) + 'px',
top: (width * raw.position[1]) + 'px',
width: (width * raw.dimensions[0]) + 'px',
height: (height * raw.dimensions[1]) + 'px'
left: (gridSize[0] * raw.position[0]) + 'px',
top: (gridSize[1] * raw.position[1]) + 'px',
width: (gridSize[0] * raw.dimensions[0]) + '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) {
return {
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) {
rawPositions[id] =
rawPositions[id] || defaultPosition(index || 0);
@ -47,37 +65,76 @@ define(
convertPosition(rawPositions[id]);
}
// Compute panel positions based on the layout's object model
function lookupPanels(model) {
var configuration = $scope.configuration || {},
ids = (model || {}).composition || [];
// Clear prior positions
// Pull panel positions from configuration
rawPositions = shallowCopy(configuration.panels || {}, ids);
// Clear prior computed positions
positions = {};
// Update width/height that we are tracking
// Pull values from panels field to rawPositions
gridSize = (model || {}).layoutGrid || DEFAULT_GRID_SIZE;
// Compute positions and add defaults where needed
ids.forEach(populatePosition);
}
// Position panes when the model field changes
$scope.$watch("model", lookupPanels);
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) {
// Called in a loop, so just look up; the "positions"
// object is kept up to date by a watch.
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) {
activeDragId = id;
activeDrag = new LayoutDrag(
rawPositions[id],
posFactor,
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) {
if (activeDrag) {
rawPositions[activeDragId] =
@ -85,13 +142,20 @@ define(
populatePosition(activeDragId);
}
},
/**
* End the active drag gesture. This will update the
* view configuration.
*/
endDrag: function () {
// Write to configuration; this is watched and
// saved by the EditRepresenter.
$scope.configuration =
$scope.configuration || {};
// Make sure there is a "panels" field in the
// view configuration.
$scope.configuration.panels =
$scope.configuration.panels || {};
// Store the position of this panel.
$scope.configuration.panels[activeDragId] =
rawPositions[activeDragId];
}

View File

@ -5,25 +5,55 @@ define(
function () {
"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) {
// Convert a delta from pixel coordinates to grid coordinates,
// rounding to whole-number grid coordinates.
function toGridDelta(pixelDelta) {
return pixelDelta.map(function (v, i) {
return Math.round(v / gridSize[i]);
});
}
// Utility function to perform element-by-element multiplication
function multiply(array, factors) {
return array.map(function (v, i) {
return v * factors[i];
});
}
// Utility function to perform element-by-element addition
function add(array, other) {
return array.map(function (v, i) {
return v + other[i];
});
}
// Utility function to perform element-by-element max-choosing
function max(array, other) {
return array.map(function (v, i) {
return Math.max(v, other[i]);
@ -46,6 +76,13 @@ define(
}
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
};
}

View File

@ -5,10 +5,23 @@ define(
function () {
"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) {
var gestureHandle;
function destroy() {
// Release any resources associated with these gestures
if (gestureHandle) {
gestureHandle.destroy();
}
@ -27,7 +40,19 @@ define(
}
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,
/**
* Release any resources associated with this representer.
*/
destroy: destroy
};
}