Excessive Use of jQuery's preventDefault() and stopPropagation() - javascript

I was recently in a discussion with a work colleague about some differences in our coding practices, where I raised an issue about his excessive use of the two above mentioned methods in his event handlers. Specifically, they all look like this...
$('span.whatever').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
/* do things */
});
He has made the claim that this is a good practice with no foreseeable blowback, and will improve cross-platform support while reducing unexpected issues down the road.
My question to you guys: If he's right, why hasn't the jQuery team implemented this behavior globally applied to all event handlers? In effect, my assumption is that he's wrong simply because the methods are available for independent use, but who knows... Your insight is much appreciated.
--
Update: I did a simple speed test, and there is a little drag caused by these two function, but nothing terribly noticeable. Still, among other things, a good reason to be deliberate in their use I think.
$('span.whatever').on('click', function(e) {
var start = new Date();
for (i = 0; i < 999999; i++) {
e.preventDefault();
e.stopPropagation();
}
console.log( new Date() - start );
});
The above logged ~9.5 seconds as is, and ~2.5 seconds when I took the function calls out of the loop.

I don't do the same thing as your colleague (pushing the 2 calls on EVERY event handler), but I do have the same practice of using these calls explicitely rather than a "return false;", and I believe that has made my life easier.
When I started with Jquery, I figured if I need to both stop propagation, and prevent default, I should just "return false", which I kind of did all over the place.
$('a.whatever').on('click', function(e) {
do_stuff();
return false;
});
But there was 2 problems I enventually encountered:
if do_stuff() has any critical error causing an exception, "return false;" will never be reached!!! The error will eventually be "nicely" swallowed by jquery; your event will bubble, and let the browser execute the default action. If you are in a single page app and a link was clicked, for all you know the page navigated away, and the entire app state went down the toilet (I've been there before).
I was too lenient with my return false: in many cases, I just needed a preventdefault(). "return false" was killing event bubbling and sometimes hindered my ability to perform another action higher up the dom hierarchy (or made some other plugin/libs I was using not work properly)
So I now prefer to be explicit. I litterally never use "return false;" any more. If I have an event handler that must either not propagate or not execute default, I deliberatly put that in my function FIRST, before any processing code. Whatever happens during event handling should NOT affect the fact that I do NOT want the default action to run, and/or event to not bubble.
And yes, that being said, I am also mindful of using just one of the 2 when required (or none at all in some cases). I do not just add both preventDefault() and stopPropagation() for no reason. Everywhere I manipulate an event in a handler, it is part of a conscious case-by-case decision.

It would be a problem if the element is part of a menu and the click event was supposed to bubble out and tell the menu to close itself too.
Or if a menu was open elsewhere and clicking outside the menu was supposed to bubble up to the body where an event handler would close the menu. But the element having stopped the bubble, prevents the menu from closing.

<div id="wrapper">
<div id="header">header
<div id="footer">footer
<div id="content">click this!!!</div>
</div>
</div>
</div>
$("#wrapper div").click(function(){
console.log( $(this) )
});
Please try clicked to div and show console...
And now added
$("#wrapper div").click(function(e){
e.stopPropagation()
})

Related

Why are event handlers being removed in this function?

I'm trying to understand why event handlers need to be removed. I'm a beginner developer, and I searched all over for the answer but couldn't find the reason.
I came across the code below and I see that the event handler is removed as soon as it is bound. Is it a good practice?
bindSubmitEvent: function() {
var self = this;
$('#submitBtn').on('click', function() {
$(this).off('click', self.bindSubmitEvent);
self.validateForm();
if (self.options.valid_selection) {
self.submitForm();
} else {
$('#submitRegistrationBtn').on('click', self.bindSubmitEvent);
console.log("not valid");
}
});
}
The code you've shown does not work.
this inside the inner click handler is #submitButton, therefore trying to detach the self.bindSubmitEvent from it makes no sense, as that function was never attached to it.
Also $('#submitRegistrationBtn').on('click', self.bindSubmitEvent) will cause more trouble, as two events will be attached at the next click, and the second one will be called with this (and therefore self being the #submitRegistrationBtn. That will probably cause the whole code to fail (silently).
Is it a good practice?
Non working code is not a good practice, no. Removing an event listener sometimes makes sense (e.g. if the form was submitted, you want the registration button to be disabled), in most cases however, it is way easier to just remove the button itself (as it serves no functionality without a listener attached).

multiple simultaneous events javascript

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();
}
}
};

Prevent default events of medium-editor

How would i replace internally triggered events of medium-editor with my custom ones or simply change internally designed behaviour?
In this hierarchy
<div>
<textarea class='editable'></textarea>
</div>
I bind a click handler to the div and do e.stopPropagation() and e.preventDefault().
I also try adding after the instantiating of medium-editor.
var editor = new MediumEditor('.editable')
.subscribe("editableClick", function(e){
e.preventDefault();
});
Every way i try textarea gets focused and cursor starts to blink.
For example intial click event adds an element to the dom with a class .medium-editor-element should i dive to source to modify this behaviour?
Or maybe i would like it to work with not a click but a double click.
Anyone familiar with the internal workings of medium-editor?
After trial and error and the help of dev tools i found the way to do what i want.
But i think this question is still answerable because i did it by modifying the source of medium-editor.
So, in medium-editor.js in line 2725 there is setupListener function.
There are 3 main events attached there to case 'externalInteraction': 2731th line.
mousedown,click,focus.
Starting from 2959th line there are the attached handlers for those events.
handleBodyClick,handleBodyFocus,handleBodyMousedown
The mousedown is important for my case because it is the first one that fires and should be prevented and accepted in different cases.
In the end i added a dblclick and handleBodyDblClick to source then put some logic in handleBodyMousedown to prevent the default behaviour of mousedown event in some cases.
Anyway, from the source as i can understand there are no override methods or hooks to modify medium-editor internal events.
It would be nice to have that feature.
Or if i am wrong i would like to know if there is a better way to do all these.

Is it possible to intercept/override all click events in the page?

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...

Opera ignoring .live() event handler

I have the following jQuery which works in all major browsers except Opera:
jQuery(document).ready(function () {
jQuery("#GetResults").live("click", function(e){
e.preventDefault(); //Opera doesn't execute anything here
});
};
Which is supposed to fire when clicking the following link:
<a id="GetResults" href="Folder/File/javascript:void(0);">Get Results</a>
Only Opera ignores this. Any ideas?
Edit:
I've just discovered that if I substitute out .live() for .bind() everything functions as expected. I can't find any documentation relating to .live() bugs in Opera though, and it does work in jsFiddle which would point at something environmental. What could be causing this behavour?
This needs clarification. The answers above are correct, but nobody clearly explained where your problem comes from.
In fact I think that you could probably reproduce the problem in other browsers too.
That's because of how .live works:
It binds to the event on document and waits for a particular event to bubble up to there. Then it checks if the event.target is what you wanted to handle. *
If you click on a link element it's quite possible that the browser goes to the new page before the event bubbles high enough to trigger your code. In an app with lots of HTML and event handlers all the browsers should have problems. Opera just starts displaying the new page and destroys the previous quicker in this case. It really depends on a particular situation more than on the browser. For example: you probably won't see this happen if you had a high network latency while connecting to the site.
To prevent default action on a a element you have to use .bind like in the old days ;) when a eveloper had to be aware of what he loads with AJAX and bind new events to that in a callback.
* There is more to that and .live is more complicated. I just described what is needed here.
What happens when you attach the handler using:
$ (something).bind ("click", function (e) {
// do something
})
You can also try to attach the handler using .click() method.
The following code works as expected in Opera 11.50.
<!doctype html>
<title></title>
<a id="GetResults" href="http://google.com">Get Results</a>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script>
jQuery(document).ready(function () {
jQuery("#GetResults").live("click", function(e){
alert('doing something');
e.preventDefault(); //Opera doesn't execute anything here
});
});
</script>
Either it is a corrected bug, or something more subtle.
Can you check whether the above works on your version of Opera / jQuery?
Read this article: http://jupiterjs.com/news/why-you-should-never-use-jquery-live
try use delegate instead
Not sure if you want to do it, or if it will work for you. I had similar issues with Opera 9.5 and e.preventDefault() not working, the only solution I found was to just return false...
jQuery(document).ready(function () {
jQuery("#GetResults").live("click", function(e){
e.preventDefault();
return false;
});
};
There are two aspects of an event bubbling worth considering in this case: propagation and the default action.
Propagation refers to the event bubbling. First the anchor tag gets the click event, then its parent element, then its parent's parent, and so forth, up to the document element. You can stop an event from propagating at any time by calling e.stopPropagation().
The default action is what the browser will do if nothing is done to prevent it. The most well-known case is when an anchor with an href is clicked, the browser will try to navigate there. There are other examples too, though, for example when you click and drag an image, many browsers will create a ghost image you can drop on another application. In both cases, you can stop the browser from doing the default action at any time by calling e.preventDefault()
As mentioned in other answers to this question, jQuery's .live() feature sets a handler at a high level element (like document) and takes action after events have propagated up. If a handler in between the anchor and the document calls e.stopPropagaiton() without calling e.preventDefault() it would stop the live handler from responding, while still allowing the browser to navigate (the default action).
I doubt this is what's happening, since it would affect all browsers, but it's one possible explanation.
Ensure that document.ready event happens before you click on link.
Try to put all lives in the top of the document.ready wrapper. It may help, if you have a lot of javascript code.

Categories

Resources