I've a table generated dynamically by jQuery, using
this.html("<table><tr><td><div>Click Me</div></td></tr></table>");
within the table, I've a few divs (my sample shows only one to keep things simple), which I want to add click event handler to. I'd like to keep html clean and use as much of jQuery power as I can, but since I'm doing an 'eval' type of things I can't quite figure out how to do that.
I know, that I can use $("div[some attribute selector]").on("click", {}, clickHandler);, but is it a good idea in my case?
You need delegated events. To do that, simply use jQuerys on() method like this:
$(document.body).on('click', 'div', function( event ) {
// do something
});
Ref.: .delegate(), .on()
What is that? Almost all events do what we call 'bubble'. That means, if you click on a nested element, your browser looks if there is any click-event handler ascociated on that node. If so, it executes them and then the parent of that element is also asked if there is any click-event handler. This continues until either some handler prevents the event from further bubbling or we have reached the document.documentElement (html).
So, you should change the above document.body into the closest node relative to your dynamically added elements.
You can use either use live or delegate to do that
Related
I always wondered which is the better way of handling events in terms of code manageability, cleanliness and code reuse.
If you use the former then say a list of 10 anchor tags with click handler will have something like:
Click Me
Click Me
Click Me
... 10 times
which looks kind of odd.
With the latter method, using anonymous function, it'd be like:
$('a').on('click', function(e){});
At the end of the day, every event is bound to some element in the DOM. In the case of .bind, you're binding directly to the element (or elements) in your jQuery object. If, for example, your jQuery object contained 100 elements, you'd be binding 100 event listeners.
In the case of .live, .delegate, and .on, a single event listener is bound, generally on one of the topmost nodes in the DOM tree: document, document.documentElement (the element), or document.body.
Because DOM events bubble up through the tree, an event handler attached to the body element can actually receive click events originating from any element on the page. So, rather than binding 100 events you could bind just one.
For a small number of elements (fewer than five, say), binding the event handlers directly is likely to be faster (although performance is unlikely to be an issue). For a larger number of elements, always use .on.
The other advantage of .on is that if you add elements to the DOM you don't need to worry about binding event handlers to these new elements. Take, for example, an HTML list:
<ul id="todo">
<li>buy milk</li>
<li>arrange haircut</li>
<li>pay credit card bill</li>
</ul>
Next, some jQuery:
// Remove the todo item when clicked.
$('#todo').children().click(function () {
$(this).remove()
})
Now, what if we add a todo?
$('#todo').append('<li>answer all the questions on SO</li>')
Clicking this todo item will not remove it from the list, since it doesn't have any event handlers bound. If instead we'd used .on, the new item would work without any extra effort on our part. Here's how the .on version would look:
$('#todo').on('click', 'li', function (event) {
$(event.target).remove()
})
Second method is preferrable, since we should not be mixing our JavaScript with the HTML. (Separation of Concerns) . This way your code is kept clean.
This also works well with dynamically inserted HTML code.
`$('a').on('click', function(e){});` // Using jQuery.
Using Vanilla JS:
document.getElementById("idName").addEventListener("click", function(){}); // Bind events to specific element.
document.addEventListener("click", function(){}); // Bind events to the document. Take care to handle event bubbling all the way upto the document level.
$(".hovertip").parent().live('hover', function() {
...
The above code doesn't seem to register.
This doesn't seem to work either:
$(".hovertip").parent().live({
mouseenter:
function() {
...
Any examples of .live, .delegate, .bind, or .on working with a jQuery selector and a .parent() selector with .hover() or mouseenter: and mouseleave:?
Update: I've created a separate question to address the dynamic DOM issue this Question has raised: jQuery .on() with a .parent() and dynamic selector
Try:
$(".hovertip").parent().on('hover', function() {
alert('yay');
});
Note: .on was introduced in jQuery 1.8.
Working demo http://jsfiddle.net/pRB8n/ Hover over test test - in the demo
If you really want to use .delegate try this please: http://jsfiddle.net/TURBX/2/ - http://api.jquery.com/delegate/
Delegate
Attach a handler to one or more events for all elements that match the
selector, now or in the future, based on a specific set of root
elements.
Hope rest fits the needs :)
P.S. - .live is deprecated: for further if you keen - my old post here: :) What's wrong with the jQuery live method?
under category you will see: http://api.jquery.com/live/ "deprecated"
I would add a comment to Tats_innit's post, but I can't.
As per the documentation on live,
Chaining methods is not supported. For example, $("a").find(".offsite, .external").live( ... ); is not valid and does not work as expected.
That's why .parent() does not work.
Binding to parent
Event delegation (handled by the deprecated live and .delegate, and now by .on/.one) only moves downwards. You can't have an upward event delegation like you seem to want to do here.
That is to say if the parent of ".hovertip" does not exist then clearly ".hovertip" does not exist so you are actually binding to nothing.
If your goal is to bind the event to the parent of ".hovertip" when it appears, then you're SOL since delegation only moves downwards (to descendants).
Your options to handle that would be:
* Bind to the parent of .hovertip when it is appended to the DOM.
* Know a selector for the parent of .hovertip ahead of time and bind to it immediately, perhaps through delegation.
Delegating to child
If your goal is to have the event fire when .hovertip is hovered, but .hovertip may not be in the DOM and its parent is not known, you must use a method like this:
$("known parent selector of .hovertip").on('hover', '.hovertip', function () {
"known parent selector of .hovertip" has to be an element that you know ahead of time. If you can't know, you have to use document, but I'd suggest to try to get as close as possible. You can only use this on elements that exist in the DOM at the time of binding.
I think what you are looking for, actually, is something along these lines:
$(document).on('mouseover', '.hovertip', function() {
// handle your mouseover changes, here
});
$(document).on('mouseout', '.hovertip', function() {
// handle your mouseout changes, here
});
.live, .bind, are all deprecated, AFAIK, which means they'll go away in the future, and you might not want to rely on their continued support.
It would also be far better to replace $(document) with a selector that's closer to your .hovertip elements, but above them in the DOM nesting, so they can respond to your event, but without forcing jQuery to watch for every event on every element in the whole document. I simply put document in there as an example, as I don't know what the rest of your structure looks like.
http://jsfiddle.net/mori57/qa7py/
As I think about it, I think it's worth pointing out that throwing things to .parent() may not always work out the way you expect, especially if you're modifying the DOM. I think it's far safer to set a higher-level event handler.
If you must use something like the .parent(), I always found more accurate results with .closest(), and giving it a selector also helps the parsing engine narrow its search. You don't want one parent triggering the hover state for /all/ the .hovertips at one time, which could happen in some cases.
I need to have multiple .click() functions populated on page load, based on how many image records are stored within a mysql database.
so far i have a page that will nicely switch between photos with a <ul> of image buttons
but i have to hand write the jquery that deals with it.
is there a way that i can populate a .js file with the correct amount of .click() functions based on the amount of records on in the data base.
In addition to Alex's answer, if you want to set the click event of elements that don't exist yet or haven't been added to the page, you could do:
$(body).on('click','a.record',function(){
//any existing or future a element with class record will have this click function
});
Instead of adding a separate onclick handler to each element, you should use event delegation and attach a single event handler to some container. Said event handles would catch all the onclick events , as the bubble up through DOM.
You don't need to write a click() for each unique element.
Instead, you could select a bunch of elements with a selector, such as $('a.record') and then chain click() to that...
$('a.record').click(function() {
// Any `a` element with a class of `record` was clicked.
});
The disadvantage of doing it this way is you add a bunch of event listeners and it won't be triggered for future elements.
As others have mentioned, event delegation using on() (if using a newer jQuery) or delegate() (if using an older) is the best, as it only attaches one event listener and will work with future elements added after the event is attached.
$(document).on('click', 'a.record', function() {
// Any `a` element with a class of `record` was clicked, now or in the future.
});
I've used document here, but you should use the nearest ancestor which won't change, which may be the ul element you have described.
I'm creating and removing HTML from inside a div with jQuery (shopping cart, adding/removing items). I need to access the .click() event of a link inside this div, but it only works when the page first loads - after that link is removed then re-added it doesn't respond to any jQuery events.
I've tried functions both inside and outside of $j(document).ready(function() {}. How can I make events work on this link again after re-creation?
Use .delegate() instead of .click() (which is short-hand for .bind('click')):
$(<root-element>).delegate('a', function () {...});
Attach a handler to one or more events for all elements that match the
selector, now or in the future, based on a specific set of root
elements.
Source: http://api.jquery.com/delegate/
The <root-element> can be the document element or if you know an element that is always present that is a descendant of the document element it is best to use that element.
You need to either reattach the event every time you overwrite the content of your container div or set handler using live/delegate/on depending on the version of jQuery you use.
Second method is in general more elegant, but has drawbacks. In particular you cannot cancel the default action from cascaded even attached to the container.
The .click() event only works for elements that are present with the function is called. You need to look into using either .live() or .delegate() to attach listeners to elements that are dynamically created after $(document).ready()
Try using .detach() instead of .remove(), that will keep the events. Alternatively use event delegation.
I'm trying to build a greasemonkey script which will dynamically create tables of data based on user interaction with... other dynamically created tables of data. My problem is that I'm having to make two passes every time I create a table: one to create the table, and another to go grab all of the objects in the table I want to add event handlers to (by id) and add the various event handlers to them.
If I attempt to, say, add an onClick event to a table td before I've created the table and inserted it into the HTML, I get a "component is not available" exception.
This is incredibly cumbersome, because I either have to maintain, separately, a list of the ids and what I should do to those elements when I make my second pass to add the handlers, or develop a naming convention by which I know, based on the id, what I should do with the element.
There HAS to be a better way to do this. I just haven't figured it out yet. Anyone have any ideas?
Firstly, I'd love to know why you need a different ID for every single TD. Is the ID holding important information, such as an index? In this situation it might be better creating each TD within a loop. Also, obviously you can't attach an event handler to a DOM element which doesn't exist! It doesn't have to be injected into the DOM but it DOES have to exist in some capacity.
jQuery's live() isn't a magical mystery, it just uses event delegation, so it attaches the event to a parent element, such as the table and then decides what happens dependent on the target of the click. Here's a rudimentary example. I register a handler to the 'body' element, and then I test each time to see what the target is, if it's a TD element I doSomething() ->
document.body.onclick = function(e) {
var realTarget = e ? e.target : window.event.srcElement;
if ( realTarget.nodeName.toLowerCase() === 'td' ) {
doSomething();
}
};
Event delegation relies on something called event bubbling (or "propogation") which is the way in which modern browsers implement the event model. Each event, when triggered will travel upwards through the DOM until it can go no further. So if you click on an anchor within a paragraph the anchor's 'click' event will fire and THEN the paragraph's 'click' event will fire etc. etc.
jQuery 1.3+ has a new live() function that can set up event handlers for elements that don't exist yet .. check it out
You have to wait for the element to be added to the page, then add the event handler then.
There is no easy way to say "add this to all elements of this type, now and in the future".
It is possible to have a timer periodically check the page for new elements, applying a queue of events (or other properties) to them as they appear, all behind the scenes. This can be abstracted out and re-used, for example Jquery can do that sort of thing.
As JimmyP pointed out, your problem can easily be solved using event bubbling. You might consider writing a wrapper function to work around browser inconsistencies - my own version can be found here and would be used like this:
capture('click', '#element-id', function(event) {
// `this` will be the originating element
// return `false` to prevent default action
});