How to manage asynchronous mouse events in Javascript - javascript

I have JavaScript code where:
When the user presses the left mouse button, a mousedown event is fired, which triggers a call to function OnMouseDown.
When the user releases the left mouse button, a mouseup event is fired, which triggers a call to function OnMouseUp.
The two events are fired asynchronously.
For example, the mouseup event can be fired immediately after the mousedown event is fired (short mouse click), before the OnMouseDown ends. (Figure1)
I want to process the events sequentially, where the function OnMouseUp will only start after OnMouseDown ends.
I can achieve this by preventing the mouseup event from firing until after OnMouseDown ends (by calling removeEventListener('mouseup', OnMouseUp) when OnMouseDown begins) (Figure2)
But then I may lose a mouseup event altogether (Figure3)
Figure3 - mouseup event is lost
I am looking for a way to insure that
a mouseup event after a mousedown event is not lost, and
the OnMouseUp function begins after OnMouseDown ends.
How can I achieve this?
The following code example demonstrates the problem:
function sleep1 () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("Finished sleeping");
}, 2000);
});
}
// The mousedown event is fired when a pointing device button (usually a mouse button) is pressed on an element.
document.addEventListener('mousedown', async function(e) {
console.log('BEG OnMouseDown');
let retval = await sleep1();
console.log('retval: ', retval);
console.log('END OnMouseDown');
});
// The mouseup event is fired when a pointing device button (usually a mouse button) is released over an element.
document.addEventListener('mouseup', function(e) {
console.log('BEG OnMouseUp');
console.log('END OnMouseUp');
});
Clicking on the left mouse button and releasing results in the following printout, which shows that the function OnMouseUp ends before the function OnMouseDown ends, which fits figure1.
BEG OnMouseDown
BEG OnMouseUp
END OnMouseUp
Inside OnMouseDown: Finished sleeping
END OnMouseDown

You can try async await,
Define an async function then use await keyword on the mouswdown event, it will not fire the next event untill the the async function is done

What i'm understanding is that you want to record events in the sequence they have fired.
Please check this solution:
// The mousedown event is fired when a pointing device button (usually a mouse button) is pressed on an element.
var ismousedown = false;
document.addEventListener('mousedown', function(e) {
e.preventDefault();
ismousedown = true;
console.log('BEG OnMouseDown');
});
// The mouseup event is fired when a pointing device button (usually a mouse button) is released over an element.
document.addEventListener('mouseup', function(e) {
e.preventDefault();
if(ismousedown){
ismousedown = false;
console.log('BEG OnMouseUp');
}
});
Is this what you're looking for? There is something else i have missed?

I solved the problem by:
implementing a sleep based on here
introducing a flag onMouseDownStillProcessing that is set to false/true at the beginning/end of OnMouseDown, respectively.
waiting in the beginnning of OnMouseUp for onMouseDownStillProcessing to become true.

Related

How is possible to get cursor position and cursor events outside and cross origin iframe?

I am working on a video player. This player is used inside an iframe on the client's page, so it is cross-origin. When the user clicks on seekbar and drags the cursor out of iframe, I can't identify the mouseup event, so it keeps selected. I notice that youtube player can do it, and can identify cursor events outside iframe. How can i do it using javascript?
One thing to consider is that the mouseup event only fires if the pointer is directly over the element with the eventListener. It doesn't keep track of what element the mousedown event fired on, it only knows about the state of the mouse at the time of the mouseup.
The mouseup event is fired at an Element when a button on a pointing
device (such as a mouse or trackpad) is released while the pointer is
located inside it.
https://developer.mozilla.org/en-US/docs/Web/API/Element/mouseup_event
Therefore you need some other method of tracking if your user has initiated the drag on your seekbar playhead.
A possible solution is to use a boolean variable. Then to listen to mouseup on document and check the value of that variable.
let scrubbingTimeline = false;
document.querySelector('button').addEventListener('mousedown', ()=> {
console.log('Mouse down')
scrubbingTimeline = true
})
document.addEventListener('mouseup', ()=> {
if(scrubbingTimeline === true) {
console.log('Mouse has been released')
scrubbingTimeline = false
}
})
Another possible solution would be to initiate the mouseup listener only once the mousedown callback has fired. Then to stop listening after one event. Like this:
function mouseUpHandler() {
console.log("Mouse Lifted")
document.removeEventListener('mouseup', mouseUpHandler)
}
document.querySelector('button').addEventListener('mousedown', ()=> {
console.log('Down')
document.addEventListener('mouseup', mouseUpHandler)
})
Important: This code is for the inner document. I.E. the one that will be the iframe.

2 event listeners, run function once

I have two event listeners, one for a Click and a Touch event. They both should run the same function, but only once. If they are both true on some devices it runs the function twice.
I want to be able to click a button and listen for two event listeners but only run the function once if either of them is triggered.
window.addEventListener("click", function(event) {
myFunction();
});
window.addEventListener("touchstart", function(event) {
myFunction();
});
function myFunction() {
console.log("Clicked");
}
See MDN's article on Supporting both TouchEvent and MouseEvent
:
If the browser fires both touch and mouse events because of a single
user input, the browser must fire a touchstart before any mouse
events. Consequently, if an application does not want mouse events
fired on a specific touch target element, the element's touch event
handlers should call preventDefault() and no additional mouse events
will be dispatched.
Here is a code snippet of the touchmove event handler calling
preventDefault().
// touchmove handler
function process_touchmove(ev) {
// Call preventDefault() to prevent any further handling
ev.preventDefault();
}
Assuming the two events are fired almost simultaneously, you can prevent myFunction from executing twice within a pre-defined threshold using the following approach:
// threshold to reset allowing the execution
const _msThreshold = 200;
// date at which the function is last executed
let _myFnExecutedAt = new Date(0);
// your function
function myFunction() {
console.log("Clicked");
}
// executer that checks if the threshold is exceeded
// to allow your function call and reset the timer
function execute() {
if (new Date() - _myFnExecutedAt > _msThreshold) {
myFunction();
_myFnExecutedAt = new Date();
}
}
window.addEventListener("click", execute);
window.addEventListener("touchstart", execute);
Bear in mind that you'll have to experiment a bit with the threshold value:
If you set it too low, it might be exceeded before the second event registers so both will trigger.
If you set it too high, subsequent real clicks/touches might not register.

d3 drag fires event even when dx and dy is 0

when i try to create a new d3.drag() function and bind it afterwards to my selection the "drag" event fires even when i didn't move the mouse (dx and dy = 0) on click.
I wan't to call a click handler when there was no "drag".
var drag = d3.drag()
.on('start', dragStart)
.on('drag', dragging)
.on('end', dragEnd);
viewPort.on('click', function () {
clicked = true;
})
.call(drag);
I expect that clicked = true and "drag" doesn't fire when i don't move the mouse on click. Now clicked is true but "drag" is also fired.
Elabourating on my comment a little more: a mousedown event will fire the dragstart event in d3.js, so you can't really prevent the callback from being invoked. That, by definition, means that dragstart always fires before the click event is registered, and that's why you cannot prevent dragstart from firing in the event handler invoked bu click. The chain of event that happens when you click and release an element:
The mousedown event is fired: this triggers dragstart event in d3.js
The mouseup event is fired: this triggers the dragend event in d3.js
The click event is fired
click fires after both the mousedown and mouseup events have fired, in that order.
Source: see MDN docs on mouse-related events
This also means that the dragstart event will always fire, even when there is no cursor/pointer/mouse movement, because at that point in time, the browser has no idea whether any movement has detected, when the mousedown event is triggered.
What you can do, however, is to set up some kind of guard clause that simply does not allow further execution of logic in the callback should the dx and dy of the event register as zero.

mouse up event after long press on mobile browser

I have a button when pressed or on mouse-down event on it sends a command. It should also send a command when the button is released(as we don't have any release event, to my knowledge), I am using mouse-up event on the button. When i use the long press on the button from computer browser the mouse-up event works, But when i use mobile browser, if i do a long press on it the mouse up event is not fired, as the mobile browser will have text selection feature on long press. Could some one help me with this.
When the user interacts with your application using a mouse, it will respond via 'click' event, but when the user uses touch enable devices and touches the screen both 'touch' and 'click' event will occur.
for the single touch following events will occur in order :
touchstart
touchmove
touchend
mouseover
mousemove
mousedown
mouseup
click
one other 'touchcancel' will occur if the touch is interrupted.
When the user touches the screen, mouse events also executes. To avoid this, stop the default actions of touch events using preventDefault() method of event handler object,(e.preventDefault(); where 'e' is the event handler object).
Example :
let timeIn, timeOut;
const touchStart=(e)=>{
e.preventDefault();
console.log('touch start');
timeIn = Date.now();
}
const touchMove=(e)=>{
e.preventDefault();
timeOut= Date.now();
console.log('touch move');
}
const touchEnd=(e)=>{
e.preventDefault();
timeOut=((Date.now()-timeIn)/1000).toFixed(2);
console.log('touch end' , timeOut);
}
const mouseOver=()=>{
console.log('mouse over');
}
const mouseMove=()=>{
console.log('mouse move');
}
const mouseUp=()=>{
console.log('mouse up');
}
const mouseDown=()=>{
console.log('mouse down');
}
const mouseClick=()=>{
console.log('mouse click');
}
const touchCancel=(e)=>{
console.log('touch interrupted')
}
<div
ontouchstart="touchStart(event)"
ontouchmove="touchMove(event)"
ontouchend="touchEnd(event)"
onmouseover="mouseOver(event)"
onmousemove="mouseMove(event)"
onmouseup="mouseUp(event)"
onmousedown="mouseDown(event)"
onclick="mouseClick(event)"
ontouchcancel="touchCancel(event)"
>
touch me
</div>
To test this code on codepane : https://codepen.io/omiGit/pen/MVapRO
There is a good article on touch and mouse, must read: https://www.html5rocks.com/en/mobile/touchandmouse

How to temporarily disable all event handlers attached to a control

Is there a way to do.
$("#controlId").suspendEvents();
$("#controlId").resumeEvents();
I'm aware of preventDefault and stopPropagation. I want to do from outside the event.
Please consider the following in your answer.
I cannot modify these bound events.
I do not know the bound events (Although it will be possible it will take me long time to do it). so it is not possible to .off() and then add them back one by one.
I was able to put together answers from 2 other questions.
1.Bind an event handler to front of the queue
2.Attach handler to all events in a control
The idea is to bind an event handler with e.stopImmediatePropagation to front of the queue for all events. It seems crude i would be glad if this can be improved.
The solution...
$.fn.preBind = function (type, data, fn) {
this.each(function () {
var $this = $(this);
$this.bind(type, data, fn);
$.each(type.split(/ +/), function () {
var currentBindings = $this.data('events')[this];
if ($.isArray(currentBindings)) {
currentBindings.unshift(currentBindings.pop());
}
});
});
return this;
};
$.fn.suspendEvents = function () {
this.preBind("click keydown keyup keypress mouseover mouseenter mouseout mouseleave mousedown mouseup mousemove change blur focus focusin focusout scroll resize load unload beforeunload", null, blockEvents);
}
$.fn.resumeEvents = function () {
var _this = this;
$.each("click keydown keyup keypress mouseover mouseenter mouseout mouseleave mousedown mouseup mousemove change blur focus focusin focusout scroll resize load unload beforeunload".split(/ +/), function () {
_this.unbind(this, blockEvents);
});
}
function blockEvents(e) {
e.stopImmediatePropagation();
}
Now i could use
$("#controlId").suspendEvents();
$("#controlId").resumeEvents();
EDIT: Modified resumeEvents() to overcome IE issue.
everything bubbles up, so catch any event in body and prevent them.
alternative
var myCtlrs = $("all i want").attr("disabled", disabled");
then
myCtlrs.removeAttr("disabled");

Categories

Resources