Drag event not firing with angular 2 - javascript

I have a canvas and I want users to be able to drag graphic elements around it. Thus, I don't want the canvas itself to drag, but I want to handle dragstart, drag, and drop events when the mouse does those things.
I'm using Angular 2, so I have:
<!-- editor.component.html -->
<div #rendererContainer
draggable="true"
(dragstart)="onDragStart($event)"
(drag)="onDrag($event)"
(dragover)="onDrag($event)"
(drop)="onDragEnd($event)"
(dragend)="onDragEnd($event)">
</div>
Then in editor.component.ts:
onDragStart(event) {
console.log(`starting`);
event.preventDefault();
}
onDrag(event) {
console.log('dragging');
event.preventDefault();
}
onDragEnd(event) {
console.log('drag end');
event.preventDefault();
}
When I try dragging something, I get starting printed in the console, but that's it. How do I get the other drag events to fire? Do I have to roll my own dragging from mousedown/move/up events?
Stand-alone example on stackblitz. I want "dragging" the div around to fire dragstart/drag/drop events, but it only fires the starting one.

You do not have to use event.preventDefault(). This is only necessary if you want to use pure JS.
Try this Stackblitz: https://stackblitz.com/edit/angular-x7umar
Also refer to the MDN implementation guide to choose the right events for your purposes: https://developer.mozilla.org/en-US/docs/Web/Events/drag
Further steps
If you want to modify the dragged element, simply implement some CSS adjustments inside your dragstart and dragend handler of the event.target:
onDragStart(event: DragEvent) {
console.log(`starting`, event);
// Hide dragged element
event.target.style.opacity = 0;
}
onDragEnd(event: DragEvent) {
console.log('drag end', event);
// Show dragged element again
event.target.style.opacity = 1;
}
With event.target you have the complete manipulable DOM element of the dragged element.

try with
(dragover)="onDragOver($event)"
(dragleave)="onDragLeave($event)"
Component
onDragOver(event) {
// do something
event.preventDefault();
}
onDragLeave(event) {
// do something
event.preventDefault();
}

Related

HTML5 Dragend event didn't fire in firefox

I am making some changes on drag start and want to revert them if drop fails. I wrote this logic in a function triggered by dragend. This works perfect in Chrome but in firefox 'Dragend' event is not fired.
Can anyone tell me something about this behaviour? I am using firefox 22.0 on ubantu.
Code is as below
$(".view-controller").on("dragover", that.dragOverMain);
$(".view-controller").on("dragenter", that.dragEnterMain);
$(".view-controller").on("dragexit dragleave", that.dragExitMain);
$(".view-controller").on("dragend", that.dragEndMain);
$(".view-controller").on("drop", that.dropMain);
$(".view-controller").children().on("dragstart", function(e) {
that.dragStartChild(e);
});
$(".view-controller").children().on("dragend", function(e) {
that.dragEndMain(e);
});
dragStartChild: function(e) { console.log('dragStartChild'); },
dragEndMain: function(e) { console.log('dragEndMain'); e.preventDefault(); },
dropMain: function(e) { console.log('dropMain'); e.preventDefault(); },
dragExitMain: function(e) { console.log('dragExitMain'); e.preventDefault(); },
dragEnterMain: function(e) { console.log('dragEnterMain'); e.preventDefault(); },
dragOverMain: function(e) { console.log('dragOverMain'); e.preventDefault(); },
Firefox requires drag data to be set (event.dataTransfer.setData(...)) in the dragstart event. Without setting this data the dragstart event will fire, but the dragend event won't.
https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#dragstart:
To make another HTML element draggable, three things must be done:
Set the draggable attribute to true on the element that you wish to make draggable.
Add a listener for the dragstart event
Set the drag data within the listener defined above.
Example:
<div draggable="true" ondragstart="event.dataTransfer.setData('text/plain', 'This text may be dragged')">
This text <strong>may</strong> be dragged.
</div>
Try this instead.
<div ondragend="dragEndMain(event)" class="viewcontroller">
<!-- some html -->
</div>
Basically bind the javascript event in html itself.
Worth adding here that Firefox has a bug that causes dragend to not fire if you're moving or deleting DOM elements as a part of your Drag and Drop functionality.
https://bugzilla.mozilla.org/show_bug.cgi?id=460801
Moving the DOM manipulations into my method called on dragend solved this problem for me.

jQuery UI : Before start draggable

How to implement a before start event to have a change to change the position and place in the DOM of the draggable element before jQueryUI start to drag?
You could extent prototype method:
SEE DEMO
var oldMouseStart = $.ui.draggable.prototype._mouseStart;
$.ui.draggable.prototype._mouseStart = function (event, overrideHandle, noActivation) {
this._trigger("beforeStart", event, this._uiHash());
oldMouseStart.apply(this, [event, overrideHandle, noActivation]);
};
$("#draggable").draggable({
beforeStart: function () {
console.log('beforeStart::');
}
});
I found that a method passed as the "helper" option to the sortable will get called before "start" and is passed (as the second argument) the item that has been clicked. You could do what you need to do in this method and then just return the element itself (the default "original" helper behavior). I'm using this to set the height of my container so that it doesn't collapse and trigger scrolling of the browser window. Here's what that looks like:
$(list).sortable({
helper: function(event, element) {
// it's too late if we wait until start to do this
$(this).css('height', this.$().height());
return element;
}
})
I didn't dare to access jQuery UI private variables, so I implemented it like this:
// The mouse interaction sequence for dragging starts with a mousedown action.
element.on('mousedown', function() {
// Mouseup cancels dragging. This is a boring click.
element.one('mouseup', function() {
element.off('mousemove.mynamespace');
});
// Moving the mouse while holding mousedown is dragging.
// This is also what sets off jQuery UI draggable,
// but we registered our event listeners first.
element.one('mousemove.mynamespace', function() {
// !! Your beforeStart code here.
});
});
// Initialize jQuery UI draggable AFTER our own event listeners.
element.draggable();
For that I used mouseup and mousedown:
var timeout;
$('.draggable').mousedown(function() {
$('#dragContainer').append($(this));
$(this).css({
top: 0,
left: 0
});
});
$('.draggable').draggable();
I also used mouseup to reset the old parent and position if the mousedown was actually a click and not a drag.
It would be nice to have a beforeStart event which work with the distance option but I didn't find it...

Firefox firing dragleave when dragging over text

I'm attempting to track a dragenter/leave for the entire screen, which is so far working fine in Chrome/Safari, courtesy of the draghover plugin from https://stackoverflow.com/a/10310815/698289 as in:
$.fn.draghover = function(options) {
return this.each(function() {
var collection = $(),
self = $(this);
self.on('dragenter', function(e) {
if (collection.size() === 0) {
self.trigger('draghoverstart');
}
collection = collection.add(e.target);
});
self.on('dragleave drop', function(e) {
// timeout is needed because Firefox 3.6 fires the dragleave event on
// the previous element before firing dragenter on the next one
setTimeout( function() {
collection = collection.not(e.target);
if (collection.size() === 0) {
self.trigger('draghoverend');
}
}, 1);
});
});
};
function setText(text) {
$('p.target').text(text);
}
$(document).ready(function() {
$(window).draghover().on({
'draghoverstart': function() {
setText('enter');
},
'draghoverend': function() {
setText('leave');
}
});
});
However Firefox is still giving me problems when I drag over text items, here's a fiddle to demonstrate: http://jsfiddle.net/tusRy/6/
Is this a Firefox bug or can this be tamed with JS? Or is there a more robust method for performing all of this?
Thanks!
UPDATE: Updated fiddle to http://jsfiddle.net/tusRy/6/ to reduce clutter a bit. To explain the expected behavior of the fiddle:
Drag a file into the window and p.target should be "ENTER" colored yellow.
Drag a file out of the window and p.target should be "LEAVE" colored red.
Drop a file in the window and p.target should be "LEAVE" colored red.
In firefox, the LEAVE event is triggered when you drag the file over text.
As of version 22.0 Firefox is still doing this. When you drag over a text node it fires two kinds of dragenter and dragleave events: one where the event target and relatedTarget are BOTH the parent element of the text node, and another where the target is the parent element and the relatedTarget is the actual text node (not even a proper DOM element).
The workaround is just to check for those two kinds of events in your dragenter and dragleave handlers and ignore them:
try {
if(event.relatedTarget.nodeType == 3) return;
} catch(err) {}
if(event.target === event.relatedTarget) return;
I use a try/catch block to check the nodeType because occasionally events fire (inexplicably) from outside the document (eg. in other iframes) and trying to access their nodeType throws a permissions error.
Here's the implementation:
http://jsfiddle.net/9A7te/
1) Your dropzone should have only one child element, which might have everything else you need. Something like
<div id="#dropzone">
<div><!--Your contents here--></div>
</div>
2) Use this CSS:
#dropzone * { pointer-events: none; }
You might need to include the :before and :after since the * don't apply to them.
This should be enough to let the drop work in Firefox and Chrome. In your example, it should be enough to add:
body * { pointer-events: none; }
At the end of the CSS. I've done it here:
http://jsfiddle.net/djsbellini/eKttq/
Other examples:
http://jsfiddle.net/djsbellini/6yZV6/1/
http://jsfiddle.net/djsbellini/yR8t8/
I came up with kind of a solution, yet to test on other browsers other than Chrome and FF but working so far. This is how the setTimeout looks now:
setTimeout( function() {
var isChild = false;
// in order to get permission errors, use the try-catch
// to check if the relatedTarget is a child of the body
try {
isChild = $('body').find(e.relatedTarget).length ? true : isChild;
}
catch(err){} // do nothing
collection = collection.not(e.target);
if (collection.size() === 0 && !isChild) {
self.trigger('draghoverend');
}
}, 1);
The entire code here - http://jsfiddle.net/tusRy/13/.
The idea is to check if the related tag is a child of the body, in which case we are still in the Browsers and the draghoverend event should be not triggered. As this can throw errors when moving out of the windows, I used a try method to avoid it.
Well, perhaps somebody with more skills on JS could improve this :)
I found the answer in a non-selected answer to this SO question asking about dragleave firing on child elements. I have a <div> that has many children elements. An semi-opaque overlay <span> becomes visible over the <div> whenever there's a dragenter on the page. As you found, 'dragover' isn't like mouseover. It triggers dragleave whenever you hover over a child element.
The solution? Dragout It makes dragover work more like mouseover. Very short.

What's the best way of constantly resizing elements with the mouse?

What's the best way of constantly resizing elements using clicking and holding a resize image in the bottom-right corner of the element? Is there a specific empty element that has resizing built in or a style to use that would be better than using a while loop in JavaScript?
Here you go man:
http://jsfiddle.net/d9hsG/
function c(a){console.log(a)}
function coords(el){
var curleft, curtop;
curleft=curtop=0;
do{
curleft+=el.offsetLeft;
curtop+=el.offsetTop;
} while(el=el.offsetParent);
return [curleft,curtop];
}
Resizer = {
attach: function(el,minh,minw){
var rs=el.resizer=el.getElementsByClassName('drag')[0];
rs.resizeParent=el;
if(minh==undefined){
el.minh=rs.offsetHeight*2;
}
if(minw==undefined){
el.minw=rs.offsetWidth*2;
}
rs.onmousedown = Resizer.begin;
},
begin: function(e){
var el=Resizer.el=this.resizeParent;
var e=e||window.event;
this.lastx=e.clientX;
this.lasty=e.clientY;
document.onmousemove=Resizer.resize;
document.onmouseup=Resizer.end;
return false;
},
resize: function(e){
var e = e || window.event;
var x,y,mx,my,el,rs,neww,newh;
el=Resizer.el;
rs=el.resizer;
mx=e.clientX;
my=e.clientY;
neww=(el.clientWidth-(rs.lastx-mx));
newh=(el.clientHeight-(rs.lasty-my));
if(neww>=el.minw){
el.style.width=neww+'px';
rs.lastx=mx;
}
else{
rs.lastx-=parseInt(el.style.width)-el.minw;
el.style.width=el.minw+'px';
}
if(newh>=el.minh){
el.style.height=newh+'px';
rs.lasty=my;
}
else{
rs.lasty-=parseInt(el.style.height)-el.minh;
el.style.height=el.minh+'px';
}
return false;
},
end: function(){
document.onmouseup=null;
document.onmousemove=null;
}
};
window.onload=function(){
Resizer.attach(document.getElementsByClassName('resize')[0]);
}
Your HTML needs to look like:
<div class="resize"><
div class="drag"></div>
</div>
Neither one needs to be a div, but the resizeable one's class needs to be "resize" and the draggable element's class needs to be "drag".
Attach it with:
Resizer.attach(element);
...where element is the one to be resized.
Works on multiple elements, as shown in the jsfiddle. You can also pass in a minimum height and minimum width. If you don't, it automatically makes them twice the height of the draggable element.
It currently does have a problem when you're scrolled all the way down. I'm not sure how to counter it, but I'll work on it more later.
The general approach goes something like this:
When onmousedown fires on the resize target, start tracking onmousemove
When onmousemove fires, resize the element
When onmouseup fires, clear the onmousemove handler
So basically you just respond to events, there are no while loops or anything involved.
It can be somewhat tricky to accomplish so that it works nicely, so I would suggest seeing if there's a JS library you could use. A pretty simple way to get this behavior would be to use jQuery UI's resizable component

Internet Explorer and <select> tag problem

I am having the following problem under Internet Explorer 7/8:
I have a popup that gets activated when user mouseover a link. The popup is a simple <div> that contains some data. Inside this <div> tag there is a <select> tag with some <option>s. I have attached mouseover/mouseout events to the <div>, so that this popup will stay open while cursor is over it. The problem comes when you click on the <select> and then move the cursor over any of the <option>s. This triggers the mouseout event of the <div> tag and respectively closes it.
How can I prevent the closing of the popup in IE ?
You should be able to detect if the situation is the one you want just with the values off the event. It is a little convoluted but it seems to work.
In the event handler of your outer div, do something like this:
<div onmouseover="if (isReal()) { toggle(); }"
onmouseout="if (isReal()) { toggle(); }">
</div>
Then implement the isReal method:
function isReal() {
var evt = window.event;
if (!evt) {
return true;
}
var el;
if (evt.type === "mouseout") {
el = evt.toElement;
} else if (evt.type === "mouseover") {
el = evt.fromElement;
}
if (!el) {
return false;
}
while (el) {
if (el === evt.srcElement) {
return false;
}
el = el.parentNode;
}
return true;
}
Basically the isReal method just detects if the event was coming from within the div. If so, then it returns false which avoids calling the hide toggle.
My suggestion would be to set another flag while the select box has focus. Do not close the div while the flag is set.
How about re-showing the div when the mouse is over the <options>s through mouseover events of <options>s.
Edit: execution order of mouseover of option and mouseout of div might cause problems though.
In the mouseout event for the div add a timeout to the div element that will hide the div in 200 milliseconds or so.
Then in the mouseover event for the div/select and the click event of the select clear the timeout stored in the div element.
This gives a very slight delay before hiding the div that allows the mouseover or click events to clear the timeout before it is executed. It's not pretty but it should work.
instead of using mouseout as the event to close the div, use mouseleave, then the event will only be triggered when the pointer leaves the boundary of the div, not when it moves onto other elements within it
you could try adding another mouseover event specifically for the options list.
Well, the reason for this behavior is because the mouseover/out events bubble, which effectively means that whenever you mouseover any of the elements inside the popup, the popup receives the event also.
You can read more here about these events, and here about event bubbling.
You have 3 possible solutions here:
Change the events to onmouseenter/leave. You've mentioned that this didn't help, which just sounds plain odd, since these aren't supposed to bubble.
Check srcElement in relation to from/toElement in the event.
An improved version of McKAMEY's check would be:
function isReal() {
var evt = window.event;
if (!evt) {
return true;
}
var el;
if (evt.type === "mouseout") {
el = evt.toElement;
} else if (evt.type === "mouseover") {
el = evt.fromElement;
}
if (!el) {
return false;
}
// this will also return true if el == evt.srcElement
return evt.srcElement.contains(el);
}
Does the same thing, just shorter.
3 . Another option would be to create a transparent, invisible div just under your popup that covers the area that the select box drops down into. I'm assuming that it's dropping outside the actual area of the popup.
Hope this helps!
have you tried hover instead of mouseover/out effects?
$(".myDiv").hover(function(){
$(this).show();
}, function {
$(this).hide();
});
What about something like this:
<div id="trigger">
Hover over me!
</div>
<div class="container">
<select>
<option>Blah</option>
<option>Blah</option>
</select>
</div>
$("#trigger").mouseover(function(){
$('.container).show();
});
$(".container").mouseleave(function(){
$(this).hide();
});
The basic idea is that you show the container element when you hover over the trigger then when you leave the container you hide the container. You'd need to position the container so it clipped the trigger element, otherwise it would hide straight away.
Why have mouseover / mouseout on the <div>? Why not just show the <div> on the mouse over, then set <body onmouseover="hidedivs();"> I don't know if this would work, but if the <div> is on top of the body, then the <div> should stay visible.
Many people posting solutions/examples do not seem to realize one thing: onmouseout event on <div> fires before onmouseover event on <select>.
When <div> loses focus (onmouseout) do not close it immediately, but after say, 500 milliseconds. If during this time <select> gets focus (mouseover) do not close <div> at all (clearTimeout).
Also, try to play with event propagation/bubling.
Given that selects in IE are a pain, especially when it comes to the whole layering issue where a select appears above a div even though it shouldn't, can I point you in the direction of YUI's Menu button controls. They look really nice, are easy to implement and won't cause this issue
Here is a link: http://developer.yahoo.com/yui/examples/button/btn_example07.html
You should use event.stopPropagation() while in <select>, or cancelBubble() in <select> element itself.

Categories

Resources