Tuesday, October 16, 2007

jQuery.Listen

This plugin brings a clean, light solution, to websites with dynamic loaded content, or full of event bindings.
Intead of bound, handlers for events, are registered along with matching selectors. And they'll still work for new added content.
This is achieved, using Event delegation, so the plugin will only work for events that bubble.

The plugin supports these kind of selectors:

  • #id
  • tag
  • .cssClass
  • tag.cssClass
The plugin now supports comma-separated selectors
I'd like to add more support in the future, but perfomance and scalability are more important. These simple selectors give enough functionality for many cases. If, for example, you want ULs, to react to clicks on its LIs, then you can do this:
$('ul').listen('click','li',function(){
    alert('you clicked an item!!');
});
No matter how many times you add/remove items, they'll still be clickable. You can also do this:
$.listen('click','li',function(){
    alert('you clicked an item!!');
});
This time, the listener will be the document. So now you can add/remove ULs as well, clicking the items will still trigger the handler.

Links

Downloads

I really advice using the minified versions. The code is optimized for those releases. Source versions should only be used to learn.

Update 2/20/08:
jQuery.Listen can now handle both focus and blur events thanks to the focusin/focusout workaround.
Many thanks to Jörn Zaefferer for lending me the code!
Update 3/7/08:
Added 1.0.3, it can handle comma-separated selectors, and there's a new experimental option. You can call:
  $.listen.cache(true);
Only after you finished with ALL the bindings, it will then start caching some things. Again, this is experimental.

72 comments:

Anonymous said...

Hello, i would like to use listen with history/remote plug in for jQuery, but I'm not seeing how i can do that.

Could you give me a tip?

Here is the other plug in:

http://stilbuero.de/jquery/history/

Flesler said...

You should be able to use jQuery.Listen the way it is showed in the examples.

However, I got a ticket saying a problem arised when combining it, with history/remote, here:http://jquery.com/plugins/node/660 .

Later on, I saw the same ticket for LiveQuery, so I assume history/remote might have some problem. There is also a bug in jQuery 1.2.1 that might affect.

If you have a specific doubt, or problem, prepare a link and I'll take a look. As for general guidelines, you can check the documentation. If you don't understand something, don't hesitate to ask.

hlz said...

I needed support for classnames like 'class-name' so i've changed the regex on line 144 into this:

Index.regex = /#([\w\d_-]+)$|(\w*)(\.[\w_-]+)?$/;

Notice the '-' somewhere at the end.

Thanx for this wonderful plugin.

Flesler said...

Thank you for letting me know, I'll change it and I'll also expose it to allow a cleaner modification.
I'll add a new release soon.

David said...

I'm a major geek for event listening APIs, and this plugin looks really cool.

How would you compare the Listen plugin to the LiveQuery plugin? Does it solve the same problem in a better way? Or is it an entirely different beast?

Ariel Flesler said...

Hi David
  I'd say jQuery.Listen is a different approach to solve a similar problem.
This plugin should work faster than LiveQuery, and use much less memory. Note that LiveQuery has some sort of Mutation events, achieved by executing a certain function when elements are added and removed.
Also jQuery.Listen won't work for events that bubble (though this remainds me Jorn's solution for focus and blur). Also the propagation must not be stopped in the way up, or the listener won't catch the event.
In short, I think LiveQuery is pretty easy to implement, and it's pretty safe too. jQuery.Listen might perform better (specially for large pages) but needs to be understood.

I'm glad you got interested, by the way.
Cheers.

Mika Tuupola said...

It would be nice improvement if you provided links for you plugins sources. Now I need to either download and unzip a zip file or search for demo page source for .js link.

Ariel Flesler said...

Hi Mika
Thanks your your comment.
I always keep the source in one place ( in the project page ). That prevents myself from having to update many places on each release. That prevents the possibility of having different versions spread out there.
All my plugins have many versions. Source, minified and packed(if large enough). Also all the ScrollTo dependant plugins, bring all the versions of ScrollTo, that seems like a lot of link when you can just get them all together and decide for yourself.

I hope you don't mind :)
Cheers

Victor said...

onchange is an event that bubbles, but it keeps telling me that it's not supported.

Ariel Flesler said...

Hi victor

onchange does bubble, but not on MSIE.
I'm not sure I documented it but you can stop those alerts by setting:
  $.listen.strict = false;

I plan to make a few improvements to the plugin, and I have a better demo, 80% done.

Cheers

Victor said...

Ah great! Looking forward to the improvements.
It would be good to be able to do any event using the one plugin rather than having multiple plugins.

Chris said...

Thanks for this plugin, solved alot of headaches, can't wait for the next release!

Ariel Flesler said...

Hi Chris
  A few things:
1- In the demo you posted, the url for jquery.listen is wrong! it has a 404.
2- The change event doesn't bubble in IE.. just letting you know, it won't work in IE. I plan to add the focusin/focusout patch to allow listening for blur and focus (maybe with a few hacks, also for change).
3- I think you are using it incorrectly. If you use matched elements, $('.url') in your demo, those are meeant to be elements that contain the actual inputs (or w/e). So, if you have a form with inputs, you use $('form').listen('click','input',function(){});
You can also use $.listen, in that case, the <html> is used, it contains everything else.

I see you deleted, if you already solved it.. then cool :)

Cheers

Chris said...

Yep, I got it all figured out. Thanks again!

Ariel Flesler said...

Release 1.0.2 includes the focusin/focusout workaround.
So now blur and focus can be safely handled (the demo exemplifies it).

James said...

How can i access the specific element that triggered the listen event. e.g:

with the following code:

jQuery.listen( 'click', 'tr', myFunction());

How can i access the specific 'tr' element that was clicked from within myFunction?

Also - if I want to access the DOM node that was clicked, how would i do that? (i.e. the event.originalTarget).

Thanks for any help. Great work on the plugin!

Ariel Flesler said...

Hi James

You access the clicked element by using the this(the scope) of the function.
e.g:

$.listen('click','tr',function(e){
this.nodeName == 'TR';//true
});

You also have e.target which is the same element, most of the time.
Only if you set an indexer to be bubbling, or you listen for links, then the this is the element you'd expect, and e.target is the originally clicked element.

In short, use this, e.target might not be the element you expect in a few cases.

Cheers

James said...

Bingo! Thanks for your help.

James said...

I just reviewed liveQuery (as referenced by David earlier in this thread). I wanted to add that while liveQuery does have some neat features, I think that jQuery.Listen is a more elegant solution to dealing with events in a situation where the DOM is dynamically updated after page load...

Instead of individual elements needing to be bound to event listners, jQuery.Listen simply allows you to catch events that occur within a specified container(s). If you have a table with 100 rows, and you want each row to have a hover effect, then jQuery.Listen could use 1 event listner (on the whole table), wheras LiveQuery would need 100 (one for each row).

(fleshler, correct me if any of that looks incorrect!)

Anonymous said...

In the following code, the alert displays successfully if the text outside the span is clicked, but not if the text inside the span is clicked. Is this a bug, intended, or am i missing something?

<div id="testDiv">
<table border="1" cellpadding="5">
<tr>
<td>
<span style="background-color:Yellow;">Test Text Inside Spam</span> Text text outside span
</td>
</tr>
</table>
</div>

<script type="text/javascript">
$(document).ready(function(){
jQuery('#testDiv').listen( 'click', 'td', function(){
alert('you clicked a row!!');
});
});
</script>

Ariel Flesler said...

Hi

It's not a bug, when you click inside the span, you are clicking the span, not the td. Normally it would work, but as Listen only gets to know it happened once it reaches the td, then it won't notice when clicking the span.

You can change this though.
If after you called Listen, you do:
$('#testDiv').indexer('click').bubbles = true;

Then it will work, note that this affects perfomance.

The other option is to listen for clicks on TDs and SPANs.
I haven't added it(dunno why) but I'll add support for comma separated selectors, will include it soon.

Cheers

Ariel Flesler said...

@james
What you say is completely right, using Listen is lighter as it only requires one handler, and it passively waits for events instead of monitoring them.

Ciao

Ariel Flesler said...

Added support for comma separated selectors, get 1.0.3.

James said...

The comma separated selectors, and the notes about event bubbling are very useful - thanks!

Anonymous said...

Can this plugin be used to listen to the hash variable in the URL?

example:

index.html#color=green&place=3&debug=on

Ariel Flesler said...

Nope, only for Firefox 3, which actually has an event that is triggered when the hash changes.

Check the plugin called History/Remote.

Kevin said...

Awesome plugin Ariel.

I was wondering if I can use it to listen for an innerHTML change on a DIV?

Thanks for the hard work.

Ariel Flesler said...

Hi Kevin

No, that's not a formal (crossbrowser) event.
If you want to listen specifically for calls to $().html(), try this: http://flesler.pastebin.com/f35d6e7af

Cheers

bmckenzie said...

Hello Ariel,

Should handlers bound with the Listen plugin respond to scripted triggers?

I tried the code below in the Firebug console on your demo page, and it had no effect.

var a = $('.click');
a.eq(0).click();

Thx,

Bruce

Ariel Flesler said...

Hi

No, triggered events in jQuery, do not bubble, thus, the listener is not notified about them.

You can make that work, using the plugin called jQuery.Bubble, also in this blog.
Instead of trigger, you use bubble.

bmckenzie said...

OK, bubble instead of trigger it is :-) Thanks much.

Bruce

synaptic love said...

listen() is a great function and much appreciated.

However, I was hoping for it to be sensitive to a microsoft IE drag event (generated by jquery draggable), but I guess there isn't such a thing?

The was to apply $(this).droppable(...) to an element just-in-time as mouse was over it. the "mouseover" event doesn't trigger in IE in that situation though it seems.

Ariel Flesler said...

Hi

Listen can handle mouseover events on IE.
If its being ignored, then it must be a mouseover event that was triggered by jQuery, then its a "fake" event.

To listen for these, you need to use something like jQuery.Bubble, as a replacement for .trigger().

But that can't be changed as it's in a plugin so... :(

Cheers

Wizzud said...

Hi Ariel,
Neat plugin. I have a slight problem with the example you use though.
You give the example of being able to test for clicks on a table's row, as if anyone should be able to lift the code from your example and get it to work 'out-of-the-box' exactly as it is stated. The trouble is, it doesn't!

The implication of using the example as stated is that a click anywhere within a TR will be caught and reported as a click on the TR.

But, as has also been demonstrated by a previous query (clicking inside a TD>SPAN versus clicking the TD directly [anonymous]), this is not the case unless some extra coding is provided - namely...

jQuery('table').listen( 'click', 'tr', function(){
alert('you clicked a row!!');
}).indexer('click').bubbles = true;

I find this confusing, particularly given the statement "...the plugin will only work for events that bubble.", when it appears that, by default, the plugin ignores/disables bubbling!

If I have misunderstood some fundamental concept, I apologise.
[using Listen() v1.0.3]

Ariel Flesler said...

Hi Wizzud

This would happen to any plugin or "piece of code" using event delegation.

You get e.target and nothing more. So unless you traverse the dom up to the listener, you can't imitate natural bubbling.

The bubbles = true is something I added as an option, but I don't really recommend its use as it needs to traverse the dom on each event.

So yes, this is a common problem of event delegation, since 1.0.3 I think, I added support for comma separated selectors, and that's in my opinion, the best approach.

If your TD's contain spans and divs, just do:
$('table').listen('click', 'td,span,div', ... );

Cheers

Wizzud said...

Thank you for responding Ariel.

Then, may I respectfully suggest that you change your 'table row' example to something that works, and works in a manner that you recommend (ie. without requiring indexer(...).bubbles=true)?

If that main example is what draws people in to the plugin (which it undoubtedly is) then it is misleading, because:
a) it doesn't work as is;
b) to make it work requires adding code that you yourself do not recommend.

Also, using...
$('table').listen('th, td, span, p, div, img, input ,..etc', callback);

...does not result in this within the callback being the TR element - no surprise there, but it does mean that it's not a direct replacement for either your example (if it worked), or my modified example.

PS. Don't get me wrong - I like the plugin ... a lot!

Ariel Flesler said...

Hi Wizzud

It's not relevant whether you use TRs, LI's or anything else.

I'd love to improve the plugin, as far as usability goes. But I'm not sure how, I'll stare at it for a while as soon as I get some time for that :)

Cheers (suggestions are welcomed)

Cordan said...

Wonderfull PlugIn :) I had a fun evening trying it out and exploring :)

I would kindly appreciate if you could take a look at my "problem" ;)


I am trying to mark a complete TR at once.

This shall allow people to see where they are "moving" with their mouse, when I have long and wide lists with some action buttons on one side.

To make it more "visible" to see in which line you are, I'm trying to mark the whole TR at once :)

I get it working as long as do it with a TD but not with the TR .. sigh :)

My Source-Example:

jQuery(document).ready(function(){

jQuery('#test').listen('mouseover','tr', function() { $(this).addClass('marked'); });
jQuery('#test').listen('mouseout','tr', function() { $(this).removeClass('marked'); });

});

Ariel Flesler said...

Hi Cordan

The problem with event delegation (the basis of Listen and similar plugins) is that you only get the exact element that got the event.

Probably the table example I used is a bad one, as TRs never get mouseovers (unless you hover the exact border).

You wouldn't get this problem with a UL/LI markup (as in the demo).

One thing you can do is this:
$('#test')
.listen('mouseover','td', function() { $(this).parent().addClass('marked'); })
.listen('mouseout','td', function() { $(this).parent().removeClass('marked'); });

I'd advice you to use .hover() or mouseenter/mouseleave instead of mouseover/mouseout for a more accurate behavior.

});

Cordan said...

Thank you very much for your kind and fast answer. Not usually today on the internet with the hordes of trolls.

As im new to jQuery and this whole "DOM/CSS" magic I appreciate your help :)

I have got it to work with .hover ;)
Thank you very much!

Adam said...

Can jquery.listen be used along side jquery.intercept? I've been having some issues using the two together. jquery.intercept slows down my pages, but I need to use it in a few spots where I have more advanced selectors.

Ariel Flesler said...

Hi Adam

There shouldn't be any problem.
Can I see the page where you're getting the problem ?

Else, at least describe it well.

Cheers

joe said...

How would i convert this livequery:

$('.box').livequery(function(){
$(this)
.hover(function() {
$(this).addClass('over');
}, function() {
$(this).removeClass('over');
});
}, function() {
$(this)
.unbind('mouseover')
.unbind('mouseout');
});

to listen code?

I tried:

$.listen('mouseover','.box',function(e) {
$(this).addClass('over'); });
$.listen('mouseout','.box',function(e) {
$(this).removeClass('over'); });

but it doesn't work quite right

Ariel Flesler said...

Hi

That should work.. not as well as hover, as it does more checks than a regulat mouse(over/out) combination.
You can't listen for mouse(enter/leave) with Listen, as they're not real events.

Maybe with some workaround...
Cheers

Adam said...

I'm having trouble getting the onchange event to fire in IE. My code works in Firefox. Is this supported in jQuery.Listen?

jQuery().listen("change", "select.update",
function () {
alert('onchange');
return false;
}
);

Ariel Flesler said...

Hi

No, IE doesn't bubble the change event.
There are different workarounds for some events (focus,blur,submit,reset).
If you find any out there for change, you can plug it into Listen.

Adam said...

It doesn't look like Intercept works either, so I guess I'm out of luck for now.

Ariel Flesler said...

It's not a matter of Listen or Intercept.
the 'change' event can't be handled using event delegation on IE.

Tim Büthe said...

First of all, thank you for the plugin, it's pretty handy.

One thing I stumbled upon is the following. If you use CSS classes in the selector and you have an element that got for example two classes, that are both in the selector, the given function gets called twice. This maybe reasonable, but in my humble opinon not what one would expect. I worked around this by listening to focus and do an addClass and listening to blur and do an removeClass instead of listening to both and do a toggle. However, just want to let you and your readers know, if you don't do already. Maybe you could note this in the documentation...?

keep up the good work,
Tim Büthe

PS: here is an small example:

<html>
<head>
<script src="js/jquery-1.2.6.min.js" type="text/javascript"></script>
<script src="js/jquery.listen-1.0.3-min.js" type="text/javascript"></script>

<style>

.style1 {
background-color: silver;
}

.style2 {
color: blue;
}

.focused {
background-color: yellow;
}

</style>

<script type="text/javascript">
$(document).ready(function(){
$('body').listen('focus blur', 'input.style1, input.style2', function(){
$(this).toggleClass('focused')
});
});
</script>


</head>

<body>
<input type="text" class="style1" value="test"/>
<input type="text" class="style2" value="test"/>
<input type="text" class="style1 style2" value="test"/>
</body>
</html>

Ariel Flesler said...

Hi Tim

Good point, it's a valid situation (not TOO frequent but valid).

I really tried to keep perfomance as high as possible.
So you'd rather patch it for yourself if you want (no one complained before).

In line 154, replace:
each( handlers,
for
each( $.unique(handlers),

Cheers

Tim Büthe said...

Hello Ariel,

i'm fine with my workaround, if i would change your sources, i would have to do this in every new version and I don't want to ;-)

I understand you can't change this anymore, because this would break somebody else code, but you could mention it in the documentation or give this function a parameter for this.

However, that just my two cents and not really important.

regards,
Tim

Ariel Flesler said...

Hi

Ok, I'll have it in mind.
Thanks :)

amir said...

Hi and thanks for this essential plugin. I have

$.listen('click','a',handler1)

as a general handler for anchors. I also have (would like to have!)

$('#special').listen('click','a',handler2)

to handle known special cases separately. But it seems only handler1 is executed and handler2 never gets called. Is this the expected behavior or am I doing something wrong. Thanks.

Ariel Flesler said...

Hi

You use classes for different sets of links.
You can either bind each set of links with a different handler, or check the classes and work accordingly inside one single handler.

What you're trying to do by calling listen on #special is that only the links inside #special will get thru that handler2.

You can avoid getting handler1 triggered by calling:

e.stopPropagation();

inside handler2, assuming that e is the name of the event object received by the function.

Cheers

Frédéric Fournaise said...
This comment has been removed by the author.
Ariel Flesler said...

You're not passing the function, you're calling it in-place.
Check some js tutorials so you get this clear.

About the 2nd argument, it's a filter, to read more, check the docs on this blog post.

Alexander said...

This is an incredible plug in! Thank you so much for putting the time in.

Everything works great for me except that when I listen for the 'click' event it grabs right click events as well. This is a pain if your are trying to grab pics off the site. Is there a way to avoid this?

Thanks

Ariel Flesler said...

That's not related to the plugin. The click event is triggered for any button, if you want to filter clicks on other buttons than the left one, you need to do something like this:

$.listen('click','#a', function(e){
if( e.button != 0 )
return;
// the code
});

More here.

http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-MouseEvent

Alexander said...

Very nice. Thank you for the quick response!

Anonymous said...

I noticed you are doing a bit of clean up when the window unloads. Why is that? Could you explain this or post a link that does?

Thanks

Ariel Flesler said...

That's done to avoid memory leaks. Same as jQuery's core does. The idea is not to keep references to DOM nodes else they might not be garbage collected.

Cheers

Anonymous said...

Hi! I would recommend that node.nodeName is converted to uppercase for plugin to work on XHTML(application/xhtml+xml) pages.

Thanks for great plugin!

Ariel Flesler said...

Right, will have this in mind for a future release. Thanks.

Fred said...

Hi!
I want to use listen with Colorbox (http://colorpowered.com/colorbox/) but I can't get it running.
I use Colorbox for example like
$("a.showbox").colorbox();
I tried this (and a few other ways):
$.listen("click", "a.showbox", function(){
$(this).colorbox();
});
But it does not work for me.
Can you figure out a way to get this working?
Thx!
Fred

Martin Edwards said...

Hi there,

This is a great little plug in BTW, I heard about it at the AJAX @media conference last year.

There's one small issue I've found, is that I can't listen for an 'li' with a class, then check the a tags within it, any idea why this might be?

$('li.activityLink').listen('click','a',function(item){
alert('I can't get to this');
});

$('li') works fine, but not when I add a class.

This has occurred because I am populating the ul with the li's, via AJAX.

Ariel Flesler said...

@Fred
Dunno anything about ColorBox. You should probably consult the plugin's owner. Listen is merely a binding plugin.

@Martin
The idea is that the context you use for listen() (that is $('li.activityLink') in your case) is not going to be destroyed.

You should rather use:
$('ul').listen('click','a',function(){
if(!$(this).parents().is('li.activityLink'))
return;

// Do stuff
});

That is, if the UL's aren't replaced.

Ed said...

Hi Ariel,

This looks like the solution I need, but I don't know how to implement it for my issue. I am using a plug-in I found online (see http://jqueryfordesigners.com/coda-popup-bubbles/) which shows a "pop-up" image/div etc. when you hover over a link, but I am using this INSIDE is div which is updated via AJAX. Once the div is updated via AJAX, this plug-in doesn't work. Is there a way to use jQuery.Listen to resolve this? I don't know if you can help because I know this also relates to a separate plug-in, but as your plug-in could be the solution I thought I'd ask! I hope you can help, thank you.

Ariel Flesler said...

Hi Ed
I don't know the plugin so w/o a demo, I really don't understand.
I think you should consult the author of the code bubbles plugin.

sheepfunk said...

How would I catch and rewrite an inline onclick with jQuery listen? I'm trying to find all onclicks of elements with a given class, and wrap them in a jConfirm dialogue. I'm having issues with pages where I'm rewriting portions of the page with Ajax, so how would I use jQuery listen to catch the onclicks of my elements and rewrite them on the fly.

Ariel Flesler said...

Hi sheepfunk
That's not what jQuery Listen is used for, sorry.

iltdev said...

This plugin looks fantastic - exactly what I need.

However I'm struggling to change my code to the correct syntax and wondered if someone could help?

My page is basically a results page with pagination. The page number links appear in a div called 'pagination' and the results appear in a div called 'results'.

I use JQuery to cycle through each href anchor in the pagination div and attach an onclick event handler to each. Clicking on an anchor in the pagination fires an ajax load to update the results.

Here is my code:

$('#pagination div a').each(function(i,item){
$(this).click(function(){
var offsetNo = $(this).attr('href').replace('http://www.mysite.com/news?&', '');
$('#results').load('results.php?'+offsetNo, function() {
//call back if needed
});
return false;
});
});

How can I modify the code to work with the plugin?

Hope you can help?
Many thanks

iltdev said...

Following on from my previous post, I've tried the following code:

$('#pagination div a').each(function(i,item){
$(this).listen('click','a',function(){
var offsetNo = $(this).attr('href').replace('http://www.mysite.com/news?&', '');
$('#results').load('results.php?'+offsetNo, function() {
//call back if needed
});
return false;
});
});

This appears to be the correct syntax, but only works once. Once the ajax load has fired, the event handlers are still unbound. I thought this plugin took care of this or am I doing something wrong?