[Representation] Initial representation bundle

Initial transition of bundle platform/representation
from sandbox branch. WTD-521.
This commit is contained in:
Victor Woeltjen 2014-11-22 09:29:41 -08:00
parent d080a67b43
commit 5f43c40afe
7 changed files with 427 additions and 0 deletions

View File

@ -0,0 +1,33 @@
{
"extensions": {
"directives": [
{
"key": "mctInclude",
"implementation": "MCTInclude.js",
"depends": [ "templates[]" ]
},
{
"key": "mctRepresentation",
"implementation": "MCTRepresentation.js",
"depends": [ "representations[]", "views[]", "gestures[]", "$q", "$log" ]
}
],
"gestures": [
{
"key": "drag",
"implementation": "gestures/DragGesture.js",
"depends": [ "$log" ]
},
{
"key": "drop",
"implementation": "gestures/DropGesture.js",
"depends": [ "$q" ]
},
{
"key": "menu",
"implementation": "gestures/ContextMenuGesture.js",
"depends": [ "$compile", "$document", "$window", "$rootScope" ]
}
]
}
}

View File

@ -0,0 +1,43 @@
/*global define,Promise*/
/**
* Module defining MCTInclude. Created by vwoeltje on 11/7/14.
*/
define(
[],
function () {
"use strict";
/**
* Defines the mct-include directive. This acts like the
* ng-include directive, except it accepts a symbolic
* key which can be exposed by bundles.
* @constructor
*/
function MCTInclude(templates) {
var templateMap = {};
templates.forEach(function (template) {
var path = [
template.bundle.path,
template.bundle.resources,
template.templateUrl
].join("/");
templateMap[template.key] = path;
});
function controller($scope) {
$scope.inclusion = templateMap[$scope.key];
}
return {
restrict: "E",
controller: controller,
template: '<ng-include src="inclusion"></ng-include>',
scope: { key: "=", ngModel: "=", parameters: "=" }
};
}
return MCTInclude;
}
);

View File

@ -0,0 +1,122 @@
/*global define,Promise*/
/**
* Module defining MCTRepresentation. Created by vwoeltje on 11/7/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function MCTRepresentation(representations, views, gestures, $q, $log) {
var pathMap = {},
representationMap = {},
gestureMap = {};
// Assemble all representations and views
// The distinction between views and representations is
// not important her (view is-a representation)
representations.concat(views).forEach(function (representation) {
var path = [
representation.bundle.path,
representation.bundle.resources,
representation.templateUrl
].join("/");
// Consider allowing multiple templates with the same key
pathMap[representation.key] = path;
representationMap[representation.key] = representation;
});
// Assemble all gestures into a map, similarly
gestures.forEach(function (gesture) {
gestureMap[gesture.key] = gesture;
});
function findRepresentation(key, domainObject) {
return representationMap[key];
}
function createGestures(element, domainObject, gestureKeys) {
return gestureKeys.map(function (key) {
return gestureMap[key];
}).filter(function (Gesture) {
return Gesture !== undefined && (Gesture.appliesTo ?
Gesture.appliesTo(domainObject) :
true);
}).map(function (Gesture) {
return new Gesture(element, domainObject);
});
}
function releaseGestures(gestures) {
gestures.forEach(function (gesture) {
if (gesture && gesture.destroy) {
gesture.destroy();
}
});
}
function link($scope, element) {
var linkedGestures = [];
function refresh() {
var representation = representationMap[$scope.key],
domainObject = $scope.domainObject,
uses = ((representation || {}).uses || []),
gestureKeys = ((representation || {}).gestures || []);
$scope.representation = {};
$scope.inclusion = pathMap[$scope.key];
// Any existing gestures are no longer valid; release them.
releaseGestures(linkedGestures);
if (!representation && $scope.key) {
$log.warn("No representation found for " + $scope.key);
}
if (domainObject) {
$scope.model = domainObject.getModel();
uses.forEach(function (used) {
$log.debug([
"Requesting capability ",
used,
" for representation ",
$scope.key
].join(""));
$q.when(
domainObject.useCapability(used)
).then(function (c) {
$scope[used] = c;
});
});
linkedGestures = createGestures(
element,
domainObject,
gestureKeys
);
}
}
$scope.$watch("key", refresh);
$scope.$watch("domainObject", refresh);
$scope.$watch("domainObject.getModel().modified", refresh);
}
return {
restrict: "E",
link: link,
template: '<ng-include src="inclusion"></ng-include>',
scope: { key: "=", domainObject: "=mctObject", parameters: "=" }
};
}
return MCTRepresentation;
}
);

View File

@ -0,0 +1,86 @@
/*global define,Promise*/
/**
* Module defining ContextMenuGesture. Created by vwoeltje on 11/17/14.
*/
define(
[],
function () {
"use strict";
var MENU_TEMPLATE = "<mct-representation key=\"'context-menu'\" " +
"mct-object=\"domainObject\" " +
"ng-class=\"menuClass\"" +
"ng-style=\"menuStyle\">" +
"</mct-representation>";
/**
* Add listeners to a view such that it launches a context menu for the
* object it contains.
*
* @constructor
*/
function ContextMenuGesture($compile, $document, $window, $rootScope, element, domainObject) {
function showMenu(event) {
var winDim = [$window.innerWidth, $window.innerHeight],
eventCoors = [event.pageX, event.pageY],
menuDim = [170, 200],
body = $document.find('body'),
scope = $rootScope.$new(),
goLeft = eventCoors[0] + menuDim[0] > winDim[0],
goUp = eventCoors[1] + menuDim[1] > winDim[1],
menu;
// Remove the context menu
function dismiss() {
menu.remove();
body.off("click", dismiss);
ContextMenuGesture.dismissExistingMenu = undefined;
}
// Dismiss any menu which was already showing
if (ContextMenuGesture.dismissExistingMenu) {
ContextMenuGesture.dismissExistingMenu();
}
// ...and record the presence of this menu.
ContextMenuGesture.dismissExistingMenu = dismiss;
// Set up the scope, including menu positioning
scope.domainObject = domainObject;
scope.menuStyle = {};
scope.menuStyle[goLeft ? "right" : "left"] =
eventCoors[0] + 'px';
scope.menuStyle[goUp ? "bottom" : "top"] =
eventCoors[1] + 'px';
scope.menuClass = { "go-left": goLeft, "go-up": goUp, "context-menu-holder": true };
// Create the context menu
menu = $compile(MENU_TEMPLATE)(scope);
// Add the menu to the body
body.append(menu);
// Dismiss the menu when body is clicked elsewhere
body.on('click', dismiss);
// Don't launch browser's context menu
event.preventDefault();
}
// When context menu event occurs, show object actions instead
element.on('contextmenu', showMenu);
return {
destroy: function () {
if (ContextMenuGesture.dismissExistingMenu) {
ContextMenuGesture.dismissExistingMenu();
}
element.off('contextmenu', showMenu);
}
};
}
return ContextMenuGesture;
}
);

View File

@ -0,0 +1,61 @@
/*global define,Promise*/
/**
* Module defining DragGesture. Created by vwoeltje on 11/17/14.
*/
define(
['./GestureConstants'],
function (GestureConstants) {
"use strict";
/**
*
* @constructor
*/
function DragGesture($log, element, domainObject) {
function startDrag(e) {
var event = (e || {}).originalEvent || e;
$log.debug("Initiating drag");
try {
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData(
'text/plain',
JSON.stringify({
id: domainObject.getId(),
model: domainObject.getModel()
})
);
event.dataTransfer.setData(
GestureConstants.MCT_DRAG_TYPE,
domainObject.getId()
);
} catch (err) {
$log.warn([
"Could not initiate drag due to ",
err.message
].join(""));
}
}
$log.debug("Attaching drag gesture");
element.attr('draggable', 'true');
element.on('dragstart', startDrag);
return {
destroy: function () {
// Detach listener
element.attr('draggable', false);
element.off('dragstart', startDrag);
}
};
}
return DragGesture;
}
);

View File

@ -0,0 +1,74 @@
/*global define,Promise*/
/**
* Module defining DropGesture. Created by vwoeltje on 11/17/14.
*/
define(
['./GestureConstants'],
function (GestureConstants) {
"use strict";
/**
*
* @constructor
*/
function DropGesture($q, element, domainObject) {
function doPersist() {
var persistence = domainObject.getCapability("persistence");
return $q.when(persistence && peristence.persist());
}
function dragOver(e) {
var event = (e || {}).originalEvent || e;
//event.stopPropagation();
// TODO: Vary this based on modifier keys
event.dataTransfer.dropEffect = 'move';
event.preventDefault(); // Required in Chrome?
return false;
}
function drop(e) {
var event = (e || {}).originalEvent || e,
id = event.dataTransfer.getData(GestureConstants.MCT_DRAG_TYPE);
if (id) {
$q.when(domainObject.useCapability(
'mutation',
function (model) {
var composition = model.composition;
if (composition && // not-contains
!(composition.map(function (i) {
return i === id;
}).reduce(function (a, b) {
return a || b;
}, false))) {
model.composition.push(id);
}
}
)).then(function (result) {
return result && doPersist();
});
}
}
element.on('dragover', dragOver);
element.on('drop', drop);
return {
destroy: function () {
element.off('dragover', dragOver);
element.off('drop', drop);
}
};
}
return DropGesture;
}
);

View File

@ -0,0 +1,8 @@
/*global define,Promise*/
/**
* Module defining GestureConstants. Created by vwoeltje on 11/17/14.
*/
define({
MCT_DRAG_TYPE: 'mct-domain-object-id'
});