diff --git a/platform/representation/bundle.json b/platform/representation/bundle.json index 96a9459cfc..250fc2f730 100644 --- a/platform/representation/bundle.json +++ b/platform/representation/bundle.json @@ -16,7 +16,7 @@ { "key": "drag", "implementation": "gestures/DragGesture.js", - "depends": [ "$log" ] + "depends": [ "$log", "dndService" ] }, { "key": "drop", @@ -42,6 +42,13 @@ "implementation": "gestures/GestureRepresenter.js", "depends": [ "gestureService" ] } + ], + "services": [ + { + "key": "dndService", + "implementation": "services/DndService.js", + "depends": [ "$log" ] + } ] } } \ No newline at end of file diff --git a/platform/representation/src/gestures/DragGesture.js b/platform/representation/src/gestures/DragGesture.js index fddfd47b7d..1b70a76dd4 100644 --- a/platform/representation/src/gestures/DragGesture.js +++ b/platform/representation/src/gestures/DragGesture.js @@ -19,7 +19,7 @@ define( * @param {DomainObject} domainObject the domain object which * is represented; this will be passed on drop. */ - function DragGesture($log, element, domainObject) { + function DragGesture($log, dndService, element, domainObject) { function startDrag(e) { var event = (e || {}).originalEvent || e; @@ -45,6 +45,12 @@ define( domainObject.getId() ); + // Finally, also pass the object instance via the dndService, + // so more than the ID can be retrieved (if desired) + dndService.setData( + GestureConstants.MCT_DRAG_TYPE, + domainObject + ); } catch (err) { // Exceptions at this point indicate that the browser // do not fully support drag-and-drop (e.g. if @@ -57,10 +63,16 @@ define( } + function endDrag() { + // Clear the drag data after the drag is complete + dndService.removeData(GestureConstants.MCT_DRAG_TYPE); + } + // Mark the element as draggable, and handle the dragstart event $log.debug("Attaching drag gesture"); element.attr('draggable', 'true'); element.on('dragstart', startDrag); + element.on('dragend', endDrag); return { /** diff --git a/platform/representation/src/services/DndService.js b/platform/representation/src/services/DndService.js new file mode 100644 index 0000000000..d367810e34 --- /dev/null +++ b/platform/representation/src/services/DndService.js @@ -0,0 +1,50 @@ +/*global define*/ + +define( + [], + function () { + "use strict"; + + /** + * Drag-and-drop service. + * Supplements HTML5 drag-and-drop support by: + * * Storing arbitrary JavaScript objects (not just strings.) + * * Allowing inspection of dragged objects during `dragover` events, + * etc. (which cannot be done in Chrome for security reasons) + * @constructor + * @param $log Angular's $log service + */ + function DndService($log) { + var data = {}; + + return { + /** + * Set drag data associated with a given type. + * @param {string} key the type's identiifer + * @param {*} value the data being dragged + */ + setData: function (key, value) { + $log.debug("Setting drag data for " + key); + data[key] = value; + }, + /** + * Get drag data associated with a given type. + * @returns {*} the data being dragged + */ + getData: function (key) { + return data[key]; + }, + /** + * Remove data associated with active drags. + * @param {string} key the type to remove + */ + removeData: function (key) { + $log.debug("Clearing drag data for " + key); + delete data[key]; + } + }; + } + + return DndService; + } +); \ No newline at end of file diff --git a/platform/representation/test/gestures/DragGestureSpec.js b/platform/representation/test/gestures/DragGestureSpec.js index 9628183158..cb7435061f 100644 --- a/platform/representation/test/gestures/DragGestureSpec.js +++ b/platform/representation/test/gestures/DragGestureSpec.js @@ -10,6 +10,7 @@ define( var JQLITE_FUNCTIONS = [ "on", "off", "attr", "removeAttr" ], LOG_FUNCTIONS = [ "error", "warn", "info", "debug"], + DND_FUNCTIONS = [ "setData", "getData", "removeData" ], DOMAIN_OBJECT_METHODS = [ "getId", "getModel", "getCapability", "hasCapability", "useCapability"], TEST_ID = "test-id"; @@ -17,14 +18,16 @@ define( describe("The drag gesture", function () { var mockLog, + mockDndService, mockElement, mockDomainObject, mockDataTransfer, - gesture, - fireGesture; + handlers, + gesture; beforeEach(function () { mockLog = jasmine.createSpyObj("$log", LOG_FUNCTIONS); + mockDndService = jasmine.createSpyObj("dndService", DND_FUNCTIONS); mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS); mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS); mockDataTransfer = jasmine.createSpyObj("dataTransfer", ["setData"]); @@ -32,12 +35,18 @@ define( mockDomainObject.getId.andReturn(TEST_ID); mockDomainObject.getModel.andReturn({}); - gesture = new DragGesture(mockLog, mockElement, mockDomainObject); - fireGesture = mockElement.on.mostRecentCall.args[1]; + handlers = {}; + + gesture = new DragGesture(mockLog, mockDndService, mockElement, mockDomainObject); + + // Look up all handlers registered by the gesture + mockElement.on.calls.forEach(function (call) { + handlers[call.args[0]] = call.args[1]; + }); }); it("listens for dragstart on the element", function () { - expect(mockElement.on.mostRecentCall.args[0]).toEqual("dragstart"); + expect(handlers.dragstart).toEqual(jasmine.any(Function)); }); it("marks an element as draggable", function () { @@ -45,19 +54,40 @@ define( }); it("places data in a dataTransfer object", function () { - fireGesture({ dataTransfer: mockDataTransfer }); + handlers.dragstart({ dataTransfer: mockDataTransfer }); expect(mockDataTransfer.setData).toHaveBeenCalledWith( GestureConstants.MCT_DRAG_TYPE, TEST_ID ); }); + it("places domain object in the dnd service", function () { + handlers.dragstart({ dataTransfer: mockDataTransfer }); + expect(mockDndService.setData).toHaveBeenCalledWith( + GestureConstants.MCT_DRAG_TYPE, + mockDomainObject + ); + }); + + it("clears domain object from the dnd service on drag end", function () { + // Start dragging + handlers.dragstart({ dataTransfer: mockDataTransfer }); + + // Verify precondition + expect(mockDndService.removeData).not.toHaveBeenCalled(); + + // End the drag + handlers.dragend({ dataTransfer: mockDataTransfer }); + expect(mockDndService.removeData) + .toHaveBeenCalledWith(GestureConstants.MCT_DRAG_TYPE); + }); + it("logs a warning if dataTransfer cannot be set", function () { // Verify precondition expect(mockLog.warn).not.toHaveBeenCalled(); // Fire the gesture without a dataTransfer field - fireGesture({}); + handlers.dragstart({}); // Should have logged a warning expect(mockLog.warn).toHaveBeenCalled(); @@ -73,7 +103,7 @@ define( // Verify that attribute/listener were removed expect(mockElement.removeAttr).toHaveBeenCalledWith("draggable"); - expect(mockElement.off).toHaveBeenCalledWith("dragstart", fireGesture); + expect(mockElement.off).toHaveBeenCalledWith("dragstart", handlers.dragstart); }); }); diff --git a/platform/representation/test/services/DndServiceSpec.js b/platform/representation/test/services/DndServiceSpec.js new file mode 100644 index 0000000000..21b799834e --- /dev/null +++ b/platform/representation/test/services/DndServiceSpec.js @@ -0,0 +1,45 @@ +/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ + + +define( + ["../../src/services/DndService"], + function (DndService) { + "use strict"; + + describe("The drag-and-drop service", function () { + var service; + + beforeEach(function () { + var mockLog = jasmine.createSpyObj("$log", ['debug']); + service = new DndService(mockLog); + }); + + it("allows setting of arbitrary objects", function () { + var foo = { + bar: function () { return 42; } + }; + + service.setData('xyz', foo); + + // Test that we can get back callable data, since this is + // a key reason for having a service separate from HTML5 DnD. + expect(service.getData('xyz').bar()).toEqual(42); + }); + + it("stores data under specified keys", function () { + service.setData('abc', 42); + service.setData('def', "some data"); + + expect(service.getData('abc')).toEqual(42); + expect(service.getData('def')).toEqual("some data"); + }); + + it("removes data", function () { + service.setData('abc', 42); + service.removeData('abc'); + expect(service.getData('abc')).toBeUndefined(); + }); + + }); + } +); \ No newline at end of file diff --git a/platform/representation/test/suite.json b/platform/representation/test/suite.json index 16483cec4f..600b81baea 100644 --- a/platform/representation/test/suite.json +++ b/platform/representation/test/suite.json @@ -4,6 +4,7 @@ "gestures/DropGesture", "gestures/GestureProvider", "gestures/GestureRepresenter", + "services/DndService", "MCTInclude", "MCTRepresentation" ] \ No newline at end of file