I have simple Javascript code, similar to this one:
var mouseIsDown = false;
...
function canvasMouseDown(e) {
...
mouseIsDown = true;
}
function canvasMouseUp(e) {
mouseIsDown = false;
}
function canvasMouseMove(e) {
if (mouseIsDown) {
...
}
}
with implemention my own user interface for tranformations (translations, scalings and rotations) with canvas.
Such implementation within canvasMouseMove() function check mouseIsDown variable. All works fine if user does not release mouse button when the cursor/pointer is outside of the canvas element. If that happen, the variable mouseIsDown stays true and is not turned off by canvasMouseUp function.
What is easy fix or solution in pure JavaScript (no jQuery) for this issue?
If you want to catch the mouseup event somewhere else in the document, you might add an event handler for this to the documentelement. Note that this won't react on mouseup events outside the viewport, so you might want to fire also when the mouse enters the viewport again without a pressed button.
If you want to catch the mouse leaving your canvas element, it gets a bit more complicated. While IE knows a mouseleave event, the standard DOM has a mouseout event that also fires when a descendant of your element is left (although canvas usually has no child elements). Read more on that at quirksmode.org.
I have created a fiddle to demonstrate the behaviour (works only with W3 DOM). You might try to change documentelement to body. In Opera, the mouseup listener on <html> event detects mouseup events outside the document when the "drag" began inside it - I do not know whether that is standard behaviour.
window.addEventListener('mouseup', function(event){
// do logic here
})
It handle releasing mouse even outside of browser
document.onmouseup will be thrown even outside the viewport, that's a nice-to-know you only get by binding to the document object. test here: http://jsfiddle.net/G5Xr2/
$(document).mouseup(function(e){
alert("UP" + e.pageX);
});
It will even receive the mouse position!
Add a higher-level event handler (perhaps to the body) that catches the mouseUp event, and sets mouseIsDown to false.
Here's a demo: http://jsfiddle.net/ZFefV/
In JavaScript, it would be the following:
myElement.addEventListener('mousedown', () => {
const handleMouseUp = (event) => {
// check whether outside the element with event.target and myElement
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mouseup', handleMouseUp);
})
I created a variable that stored the id string of the target element upon mousedown. On a mouseup event, I check the string to see if it is null. If it isn't, I use the string to get the element and do whatever I want in the code block and then reset the variable to null.
IN other words, steps are:
1.) make a variable, set it to null. 2.) on "mousedown", save the id or class string to the variable. 3.) on "mouseup", check the variable (for safety). If it's not null. then use the string to grab the element and use it to do something. 4.) Then reset the variable to null.
var thisTarget = null
// when I know they click in the right place, save the target.
// you may not need parent(). I was using a UI slider and needed it.
$(".section").mousedown( function(e) {
thisTarget = $(e.target.parent().attr('id');
console.log(thisTarget);
});
// anywhere on the page...
$(document).mouseup( function(e) {
// because I'll make it null after every mouseup event.
if (thisTarget !== null) {
console.log( $("#" + thisTarget) ); // returns element
// do something with it
thisTarget = null;
}
});
Related
I have a program that simulates a cloth (made by spring and vertices) in WebGL. What the program must do is to permit the user to click on a point in the cloth, dragging it following the mouse and if the user clicks again, release it. I need to check which click event is the one that starts the dragging and which one terminates it, then I used a global variable "clicked", set it to false at the beginning and swapped everytime the event click is triggered. Now for the dragging I need periodically to take the coordinates of the mouse, then I though using a "mousemove" event was the best solution: if the "clicked" is set to true, this means that we are in the "dragging phase", then the mousemove event must be called and continue to work until the next click event. The problem is that I don't know how to use the mousemove in connection to the click event: if I put the mousemove event outside the click event, we never enter in that, and if I put the mousemove event inside the click event, also if the "clicked" variable change from true to false, the mousemove event is always called. The skeleton of the code is the following one:
var clicked = false;
function exec() {
let web_gl = document.getElementById("webgl-canvas").getContext('webgl2');
web_gl.canvas.addEventListener('click', (e) => {
clicked = !clicked;
console.log(clicked);
if (clicked == true) {
web_gl.canvas.addEventListener('mousemove', (e) => {
//do something for dragging
});
};
});
}
function main() {
exec();
};
Here also if the console prints both false and true in the correct way, we enter always in the if condition, it seems the condition sees "clicked" always true.
The other option was this one:
var clicked = false;
function exec() {
let web_gl = document.getElementById("webgl-canvas").getContext('webgl2');
web_gl.canvas.addEventListener('click', (e) => {
clicked = !clicked;
console.log(clicked);
});
if (clicked == true) {
web_gl.canvas.addEventListener('click', (e) => {
\\do something for dragging
});
}
}
function main() {
exec();
};
In this case we never enter in the event, and I don't understand why.
Does someone have an hint about this problem, or a better idea to handle what I need to implement?
Adressing your second snippet: your code is executed asynchronously, the event handlers are not executed until the event is emitted, hence clicked will still be in its initial state (false) when the condition to add the mousemove handler is evaluated, thus is will not be added and hence never be executed.
What you want to do is to add both event handlers and check within the mousemove event handler if clicked is true.
Btw. the logic you're about to implement means that a user has to click(mouse down and up) to activate the dragging and click again to disengage, rather than what's common UI practice: click(mousedown) and hold to drag things.
I found the solution. I used the click event in the main() function that is called only once, set a flag such that it is negated when a click is detected (passing from false to true and vice-versa). Inside the click event, if the flag is true, this is the first click, I add the mousemove event, picking the coordinates.
I have a special scenario where I need to capture key events on a higher level, and redirect them to a lower level. I'm trying to write code like this, but actually it seems like jQuery is dumbly using the exact same event object, so target is not changed, so Maximum callstack size exceeded...
$("#outer").on("keydown", function(evt) {
if (evt.target !== $("#inner")[0] && !$.contains($("#inner")[0], evt.target)) {
$("#inner").trigger(evt);
}
});
With a sample markup
<div id="#outer">
<div id="#inner">
</div>
</div>
What is the proper way to redirect an event to another element? I could of course manually "hack" the target, but is it safe?
I'm working on a plugin which dynamically shows a menu as a popup or as a modal based on screen size. If I have a modal, I need this key redirection. The consumer should not know about wether it's a popup or a modal. I'm using bootstrap's modal plugin, which captures key events because of a "focus-lock thing", so the only way I can make it transparent is that I redirect these events to my real menu.
You could attach the event handler onto the outer and specify a selector to use to match the event:
$('#outer').on('keydown', '#inner', event => {
/* this is executed when #inner is clicked */
});
If you really want to use trigger, you could put it in a setTimeout:
$('#outer').on('keydown', event => {
if (yourCondition) {
setTimeout(() => $('#inner').trigger(event), 0);
}
});
The event will bubble up and you're right, it's the same event. This isn't a jQuery thing but actually just how javascript works. You may want to set a listener on the child element, and use preventDefault();
$("#outer").on("keydown", doSomeThing);
$("#inner").on("keydown", doSomeThingElse);
function doSomeThing(e) {
}
function doSomeThingElse(e) {
e.preventDefault();
}
this will allow you to separate your listeners into distinct functions.
Thank you all for the answers and comments. A few of you suggested preventing propagation of the event at the #inner div. I need to avoid this, I need a way where any external consumer will see this event just as it was triggered by the #inner element for real.
In the meantime I digged into jQuery source and found this line in trigger.
if ( !event.target ) {
event.target = elem;
}
So when jQuery initializes the event to trigger, it only assignes the element to the target if the target is not yet specified. sure they have some good reasons for this behavior, which I cannot see at this moment.
So the best thing I could come up with is a function like this.
function redirectEvent(currentTarget, evt, newTarget) {
if (currentTarget !== newTarget && !$.contains(currentTarget, newTarget)) {
evt = $.Event(evt.type, evt);
delete evt.target;
$(newTarget).trigger(evt);
}
}
As far as first tests go, I can't see any side-effect or drawback.
Can someone explain the difference between the "click" event and the "contentClick" event ?
stage.on("contentClick", function(e) {
console.log("stage contentClick");
})
stage.on("click", function(e) {
console.log("stage click");
})
//Both events get fired equally
I've already noticed that "contentClick" seems to work on the stage only :
rect.on("contentClick", function(e) {
//This never gets fired
console.log("rect contentClick");
})
... and "contentClick" doesn't play well with cancelBubble :
rect.on("click", function(e) {
console.log("rect click");
e.cancelBubble = true;
})
stage.on("contentClick", function(e) {
//This fires even though cancelBubble should prevent it
console.log("stage contentClick");
})
Apart from these differences, what exactly IS "contentClick" and what is it usually used for ?
Thanks !
Any contentEvent will fired on events on DOM element. First argument of callback is special Kinetic event object, you can access to native DOM event object via evt property (for v.5.1.0):
stage.on("contentClick", function(e) {
var nativeEvent = e.evt;
console.log("stage contentClick", e);
});
Other events (with no 'content' prefix) are fired on Kinetic Node events.
Look at demo: http://jsbin.com/pomemo/1/edit
Try to click on image. You will see two events in console contentClick (bubbled from canvas element) and click ("bubled" from Kinetic.Image).
Then try to click on empty space. You will see only one event contentClick and no click event (because you didn't click on any Kinetic.Node)
I noticed that when the event is triggered on a specific node, the targetNode value in the event is set to that node.... when the event is triggered only by clicking on the stage, then targetNode is NOT set on the event.
So, I set my handler to test for existence of targetNode:
UIUtils_stageClick = function(event){
// unselect anything, only if we did not click on a card.... clicking on the table background only
if (!event.targetNode) {
voidFunctions_clearSelection();
}
// hide any error message flag or card label tag
UIUtils_hideErrorToolTip();
UIUtils_hideCardToolTip();
window.game.stage.draw();
}
I am quite new to jquery and looking for event that handles mouse down event. Over now I have found and use something like:
$("#canva").live('mousedown mousemove mouseup', function(e){ ....
but it fires function(e) even when mouse is over canvas element (both when the left button is clicked or not).
I am looking forward to event that will be fired in every change of coordinates above #canva element and when my mouses button is clicked. Releasing left mouse button should cause end of this event.
One way to do this would be to create a global variable and to modify its value when the user clicks over the element as such:
var mouseIsDown = false;
$('#mycanvas').on('mousedown', function () {mouseIsDown = true});
$('#mycanvas').on('mouseup', function () {mouseIsDown = false});
Now all you have to do is to bind 'mousemove' to the function you want to fire and simply appending a conditional to the body of the function that checks the state of the left click and escapes if necessary.
function fireThis(e) {
// if the user doesn't have the mouse down, then do nothing
if (mouseIsDown === false)
return;
// if the user does have the mouse down, do the following
// enter code here
// ....
// ....
}
// bind mousemove to function
$('#mycanvas').on('mousemove', fireThis);
Now, if the user were to click inside the element and then drag the mouse outside the element before releasing, you'll notice that 'mouseup' will never get triggered and the variable mouseIsDown will never be changed back to false. In order to get around that, you can either bind 'mouseout' to a function that resets mouseIsDown or bind 'mouseup' globally as such:
$('#mycanvas').on('mouseout', function () {mouseIsDown = false});
OR
$(document).on('mouseup', function () {mouseIsDown = false});
Here's an example: http://jsfiddle.net/pikappa/HVTWb/
I have a mousedown event attached in an anchor element which does many stuffs.
I also have a mousedown event attached to the document, and because of bubbling this event is called whenever the event attached to anchor is triggered. This is not what I want.
Can I bind a event with delay?
I dont want to use stopPropagation.
$('a').mousedown ->
...
openWindow()
$(document).mousedown ->
...
closeWindow()
Edit
I create a hack
$.fn.onBubble = (events, selector, data, handler) ->
setTimeout =>
this.on events, selector, data, handler
, 0
Work but like very ugly
As one of the comments mentions, the only way to stop events from bubbling is with stopPropagation. That said, if there are both conditions where you do want to prevent bubbling and others where you do not, you can put event.stopPropagation() into an if-statement:
$(...).mousedown(function(event) {
if(/* some condition */) { event.stopPropagation(); }
});
Alternatively you can add a conditional to the event handler attached to the document. For example:
$(document).mousedown(function(event) {
if($(event.target).is("a")) {
return; // if the element that originally trigged this event
// (i.e. the target) is an anchor, then immediately return.
}
/** code that runs if event not from an anchor **/
});
This snippet uses $.fn.is to determine if the event was triggered by an anchor. If it is generated by an anchor, the code immediately returns, which in effect ignores the event bubble.
EDIT in response to comment:
If I understand correctly, you want to close the window, if the user clicks on anything that is not in the window. In that case try this:
function whenWindowOpens { // Called when the window is opened
var windowElement; // Holds actual window element (not jQuery object)
$(document).bind("mousedown", function(event) {
if($.contains(windowElement, event.target)) {
return; // Ignore mouse downs in window element
}
if($(event.target).is(windowElement)) {
return; // Ignore mouse downs on window element
}
/** close window **/
$(this).unbind(event); // detaches event handler from document
});
}
This is basically a variation on the second solution suggested above. The first two if-statements ensure the mouse down did not occur in (using $.contains) or on (using $.fn.is again) the windowElement. When both statements are false, we close the window and unbind the current event handler. Note that $.contains only takes raw DOM elements -- not jQuery objects. To get the raw DOM element from a jQuery object use $.fn.get.