Canceling event bubbling on specific elements - javascript - javascript

Basically, I want to make clicking anywhere on a page except in an input field and on one block level element (an by extension all children there in) erase said input field. So I put an onclick event on the whole document. To keep the conditions above I put conditions before the clear instructions to only do it if the event did not arise from specific elements.
clearSearch = function (e){
e ? e : e = window.event;
e.target ? target = e.target : target = e.srcElement;
if (target.nodeName != "INPUT" && document.getElementById('ul'))
document.getElementById('input').value = "";
}
With this method if I want to keep clicking on the ul from causing the action I have to explicitly state it in this if statement. Then I have to do the same for the li elements under the ul. Then any other children I create.
Basically this seems really inefficient and I was wondering if someone could help me out in thinking up a better solution to this mess I have created for myself.

You can remove the complex logic from clearSearch and just have it clear the search box. Then add some new onClick handlers to the elements you don't want to call this method. In those handlers set event.cancelBubble to true to prevent the clearSearch function being called.
From quirksmode:
For a complete cross-browser experience do:
function doSomething(e)
{
if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
}

Related

Closure event delegation - event listener on DOM parent that covers children/descendants of a given class

In jQuery, you can do the following:
$('#j_unoffered').on('click', '.icon_del', function () {...
This puts one handler on the element j_unoffered that fires if any descendant element with class icon_del is clicked. It applies, furthermore, to any subsequently created icon_del element.
I can get this working fine in Closure where the click is on the element itself.
goog.events.listen(
goog.dom.getElement('j_unoffered'),
goog.events.EventType.CLICK,
function(e) {...
How can I specify a parent event target in Closure that works for its children/descendants in the same way as the jQuery example?
I'm assuming I need to use setParentEventTarget somehow, but I'm not sure how to implement it for DOM events. Most of the documentation I've found pertains to custom dispatch events.
-- UPDATE --
I'm wondering if there is anything wrong with this rather simple solution:
goog.events.listen(
goog.dom.getElement('j_unoffered'),
goog.events.EventType.CLICK,
function(e) {
if (e.target.className.indexOf('icon_del') !== -1) {...
It still leaves this bound to the parent, but e.target allows a work-around. The fifth argument in listen (opt_handler) allows you to bind this to something else, so I guess that's an avenue, too.
I don't know about such possibility too, so I suggest other piece of code:
var addHandler = function(containerSelector, eventType, nestSelector, handler) {
var parent = goog.isString(containerSelector) ?
document.querySelector(containerSelector) :
containerSelector;
return goog.events.listen(
parent,
eventType,
function(e) {
var children = parent.querySelectorAll(nestSelector);
var needChild = goog.array.find(children, function(child) {
return goog.dom.contains(child, e.target);
});
if (needChild)
handler.call(needChild, e);
});
});
Usage:
addHandler('#elem', goog.events.EventType.CLICK, '.sub-class', function(e) {
console.log(e.target);
});
Update:
If you will use this e.target.className.indexOf('icon_del') there will be possibility to miss the right events. Consider a container div with id = container, it has couple of divs with class innerContainer, and each of them contains couple of divs with class = finalDiv. And consider you will add event handler with your code above, which will check e.target for innerContainer class. The problem is when user will click on finalDiv your handler will be called, but the event target will be finalDiv, which is not innerContainer, but contained by it. Your code will miss it, but it shouldn't. My code checks if e.target has nested class or contained by it, so you will not miss such events.
opt_handler can't really help you either, because there might be many nested elements you want to hanlde (which of them will you pass here? maybe all, but not that helpful, you can get them in event handler whenever you want), moreover they can be added dynamically after, so when you add handler you could not know about them.
In conclusion, I think doing such a job in an event handler is justified and most efficient.
What you are referring to is called event delegation
It seems that this is not possible (out of the box) with Google Closure Library; So my recommandation is to use jQuery or another similar event handling library that offers this functionality. If this is not possible or if you wanna do it by hand here's one possible approach (NOTE: this is not for production use)
var delegateClick = function(containerId, childrenClass, handler){
goog.events.listen(goog.dom.getElement(containerId), goog.events.EventType.CLICK, function(event){
var target = event.target;
//console.log(event);
while(target){
if ( target.className && target.className.split(" ").indexOf(childrenClass)!== -1) {
break;
}
target = target.parentNode;
}
if(target){
//handle event if still have target
handler.call(target, event);
}
});
}
//then use it, try this here: http://closure-library.googlecode.com/git/closure/goog/demos/index.html
//..select the nav context
delegateClick( 'demo-list' ,'goog-tree-icon', function(event){console.log(event);})
Here's a more in depth analysis of event delegation
Again, you should use a proven library for this, here are some options: jQuery, Zepto, Bean

IE attach event to checkbox?

I have an html list of checkboxes but I don't know how to get the 'document.attachEvent' to find those checkboxes on an onclick?
I wasn't sure if I could set the event model specific to the IE then in a document.attachEvent create a for loop that goes through every checkbox and handles each one? Also, my checkboxes are of all different names so I can't checkboxname.attachEvent unless I did that to each one.
My elements are dynamics enough that I tried adding en event to the broadest ancestor, which was the document to that I could use the event to get the target and type with no avail.
Much Thanks.
Some fixes to your code:
document.attachEvent('onclick', function (e) {
var target = e.srcElement;
if (target.type === 'checkbox') {
if(target.checked){
button.disabled = false;
} else {
button.disabled = true;
}
}
});
The third argument is not used in IE's event handling model. e.srcElement refers to the clicked element.
I'd suggest you to wrap the checkbox(es) in a div or some other element, and then attach the event listener to the wrapper. When your page gets larger, checking all clicks on the document will be a time consuming operation. If you've only one checkbox, it's better to attach the handler to itself ofcourse.

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.

focusin event triggers in IE for floated and Inline Block element

The focusin event which is a part of form inputs and link tag, is getting applied for div and span tags. It happens for floated and inline-block element. The issue is spotted in IE (8 and 9).
Demo ==> http://jsfiddle.net/Agczq/
Is there a way to stop this?
Unlike you might expect focusin and focusout do bubble up, unlike the regular focus event. The two main ways of dealing with this are: Event delegation, or simply stopping the event from bubbling up.
Your fiddle is hard to make sense of (ie: don't know what you're trying to do). The first thing that comes to mind (after moving your JS code to the JavaScript field on jsfiddle) is that you're capturing a focus event in good browsers, and the bubbling focusin in crummy IE. This can be quite annoying, particularly since IE doesn't support actual event capturing. Nevertheless, here's a (simple, but tested and working) suggestion:
function mycb(evt)
{
evt = evt || window.event;//get event object
var from = evt.target || evt.srcElement;//get source
if (evt.stopPropagation)//stop event from bubbling
{
evt.stopPropagation();//shouldn't be necessary, but you never know
}
else
{
evt.cancelBubble = true;//stop propagation in IE-lingo
}
if (from.tagName === 'DIV' || from.tagName === 'SPAN')
{//if source was a tag that souldn't fire event, return false;
if (evt.preventDefault)
{
evt.preventDefault();
return false;
}
evt.returnValue = false;
return false;
}
alert("fired!!!");
}
var elem = document.getElementById("mybox");
if( elem.attachEvent )
{
elem.attachEvent("onfocusin", mycb);
}
else
{
elem.addEventListener("focus", mycb, true);
}
This code was tested in IE8, fiddle can be found here. It just so happens I read about this event today, here. A source worth mentioning, I reckon.
Hope this helps

How to disable JQuery keypress event for just one element?

On my site I have registered a keypress event handler for the whole document.
$(document).keypress(myhandler);
And I handle the 'space' key to scroll a list.
Trouble is, there is an <input type='text' /> element, and I don't want 'space' keypress to scroll the list when it is entered in the input.
I couldn't find any information in the "event" object passed by JQuery to the handler, to identify where the source of the event is.
Alternatively, you could attach another event handler to the input field, and in this handler stop the propagation of the event:
jQuery('#input-field-id').bind('keypress', function(e) {
e.stopPropagation();
});
This way, you can leave the global event handler as it is. Might be cleaner.
you're looking for event.target.id, which will be the id of the element that the event was raised on. So inside myhandler you would need something like the following
function myhandler(e) {
if (e.target.id !== 'id of input') {
/* rest of event handler */
}
}
See the QuirksMode docs about event order, and especially about how to turn off the events, which is browser-specific. Quote:
For a complete cross-browser experience do
function doSomething(e)
{
if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
}

Categories