Here is the fiddle.
Ignore the styling, that's not important.
Basically I needed to open the Fancybox with two links present, but I only want one gallery image. I figured that out easily enough. When the thumbnail is clicked it triggers the li anchor.
To keep the galleries separate I did unique classes for each ol.
The problem I have run into is I will be repeating myself.
I attempted to do a loop (commented out), but the logic is beyond my grasp.
What is the best way to attach a new click handler (I need to add 8 more) without repeating myself in my current fashion? I've also tried a function with a couple parameters, but I had trouble with the e.preventDefault().
I greatly appreciate any guidance, thanks!
This looks like a great use case to use jQuery's on() method. on() is a method that will allow you to establish a handler on an outer container that can listen to its children for click events. So, for example: if you specified a class of .js-listen on your lists, you could call on() like this:
$('.js-listen').on('click', 'other-selector', function(e){
// function logic with either $(this) or e.target goes here
}
This block would essentially look for all elements with .js-listen and then when something inside the element with the .js-listen class is clicked, the event will bubble up through the DOM and the event will be handled according to the element that was clicked. The second parameter I have 'other-selector' can be a class name, element, or ID. so you could essentially put something like img there and it would fire the event if the child element clicked was an <img> tag.
This prevents you from attaching a handler a million times, and one of the benefits of on() is that if elements are dynamically added to the container with the handler, you don't have to worry about attaching handlers to those elements, because again, they bubble up!
Hope this helps!
Related
I have a set of three elements. And there are many coming elements after the page load by ajax when the user clicks on a certain element. I iterate over initial elements with each and attach an event to them like this:
tagSet.each(function(index, element){
tagSet.eq(index).on("click",function(){alert("Alerted! Clicked")});
}
But with this approach those ajax-created elements would not be attached with an event. Thus I have to make a little bit of change to on() for it accommodate the ajax-created elements:
tagSet.each(function(index, element){
$(document).on("click",tagSet.eq(index),function(){alert("Alerted! Clicked")});
}
It solve the problem of event attachment to those ajax-created elements but raises another issue. It attaches the event in a way that all of the elements of the page gets trigged with that specific event at once. I mean, since I have three elements, a click on one of them cause all three alert("Alerted!Clicke!) to trigger. What is the issue and the solution?
Use event delegation to bind to all current and future items that match a query:
$(document).on("click", ".some-class-here", function(){
alert("Alerted! Clicked")
});
Replace .some-class-here with a selector that matches your tagSet elements. Then remove your call to $.each.
I have a method to dynamically add a container which is attached to the target container . When the action has been performed i removed the action container but now I have to add a class to parent container but problem is it is getting set (class name to parent container ) but it gets removed itself. For more clarification I am adding an sample code
<div class="main">
<ul>
<li>some contents</li>
................
................
</ul>
<ul class="dynamic_container">
<li>actions</li>
................
</ul>
</div>
dynamic_container is added when user mouseover the main class container and this get removed once action has been executed . But now it gets added(class name) but it gets removed too .
I believe this is basically because $(e.target) get removed ....
Any advice/suggestion will be appreciated . Thanks in advance.
edit :
$(e.target).parents('.main').addClass('current'); (this code does not able to add class to .main div) . This code is executed from ul.dynamic_container (which is added to the dom on mouseover on div.main)
edit 2:
jsfiddle link: this is the structure (not the actual code)
http://jsfiddle.net/CASy6/
Thanks for posting the fiddle. Now we can identify some problems that are preventing it from working.
The first problem is that when you try to move the mouse over the buttons, they disappear. If you add some console.log statements to the mouseover and mouseout handler functions, you will see that they get called way too much. This is due to the way these events work in a situation with nested elements.
jQuery provides a good solution to this problem: the mouseenter and mouseleave events. Read about them in the API docs, specifically the section describing the difference between mouseover and mouseenter.
See this fix implemented in this version of the jsFiddle: http://jsfiddle.net/CASy6/1/. With this, you can now actually click on the buttons, and also, a whole bunch of unnecessary .append() and .remove() calls are avoided.
The next problem is that no handler is called when you click a button.
The reason for this is that you set up the handlers by calling e.g.
$('li.first').on('click', function(e){ ... });
when the page loads, but at that moment, the selector li.first matches nothing, because you haven't appended the buttons to one of the divs yet. So handlers are only attached at page load, and they have nothing to attach to.
One solution for this problem is to use delegated events (see docs). This means we attach a handler to a container element (which is always present, including at page load), and handle events that bubble up from a descendant element.
In this case, we can attach a delegated event handler to the .main divs, which handles a click coming from one of the buttons:
$('div.main').on('click', 'button.first', function(e){
$(this).closest('.main').addClass('current');
alert('first action');
});
The second argument button.first is a selector which determines which descendant events will be handled by this handler. (I fixed the appended html so the class attribute is attached to the button element instead of the li element; it was inconsistent between the two buttons.)
See these fixes in action here: http://jsfiddle.net/CASy6/2/
I've got a page with some Javascript / jQuery stuff, for example:
(function()
{
$('.tip').tooltip();
$('.test').click(function()
{
alert('Clicked!')
});
}();
On the page I insert some HTML with jQuery so the DOM changes. For example, I insert a extra element with the class "tip" or "test". The just new inserted elements doesn't work because jQuery is working with the non-manipulated DOM and the just inserted elements aren't there. So I've searched around and came to this solution for the "click":
$('body').on('click','.click',function()
{
alert('Clicked!')
});
I don't understand why, but this way it's working with the manipulated DOM and the jQuery stuff works on the new inserted elements. So my first question is, why does this work and just the click() function not? And the second question, why do I have to point to the "body"?
Finally, my third question is, how get this done with the tooltip?
I know that there is so many information about this subject (previous the delegate() and live() function I've read) but I can't found a explanation about it. And I can't get my third question solved with the information I found.
I'm looking forward to your responses!
Extra question:
4) Is it recommended to point always to the "body" for this kind of situations? It's always there but for possible performance issues?
So my first question is, why does this work and just the click()
function not?
Because the event handler is now delegated to a parent element, so it remains even after replacing/manipulating child elements.
Ancient article on event delegation for your perusal - but the concepts remain the same:
http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/
And the second question, why do I have to point to the "body"
You don't, any suitable parent element will do. For example, any direct parent (a div wrapper, for instance) which does not get replaced.
Finally, my third question is, how get this done with the tooltip?
You need to re-initialize your tooltip plugin on the newly inserted elements. For example:
$.get("foo.html", function (html) {
$("#someDiv").html(html);
$("#someDiv").find(".tip").tooltip();
});
The click() event doesn't work when you manipulate the DOM because JQuery is not watching for DOM changes. When you bind the click() event it is selecting the elements that are on the page at that time. New ones are not in the list unless you explicitly bind the event.
Because you have pointed the click() event on the body. JQuery then checks to see if the target of the click matches any of the event handlers (like what you have created) match the element clicked. This way any new elements will get the event 'associated' with them.
Because the tooltip isn't an event that you can place on the body, you will need to re-initialize it when the element is created.
EDIT:
For your fourth question, is it depends. The advantage of binding to the body is that you don't accidentally bind an event to an element more than once. The disadvantage is that you are adding event handlers that need to be checked on each event and this can lead to performance issues.
As for your concerns about DRY, put the initialization of the tooltips into a function and call that when you add them. Trying to avoid having the same function call is a little overkill in this regard, IMO.
Events are bound to the specific object you are binding it to.
So something like $('.tip').tooltip() will perform the tooltip() functionality on $('.tip') which is actually just a collection of objects that satisfies the css selector .tip. The thing you should take note of is, that collection is not dynamic, it is basically a "database" query of the current page, and returns a resultset of HTML DOM objects wrapped by jQuery.
Therefore calling tooptip() on that collection will only perform the tooltip functionality on the objects within that collection, anything that was not in that collection when tooltip is called will not have the tooltip functionality. So adding an element that satisfies the .tip selector, after the tooltip() call, will not give it the tooltip functionality.
Now, $('body').on('click','.click', func) is actually binding the click event to the body tag (which should always exist :P), but what happens is it captures whether the click event has passed through an element your target css selector (.click in this case), so since the check is done dynamically, new elements will be captured.
This is a relatively short summary of what's going on... I hope it helped
UPDATE:
Best way for your tooltip thing is to bind tooltip after you have added elements, e.g.
$('#container').load('www.example.com/stuff', function() {
$('.tip', $(this)).tooltip();
});
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 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
});