JQuery Bind click event to appended element with an argument - javascript

I'm trying to populate a ul list with some li elements and give each li element a link that calls the same function with a different argument. However it doesn't seem to work, i've attached the code below, CheckForNewMail is called on document load. Can anyone please help me out?
function CheckForNewMail() {
//////////////////////////////////////////////////////////////////
// This will be dynamic
MailInInbox[0] = new Array("0", "Mail One");
MailInInbox[1] = new Array("12", "Mail Two");
MailInInbox[2] = new Array("32", "Mail Three");
MailInInbox[3] = new Array("5", "Mail Four");
//////////////////////////////////////////////////////////////////////
$('#mail-in-inbox').children().remove();
size = 4; element = $('#mail-in-inbox');
for(i = 0; i < size; ++i) {
var link = $(''+ MailInInbox[i][1] +'');
link.live('click', function() {
LoadMailById(i);
});
li = $('<li></li>');
li.append(link);
element.append(li);
}
}
function LoadMailById(id) {
alert("Button "+ id +" clicked!");
}

First, I'm making an assumption here:
That you want the id of the email in that click function, not the index e.g. 0, 12, 32, 5, rather than 0, 1, 2, 3.
Given that (easy to adjust with .index() if the assumption is incorrect), you can do this:
function CheckForNewMail() {
MailInInbox[0] = ["0", "Mail One"];
MailInInbox[1] = ["12", "Mail Two"];
MailInInbox[2] = ["32", "Mail Three"];
MailInInbox[3] = ["5", "Mail Four"];
$('#mail-in-inbox').empty();
var size = 4, element = $('#mail-in-inbox');
for(i = 0; i < size; ++i) {
$(''+ MailInInbox[i][1] +'')
.data('id', MailInInbox[i][0]).wrap('<li/>').parent().appendTo(element);
}
}
There are a few changes here:
Array literals, much simpler notation
Proper var declaration on variables (if they aren't already defined elsewhere)
Using .data() to store the id on the <a> we just created
Then, using that stored data, we can attach a single handler to the #mail-in-inbox container one time (on DOM ready), rather than one to each <a> each time this function runs, it should look like this:
$('#mail-in-inbox').delegate('.inbox-link', 'click', function() {
alert("Button "+ $.data(this, 'id') +" clicked!");
});
This would display "Button 0 clicked!", "Button 12 clicked", etc. You can test out all of the above in a demo here.

One problem is that the variable i will always be that of the last link because by the time the user clicks any link, the loop would have completed. One way to fix this would be to use the .data() method to associate a piece of information with each li element:
link.data('mailId', i); // within the loop
Then you can bind a single event handler for all inbox links inside #mail-in-inbox:
$('#mail-in-inbox').on('click', '.inbox-link', function() {
LoadMailById($(this).data('mailId'));
});
Edited to add: .on() was introduced in jQuery 1.7. If you are using jQuery 1.6 or older, use .delegate() or .live() (as in the question and the original version of this answer) instead.

There are a couple issues.
First, that's not how you use .live(). The .live() method is called against a jQuery object that was given a selector. You can do it at any point (even before the DOM loads), and any clicks on the page that match the selector will fire the handler.
In your case, it would go something like:
$('.inbox-link').live('click',function() {
// whatever
});
In your case, where you're assign a separate handler to each item, you would use .bind instead of live().
As another user mentioned, you'll not have the correct value of i in the handler though. You can use $.each() to overcome this.
$.each(MailInInbox, function( i ) {
var link = $(''+ MailInInbox[i][1] +'');
link.bind('click', function() {
LoadMailById(i);
});
$('<li></li>').append( link ).appendTo( element );
});
Now you'll have the correct value of i in the handler for each link.

LoadMailById(i);
i is a reference here and increased on each for-iteration. Thus, after the for loop it holds the value of size; 4.

link.live('click', (function(index) {
return function() {
LoadMailById(index);
};
)(i);
});
I think this should work to fix the problem. The anonymous function takes your argument i and binds it to the variable index so that when the click even occurs, it's not using the value i leftover from the end of the loop but is instead using it's value at the time of the binding.

Related

Unexpected scoping issue when assigning click listener

When my loadNav() function loops through the nav array and assigns a click listener to a dynamically-created element, all seems well. If I break on the first loop through the navItem.id is 'newGL' and the second time through it is 'litGL', as it should be.
But when I check the listeners after the loop is done, they both have an id of 'litGL' (and also a type of 'b'). But the elements themselves have the correct navItem.text, so I guess I’m not understanding how variables are assigned within the passed function to the .click method.
Any hints why my first click listener is getting overwritten with the values meant for the second one?
var nav = [
{
id:'newGL',
text:'new',
type:'a'
},
{
id:'litGL',
text:'lit',
type:'b'
}
]
function loadNav(nav){
for(item in nav){
var navItem = nav[item];
var element = $("<div>" + navItem.text + "</div>");
element.click(function(){
getContent(navItem.id,navItem.type);
//in practice I'm getting two click listeners with a navItem.id of 'litGL' and a navItem.type of 'b'
});
$('#horzNav').append(element);
}
}
The immediate problem is that the navItem is not the same value when the event is triggered much later. To fix this common issue use a scoping IIFE (immediately invoked function expression):
function loadNav(nav){
for(item in nav){
var navItem = nav[item];
var element = $("<div>" + navItem.text + "</div>");
(function(navItem){
element.click(function(){
getContent(navItem.id,navItem.type);
//in practice I'm getting two click listeners with a navItem.id of 'litGL' and a navItem.type of 'b'
});
})(navItem);
$('#horzNav').append(element);
}
}
Better solutions involve putting the require attributes into the injected elements, then extracting them at event time. This simplifies the event handler and removes the dependency on the original navItem variable.
e.g. something like:
function loadNav(nav){
for(var i = 0; i < nav.length; i++){
var navItem = nav[i];
var element = $("div", {id: navItem.id}).html(navItem.text).data('type', navitem.type ).appendTo('#horzNav');
}
}
and use a delegated event handler with a selector:
$(function () {
$(document).on("click", ".navItem", function () {
getContent($(this).attr("id"), $(this).data("type"));
});
});
This works by listening for the event (e.g. click) to bubble up to a non-changing ancestor element (e.g. document), then applying the selector to the items in the bubble-chain, then applying the function only to those matching element that caused the event.
The upshot of this is the the items only need to match at event time and not when the event was registered. Great for dynamically added items.
document is the best default if no other ancestor else is closer/convenient. Do not use body for delegated events as it has a bug (styling can cause it to not get mouse events).
Your issue is scoping, but you really need to delegate that event (otherwise if you had 100 elements, that'd be 100 events :/), then you can specify the properties you need within the markup using data attributes, something like:
$(function () {
$(document).on("click", ".navItem", function () {
getContent($(this).attr("id"), $(this).data("type"));
});
});
Then your loadNav would be:
function loadNav(nav){
for(item in nav){
var navItem = nav[item];
var element = $(document.createElement("div"));
element.html(navItem.text);
element.prop("id", navItem.id);
element.data("type", navItem.type);
$('#horzNav').append(element);
}
}

Adding click event listener to elements with the same class

I have a list view for delete id. I'd like to add a listener to all elements with a particular class and do a confirm alert.
My problem is that this seems to only add the listener to the first element with the class it finds. I tried to use querySelectorAll but it didn't work.
var deleteLink = document.querySelector('.delete');
deleteLink.addEventListener('click', function(event) {
event.preventDefault();
var choice = confirm("sure u want to delete?");
if (choice) {
return true;
}
});
List:
<?php
while($obj=$result->fetch_object())
{
echo '<li><a class="delete" href="removeTruck.php?tid='.$obj->id.'">'.$obj->id.'</a>'
. '
</li>'."\n";
}
/* free result set */
$result->close();
$mysqli->close();
?>
You should use querySelectorAll. It returns NodeList, however querySelector returns only the first found element:
var deleteLink = document.querySelectorAll('.delete');
Then you would loop:
for (var i = 0; i < deleteLink.length; i++) {
deleteLink[i].addEventListener('click', function(event) {
if (!confirm("sure u want to delete " + this.title)) {
event.preventDefault();
}
});
}
Also you should preventDefault only if confirm === false.
It's also worth noting that return false/true is only useful for event handlers bound with onclick = function() {...}. For addEventListening you should use event.preventDefault().
Demo: http://jsfiddle.net/Rc7jL/3/
ES6 version
You can make it a little cleaner (and safer closure-in-loop wise) by using Array.prototype.forEach iteration instead of for-loop:
var deleteLinks = document.querySelectorAll('.delete');
Array.from(deleteLinks).forEach(link => {
link.addEventListener('click', function(event) {
if (!confirm(`sure u want to delete ${this.title}`)) {
event.preventDefault();
}
});
});
Example above uses Array.from and template strings from ES2015 standard.
The problem with using querySelectorAll and a for loop is that it creates a whole new event handler for each element in the array.
Sometimes that is exactly what you want. But if you have many elements, it may be more efficient to create a single event handler and attach it to a container element. You can then use event.target to refer to the specific element which triggered the event:
document.body.addEventListener("click", function (event) {
if (event.target.classList.contains("delete")) {
var title = event.target.getAttribute("title");
if (!confirm("sure u want to delete " + title)) {
event.preventDefault();
}
}
});
In this example we only create one event handler which is attached to the body element. Whenever an element inside the body is clicked, the click event bubbles up to our event handler.
A short and sweet solution, using ES6:
document.querySelectorAll('.input')
.forEach(input => input.addEventListener('focus', this.onInputFocus));
You have to use querySelectorAll as you need to select all elements with the said class, again since querySelectorAll is an array you need to iterate it and add the event handlers
var deleteLinks = document.querySelectorAll('.delete');
for (var i = 0; i < deleteLinks.length; i++) {
deleteLinks[i].addEventListener('click', function (event) {
event.preventDefault();
var choice = confirm("sure u want to delete?");
if (choice) {
return true;
}
});
}
(ES5) I use forEach to iterate on the collection returned by querySelectorAll and it works well :
document.querySelectorAll('your_selector').forEach(item => { /* do the job with item element */ });

pass through current target name to function

I'd like to dynamically create event listeners for multiple buttons, and subsequently, show a particular frame label depending on the button clicked, but I'm unsure what to pass through (FYI, this is will be used for HTML5 canvas in Flash CC, but principally the same should apply to a web page for showing divs etc). I currently have this:
var butTotal = 4;
var selfHome = this;
function createListeners () {
for (var i=0; i<butTotal; i++) {
selfHome["btn" + i].addEventListener('click', openPop);
}
}
function openPop () {
alert("test");
selfHome.gotoAndPlay("pop"+event.currentTarget.name.substr(3));
}
createListeners();
It creates the listeners fine, but I don't really know where to start with passing through the current button instance name to tell it which frame label to gotoAndPlay.
Based on the code that you have, I'd simply change the .addEventListener() to call a generic function (rather than openPop, directly), and pass it the reference to the button. So, this:
selfHome["btn" + i].addEventListener('click', openPop);
. . . would become this:
selfHome["btn" + i].addEventListener('click', function() {
openPop(this);
});
At that point, you would then have to update openPop to accept a parameter for the reference to the element that triggered it . . . something like:
function openPop (currentButton) {
At that point, you could reference the clicked button, by using currentButton in the openPop logic.
I'm not sure I totally understand your question. However if you just need to pass the button instance (in you case "selfHome["btn" + i]") you could call an anonymous function in your event handler which calls openPop() with the button instance as an arugment. Would this work for you?
var butTotal = 4;
var selfHome = this;
function createListeners () {
for (var i=0; i<butTotal; i++) {
var currentBtn = selfHome["btn" + i];
currentBtn.addEventListener('click', function(){openPop(currentBtn);} );
}
}
function openPop (btn) {
alert("test");
selfHome.gotoAndPlay(/*use button instance 'btn' to find frame*/);
}
createListeners();
When the event is triggered the this keyword inside the handler function is set to the element is firing the event EventTarget.addEventListener on MDN. If the button have the data needed to be retrieved just get it from the this keyword:
function openPop (btn) {
alert(this.name);
/* ... */
}
It looks like you expect it to contain the function gotoAndPlay() as well as the btn elements (which contain both an ID (of btn[number]) and a name with something special at substr(3) (I assume the same as the id). If those things were all true, it should work in chrome... in other browsers you'll need to add event to the openPop() method signature.
function openPop (event) {
alert("test");
selfHome.gotoAndPlay("pop"+event.currentTarget.name.substr(3));
}
I believe this is what you are looking for and adding that one word should fix your problem (assuming some things about your dom and what selfHome contains):
JSFiddle
You could also leave out the event from openPop() and replace event.currentTarget with this:
function openPop () {
alert("test");
selfHome.gotoAndPlay("pop"+this.name.substr(3));
}
JSFiddle

Increasement number not rising in for loop

I am trying to figure this out. I have 3 buttons id (test1, test2, test3) in my HTML! In my jQuery I have an click function in a for loop like this:
$(document).ready(function() {
for (var i = 0; i < 3; i++) {
$("#test"+i).on('click', function() {
alert("I am clicked! ("+i+")");
});
}
});
Now, I am alerting a message for each of them, nut all the (i) in the alert is giving the last number "3"... How do i get it to write "I am clicked (1)" if I press test1 and equally if I press test2 and test3?
I have a jsfiddle to explain here.
Hoping for help and thanks in advance ;-)
The problem is that the handlers you're assigning have an enduring reference to the i variable, not a copy of that variable as of when the function was created. They're closures over the i variable (and other things). More: Closures are not complicated
There are several ways to solve this.
You can put a piece of information on the elements so they can all share a single handler, which is probably preferred. (You actually already have that information in your example, we can figure out i from the elements' id values, but I'm assuming in the real world things are more complex.)
$(document).ready(function() {
for (var i = 0; i < 3; i++) {
$("#test"+i).attr("data-index", i).click(clickHandler);
}
function clickHandler() {
alert("I am clicked! ("+this.getAttribute("data-index")+")");
// Or:
alert("I am clicked! ("+$(this).attr("data-index")+")");
}
});
Updated Fiddle
Note how we have just one handler function, and it handles clicks on all of the elements.
Using a single handler may also mean you can take advantage of event delegation, hooking the click on an ancestor element rather than on each of these individual elements, e.g.:
$("selector for ancestor").on("click", "[id^=test]", ...);
Updated Fiddle
You can use a builder function to create the click handlers so that they close over something that doesn't change (an argument we pass to the builder):
$(document).ready(function() {
for (var i = 0; i < 3; i++) {
$("#test"+i).on('click', buildHandler(i));
}
function buildHandler(index) {
return function() {
alert("I am clicked! ("+index+")");
};
}
});
Updated Fiddle
Try something like this
$(document).ready(function() {
for (var i = 0; i < 3; i++) {
$("#test"+i).on('click', function() {
var value = $(this).attr('id');
value=value.replace("test", "");
value=parseInt(value)+1;
alert("I am clicked! ("+value+")");
});
}
});

assigning click method to variable

I am creating an array & assigning the value to each index in a function through variables.
I also want to attach a jquery click method to each variable. However, I am getting 'undefined' in return when the click method is called.
var i = 0;
var eCreditTransactions = new Array(6); // 6 members created which will be recycled
function abc()
{
addingElements (i);
}
/* **** THE FOLLOWING IS THE PROBLEM AREA **** */
$(eCreditTransactions[i]).click (function () // if user clicks on the transaction box
{
creditTransactionSlideIn (eCreditTransactions[0], 150); //another function called
});
/* **** this is the function being called in the first function above **** */
function addingElements (arrayIndex) // func called from within the 'createCreditTransaction()' func
{
eCreditTransactions[i] = $(document.createElement('div')).addClass("cCreditTransaction").appendTo(eCreditSystem);
$(eCreditTransactions[i]).attr ('id', ('trans' + i));
$(eCreditTransactions[i]).html ('<div class="cCreditContainer"><span class="cCreditsNo">-50</span> <img class="cCurrency" src="" alt="" /></div><span class="cCloseMsg">Click box to close.</span><div class="dots"></div><div class="dots"></div><div class="dots"></div>');
creditTransactionSlideOut (eCreditTransactions[i], 666); // calling slideOut animation
counterFunc ();
return i++;
}
Try this:
$(document).ready(function() {
$(".cCreditTransaction").click(function() {
//do what you want on click event
});
});
Hope it helps
Given that it looks like each element you're adding to the array has a classname (cCreditTransaction) you can hookup the click events using something like
$(document).delegate(".cCreditTransaction", "click", function() {
// code to fire on click goes here.
});
or in jQuery 1.7+ you can use .on instead of .delegate
You don't then need to hook up n events, but just one event that matches all items in the selector (in your case, the class name)
You should also change $(document) to a container element that has an Id, so that the DOM traversal to find the classes is trimmed down as much as possible. Why? Because finding elements by class name is a relatively expensive procedure, as opposed to finding tags or even better, an ID.
it looks like there should be a loop in this part:
function abc()
{
addingElements (i);
}
there is a call to addingElements, and an 'i' parameter being passed, but 'i' is at that moment still defined as 0.
it should say something like
function abc()
{
for (i=0;i<=7;i++)
{
addingElements (i);
}
}

Categories