This may be a stupid question. I know I am a little green.
I was set with a task of modifying this old, old system's navigation. There are two nav bars. The second has only search buttons. I was asked to remove the second nav bar, and replace it with a drop down that shows the search functions. I am restricted on what I can change due to the age of this system. There are no restrictions on the JS I can write. They are running jQuery 1.11.1, on an Adobe ColdFusion system (two months ago they upgraded from 1.3.2)
First: when the target is clicked, both the mouseenter and the click event trigger. The mouseenter fires first. This causes a problem on a desktop that is visible to the keen viewer, but on mobile, this creates a horriable usability issue. A: From my understanding mouse events do not happen on a mobile device but do for me. And B: since the mouseenter event runs first, it activates the closeDropDown function before the click event is processed. With the closeDropDown running, its .on('click', f(...eventstuff...)) hears the open click that is intended to trigger the openDropDown function, thus the drop down does not open.
Here are the functions. The console.logs are for checking what runs when.
function openDropDown(){
$('div.dropdown').parent().on('click.open mouseenter', function(event){
$subject = $(this).find('.dropdown-menu')
// console.log(event.type, $subject, "first o");
if(!$subject.is(":visible")){
// console.log($subject, 'second o');
$subject.show()
}else {
if(event.type == 'click'){
// console.log('third o');
$subject.toggle()
}
}
closeDropDown($subject)
// console.log('open complete');
})
}
function closeDropDown($x){
// console.log('first c');
$(document).on("click.close",function(e){
// console.log("second c", e.type, "this type");
if(!$(e.target).closest(".dropdown-menu").parent().length){
// console.log("third c");
if($x.is(":visible")){
// console.log('forth c');
$x.hide()
}
}
$(document).off("click.close")
// console.log('complete close');
})
}
openDropDown()
onSearchClick()
I have read a few posts hoping for some help (like this and that
Over all, I know I need to condense my code. I understand a few ways to fix this (add an if(... are we on a mobile device...) or some counter/check that prevents the closeDropDown from running when the dropdown is closed)
I really want to understand the fundamentals of event listeners and why one runs before the other stuff.
Although suggestions on how to fix this are great, I am looking to understand the fundamentals of what I am doing wrong. Any fundamental pointers are very helpful.
Of note: I just read this: .is(':visible') not working. I will be rewriting the code with out the .is('visible').
Other things that might help:
This is the Chrome Dev Tools console when all my console.log(s) are active.
First, click after page load....
Drop down opens and quickly closes.
Second click....
Thanks! All your help is appreciated!
This is a pretty broad question. I'll try to be terse. I don't think ColdFusion should be tagged here, because it seems like it only has to do with HTML/CSS/JS.
Configuring Events
First, I'd like to address the way you have your script configured.
You'd probably benefit from looking at the event handling examples from jquery.
Most people will create events like the following. It just says that on a click for any document element with the ID of "alerter", run the alert function.
// Method 1
$(document).on(click, "#alerter", function(event){
alert("Hi!");
});
OR
// Method 2
$(document).on("click", "#alerter", ClickAlerter);
function ClickAlerter(event) {
alert("Hi!");
}
Both methods are totally valid. However, it is my opinion that the second method is more readable and maintainable. It separates event delegation from logic.
For your code, I would highly recommend removing the mixing of event assignment and logic. (It removes at least one layer of nesting).
Incidentally, your event listeners don't appear to be configured correctly. See the correct syntax and this example from jQuery.
$( "#dataTable tbody" ).on( "click", "tr", function() {
console.log( $( this ).text() );
});
Regarding Multiple Events
If you have multiple event listeners on an object, then they will be fired in the order which they are registered. This SO question already covers this and provides an example.
However, this doesn't mean that a click will occur before a mouseenter. Because your mouse has to literally enter the element to be able to click it, the event for mouseenter is going to be fired first. In other words, you have at least 2 factors at play when thinking about the order of events.
The order in which the browser will fire the events
The order in which they were registered
Because of this, there isn't really such a thing as "simultaneous" events, per se. Events are fired when the browser wants to fire them, and they will go through events and fire the matches in the order that you assigned them.
You always have the option of preventDefault and stopPropagation on these kinds of events if you want to alter the default event behavior. That will stop the browser's default action, and prevent the event from bubbling up to parent elements, respectively.
Regarding Mobile Mouse Events
Mouse events absolutely happen on mobile devices, and it's not safe to assume they don't. This article covers in great depth the scope of events that get fired. To quote:
"[Y]ou have to be careful when designing more advanced touch interactions: when the user uses a mouse it will respond via a click event, but when the user touches the screen both touch and click events will occur. For a single click the order of events is:
touchstart
touchmove
touchend
mouseover
mousemove
mousedown
mouseup
click
I think you would benefit from reading that article. It covers common problems and concepts regarding events in mobile and non-mobile environments. Again, a relevant statement about your situation:
Interestingly enough, though, the CSS :hover pseudoclass CAN be triggered by touch interfaces in some cases - tapping an element makes it :active while the finger is down, and it also acquires the :hover state. (With Internet Explorer, the :hover is only in effect while the user’s finger is down - other browsers keep the :hover in effect until the next tap or mouse move.)
An Example
I took all these concepts and made an example on jsFiddle to show you some of these things in action. Basically, I'm detecting whether the user is using a touchscreen by listening for the touchstart event and handling the click differently in that case. Because I don't have your HTML, I had to make a primitive interface. These are the directives:
We need to determine if the user has a touchscreen
When the user hovers over the button, the menu should appear
On a mobile device, when a user taps the button, the menu should appear
We need to close the menu when the user clicks outside of the button
Leaving the button should close the menu (mobile or otherwise)
As you will see, I created all my events in one place:
$(document).on("mouseover", "#open", $app.mouseOver);
$(document).on("mouseout", "#open", $app.mouseOut);
$(document).on("click", "#open", $app.click);
$(document).on("touchstart", $app.handleTouch);
$(document).on("touchstart", "#open", $app.click);
I also created an object to wrap all the logic in, $app which gives us greater flexibility and readability down the road. Here's a fragment of it:
var $app = $app || {};
$app = {
hasTouchScreen: false,
handleTouch:function(e){
// fires on the touchstart event
$app.hasTouchScreen = true;
$("#hasTouchScreen").html("true");
$(document).off("touchstart", $app.handleTouch);
},
click: function(e) {
// fires when a click event occurrs on the button
if ($app.hasTouchScreen) {
e.stopPropagation();
e.preventDefault();
return;
}
// since we don't have a touchscreen, close on click.
$app.toggleMenu(true);
},
touch: function(e) {
// fires when a touchstart event occurs on the button
if ($("#menu").hasClass("showing")) {
$app.toggleMenu(true);
} else {
$app.toggleMenu();
}
}
};
Related
On virtually all current browsers (extensive details from patrickhlauke on github, which I summarised in an SO answer, and also some more info from QuirksMode), touchscreen touches trigger mouseover events (sometimes creating an invisible pseudo-cursor that stays where the user touched until they touch elsewhere).
Sometimes this causes undesirable behaviour in cases where touch/click and mouseover are intended to do different things.
From inside a function responding to a mouseover event, that has been passed the event object, is there any way I can check if this was a "real" mouseover from a moving cursor that moved from outside an element to inside it, or if it was caused by this touchscreen behaviour from a touchscreen touch?
The event object looks identical. For example, on chrome, a mouseover event caused by a user touching a touchscreen has type: "mouseover" and nothing I can see that would identify it as touch related.
I had the idea of binding an event to touchstart that alters mouseover events then an event to touchend that removes this alteration. Unfortunately, this doesn't work, because the event order appears to be touchstart → touchend → mouseover → click (I can't attach the normalise-mouseover function to click without messing up other functionality).
I'd expected this question to have been asked before but existing questions don't quite cut it:
How to handle mouseover and mouseleave events in Windows 8.1 Touchscreen is about C# / ASP.Net applications on Windows, not web pages in a browser
JQuery .on(“click”) triggers “mouseover” on touch device is similar but is about jQuery and the answer is a bad approach (guessing a hard-coded list of touchscreen user agents, which would break when new device UAs are created, and which falsely assumes all devices are mouse or touchscreen)
Preventing touch from generating mouseOver and mouseMove events in Android browser is the closest I could find, but it is only about Android, is about preventing not identifying mouseover on touch, and has no answer
Browser handling mouseover event for touch devices causes wrong click event to fire is related, but they're trying to elumate the iOS two-tap interaction pattern, and also the only answer makes that mistake of assuming that touches and mouse/clicks are mutually exclusive.
The best I can think of is to have a touch event that sets some globally accessible variable flag like, say, window.touchedRecently = true; on touchstart but not click, then removes this flag after, say, a 500ms setTimeout. This is an ugly hack though.
Note - we cannot assume that touchscreen devices have no mouse-like roving cursor or visa versa, because there are many devices that use a touchscreen and mouse-like pen that moves a cursor while hovering near the screen, or that use a touchscreen and a mouse (e.g. touchscreen laptops). More details in my answer to How do I detect whether a browser supports mouseover events?.
Note #2 - this is not a jQuery question, my events are coming from Raphael.js paths for which jQuery isn't an option and which give a plain vanilla browser event object. If there is a Raphael-specific solution I'd accept that, but it's very unlikely and a raw-javascript solution would be better.
Given the complexity of the issue, I thought it was worth detailing the issues and edge cases involved in any potential solution.
The issues:
1 - Different implementations of touch events across devices and browsers. What works for some will definitely not work for others. You only need to glance at those patrickhlauke resources to get an idea of how differently the process of tapping a touch-screen is currently handled across devices and browsers.
2 - The event handler gives no clue as to its initial trigger. You are also absolutely right in saying that the event object is identical (certainly in the vast majority of cases) between mouse events dispatched by interaction with a mouse, and mouse events dispatched by a touch interaction.
3 - Any solution to this problem which covers all devices could well be short-lived as the current W3C Recommendations do not go into enough detail on how touch/click events should be handled (https://www.w3.org/TR/touch-events/), so browsers will continue to have different implementations. It also appears that the Touch Events standards document has not changed in the past 5 years, so this isn't going to fix itself soon. https://www.w3.org/standards/history/touch-events
4 - Ideally, solutions should not use timeouts as there is no defined time from touch event to mouse event, and given the spec, there most probably won't be any time soon. Unfortunately, timeouts are almost inevitable as I will explain later.
A future solution:
In the future, the solution will probably be to use Pointer Events instead of mouse / touch events as these give us the pointerType (https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events), but unfortunately we're not there yet in terms of an established standard, and so cross-browser compatibility (https://caniuse.com/#search=pointer%20events) is poor.
How do we solve this at the moment
If we accept that:
You can't detect a touchscreen (http://www.stucox.com/blog/you-cant-detect-a-touchscreen/)
Even if we could, there's still the issue of non-touch events on a touch capable screen
Then we can only use data about the mouse event itself to determine its origin. As we've established, the browser doesn't provide this, so we need to add it ourselves. The only way to do this is using the touch events which are triggered around the same time as the mouse event.
Looking at the patrickhlauke resources again, we can make some statements:
mouseover is always followed by the click events mousedown mouseup and click - always in that order. (Sometimes separated by other events). This is backed up by the W3C recommendations: https://www.w3.org/TR/touch-events/.
For most devices / browsers, the mouseover event is always preceded by either pointerover, its MS counterpart MSPointerOver, or touchstart
The devices / browsers whose event order begins with mouseover have to be ignored. We can't establish that the mouse event was triggered by a touch event before the touch event itself has been triggered.
Given this, we could set a flag during pointerover, MSPointerOver, and touchstart, and remove it during one of the click events. This would work well, except for a handfull of cases:
event.preventDefault is called on one of the touch events - the flag will never be unset as the click events will not be called, and so any future genuine click events on this element would still be marked as a touch event
if the target element is moved during the event. The W3C Recommendations state
If the contents of the document have changed during processing of the
touch events, then the user agent may dispatch the mouse events to a
different target than the touch events.
Unfortunately this means that we will always need to use timeouts. To my knowledge there is no way of either establishing when a touch event has called event.preventDefault, nor understanding when the touch element has been moved within the DOM and the click event triggered on another element.
I think this is a fascinating scenario, so this answer will be amended shortly to contain a recommended code response. For now, I would recommend the answer provided by #ibowankenobi or the answer provided by #Manuel Otto.
What we do know is:
When the user uses no mouse
the mouseover is directly (within 800ms) fired after either a touchend or a
touchstart (if the user tapped and held).
the position of the mouseover and the touchstart/touchend are identical.
When the user uses a mouse/pen
The mouseover is fired before the touch events, even if not, the position of the mouseover will not match the touch events' position 99% of time.
Keeping these points in mind, I made a snippet, which will add a flag triggeredByTouch = true to the event if the listed conditions are met. Additionally you can add this behaviour to other mouse events or set kill = true in order to discard mouseevents triggered by touch completely.
(function (target){
var keep_ms = 1000 // how long to keep the touchevents
var kill = false // wether to kill any mouse events triggered by touch
var touchpoints = []
function registerTouch(e){
var touch = e.touches[0] || e.changedTouches[0]
var point = {x:touch.pageX,y:touch.pageY}
touchpoints.push(point)
setTimeout(function (){
// remove touchpoint from list after keep_ms
touchpoints.splice(touchpoints.indexOf(point),1)
},keep_ms)
}
function handleMouseEvent(e){
for(var i in touchpoints){
//check if mouseevent's position is (almost) identical to any previously registered touch events' positions
if(Math.abs(touchpoints[i].x-e.pageX)<2 && Math.abs(touchpoints[i].y-e.pageY)<2){
//set flag on event
e.triggeredByTouch = true
//if wanted, kill the event
if(kill){
e.cancel = true
e.returnValue = false
e.cancelBubble = true
e.preventDefault()
e.stopPropagation()
}
return
}
}
}
target.addEventListener('touchstart',registerTouch,true)
target.addEventListener('touchend',registerTouch,true)
// which mouse events to monitor
target.addEventListener('mouseover',handleMouseEvent,true)
//target.addEventListener('click',handleMouseEvent,true) - uncomment or add others if wanted
})(document)
Try it out:
function onMouseOver(e){
console.log('triggered by touch:',e.triggeredByTouch ? 'yes' : 'no')
}
(function (target){
var keep_ms = 1000 // how long to keep the touchevents
var kill = false // wether to kill any mouse events triggered by touch
var touchpoints = []
function registerTouch(e){
var touch = e.touches[0] || e.changedTouches[0]
var point = {x:touch.pageX,y:touch.pageY}
touchpoints.push(point)
setTimeout(function (){
// remove touchpoint from list after keep_ms
touchpoints.splice(touchpoints.indexOf(point),1)
},keep_ms)
}
function handleMouseEvent(e){
for(var i in touchpoints){
//check if mouseevent's position is (almost) identical to any previously registered touch events' positions
if(Math.abs(touchpoints[i].x-e.pageX)<2 && Math.abs(touchpoints[i].y-e.pageY)<2){
//set flag on event
e.triggeredByTouch = true
//if wanted, kill the event
if(kill){
e.cancel = true
e.returnValue = false
e.cancelBubble = true
e.preventDefault()
e.stopPropagation()
}
return
}
}
}
target.addEventListener('touchstart',registerTouch,true)
target.addEventListener('touchend',registerTouch,true)
// which mouse events to monitor
target.addEventListener('mouseover',handleMouseEvent,true)
//target.addEventListener('click',handleMouseEvent,true) - uncomment or add others if wanted
})(document)
a{
font-family: Helvatica, Arial;
font-size: 21pt;
}
Click me
According to https://www.html5rocks.com/en/mobile/touchandmouse/
For a single click the order of events is:
touchstart
touchmove
touchend
mouseover
mousemove
mousedown
mouseup
click
So you might be able to set some arbitrary boolean isFromTouchEvent = true; in onTouchStart() and isFromTouchEvent = false; in onClick() and check for that inside of onMouseOver(). This doesn't work very well since we're not guaranteed to get all those events in the element that we're trying to listen on.
I usually have couple of general schemes which I use for this, one of them uses a manual principle of setTimeout to trigger a property. I will explain this one here, but first try to reason about using touchstart, touchmove and touchend on touch devices and use mouseover on destop.
As you know, calling event.preventDefault (event has to be not passive for this to work with touchstart) in any of the touchevents will cancel the subsequent mousecalls so you do not need to deal with them. But in case this is not what you want, here is what I use sometimes (I refer as "library" to your dom manipulation library, and "elem" as your element):
with setTimeout
library.select(elem) //select the element
.property("_detectTouch",function(){//add a _detectTouch method that will set a property on the element for an arbitrary time
return function(){
this._touchDetected = true;
clearTimeout(this._timeout);
this._timeout = setTimeout(function(self){
self._touchDetected = false;//set this accordingly, I deal with either touch or desktop so I can make this 10000. Otherwise make it ~400ms. (iOS mouse emulation delay is around 300ms)
},10000,this);
}
}).on("click",function(){
/*some action*/
}).on("mouseover",function(){
if (this._touchDetected) {
/*coming from touch device*/
} else {
/*desktop*/
}
}).on("touchstart",function(){
this._detectTouch();//the property method as described at the beginning
toggleClass(document.body,"lock-scroll",true);//disable scroll on body by overflow-y hidden;
}).on("touchmove",function(){
disableScroll();//if the above overflow-y hidden don't work, another function to disable scroll on iOS.
}).on("touchend",function(){
library.event.preventDefault();//now we call this, if you do this on touchstart chrome will complain (unless not passive)
this._detectTouch();
var touchObj = library.event.tagetTouches && library.event.tagetTouches.length
? library.event.tagetTouches[0]
: library.event.changedTouches[0];
if (elem.contains(document.elementFromPoint(touchObj.clientX,touchObj.clientY))) {//check if we are still on the element.
this.click();//click will never be fired since default prevented, so we call it here. Alternatively add the same function ref to this event.
}
toggleClass(document.body,"lock-scroll",false);//enable scroll
enableScroll();//enableScroll
})
Another option without setTimeout is to think mousover is counter to touchstart and mouseout counter to touchend. So former events (the touch events) will set a property, if the mouse events detect that property then they do not fire and reset the property to its initial value and so on. In that case something along these lines will also do:
without setTimeout
....
.on("mouseover",function(dd,ii){
if (this._touchStarted) {//touch device
this._touchStarted = false;//set it back to false, so that next round it can fire incase touch is not detected.
return;
}
/*desktop*/
})
.on("mouseout",function(dd,ii){//same as above
if(this._touchEnded){
this._touchEnded = false;
return;
}
})
.on("touchstart",function(dd,ii){
this._touchStarted = true;
/*some action*/
})
.on("touchend",function(dd,ii){
library.event.preventDefault();//at this point emulations should not fire at all, but incase they do, we have the attached properties
this._touchEnded = true;
/*some action*/
});
I removed a lot of details but I guess this is the main idea.
You can use modernizr for that! I just tested this on a local development server and it works.
if (Modernizr.touch) {
console.log('Touch Screen');
} else {
console.log('No Touch Screen');
}
So I would start there?
Pointer Events are widely supported now. So now we can use pointerenter and check event.pointerType:
const element = document.getElementById("hoverableElement")
element.addEventListener("pointerenter", (event) => {
if (event.pointerType === "mouse") {
alert("Hovered")
}
})
<div id="hoverableElement">Trigger on hover, but not on touch</div>
I've written an html5 application which is supposed to work on mobile devices. 90% of the time it works fine however in certain devices (mostly androids 4.0+) the click events fire twice.
I know why that happens, I'm using iScroll 4 to simulate native scrolling and it handles the events that happen inside the scroll.(line 533 dispatches the event if you're interested) Most of the time it works fine but in certain devices both the iScroll dispatched event and the original onClick event attached to the element are fired, so the click happens twice. I can't find a pattern on which devices this happen so I'm looking for alternatives to prevent double clicks.
I already came up with an ugly fix that solves the problem. I've wrapped all the clicks in a "handleClick" method, that is not allowed to run more often than 200ms. That became really tough to maintain. If I have dynamically generated content it becomes a huge mess and it gets worse when I try to pass objects as parameters.
var preventClick = false;
function handleClick(myFunction){
if (preventClick)
return;
setTimeout(function(){preventClick = true;},200);
myFunction.call():
}
function myFunction(){
...
}
<div onclick='handleClick(myfunction)'> click me </div>
I've been trying to find a way to intercept all click events in the whole page, and there somehow work out if the event should be fired or not. Is it possible to do something like that?
Set myFunction on click but before it's called, trigger handleClick()? I'm playing with custom events at the moment, it's looking promising but I'd like to not have to change every event in the whole application.
<div onclick='myfunction()'> click me </div>
You can do that with the following ( i wouldn't recommend it though):
$('body').on('click', function(event){
event.preventDefault();
// your code to handle the clicks
});
This will prevent the default functionality of clicks in your browser, if you want to know the target of the click just use event.target.
Refer to this answer for an idea on how to add a click check before the preventDefault();
I don't like events on attributes, but that's just me.
Thinking jquery: $(selector).click(function(){ <your handler code> } you could do something like:
$(selector).click(function(event){
handleClick(window[$(this).attr("onclick")]);
};
of course, there wouldn't be any parameters...
So I used this totally awesome tool called Visual Event, which shows all the event handlers bound to an object - and I noticed that every time I clicked or played around with my object and checked the list of event handlers bound to it, there were and more every time. My problem is this one: console.trace or stack trace to pinpiont the source of a bug in javascript? After using Visual Event and someone else's suggestion, I'm thinking my problem is that I'm probably binding the same handlers to the same events over and over again. Is there a way to unbind things regularly?
My application has a bunch of plugins connect to dynamically created divs. These divs can be resized and moved around the place. The application is a kind of editor, so users arrange these divs (which contain either images or text) in any design they like. If the user clicks on a div, it becomes "activated", while all other divs on the page get "deactivated". I have a bunch of related plugins, like activateTextBox, initTextBox, deactivateTextBox, readyTextBox, and so on. Whenever a div is first created, the init plugin is called once, just the first time after creation, like so:
$(mydiv).initTextBox();
But readyTextBox and activateTextBox and deactivateTextBox are called often, depending on other user events.
In init, I first use bind things like resizable() and draggable(), then I make the box "ready" for use
$.fn.extend({
initTextBox: function(){
return this.each(function() {
// lots of code that's irrelevant to this question
$this.mouseenter(function(){
if(!$this.hasClass('activated'))
$this.readyTextBox();
}
$this.mouseleave(function(){
if($this.hasClass('ready')){
$this.deactivateTextBox();
$this.click(function(e){
e.preventDefault();
});
}
});
});
});
Here's a simplified summary version of the readyTextBox plugin:
(function($){
$.fn.extend({
readyTextBox: function(){
return this.each(function() {
// lots of code that's irrelevant to this question
$this.resizable({ handles: 'all', alsoResize: img_id});
$this.draggable('enable');
$this.on( "dragstop", function( event, ui )
{/* some function */ });
$this.on("resizestop", function( event, ui ){ /* another function */ });
// and so on
});
Then there's activateTextBox():
$.fn.extend({
activateTextBox: function(){
return this.each(function() {
// lots of code that's irrelevant to this question
$this.resizable('option','disabled',true); //switch of resize & drag
$this.draggable('option', 'disabled', true);
});
Then deactivate, where I turn on draggable and resizable again, using the code:
$this.draggable('enable'); $this.resizable('option','disabled',false);
These divs, or "textboxes" are contained within a bigger div called content, and this is the click code I have in content:
$content.click(function(e){
//some irrelevant code
if( /* condition to decide if a textbox is clicked */)
{ $(".textbox").each(function(){ //deactivate all except this
if($(this).attr('id') != $eparent.attr('id'))
$(this).deactivateTextBox();
});
// now activate this particular textbox
$eparent.activateTextBox();
}
});
This is pretty much the relevant code related to text boxes. Why is it that whenever I drag something around and then check Visual Event, there are more clicks and dragstops and mouseovers than before? Also, the more user interacts with the page, the longer the events take to complete. For example, I mouseout from a div, but the move cursor takes a loooong time to get back to default. I quit dragging, but everything gets stuck for a while before getting ready to take more user clicks, etc. So I'm guessing the problem has to be that I'm binding too many things to the same events need to be unbinding at some point? It gets so bad that draggable eventually stops working at some point. The textboxes just get stuck - they're still able to be resized, but dragging stops working.
Am I binding events over and over
Yes. Have a look at your code:
$this.mouseenter(function(){
…
$this.mouseleave(function(){
…
$this.click(function(e){
…
});
});
});
That means every time you mouseover the element, you add another leave handler. And when you leave the element, every of those handlers adds another click event.
I'm not sure what you want to do, but there are several options:
bind the event handlers only once, and keep track of the current state with boolean variables etc.
before binding, remove all other event handlers that are already bound. jQuery's event namespacing can help you to remove only those which your own plugin added.
use the one() method that automatically unbinds a listener after firing it.
I am wondering if mouseenter and click event can exist together and they can both exist to TRUE when checked with:
if ((evt.type === 'mouseenter') && (evt.type === 'click'))
It is because when I mouse over the link, the mouseenter triggers (set to TRUE) and even when I clicked on it, the hover is still shown. Probably they could exist together but I'm no expert on this.
If someone can give insights, I would appreciate it a lot.
Also how can I trigger the click event during the mouseenter event?
The mouseenter event fires when the mouse enters the control. The click event fires when the mouse is clicked. They are two separate events which call two separate event handlers. If you click just as the mouse enters the element they will be called within a short timespan of one another but they are still two distinct events.
It is also important that you differentiate between the mouseenter and the mouseover events. mouseenter fires when the mouse physically enters an element, whereas mouseover fires continually while the mouse remains over an element.
While you cannot trigger the click event per se, you can call the same function that is called by the click event handler. For example if you have this:
var myfunc = function (e) { ... }
document.getElementById("id").onclick = myfunc;
Then you could simply call myfunc directly and you would get the same result as if the mouse was clicked.
They can 100% exist together, and this is a great question with no good answer... When you're on a mobile device, a mouseenter event will be thrown on tap... If you are also detecting onclick as well as mouseenter, then there will be a discrepancy between mobile devices and desktop machines.
It's kind of hard to solve such a small issue at the moment.
const x = document.getElementById('some_node')
x.onclick=(e)=>{
e.stopPropagation()
// this logic will be triggered on click for both desktop and mobile
}
x.onmouseenter=(e)=>{
e.stopPropagation()
// this logic will be triggered on click for mobile only (but will
//have already been triggered on desktop when cursor entered node)
}
The only workaround I came up for this, and I think it's pretty clever, is using a eventlistener for taps/touches. The order/priority that these events are fired goes: touch > mouseenter > click.
Since the touch event is fired first, you can add a touch event listener (which will only register on a mobile device), and change a variable that prevents the mouseenter event from being triggered (which is the logic that would generally be conflicting with the onclick logic)... like this:
let isMobile = false
x.addEventListener('touchstart',(e)=>{
isMobile = true
}, false);
Then your mouseenter would need to look like this:
x.onmouseenter=(e)=>{
e.stopPropagation()
if(!isMobile){
// this logic will no longer cause a conflict between desktop and mobile
}
}
they can exist on the same object, think a button with a hover state and then a click action. The click event, though will only read the click event since the enter event actually occurred earlier.
You can create a var like mouseIsOverand set it to true when the enter event fires. I can be safely assumed, though that if a click happens the mouse is over the same target.
The two events may happen at the same time, but they will still be processed on after the other. So the if you posted will never evaluate to true.
If you look at your code again you can see that it doesn't make sense. How can something be X and Y at the same time? It can't.
for the first question i think u got an answer....
however, for Also how can I trigger the click event during the mouseenter event?
u can use trigger() function..
http://jsfiddle.net/PDhBW/2/
if u want to read more about trigger
here is the link
http://api.jquery.com/trigger/
With Jquery event delegation, You can use binding multiple events at once
$('#IdElement').on('mouseenter click', function () {
//Your Code
});
http://jqfundamentals.com/chapter/events
In my Backbone View, I have defined events like this:
events : {
'click .elm' : 'select',
'dblclick .elm' : 'toggle'
},
select: function(e){
e.preventDefault();
console.log('single clicked');
}
toggle : function(e){
e.preventDefault();
console.log('double clicked');
}
I have bound single and double click event listeners to same element .elm. When I double click on this element, I get this output:
single clicked
single clicked
double clicked
Tried preventDefault() and stopImmediatePropagation() and that didn't solve the issue.
So, how can I prevent the single click event getting fired when I double click?
The jQuery documentation specifically recommends against what you're doing:
It is inadvisable to bind handlers to both the click and dblclick events for the same element. The sequence of events triggered varies from browser to browser, with some receiving two click events before the dblclick and others only one. Double-click sensitivity (maximum time between clicks that is detected as a double click) can vary by operating system and browser, and is often user-configurable.
What you're seeing is exactly what is expected (depending on the browser of course). The only way around your problem is to set a timer and manually differentiate between a single- and double-click yourself; then you'll have to adjust the timer value and check various browsers and operating systems until you get something that sort of pretends to work in most place.
I'd strongly recommend that you use separate controls with single-click actions instead. Double-click is pretty unfriendly period and we only put up with it because we're used to it.
This isn't an issue with Backbone itself.
It's about how to handle both single click and double click events on the same button.
See
Need to cancel click/mouseup events when double-click event detected
Javascript with jQuery: Click and double click on same element, different effect, one disables the other
Update: But it would be better if you didn't have to deal with it. See mu is too short's answer below!
Try adding return false; at the end of select & toggle.