I was following a great tutorial about implementing Drag & Drop in Angular which I found originally in this StackOverflow answer which links to the writer's blog and GitHub code.
When I implemented it and tested it in my project, I got the error:
[$parse:isecdom] Referencing DOM nodes in Angular expressions is disallowed! Expression: dropped(dragEl, dropEl)
According to Angular Docs, the problem is that I am referencing a function that takes DOM objects as parameters inside a DOM attributes - it's happening in this line:
<div x-lvl-drop-target='true' x-on-drop='dropped(dragEl, dropEl)'>drop zone</div>
As you can see, the on-drop
attribute is passing this dropped
function (which is defined in my controller, to the directive lvlDropTarget
(posted below) which calls it to take action of the drag-drop action the user makes.
I like this design, as it makes the directive reusable for a number of different drag-drop possiblities in the same app. Essentially, I just need to define a different function in my controller and pass it to the directive through the on-drop
attribute.
However, this unfortunately seems to have been shot down by Angular. Does anyone have any other as to how to achieve this same design and functionality but in a way Angular is more OK with?
Here's the lvlDropTarget
directive
module.directive('lvlDropTarget', ['$rootScope', 'uuid',
function ($rootScope, uuid) {
return {
restrict: 'A',
scope: {
onDrop: '&'
},
link: function (scope, el, attrs, controller) {
var id = angular.element(el).attr("id");
if (!id) {
id = uuid.new()
angular.element(el).attr("id", id);
}
el.bind("dragover", function (e) {
if (e.preventDefault) {
e.preventDefault(); // Necessary. Allows us to drop.
}
if (e.stopPropagation) {
e.stopPropagation();
}
e.dataTransfer.dropEffect = 'move';
return false;
});
el.bind("dragenter", function (e) {
angular.element(e.target).addClass('lvl-over');
});
el.bind("dragleave", function (e) {
angular.element(e.target).removeClass('lvl-over'); // this / e.target is previous target element.
});
el.bind("drop", function (e) {
if (e.preventDefault) {
e.preventDefault(); // Necessary. Allows us to drop.
}
if (e.stopPropogation) {
e.stopPropogation(); // Necessary. Allows us to drop.
}
var data = e.dataTransfer.getData("text");
var dest = document.getElementById(id);
var src = document.getElementById(data);
scope.onDrop({
dragEl: src,
dropEl: dest
});
});
$rootScope.$on("LVL-DRAG-START", function () {
var el = document.getElementById(id);
angular.element(el).addClass("lvl-target");
});
$rootScope.$on("LVL-DRAG-END", function () {
var el = document.getElementById(id);
angular.element(el).removeClass("lvl-target");
angular.element(el).removeClass("lvl-over");
});
}
}
}
]);
An issue was logged on github for this on Oct 14:
The following solution has been proposed:
So, if this change is accepted, the directive will be modified to return the dragged and dropped element ids. Then it is up to your callback to do
document.getElementById
:Having said that, Angular added
isecdom
validation to their expression sandboxing with good reason:Rather than attempt to circumvent this protection, I prefer your choice to use
ngDraggable
- which encourages use of declarative views and doesn't require DOM manipulation in your controller. Good call.