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...
Related
My intent is to throttle the click listener on some links and form submit buttons. The main idea was something like:
Click
<script>
window.onload = function() {
tags = document.findElementsByClassName("throttled-click");
for (let tag of tags) {
tag.onclick = _.throttle(tag.click, 1000, { 'trailing': false });
// Clearly doesn't work
}
}
</script>
The code above doesn't really work since no matter what I do, the default click event listener won't get throttled. If I pass in some other function (e.g. console.log("Throttled")), it will be throttled but the default click event listener won't.
Other than attempting to write my own throttling function, I'm out of ideas.
Note that I'm not a js dev so I may be missing something obvious.
EDIT: The goal of throttling the default click event listener is to prevent users from submitting too many forms when something hangs. Granted, form submissions usually entail a redirection which implicates that it's enough to simply disable the HTML click event after the first click.
My idea was to implement a throttle for cases when the page won't refresh or some edge case occurs where the request never reaches the server and the user actually has to click the submit button again.
I was able to do it with a custom implementation, I don't think there's a way to do it with existing standard libraries which I find kind of strange.
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();
}
}
};
Whenever a blur event is triggered from any input element, I want to set focus to one particular element.
This issue arises when I am trying to focus to the same element triggering the blur event.
Why does this only works, when the element I am trying to focus on to, is not the one triggering the event?
[Issue Illustration]
and explanation as per the fiddle:
The element I am trying to focus to is col0
Unless the element to trigger the blur event is not col0 it works perfect
But when blur is triggered from col0 itself, then $("#col0").focus() does not work.
Q: Why? & What is the workaround/solution?
P.S: I am just trying to know the cause of the behavior and ways to overcome it. Concerns about the usability, is NOT THE QUESTION.
This works in FF for me...
$('input').on('blur', function() {
setTimeout(function () { $("#col0").focus(); }, 0);
});
it is just to postpone a UI action a bit (after processing the blur event is finished).
Warning: in jsfiddle FF won't let you edit the code after you try it, once you get to the input you are stuck there until refresh
Update: The explanation is tricky, as it is a matter of implementation in FF (as Chrome and IE behave as you expected), my guess is that FF prevents firing related events when you are in the event handler for the same element (a thing that may potentially lead to infinite cycle), using setTimeout you are firing the event soon after you leave the handler (and even UI has a chance to redraw itself)
It looks like you're trying to keep focus on everything except the control you're on. Try this:
$('input:not(#idofcontrol)').blur(function() {
$('#idofcontrol').focus();
});
I have designed a website with a menu that is initially invisible. When the user clicks on a button, the menu becomes visible. There are two ways for the user to hide the now visible menu:
Click the button that caused the menu to become visible
Click anywhere on the web page that isn't the menu
The way I have coded the second option is to tie an onclick event to the window element, and have it compare where the user clicked to the menu's position to determine if the menu should be hidden. This works great in Firefox and Safari, but it fails in Mobile Safari.
I noticed that the window onclick event only fires when I click on another element with an onclick event already assigned. If I click on an element with no event(s) assigned, the window's onclick event never fires. If I click on the button which displays the menu, it fires along with the event tied to the button.
Is it possible to assign events to the window element in Mobile Safari?
I'v been encountering this same problem. Here is what worked for me. (Note: I am working within a Modernizr and jQuery context)
First, I add a custom Modernizr class using Modernizr's addTest Plugin API to test for iOS, which will add the class appleios or no-appleios accordingly.
Because in my research the body seems to fire events on it's own agenda, I am taking a little precaution by wrapping all the document's content with an element in an iOS context. Then I add an event handler to this element.
$(".appleios body").wrapInner('<div id="appleios-helper" />');
$("#appleios-helper").bind("mouseup", function(){return;});
What was suggested earlier in this thread is using void(0). I did some quick testing, and found that void(0) as the event just wasn't causing touches on the body to be recognized. When I plugged in my own "empty" function in the form of function(){return;} things started working.
This all hinges on the fact that no events are fired in Mobile Safari unless the element explicitly has events to fire (Safari Web Content Guide.) By inserting this empty event on the wrapper, things will bubble up to the body.
If you're doing strait JavaScript with none of these libraries, the same effect could be achieved in the HTML markup
<html>
...
<body>
<div id="appleios-helper" onmouseup="function(){return;}">
...
</div>
</body>
</html>
This worked for me to hide tooltips when touching anywhere on the document's body. Your mileage may vary.
Simply adding the dummy onclick handler to the html body works for me:
<body onclick="void(0)">
Note that I am using usual live event handlers as shown below:
function liveHandler( event ) {
var target = event.target; ...}
window.addEventListener(evtype, liveHandler, true);
// evtype such as 'mousedown' or 'click'
// we use the capturing mode here (third parameter true)
This is an old question, but I struggled with the same thing today.
I found that using touchstart event works.
I solved it like this:
var isTouchDevice = 'ontouchstart' in document.documentElement;
if (isTouchDevice) {
// Do touch related stuff
$(document).on('touchstart', function (event) {
// Do stuff
});
} else {
// Do non-touch related stuff
$(document).on('click', function () {
// Do stuff
});
}
You could just add onclick="void(0);" to some <div> that covers the whole page so that no matter what, you are always clicking on an element that has an onclick event. Not a great solution, though.
I'd prefer not having the onclick event be tied to the window. Why don't you create a container <div> that has that event on it. Then handle it just like you currently are.
You can also:
$('body').css('cursor', 'pointer');
No idea what those "engineers" at Apple are doing. LOL.
This has problems though. You wouldn't want to do this on every touch device. Only touch devices that don't also have a pointing device (Laptops with Touch Screens, for example).
Source: http://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
The conclusion of the article is this:
So I don’t understand why all this is the case, but it most certainly is the case. If you’re having bubbling problems, just add an empty-function event handler anywhere between the body and the element, and you’re set to go. But it shouldn’t be necessary.
Heres my link:
http://tinyurl.com/6j727e
If you click on the link in test.php, it opens in a modal box which is using the jquery 'facebox' script.
I'm trying to act upon a click event in this box, and if you view source of test.php you'll see where I'm trying to loacte the link within the modal box.
$('#facebox .hero-link').click(alert('click!'));
However, it doesn't detect a click and oddly enough the click event runs when the page loads.
The close button DOES however have a click event built in that closes the box, and I suspect my home-grown click event is being prevented somehow, but I can't figure it out.
Can anyone help? Typically its the very last part of a project and its holding me up, as is always the way ;)
First, the reason you're getting the alert on document load is because the #click method takes a function as an argument. Instead, you passed it the return value of alert, which immediately shows the alert dialog and returns null.
The reason the event binding isn't working is because at the time of document load, #facebox .hero-link does not yet exist. I think you have two options that will help you fix this.
Option 1) Bind the click event only after the facebox is revealed. Something like:
$(document).bind('reveal.facebox', function() {
$('#facebox .hero-link').click(function() { alert('click!'); });
});
Option 2) Look into using the jQuery Live Query Plugin
Live Query utilizes the power of jQuery selectors by binding events or firing callbacks for matched elements auto-magically, even after the page has been loaded and the DOM updated.
jQuery Live Query will automatically bind the click event when it recognizes that Facebox modified the DOM. You should then only need to write this:
$('#facebox .hero-link').click(function() { alert('click!'); });
Alternatively use event delegation
This basically hooks events to containers rather than every element and queries the event.target in the container event.
It has multiple benefits in that you reduce the code noise (no need to rebind) it also is easier on browser memory (less events bound in the dom)
Quick example here
jQuery plugin for easy event delegation
P.S event delegation is pencilled to be in the next release (1.3) coming very soon.