diff --git a/platform/representation/bundle.json b/platform/representation/bundle.json new file mode 100644 index 0000000000..00a4e5ee41 --- /dev/null +++ b/platform/representation/bundle.json @@ -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" ] + } + ] + } +} \ No newline at end of file diff --git a/platform/representation/src/MCTInclude.js b/platform/representation/src/MCTInclude.js new file mode 100644 index 0000000000..aa9e36da5f --- /dev/null +++ b/platform/representation/src/MCTInclude.js @@ -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: '', + scope: { key: "=", ngModel: "=", parameters: "=" } + }; + } + + return MCTInclude; + } +); \ No newline at end of file diff --git a/platform/representation/src/MCTRepresentation.js b/platform/representation/src/MCTRepresentation.js new file mode 100644 index 0000000000..1ce2f61970 --- /dev/null +++ b/platform/representation/src/MCTRepresentation.js @@ -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: '', + scope: { key: "=", domainObject: "=mctObject", parameters: "=" } + }; + } + + return MCTRepresentation; + } +); \ No newline at end of file diff --git a/platform/representation/src/gestures/ContextMenuGesture.js b/platform/representation/src/gestures/ContextMenuGesture.js new file mode 100644 index 0000000000..2a553cca53 --- /dev/null +++ b/platform/representation/src/gestures/ContextMenuGesture.js @@ -0,0 +1,86 @@ +/*global define,Promise*/ + +/** + * Module defining ContextMenuGesture. Created by vwoeltje on 11/17/14. + */ +define( + [], + function () { + "use strict"; + + var MENU_TEMPLATE = "" + + ""; + + /** + * 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; + } +); \ No newline at end of file diff --git a/platform/representation/src/gestures/DragGesture.js b/platform/representation/src/gestures/DragGesture.js new file mode 100644 index 0000000000..9832010cc9 --- /dev/null +++ b/platform/representation/src/gestures/DragGesture.js @@ -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; + } +); \ No newline at end of file diff --git a/platform/representation/src/gestures/DropGesture.js b/platform/representation/src/gestures/DropGesture.js new file mode 100644 index 0000000000..0a25f49068 --- /dev/null +++ b/platform/representation/src/gestures/DropGesture.js @@ -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; + } +); \ No newline at end of file diff --git a/platform/representation/src/gestures/GestureConstants.js b/platform/representation/src/gestures/GestureConstants.js new file mode 100644 index 0000000000..addbf50ae0 --- /dev/null +++ b/platform/representation/src/gestures/GestureConstants.js @@ -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' +}); \ No newline at end of file