Drop detection in easeljs - drag-and-drop

I'm new to EaselJS. I wonder how I can detect a drop of one container on another container in EaselJS.
So I want to get the dropped container in an eventlistener of the drop target container.
Any examples on this?
I could not find this in the drag drop examples of EaselJS.
Thanks

You can also use getObjectsUnderPoint. Here is a quick sample I put together.
http://jsfiddle.net/lannymcnie/6rh7P/1/
var targets = stage.getObjectsUnderPoint(stage.mouseX, stage.mouseY);
This is from another post asking a similar question. I also posted more info about it.
EaselJS: connect 2 containers/shapes using a line

In the pressmove or stagemouseup event, you can verify if the mouse position (stage.mouseX and stage.mouseY) if over the parent container. To do the verification, you can use the hitTest.
Notice that, hitTest will only if your parent container has at least one mouse event listener, which I think is a bug on EaselJS 0.7.1

I made this class on coffeescript to solve that problem:
class DragContainer
DragContainer.prototype = new createjs.Container()
DragContainer::Container_initialize = DragContainer::initialize
constructor: (opts) ->
#initialize opts
DragContainer::initialize = (opts) ->
#Container_initialize()
#droptargets = new Array()
#on 'mousedown', #handleMouseDown
handleMouseDown: (e) =>
#on 'pressup', (ev)=>
#removeAllEventListeners 'pressup'
if #droptargets and #droptargets.length > 0
#evaluateDrop e
evaluateDrop: (e) =>
target = null
dropped = false
for drop in #droptargets
pt = drop.globalToLocal stage.mouseX, stage.mouseY
if drop.hitTest pt.x, pt.y
target = drop
dropped = true
if dropped
#dispatchEvent {type: 'dropped', currentTarget: target}
else
#dispatchEvent {type: 'dropped', currentTarget: null}
The droptargets property is an array that keeps the objects you want to associate with the drop of your container.

Related

Dragula Copy and removeOnSpill

I'm trying to use the Dragula Drag & Drop library to Clone elements into a target container AND allow the user to remove cloned elements from the Target Container by drag & dropping them outside of the target container (spilling).
Using the examples provided I have this:
dragula([$('left-copy-1tomany'), $('right-copy-1tomany')], {
copy: function (el, source) {
return source === $('left-copy-1tomany');
},
accepts: function (el, target) {
return target !== $('left-copy-1tomany');
}
});
dragula([$('right-copy-1tomany')], { removeOnSpill: true });
Which does not work - it seems that 'removeOnSpill' simply doesn't work if the container accepts a copy.
Does anybody know what I am not doing, doing wrong or if there is a work-around?
TIA!
I came here after looking for a while for a solution to a similar issue using the ng2-dragula for angular2.
dragulaService.setOptions('wallet-bag', {
removeOnSpill: (el: Element, source: Element): boolean => {
return source.id === 'wallet';
},
copySortSource: false,
copy: (el: Element, source: Element): boolean => {
return source.id !== 'wallet';
},
accepts: (el: Element, target: Element, source: Element, sibling: Element): boolean => {
return !el.contains(target) && target.id === 'wallet';
}
});
I've got 4 divs that can all drag into one which has the id of wallet
They are all part of the wallet-bag
using this code, they can all copy into the wallet, not copy between each other, and you can remove them from the wallet using the spill but not from the others.
I'm posting my solution as it may also help someone.
Ok, so the general answer I came trough is that:
you can have 'removeOnSpill' working - even with 'copy' option set to true - , only if you set the 'copy' option applying ONLY when the 'source' container IS NOT the one you are trying to remove elements from.
In my case I had 3 containers from which I can drag in another one called 'to_drop_to'.
Those container have all id starting with 'drag'.
So I set:
var containers = [document.querySelector('#drag1'),
document.querySelector('#drag2'),
document.querySelector('#drag3'),
document.querySelector('#to_drop_to')];
dragula(containers, {
accepts: function (el, target, source, sibling) {
return $(target).attr('id')=="gadget_drop"; // elements can be dropped only in 'to_drop_to' container
},
copy: function(el,source){
return $(source).attr('id').match('drag'); //elements are copied only if they are not already copied ones. That enables the 'removeOnSpill' to work
},
removeOnSpill: true
}
and this worked for me.
Hope it helps.
From the dragula documentation
options.removeOnSpill
By default, spilling an element outside of any containers will move
the element back to the drop position previewed by the feedback
shadow. Setting removeOnSpill to true will ensure elements dropped
outside of any approved containers are removed from the DOM. Note that
remove events won't fire if copy is set to true.

Why doesn't marker.dragging.disable() work?

The following code receives an error on the lines for enabling and disabling the marker dragging ("Unable to get property 'disable' of undefined or null reference"). The markers show up on the map just fine and are draggable as the creation line indicates. Placing an alert in place of the enable line produces a proper object so I believe the marker is defined. Is there something I need to do to enable the IHandler interface? Or am I missing something else?
var marker = L.marker(L.latLng(lat,lon), {icon:myIcon, draggable:'true'})
.bindLabel(name, {noHide: true,direction: 'right'});
marker._myId = name;
if (mode === 0) {
marker.dragging.enable();
} else {
marker.dragging.disable();
}
I had a similar problem today (perhaps the same one) it was due to a bug in leaflet (see leaflet issue #2578) where changing the icon of a marker invalidates any drag handling set on that marker. This makes any calls to marker.dragging.disable() fail.
The fix hasn't made it into leaflets master at time of writing. A workaround is to change the icon after updating the draggable status if possible.
marker.dragging.disable();
marker.setIcon(marker_icon);
Use the following code to make an object draggable. Set elementToDrag to the object you wish to make draggable, which is in your case: "marker"
var draggable = new L.Draggable(elementToDrag);
draggable.enable();
To disable dragging, use the following code:
draggable.disable()
A class for making DOM elements draggable (including touch support).
Used internally for map and marker dragging. Only works for elements
that were positioned with DomUtil#setPosition
leaflet: Draggable
If you wish to only disable the drag option of a marker, then you can use the following code (where "marker" is the name of your marker object):
marker.dragging.disable();
marker.dragging.enable();
I haven't found an answer but my workaround was this:
var temp;
if (mode === 0) {
temp = true;
} else {
temp = false;
}
var marker = L.marker(L.latLng(lat,lon), {icon:myIcon, draggable:temp})
.bindLabel(name, {noHide: true,direction: 'right'});
marker._myId = name;
Fortunately I change my icon when it is draggable.

telerik kendo treeview - prevent dragging outside of parent

I'm trying to prevent the dragging and dropping of nodes outside of the parent node ("LLCA") with no luck.
Any suggestions?
Image of Treeview
I ended up getting it to work using your code below:
function onDrop(e) {
var dst = e.destinationNode;
var first = $('.k-item:first');
var pos = e.dropPosition;
if (dst && dst.uid === first.uid && pos !== "over") {
e.setValid(false);
}
}
Lets define the treeview:
var tree = $("#tree").kendoTreeView({
dataSource :content,
dragAndDrop:true
}).data("kendoTreeView");
What I'm going to do is add a drop callback where I will control that:
We are not dropping outside the tree
We are not dropping before or after the first node of the tree
The definition of the tree would be:
var tree = $("#tree").kendoTreeView({
dataSource :content,
dragAndDrop:true,
drop :function (ev) {
var dst = tree.dataItem(ev.destinationNode);
var first = tree.dataItem(".k-item:first");
var pos = ev.dropPosition;
if (dst && dst.uid === first.uid && pos !== "over") {
console.log("invalid");
ev.setValid(false);
}
}
}).data("kendoTreeView");
Check http://docs.kendoui.com/api/web/treeview#drop for information on drop event.
Because I cannot comment on an answer, I will write my own.
User Mithrilhall asked about MVC wrappers, also the top answer only prevents movement to the root node.
I will attempt to answer both Mithrilhall and provide an example where you can only move a child within the context of its parent. To put it another way, to only allow children of any parent to change their order within the parent.
Firstly, for MithrilHall, this is how you get to the events in MVC.
#(Html.Kendo().TreeView()
.Name("ourTreeView")
.Events(e => e.Drop("treeViewDrop"))
There are other events in treeview, you can take a gander for yourself. The argument is the name of a javascript function. Here is an example javascript function for this MVC wrapper to prevent children from moving outside of their parent, but allowing them to still move within the parent.
<script type="text/javascript">
function treeViewDrop(dropEvent) {
var treeView = $("#ourTreeView").data("kendoTreeView");
var destination = treeView.dataItem(dropEvent.destinationNode);
var source = treeView.dataItem(dropEvent.sourceNode);
if (!(destination && destination.parentID == source.parentID)) {
dropEvent.setValid(false);
}
}
</script>
I had a parentID field modeled in my datasource. You could accomplish this in many ways. The dataItem method returns a kendo treeview item, so it has all of your modeled fields in it.
Also understand, this solution does not change the widget to show an X when you are moving to a place you cannot drop to. This is another problem with another solution.
I hope this helps, good luck!

jsTree Node Expand/Collapse

I ran into the excellent jstree jQuery UI plug in this morning. In a word - great! It is easy to use, easy to style & does what it says on the box. The one thing I have not yet been able to figure out is this - in my app I want to ensure that only one node is expanded at any given time. i.e. when the user clicks on the + button and expands a node, any previously expanded node should silently be collapsed. I need to do this in part to prevent the container div for a rather lengthy tree view from creating an ugly scrollbar on overflow and also to avoid "choice overload" for the user.
I imagine that there is some way of doing this but the good but rather terse jstree documentation has not helped me to identify the right way to do this. I would much appreciate any help.
jsTree is great but its documentation is rather dense. I eventually figured it out so here is the solution for anyone running into this thread.
Firstly, you need to bind the open_node event to the tree in question. Something along the lines of
$("tree").jstree({"themes":objTheme,"plugins":arrPlugins,"core":objCore}).
bind("open_node.jstree",function(event,data){closeOld(data)});
i.e. you configure the treeview instance and then bind the open_node event. Here I am calling the closeOld function to do the job I require - close any other node that might be open. The function goes like so
function closeOld(data)
{
var nn = data.rslt.obj;
var thisLvl = nn;
var levels = new Array();
var iex = 0;
while (-1 != thisLvl)
{
levels.push(thisLvl);
thisLvl = data.inst._get_parent(thisLvl);
iex++;
}
if (0 < ignoreExp)
{
ignoreExp--;
return;
}
$("#divElements").jstree("close_all");
ignoreExp = iex;
var len = levels.length - 1;
for (var i=len;i >=0;i--) $('#divElements').jstree('open_node',levels[i]);
}
This will correctly handle the folding of all other nodes irrespective of the nesting level of the node that has just been expanded.
A brief explanation of the steps involved
First we step back up the treeview until we reach a top level node (-1 in jstree speak) making sure that we record every ancestor node encountered in the process in the array levels
Next we collapse all the nodes in the treeview
We are now going to re-expand all of the nodees in the levels array. Whilst doing so we do not want this code to execute again. To stop that from happening we set the global ignoreEx variable to the number of nodes in levels
Finally, we step through the nodes in levels and expand each one of them
The above answer will construct tree again and again.
The below code will open the node and collapse which are already opened and it does not construct tree again.
.bind("open_node.jstree",function(event,data){
closeOld(data);
});
and closeOld function contains:
function closeOld(data)
{
if($.inArray(data.node.id, myArray)==-1){
myArray.push(data.node.id);
if(myArray.length!=1){
var arr =data.node.id+","+data.node.parents;
var res = arr.split(",");
var parentArray = new Array();
var len = myArray.length-1;
for (i = 0; i < res.length; i++) {
parentArray.push(res[i]);
}
for (var i=len;i >=0;i--){
var index = $.inArray(myArray[i], parentArray);
if(index==-1){
if(data.node.id!=myArray[i]){
$('#jstree').jstree('close_node',myArray[i]);
delete myArray[i];
}
}
}
}
}
Yet another example for jstree 3.3.2.
It uses underscore lib, feel free to adapt solution to jquery or vanillla js.
$(function () {
var tree = $('#tree');
tree.on('before_open.jstree', function (e, data) {
var remained_ids = _.union(data.node.id, data.node.parents);
var $tree = $(this);
_.each(
$tree
.jstree()
.get_json($tree, {flat: true}),
function (n) {
if (
n.state.opened &&
_.indexOf(remained_ids, n.id) == -1
) {
grid.jstree('close_node', n.id);
}
}
);
});
tree.jstree();
});
I achieved that by just using the event "before_open" and close all nodes, my tree had just one level tho, not sure if thats what you need.
$('#dtree').on('before_open.jstree', function(e, data){
$("#dtree").jstree("close_all");
});

Can a dojo.dnd.Source object contains another dojo.dnd.Source object as one of the child nodes?

I have looked at the this link for a tutorial on dojo drag and drop feature. But one thing I have noticed is that in all cases of the examples, the items to be dragged around are always a simple item, just a string object...
I need to create something like an item group where you can drag an item into the item group to append into the group and to drag the item group around as a whole.
Hence my question, is it possible to drag and drop a dojo.dnd.Source item into another dojo.dnd.Source item?
Short answer: no. Many people tried to patch it, but found more and more non-working edge cases, so those patches never made the Dojo proper.
If you truly need to show and manipulate a hierarchical data, consider a Tree Dijit.
The problem is that when you start dragging and you drag over a Source of a child container, everything gets messed up. (Not exactly sure how). What you can do, is hide those child sources so that their overSource events never trigger:
1) Overrode the checkAcceptance function in Source.js. Just added the following for the if(!flag) return false;:
if(!flag){
/**
* Main Source
* - Group 1
* -- Child 1
* -- Child 2
* - Group 2
*/
var node = dojo.byId(this.node);
// If the node being moved is the source, skip, but don't hide from view.
if('#'+dojo.attr(source.node, 'id') != '#'+dojo.attr(node, 'id')){
// If the node being moved is an immediate child of the container, you can move it.
if(dojo.query('#'+dojo.attr(source.node, 'id') + '>#'+dojo.attr(node, 'id')).length) {
return true;
}
// If this source is not a parent of the element, hide it.
if(dojo.query('#'+dojo.attr(node, 'id') + ' #'+dojo.attr(source.node, 'id')).length == 0)
dojo.addClass(node, 'hiddenSource');
}
return false;
}
2) You need to also add the following as the first line under if(this.isDragging) in onMouseMove (important)
var node = dojo.byId(this.node);
// If this is immeditae child, drop it.
if(dojo.query('#'+dojo.attr(m.source.node, 'id') + '>#'+dojo.attr(node, 'id')).length){
m.canDrop(true);
return;
}
3) Extended onDndDrop to remove the added class to re display the hidden elements.
onDndDrop: function(source, nodes, copy, target)
{
this.inherited(arguments);
dojo.forEach(dojo.query('.hiddenSource'),
function(el){dojo.removeClass(el, 'hiddenSource');}
);
}
4) Extend onDndCancel to do the above
onDndCanel: function()
{
this.inherited(arguments);
dojo.forEach(dojo.query('.hiddenSource'),
function(el){dojo.removeClass(el, 'hiddenSource');}
);
}
This isn't the best solution since it hides the elements that can't be used with the current element that you are positioning, but it worked for me.