How attach document.click event without touching some element? - javascript

I need to click on a document to call some function, but the problem is that when I click on some element that want it doesnt react, so the code:
<body>
<div class="some_element">
some element
</div>
</body>
and js:
$(document).click(function(){
//something to happen
})
and now if I click on the div with class="some_element" the document.click event will be called, but I need to call that event ONLY when I click on the document; or it is possible the make this element an exception?
More detailed:
$('#forma').click(function(e){
e.stopPropagation();
$('#assignment_type_list').slideUp();
})
Lets say #forma - its a parent element of those element, so when I click on the page I want to slideUp someElement and:
$('#assignment_type_select, #assignment_type_label').click(function(){
$('#assignment_type_list').slideToggle();
})
this is the elements when they are clicked the other element is toggled, but the problem is that when I click on this elements the $('#forma').click - also executes, because its parent and the e.stopPropagation() - doesn't help.

All this stopPropagation stuff is right, though this'll cause your script to throw errors on older versions of a certain browser. Guess which one? a cross-browser way:
$('#elem').click(function(e)
{
e = e || window.event;//IE doesn't pass the event object as standard to the handler
//event would normally work, but if you declared some event variable in the current scope
//all falls to pieces, so this e || window.event; thing is to be preferred (IMO)
if (e.stopPropagation)//check if method exists
{
e.stopPropagation();
return;
}
e.cancelBubble = true;//for IE
});
However, you wanted to check if the element that was actually clicked, is the one you need. The problem with that is, that the way the event is passed through the DOM. In W3C browsers the event is first passed to the document, and then clambers down to the element that was actually clicked (propagates through the dom). By contrast IE dispatches its events on the element itself, and then sends it up to the document (except for the change event triggered by select elements... to add insult to injury). What this effectively means is that a click event that is registered in to body element in W3C browsers might be on its way to a checkbox of sorts, or it could be a click inside an empty div. Again, in IE, when a click event reaches the body tag, it could have been dispatched too any element on the page. So it may prove useful in your case to google: event delegation, or turn to jQuery's .delegate() method.
Or check the event object to see if the event is allowed to propagate through or not:
var target = e.target || e.srcElement;//target now holds a reference to the clicked element
The property names neatly show the difference between the bubbling model and the propagating one: in the first case (srcElement), the event is coming from a source element in the dom. In the W3C propagating model, the event is cought while it's headed for a target element somewhere in the dom. Look at it like a heat-seeking missile (w3c) versus a shower of debris after the target was shot down (IE, always the destructive one, and in this case often to late to respond to the events, and therefore to late to handle them:P)

One way to do it is to check for the event's target.
$('html').click(function(event){
if (event.target != this){
}else{
//do stuff
}
});
Here's a working fiddle

Elements on the document are part of the document, so if you click "some_element" in the document, it is obvious that event registered on document will be fired/triggered. If you dont want to execute code which was for "document" then first get the element OR "event source" which originates this event, and check if it was "some_element" in your question above.

Related

Need to get info from any element, which was clicked, but not from parent elements

Need to get info from any element, which was clicked.
Example:
<div>text1<section>text2</section></div>
and JS
$(function(){
$('body *').click(function(){
alert($(this).get(0).tagName.toLowerCase());
});
});
If I click text2, parent element throw alert too. I need only first alert from section. How I can block next alerts from all parent elements of section.
Use event.stopPropagation() to prevent the event from firing on the containing elements.
$(function(){
$('body *').click(function(e){
e.stopPropagation();
alert($(this).get(0).tagName.toLowerCase());
});
});
Just wanted to expand on Kooilnc answer - Using on with event delegation is another option.
Event delegation would be nice if you have an event listener bound before or after on a node that needs to listen to a click handler that has bubbled up. If you stopPropagation, this obviously would be an issue.
Here's a fiddle with a demo:
http://jsfiddle.net/ahgtLjbn/
Let's say a buddy of yours has bound an event listener to a node higher up in the DOM tree. He expects any events that bubble up to it, to be handled by his script.
Using event delegation, the event still bubbles up (so your buddies code will still fire), but it will only alert once (since we called e.stopPropagation).
Calling on without event delegation, or binding the event directly using click (which, under the hood, is just calling on) will prevent the event from bubbling, so your buddies code will never run.

Best practices for where to add event listeners

On my page, the user clicks on an element in order to edit it. To facilitate this, I assign the class editable to all such elements.
How should I listen for clicks on all these elements? Currently, I'm doing this:
document.body.addEventListener("click", (event) => {
if (event.target.classList.contains("editable")) {
// do stuff
}
});
The alternative would be to set a listener on every element, like this:
const editables = document.getElementsByClassName("editable");
for (const editable of editables) {
editable.addEventListener("click", editElement);
}
It seems to me that the first way must be better for performance, since it's only one element being listened on, but is it possible to degrade performance by attaching all such events to the body element? Are there any other considerations (e.g. browser implementations of event handling) that I'm neglecting which would suggest doing it the second way?
Short answer: definitely do it the first way. Event delegation is way more performant, but requires extra conditionals in your code, so it's basically a complexity versus performance tradeoff.
Longer Answer: For a small number of elements, adding individual event handlers works fine. However, as you add more and more event handlers, the browser's performance begins to degrade. The reason is that listening for events is memory intensive.
However, in the DOM, events "bubble up" from the most specific target to the most general triggering any event handlers along the way. Here's an example:
<html>
<body>
<div>
<a>
<img>
</a>
</div>
</body>
</html>
If you clicked on the <img> tag, that click event would fire any event handlers in this order:
img
a
div
body
html
document object
Event delegation is the technique of listening to a parent (say <div>) for a bunch of event handlers instead of the specific element you care about (say <img>). The event object will have a target property which points to the specific dom element from which the event originated (in this case <img>).
Your code for event delegation might look something like this:
$(document).ready(function(){
$('<div>').on('click', function(e) {
// check if e.target is an img tag
// do whatever in response to the image being clicked
});
});
For more information checkout Dave Walsh's blog post on Event Delegation or duckduckgo "event delegation".
NOTE ON CODE SAMPLE IN OP: In the first example, target.hasClass('editable') means that the specific thing clicked on must have the class editable for the if block to execute. As one of the commenters pointed out, that's probably not what you want. You might want to try something along these lines instead:
$(document).on('click', function(e) {
if ($(e.target).parents(".editable").length) {
// Do whatever
}
});
Let's break that down a bit:
$(e.target) - anything that on the page that was clicked converted to jQuery
.parents(".editable") - find all the ancestors of the element clicked, then filter to only include ones with the class "editable"
.length - this should be an integer. If 0, this means there are no parents with "editable" class
Another plus point for the first method
I was using the second (alternative) method that you have mentioned I noticed that when the ajax loaded... the newly created elements were not listening the event. I had to redo the for loop after ajax every time.
With the first method which looks like following in my code also works with ajax.
document.addEventListener('click', function (e) {
if (hasClass(e.target, 'classname')) {
// do stuff
}
}, false);
So first one is better

Multiple JS event handlers on single element

I am working with an existing web app, in the app there are a variety of submit buttons on different forms, some using regular http post, some defining an onClick function, and some binding a js event handler to the button using a class on the element.
What I want to do, is bind another event handler to these buttons by just adding a class to the buttons, but what I want to determine is will the new event handler be guaranteed to be executed, or could one of the form submit actions happen before it does meaning my new function isn't hit.
The example scenario is I want to add a class to these buttons that bimds them all to a common js function that simply logs usage to some api. Is there a risk that the logging function isn't called because the form submit has navigated away from the page?
I've not done loads of js development, and I could test this 100 times over and just get lucky with it firing.
Below is some code I have tested with for one of the examples - again, I'm not asking how to bind multiple events, the question is to about my understanding of the spec and whether execution of all handlers is guaranteed.
$(document).ready(function(){
$('.testingBtn').click(function() {
window.location.replace("http://stackoverflow.com");
});
$( ".testingBtn" ).click(function(){
alert('submitting!');
});
});
<input class="testingBtn" type="submit" id="submitform" value="Complete Signup" />
As seen above, I can bind the multiple events, and in this example, just directed to another url, but this could be a form.submit() etc. In my testing the alert has always fired first, but am I just getting lucky with the race conditions?
In JS, you don't really have control over what order the event handlers are called, but with careful delegation and well-placed listeners, it is possible.
Delegation is one of the most powerful features of the event model. As you may or may not know: in JS, an event is handed to the top of the dom, from where it propagates down to the element onto which the event should be applied. It stands to reason, therefore, that an event listener attached to the global object will call its handler prior to a listener that has been attached to the element itself.
window.addEventListener('click',function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
console.log('window noticed you clicked something');
console.log(target);//<-- this is the element that was clicked
}, false);//<-- we'll get to the false in a minute
It's important to note we actually have access to the event object in the handlers. In this case, we left the event object untouched, so it'll just continue to propagate down to the target, on its way down, it might meet with something like this:
document.getElementById('container').addEventListener('click', function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
if (target.tagName.toLowerCase() !== 'a' || target.className.match(/\bclickable\b/))
{
return e;//<return the event, unharmed
}
e.returnValue = false;
if (e.preventDefault)
{
e.preventDefault();
}
}, false);
Now, this handler will be called after the listener at the window level calls its helper. This time, the event is changed if the clicked element didn't have the clickable class, or the element is a link. The event is canceled, but it lives on, still. The event is still free to propagate further down the dom, so we might encounter something like:
document.getElmentById('form3').addEventListener('click',function(e)
{
e = e || window.event;
if (e.returnValue === false || e.isDefaultPrevented)
{//this event has been changed already
//do stuff, like validation or something, then you could:
e.cancelBubble = true;
if (e.stopPropagation)
{
e.stopPropagation();
}
}
}, false);
Here, by calling stopPropagation, the event is killed off. It can't propagate further down the dom to its target unless the event was already altered. If not, the event object travels further down the DOM, as if nothing happened.
Once it reaches its target node, the event enters its second phase: the bubble phase. Instead of propagating down into the deeps of the DOM, it climbs back up, to the top level (all the way to the global object, where it was dispatched... from whence it came and all that).
In the bubble phase, all the same rules apply as in the propagation phase, only the other way around. The event object will encounter the elements that are closest to the target element first, and the global object last.
There's a lot of handy, and clear diagrams for this here. I can't put it any better than good 'ol quirksmode, so I suggest you read what they have to say there.
Bottom line: when dealing with 2 event listeners, attach them both on a different level to sort-of queue them the way you like.
If you want to guarantee both are called, only stop the event from propagating in that handler that will be called last.
When you've got two listeners, attached to the same element/object for the same event, I've never come across a situation where the listener that was attached first, wasn't also called first.
That's it, I'm off to bed, hoping I made sense
jQuery makes this easy.
$(document).on('click', '.someclass', function() {
doStuff();
});
$(document).on('click', '.someclass', function() {
doMoreStuff();
});
Handlers then both will fire on click. jQuery keeps a queue of handers for you. And handles document clicks that match a selector of your choice so that they can be triggered no matter when your buttons are created.
I am/was having a similar issue as this. However I can not affect the order of/delegate the pre-existing 'click' events (added by Wicket framework).
But I still need to execute a new custom event before any of the 'click' or 'change' events handled by the framework.
Luckily there are several events that are actually executed in order. The 'mousedown' and the 'mouseup' happens to happen before the 'click'.
http://en.wikipedia.org/wiki/DOM_events
$(document).on('mousedown', function (event) {
event = event || window.event
var target = event.target || event.srcElement;
console.log(target + ' before default event'); // Hold mouse button down to see this message in console before any action is executed
});
OR
$(document).on('mouseup', function (event) {
event = event || window.event
var target = event.target || event.srcElement;
alert(target + ' before default event'); // You may not notice this event fires when the page changes unless this is an alert
});
This will allow the logging to be done (e.g. via ajax) before the actual event is executed e.g. a page change via (ajax) link.
Of course you may need to have more sophisticated means to detect for what the additional event handling should be done, but you can use for example the 'target' information for this. => This script monitors everything on the page, as this is how I need this to be done.

Javascript cancel POST from another piece of code using one single function

Ok, in my code I have for example, this:
$('.follow').click(function(){
var myself = $(this);
var data = {
id: this.getAttribute('data-id')
}
$.post('/users/setFriend', data, function(msg){
myself.text(msg);
myself.attr('data-status-friends', (myself.attr('data-status-friends').toLowerCase() == 'follow') ? 'following' : 'follow');
});
})
However, i put a class of 'auth' on certain elements that if the user is logged out, run this bit of JS:
$('.auth').click(function(e){
e.preventDefault();
alert('not logged in');
});
This works for the majority of elements, but with the above POST, it seems to still action the POST. How can I definitively cancel the events fired by other bits of code if .auth is clicked?
I don't think we should talk about propagation or defaultAction preventing here. The point is that you create two different series of event handlers: one attached to the .follow elements, another - to the .auth elements. Of course, if an element has two classes, clicking it will trigger both handlers automatically - and they both will be attached to this element (hence no propagation).
The most simple solution here, I think, is to remove click handler from an element when you assign .auth class to it.
Or, alternatively, you can check $(this).hasClass('auth') condition within the .follow handler function - and return false immediately if that's the case.
Rather than stopping the events, I think a better approach would be either to simply not put events into elements which aren't supposed to be doable without logging in, or track the auth state in your JS code with a variable and check that before doing actions. The latter would probably be a better approach to use if you are building a client-side MVC style application.
You could have two issues going on here with different sets of solutions.
The listeners are attached to different elements in reverse of what they should be
The listeners are being attached to the same element
Different Elements
Your handlers are out of order, swap them so that e.stopPropogation() is on the inner(child) element and the $.post() call is on the outter(parent) element.
Same Element
If the listeners are on the same element, neither e.stopPropogation() nor e.preventDefault() will do what you wish as the event listeners will still fire on the same element.
stopPropogation()
Description: Prevents the event from bubbling up the DOM tree,
preventing any parent handlers from being notified of the event.
Propagation according to the DOM Level 2 Spec will still execute all listeners on the same element but not events attached to the parent:
If the capturing EventListener wishes to prevent further processing of
the event from occurring it may call the stopProgagation method of the
Event interface. This will prevent further dispatch of the event,
although additional EventListeners registered at the same hierarchy
level will still receive the event.
preventDefault()
Description: If this method is called, the default action of the event
will not be triggered.
Default actions are are based on which element is being acted upon (W3C). For a link this would be the redirect to the href="" value, input element to focus it, etc. This is not what you desire as you are most likely not the 'default behavior'.
One option is to attach the handler that calls $.post to an element that is higher on the DOM.
$('.follow').parent().click(function(e){e.stopPropogation()})
You might have to alter your target HTML so that you have an inner(child) and outter(parent) element to attach your events to. The goal being to have the $.post handler as the outer(parent) and the inner(child) handler cancel the event.
Another option is to add a check to see if the other class is present on your element.
$('.follow').click(function(){
var myself = $(this);
if(!$(this).hasClass('.auth')){
var data = {
id: this.getAttribute('data-id')
}
$.post('/users/setFriend', data, function(msg){
myself.text(msg);
myself.attr('data-status-friends', (myself.attr('data-status- friends').toLowerCase() == 'follow') ? 'following' : 'follow');
});
}
});

Javascript Event Delegation - Click Event Not Bubbling As Expected

I'm probably making a really simple error somewhere, but for some reason the event doesn't seem to be bubbling properly.
The Javascript is fairly trivial, uses jQuery at the moment for ease of DOM manipulation but don't want to rely on it for event handling.
jQuery(document).ready(function($) {
document.onclick = function(event) {
event = event || window.event;
var target = event.target || event.srcElement;
var target_class= target.className.toLowerCase();
if(target_class.indexOf('test_class') >= 0)
{
$('#log').append('<p>clicked!</p>');
event.preventDefault();
}
}
});
The HTML for the purpose of demonstration is similarly trivial.
text
<img src="http://img810.imageshack.us/img810/9111/trianglesl.png">
<div id="log">
<h3>Log</h3>
</div>
As you can see at http://jsfiddle.net/SuBQQ/1/ the hyperlink works, whereas clicking on the image doesn't trigger the correct event, and instead uses the event on the image (which I thought would then bubble up the DOM but it appears I'm wrong).
Am I doing this all wrong, or just missing something small?
The issue with your code is that clicking on the image sets the target to the img DOM object and then your code tests to see if test_class is on that object. But, the image doesn't have that class. If you set a breakpoint or an alert inside the event handler, you can see that it is working - it's just your test for the presence of the class name on the image that fails.
The event is handled by the tag and the page location is changed so there's no opportunity for the document event handler to do anything with it. If you change the href on the link to "#" so it doesn't change the page as an experiment and put some debugging in your click handler, you will see that it does bubble up to the document. But, in your jsfiddle, it doesn't work because the link is processing the event before the document and changing the page.
See this for a demo: http://jsfiddle.net/jfriend00/7a9Ha/.

Categories