I am using xui js for a mobile web app. Xui js doesn't support the live event like jquery $("#selector").live(). I would like to write some thing at approximates the jquery live.
How does jquery handle event delegation?
Thank you for your time
Mac
To expand on Gaby's answer...
I needed to also look to see if the immediate child was clicked. This can be expanded further to have more of a true delegate like we have in jQuery. However, this code sufficed for my needs.
xui.extend({
is: function (selector) {
var matchedNodes = x$(selector), i=0;
for (i; i<matchedNodes.length; i++)
if (this[0] == matchedNodes[i]) return true;
return false;
},
delegate: function(selector, event, handler){
this.on(event, function(evt){
var elem = evt.target;
if ( x$(elem).is(selector) ){
handler.apply(x$(elem), arguments);
} else{
if ( x$(elem.parentElement).is(selector) ){
handler.apply(x$(elem.parentElement), arguments);
}
}
});
}
});
You can just set the event handling on the document (as live does), or even better find a parent of the elements you want to handle and bind to that (as the delegate does).
Here is a plugin to simulate the delegate function of jquery.
xui.extend({
is: function (selector) {
var matchedNodes = x$(selector), i=0;
for (i; i<matchedNodes.length; i++)
if (this[0] == matchedNodes[i]) return true;
return false;
},
delegate: function(selector, event, handler){
this.on(event, function(evt){
var elem = evt.target;
if ( x$(elem).is(selector) ){
handler.apply(x$(elem), arguments);
}
});
}
});
Usage
x$('ul').delegate('li','click', function(){ this.attr('style','color:red'); });
This will bind an listener to the ul elements, that will handle click events initiated by their descendant li elements. (it will change the clicked elements color to red)
Demo at http://jsfiddle.net/gaby/vhsPU/
As every event delegation system, it makes usage of the fact that browser events do bubble "bottom up". That means, if we have a structure like
<body>
<div>
<span>foobar 1</span>
<span>foobar 2</span>
<span>foobar 3</span>
</div>
</body>
and somebody clicks on the inner foobar span, the browser checks if any event handlers are bound to the span for that click event. If so, execute them. Now the event "bubbles up" to its parent (div). Again, the browser checks if any click event handlers are bound to it, if so execute, and so forth up until the document.documentElement.
In the above example, we would need to bind three event handlers for every <span> element. Knowing the above described concept, we now can just bind one event handler to the <div> parent node and check for the target within the event object. That target is always the original element where the event happened.
There are a couple more things to know. You can stop the propagation of events for instance to explicitly not allow an event to bubble up any further. That can be done by invoking .stopPropagation() which is a method from the event object.
Related
I use jQuery 1.6.2. I want to call a click event just for once when that element appears at page. This is my selector:
span[resizer='north'][title=''] :first-child"
This can work for triggering click event:
$("span[resizer='north'][title=''] :first-child").click();
How can I make it work only once or after that element appears on the page?
Answer as per how I understand your last comment: I mean I will call click event only once. Let's assume that there is a button. I will call click event of that button once. I mean I will programmatically click that button.
$('button').click(function() {
$('span[resizer='north'][title='']:first-child').click();
$(this).unbind('click');
});
You may use DOMNodeInserted event to detect when new element is inserted dynamically.
var alreadyclicked = false; // variable to check that it will be clicked just once
$(document).bind('DOMNodeInserted', function(e) {
if (!alreadyclicked){
$("span[resizer='north'][title=''] :first-child").click();
alreadyclicked = true;
}
});
Update
The DOMNodeInserted event is deprecated along with other mutation events. Some browsers support it, but looks like this won't last long.
You can use mutation observers instead to keep track of changes in DOM
var alreadyclicked = false;
var observer = new MutationObserver(function(mutations, observer){
//if new nodes were added
if( mutations[0].addedNodes.length && !alreadyclicked )
$("span[resizer='north'][title=''] :first-child").click();
alreadyclicked = true;
});
observer.observe( document, { childList:true, subtree:true });
Use a custom event, with a delegated handler attached using .one().
An event has 2 components, triggering and handling. Don't focus on how many times it's triggered, focus on how many times it's handled.
$("document").one("myclick", "span[resizer='north'][title=''] :first-child", function(e) {
do something...;
$(this).click(); //if you actually need the "click"
});
$("span[resizer='north'][title=''] :first-child").trigger("myclick");
This example will mean it's "handled" one time. The other answers on here can be used to ensure it's triggered when the element is added if it's after document ready.
You could also use a pub/sub which would make it even easier. Then you just have to trigger your custom event from the ajax or whatever code that would add the element and not worry about specifically targeting the element. Last line of code in the subscription would be to unsubscribe.
I want an element to listen for a custom event that is actually triggered by itself. The custom event could possibly be triggered from a descendant but should then be ignored. It's important that it origins from itself. It also needs to be an event since I might need additional ancestors to listen for the events.
The .on (http://api.jquery.com/on/) method is able to provide this functionality. The selector argument can be used as filter. However this does not work to filter out the listener element itself.
In short:
-The event must be able to bubble
-The trigger and the listener is the same element
-The listener must ignore the custom event if it's triggered by an ancestors
How is this achieved?
Use case as requested
I use the jquery UI dialog widget
$el = $('#dialogDiv');
$el.on('customEvent', $el /* Won't work */, function() {
//Do whatever
});
$el.dialog({
open: function() {
$el.trigger('customEvent');
}
});
.on works fine; to ignore ancestors check e.target:
$el.on('customEvent', function(e) {
if(e.target === this) {
//Do whatever
}
});
The selector that you can pass to .on() is used for the delegate target to match elements that can handle the click event (and it should be a string, not an object).
But in your case that's not necessary because your selector and the delegate target is the same thing, so this should work as expected:
$el.on('customEvent', function(evt) {
//Do whatever
});
To detect if an event came from a descendent you would compare evt.target against the element itself.
Removing the part that doesn't work, will make it work.
$el = $('#dialogDiv');
$el.on('customEvent', function(e) {
//Do whatever
});
$el.dialog({
open: function() {
$el.trigger('customEvent');
}
});
However, you are asking for other features that a normal event might not support. You should look into setting up a jQuery special event. Check this awesome article by Ben Alman.
When it comes to your prerequisites:
An event is always able to bubble unless its propagation is hindered with event.stopPropagation() or event.stopImmediatePropagation()
The trigger and the listener is already on the same element
The listener will not know what triggered it unless you pass some an argument that can identify the element that triggered it and check if it's an ancestor
See test case on jsFiddle.
Say I have structure like:
<ul id="a">text
<li id="b">text</li>
<li id="c">text</li>
</ul>
How can I assign different event handlers (say, onclick listener) to a, b and c? When I assign a handler to <ul>, it will be triggered when any of the <li> is clicked.
instead of setting a single handler for each element inside your list it's better to use a single event listener on the parent and, using event delegation, detect which is the id of the element the user clicked
$('ul').on('click', function(evt) {
id = evt.target.id;
switch (id) {
"a" : ... break;
"b" : ... break;
"c" : ... break;
default: ... ;
}
});
Whenever an event is fired, it "bubbles". This means it is called on the actual element associated with it, but it is also fired once for every parent element in the chain all the way up to the top. So in your case, whenever you click on b or c, the event will also be fired for a. To avoid this, you need to stop the bubbling.
Specifically, in your event handlers for b and c, you should stop the event bubbling by doing this:
if (event.stopPropagation) event.stopPropagation();
else event.cancelBubble = true;
Most browsers support stopPropagation on the event to end the bubbling. Older versions of IE use the cancelBubble property. So the above should be cross-browser compatible.
I know you didn't specify jQuery, but since it's so popular, it's worth noting that if you are using jQuery, this condition is hidden behind the framework, so you can just do:
event.stopPropagation()
Every event bubbles up the DOM tree (or at least "should", as there is IE). You may want to stop that bubbling, see Ben Lee's answer. But there is a better way:
Just check whether the event triggering your listener was fired for the element you're watching or not. Then execute your handler, or escape.
document.getElementById("a").addEventListener("click", function(e) {
var a = e.currentTarget; // reference to #a
var t = e.target; // this may be #b or #c or any of their children - or not
if (a != t)
return;
// else
// do what you wanted to do
}, false);
You can use this.
$('ul li#a').click(function(){
//some thing here...
});
$('ul li#b').click(function(){
//some thing here...
});
I also faced the same problem in one of my projects. What I did was first declared a global variable(for flag purpose and default value false). Then I triggered the onmouseover and onmouseout events on all the children and changed the flag value to true whenever the cursor is over a child ( and reverting it back to false whenever it leaves the child). So in declaring the onclick function for the parent, just put a condition to check the value of flag.
We are working on a JavaScript tool that has older code in it,
so we cannot re-write the whole tool.
Now, a menu was added position fixed to the bottom and the client would very much like it to have a toggle button to open and close the menu,
except closing needs to happen automatically when a user starts doing things out side of the menu, for example, when a user goes back into the page, and selects something or clicks on a form field.
This could technically work with a click event on the body, triggering on any click,
however there are numerous items in the older code, where a click event was handled on an internal link, and return false was added to the click function, in order for the site not to continue to the link's href tag.
So clearly, a general function like this does work, but not when clicked on an internal link where the return false stops the propagation.
$('body').click(function(){
console.log('clicked');
});
Is there a way I can force the body click event anyway,
or is there another way I can let the menu dissappear, using some global click event or anything similar?
Without having to rewrite all other clicks in the application that were created years ago.
That would be a monster task, especially since I have no clue how I would rewrite them, without the return false, but still don't let them go to their href.
Events in modern DOM implementations have two phases, capturing and bubbling. The capturing phase is the first phase, flowing from the defaultView of the document to the event target, followed by the bubbling phase, flowing from the event target back to the defaultView. For more information, see http://www.w3.org/TR/DOM-Level-3-Events/#event-flow.
To handle the capturing phase of an event, you need to set the third argument for addEventListener to true:
document.body.addEventListener('click', fn, true);
Sadly, as Wesley mentioned, the capturing phase of an event cannot be handled reliably, or at all, in older browsers.
One possible solution is to handle the mouseup event instead, since event order for clicks is:
mousedown
mouseup
click
If you can be sure you have no handlers cancelling the mouseup event, then this is one way (and, arguably, a better way) to go. Another thing to note is that many, if not most (if not all), UI menus disappear on mouse down.
In cooperation with Andy E, this is the dark side of the force:
var _old = jQuery.Event.prototype.stopPropagation;
jQuery.Event.prototype.stopPropagation = function() {
this.target.nodeName !== 'SPAN' && _old.apply( this, arguments );
};
Example: http://jsfiddle.net/M4teA/2/
Remember, if all the events were bound via jQuery, you can handle those cases just here. In this example, we just call the original .stopPropagation() if we are not dealing with a <span>.
You cannot prevent the prevent, no.
What you could do is, to rewrite those event handlers manually in-code. This is tricky business, but if you know how to access the stored handler methods, you could work around it. I played around with it a little, and this is my result:
$( document.body ).click(function() {
alert('Hi I am bound to the body!');
});
$( '#bar' ).click(function(e) {
alert('I am the span and I do prevent propagation');
e.stopPropagation();
});
$( '#yay' ).click(function() {
$('span').each(function(i, elem) {
var events = jQuery._data(elem).events,
oldHandler = [ ],
$elem = $( elem );
if( 'click' in events ) {
[].forEach.call( events.click, function( click ) {
oldHandler.push( click.handler );
});
$elem.off( 'click' );
}
if( oldHandler.length ) {
oldHandler.forEach(function( handler ) {
$elem.bind( 'click', (function( h ) {
return function() {
h.apply( this, [{stopPropagation: $.noop}] );
};
}( handler )));
});
}
});
this.disabled = 1;
return false;
});
Example: http://jsfiddle.net/M4teA/
Notice, the above code will only work with jQuery 1.7. If those click events were bound with an earlier jQuery version or "inline", you still can use the code but you would need to access the "old handler" differently.
I know I'm assuming a lot of "perfect world" scenario things here, for instance, that those handles explicitly call .stopPropagation() instead of returning false. So it still might be a useless academic example, but I felt to come out with it :-)
edit: hey, return false; will work just fine, the event objects is accessed in the same way.
this is the key (vs evt.target). See example.
document.body.addEventListener("click", function (evt) {
console.dir(this);
//note evt.target can be a nested element, not the body element, resulting in misfires
console.log(evt.target);
alert("body clicked");
});
<h4>This is a heading.</h4>
<p>this is a paragraph.</p>
If you make sure that this is the first event handler work, something like this might do the trick:
$('*').click(function(event) {
if (this === event.target) { // only fire this handler on the original element
alert('clicked');
}
});
Note that, if you have lots of elements in your page, this will be Really Very Slow, and it won't work for anything added dynamically.
What you really want to do is bind the event handler for the capture phase of the event. However, that isn't supported in IE as far as I know, so that might not be all that useful.
http://www.quirksmode.org/js/events_order.html
Related questions:
jQuery equivalent of JavaScript's addEventListener method
Emulate W3C event capturing model in IE
I know this is an old question, but to add to #lonesomeday's answer, you can do the same in vanilla JavaScript with:
document.querySelectorAll('*')
.forEach(element => element.addEventListener('click', e => {
console.log('clicked: ', e.target)
}))
This will add the listener to each element instead of to the body, and from experience this will let you execute the click event even if the page is navigating away or if there's already an onclick with stopPropagation in it.
I think this is what you need:
$("body").trigger("click");
This will allow you to trigger the body click event from anywhere.
You could use jQuery to add an event listener on the document DOM.
$(document).on("click", function () {
console.log('clicked');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
document.body.addEventListener("keyup", function(event) {
if (event.keyCode === 13) {
event.preventDefault();
console.log('clicked ;)');
}
});
DEMO
https://jsfiddle.net/muratkezli/51rnc9ug/6/
My fix in Feb 2023:
To trigger a function anywhere on the page/document:
JS code:
document.onmouseup = closeMenus
'closeMenus' would be a function that turns each menu's display value to none.
Any 'mouseup' event anywhere on the document, calls the function.
I have a div, I want to set it so that when I click on something else, it would hide the div.
So I did
$('body').click(function(){
if(loginOpened)
{
$('#loginWindow').animate({
'width':'0px',
'height':'0px'
},"fast");
}
loginOpened=false;
});
However, even when I click in the div itself the event is fired, is there anyway to prevent this?
You can stop it using
e.stopPropagation(); if there is a click event bound to the <div /> tag.
See event.stopPropagation()
Prevents the event from bubbling up
the DOM tree, preventing any parent
handlers from being notified of the
event.
Otherwise you can check the target of the event inside the body click. Check whether event.target is the same as your div.
See event.target
Just check the event.target. If the element that triggered the event is your div do not execute the code.
$('body').click(function(evt){
evt = evt || window.event
if ($(evt.target) != $('#loginWindow')) {
if(loginOpened)
{
$('#loginWindow').animate({
'width':'0px',
'height':'0px'
},"fast");
}
loginOpened=false;
}
});
Yes, but of course Microsoft and the rest of the world came to different conclusions about how to do it. This site gives a good clear rundown of what's needed: http://www.quirksmode.org/js/events_order.html .
I don't use jQuery but the jQuery way appears to be event.stopImmediatePropagation(); as seen in this question: jQuery Multiple Event Handlers - How to Cancel? .
A couple of changes from John's code:
$('body').click(function(ev){
// jQuery means never having to say "window.event"!
// Also, code's cleaner and faster if you don't branch,
// and choose simple breaks over more complex ones
if(!loginOpened) return;
// Lastly, compare using the DOM element;
// jQuery objects never compare as the "same"*
if (ev.target == $('#loginWindow').get(0)) return;
$('#loginWindow').animate({
'width':'0px',
'height':'0px'
},"fast");
loginOpened=false;
});
If trapping it in the body event doesn't work for you, you can just add a simple event handler to the div:
$('#loginWindow').click(function (ev) { ev.stopPropagation(); });
I was going to say return false, but that would prevent other things from firing off the div. stopPropagation just keeps the event from bubbling outward.
I could be really picky, of course...
//Delegation via the document element permits you to bind the event before
// the DOM is complete; no flashes of unbehaviored content
$(document).delegate('body', 'click', function(ev){
//You only have one instance of an id per page, right?
if(!loginOpened || ev.target.id == 'loginWindow') return;
//quotes and px? not necessary. This isn't json, and jQ's smart
$('#loginWindow').animate({width:0,height:0},"fast");
loginOpened=false;
});
* Don't believe me? Try:
jQuery('#notify-container') == jQuery('#notify-container')
Then try
jQuery('#notify-container').get(0) == jQuery('#notify-container').get(0)