Skip to content
This repository was archived by the owner on Mar 10, 2024. It is now read-only.

enable retention of prototypes when dragging #46

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 35 additions & 20 deletions angular-drag-and-drop-lists.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,19 @@ angular.module('dndLists', [])
* it's source position, and not the "element" that the user is dragging with
* his mouse pointer.
*/
.directive('dndDraggable', ['$parse', '$timeout', 'dndDropEffectWorkaround', 'dndDragTypeWorkaround',
function($parse, $timeout, dndDropEffectWorkaround, dndDragTypeWorkaround) {
.directive('dndDraggable', ['$parse', '$timeout', 'dndDropEffectWorkaround', 'dndDragTypeWorkaround', '$cacheFactory',
function($parse, $timeout, dndDropEffectWorkaround, dndDragTypeWorkaround, $cacheFactory) {

// cache to manage data getting dragged and dropped
var cache = $cacheFactory.get('dndLists') || $cacheFactory('dndLists'),

// our data needs a unique ID; this is used to get one
nextBufferId = 0;

return function(scope, element, attr) {
// data buffer id
var id;

// Set the HTML5 draggable attribute on the element
element.attr("draggable", "true");

Expand All @@ -78,10 +88,11 @@ angular.module('dndLists', [])
* which is the primary way we communicate with the target element
*/
element.on('dragstart', function(event) {
id = (nextBufferId++).toString();
event = event.originalEvent || event;

// Serialize the data associated with this element. IE only supports the Text drag type
event.dataTransfer.setData("Text", angular.toJson(scope.$eval(attr.dndDraggable)));
event.dataTransfer.setData("Text", id);
cache.put(id, scope.$eval(attr.dndDraggable));

// Only allow actions specified in dnd-effect-allowed attribute
event.dataTransfer.effectAllowed = attr.dndEffectAllowed || "move";
Expand All @@ -99,7 +110,7 @@ angular.module('dndLists', [])
dndDragTypeWorkaround.dragType = attr.dndType ? scope.$eval(attr.dndType) : undefined;

// Invoke callback
$parse(attr.dndDragstart)(scope, {event: event});
$parse(attr.dndDragstart)(scope, {event: event, item: cache.get(id)});

event.stopPropagation();
});
Expand All @@ -120,11 +131,11 @@ angular.module('dndLists', [])
scope.$apply(function() {
switch (dropEffect) {
case "move":
$parse(attr.dndMoved)(scope, {event: event});
$parse(attr.dndMoved)(scope, {event: event, item: cache.get(id)});
break;

case "copy":
$parse(attr.dndCopied)(scope, {event: event});
$parse(attr.dndCopied)(scope, {event: event, item: cache.get(id)});
break;
}
});
Expand All @@ -134,6 +145,11 @@ angular.module('dndLists', [])
element.removeClass("dndDraggingSource");
dndDragTypeWorkaround.isDragging = false;
event.stopPropagation();

// remove the data from the cache, but not before the drop occurs.
$timeout(function() {
cache.remove(id);
})
});

/**
Expand All @@ -144,7 +160,7 @@ angular.module('dndLists', [])
event = event.originalEvent || event;

scope.$apply(function() {
$parse(attr.dndSelected)(scope, {event: event});
$parse(attr.dndSelected)(scope, {event: event, item: cache.get(id)});
});

event.stopPropagation();
Expand Down Expand Up @@ -207,8 +223,11 @@ angular.module('dndLists', [])
* dndPlaceholder set.
* - dndDragover Will be added to the list while an element is dragged over the list.
*/
.directive('dndList', ['$parse', '$timeout', 'dndDropEffectWorkaround', 'dndDragTypeWorkaround',
function($parse, $timeout, dndDropEffectWorkaround, dndDragTypeWorkaround) {
.directive('dndList', ['$parse', '$timeout', 'dndDropEffectWorkaround', 'dndDragTypeWorkaround', '$cacheFactory',
function($parse, $timeout, dndDropEffectWorkaround, dndDragTypeWorkaround, $cacheFactory) {

var cache = $cacheFactory.get('dndLists') || $cacheFactory('dndLists');

return function(scope, element, attr) {
// While an element is dragged over the list, this placeholder element is inserted
// at the location where the element would be inserted after dropping
Expand Down Expand Up @@ -302,13 +321,8 @@ angular.module('dndLists', [])

// Unserialize the data that was serialized in dragstart. According to the HTML5 specs,
// the "Text" drag type will be converted to text/plain, but IE does not do that.
var data = event.dataTransfer.getData("Text") || event.dataTransfer.getData("text/plain");
var transferredObject;
try {
transferredObject = JSON.parse(data);
} catch(e) {
return stopDragover();
}
var id = event.dataTransfer.getData("Text") || event.dataTransfer.getData("text/plain");
var transferredObject = cache.get(id);

// Invoke the callback, which can transform the transferredObject and even abort the drop.
if (attr.dndDrop) {
Expand Down Expand Up @@ -426,10 +440,11 @@ angular.module('dndLists', [])
* Invokes a callback with some interesting parameters and returns the callbacks return value.
*/
function invokeCallback(expression, event, item) {
var item = item || cache.get(event.dataTransfer.getData("Text") || event.dataTransfer.getData("text/plain"));
return $parse(expression)(scope, {
event: event,
index: getPlaceholderIndex(),
item: item || undefined,
item: item,
external: !dndDragTypeWorkaround.isDragging,
type: dndDragTypeWorkaround.isDragging ? dndDragTypeWorkaround.dragType : undefined
});
Expand Down Expand Up @@ -457,12 +472,12 @@ angular.module('dndLists', [])
* here. When a dropover event occurs, we only allow the drop if we are already dragging, because
* that means the element is ours.
*/
.factory('dndDragTypeWorkaround', function(){ return {} })
.value('dndDragTypeWorkaround', {})

/**
* Chrome on Windows does not set the dropEffect field, which we need in dragend to determine
* whether a drag operation was successful. Therefore we have to maintain it in this global
* variable. The bug report for that has been open for years:
* https://code.google.com/p/chromium/issues/detail?id=39399
*/
.factory('dndDropEffectWorkaround', function(){ return {} });
.value('dndDropEffectWorkaround', {});
2 changes: 1 addition & 1 deletion demo/advanced/advanced-frame.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ <h1>Demo: Advanced Features</h1>
<div view-source="advanced"></div>

<h2>Generated Model</h2>
<pre>{{modelAsJson}}</pre>
<pre>{{toJson(model)}}</pre>
12 changes: 6 additions & 6 deletions demo/advanced/advanced.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ <h3>Dropzone {{$index + 1}}</h3>
dnd-draggable="items"
dnd-type="'containerType'"
dnd-effect-allowed="copyMove"
dnd-dragstart="logEvent('Started to drag a container', event)"
dnd-moved="containers.splice($index, 1); logEvent('Container moved', event)"
dnd-copied="logEvent('Container copied', event)">
dnd-dragstart="logEvent('Started to drag a container', event, item)"
dnd-moved="containers.splice($index, 1); logEvent('Container moved', event, item);"
dnd-copied="logEvent('Container copied', event, item)">
<div class="container-element box box-blue">
<h3>Container</h3>
<ul dnd-list="items"
Expand All @@ -24,9 +24,9 @@ <h3>Container</h3>
dnd-draggable="item"
dnd-type="'itemType'"
dnd-effect-allowed="copyMove"
dnd-dragstart="logEvent('Started to drag an item', event)"
dnd-moved="items.splice($index, 1); logEvent('Item moved', event)"
dnd-copied="logEvent('Item copied', event)">
dnd-dragstart="logEvent('Started to drag an item', event, item)"
dnd-moved="items.splice($index, 1); logEvent('Item moved', event, item); item.moved()"
dnd-copied="logEvent('Item copied', event, item); item.copied()">
{{item.label}}
</li>
</ul>
Expand Down
33 changes: 25 additions & 8 deletions demo/advanced/advanced.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,50 @@ angular.module("demo").controller("AdvancedDemoController", function($scope) {
return item;
};

$scope.logEvent = function(message, event) {
console.log(message, '(triggered by the following', event.type, 'event)');
$scope.logEvent = function(message, event, item) {
console.log(message + ' (triggered by the following ' + event.type + ' event)%s',
(item ? ' of "' + item.label + '"': ''));
console.log(event);
};

$scope.logListEvent = function(action, event, index, external, type) {
$scope.logListEvent = function(action, event, index, external, type, item) {
var message = external ? 'External ' : '';
message += type + ' element is ' + action + ' position ' + index;
$scope.logEvent(message, event);
$scope.logEvent(message, event, item);
};

$scope.model = [];

var Item = function Item(label) {
this.label = label;
this.moveCount = 0;
this.copyCount = 0;
};

Item.prototype.moved = function moved() {
this.moveCount++;
};

Item.prototype.copied = function copied() {
this.copyCount++;
};

// Initialize model
var id = 10;
for (var i = 0; i < 3; ++i) {
$scope.model.push([]);
for (var j = 0; j < 2; ++j) {
$scope.model[i].push([]);
for (var k = 0; k < 7; ++k) {
$scope.model[i][j].push({label: 'Item ' + id++});
$scope.model[i][j].push(new Item('Item ' + id++));
}
}
}

$scope.$watch('model', function(model) {
$scope.modelAsJson = angular.toJson(model, true);
}, true);
// this will replace the $watch, which will no longer work, because the
// item references do not change.
$scope.toJson = function toJson(model) {
return angular.toJson(model, true);
};

});