This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
I'm trying to loop over a simple array, find elements based on the array values, and then add a click event for each element. For some reason (maybe related to the scope?) all the events think they're at the end of the array.
Example HTML:
<!-- "Sectors" -->
<div class="a">a</div>
<div class="b">b</div>
<div class="c">c</div>
Corresponding javascript:
var sectorArray = ["a", "b", "c"];
// Loop over sector letters
for (var s in sectorArray) {
var sector = sectorArray[s];
console.log("Adding click event for sector: " + sector);
$('div.' + sector).on("click", function(e){
console.log("Clicked sector: " + sector);
});
}
When I click on any div, I get the message that I'm on sector "c". Here is a jsfiddle: http://jsfiddle.net/luken/Pd66m/
I was able to fix the problem by making everything inside the for-loop into it's own, separate function... and there are other solutions... But I'd like to figure out why this, above, won't work. Thanks.
closure issue, try this. I added an anonymous function to "enclose" loop values.
var sectorArray = ["a", "b", "c"];
// Loop over sector letters
for (var s in sectorArray) {
var sector = sectorArray[s];
(function(sec){
$('div.' + sec).on("click", function(e){
console.log("Clicked sector: " + sec);
});
}(sector))
}
Another way with pure js, by adding a closure:
for (var s in sectorArray) {
(function(s){ //This line creates a 'per loop cycle' s var
var sector = sectorArray[s];
console.log("Adding click event for sector: " + sector);
$('div.' + sector).on("click", function(e){
console.log("Clicked sector: " + sector);
});
})(s); //Calls the function
}
This way the inner s is not 'shared' and each loop cycle will have its own copy, so it does not get overwritten.
Hope this helps. Cheers
It is a known issue with using closure in a loop
$.each(sectorArray, function (i, sector) {
console.log("Adding click event for sector: " + sector);
$('div.' + sector).on("click", function (e) {
$selection.html(sector);
console.log("Clicked sector: " + sector);
});
})
You can also make a function which returns the the event handler function, pass in the sector and execute it immediately like this:
$('div.' + sector).on("click",
function(sec){
return function(e) {
console.log("Clicked sector: " + sec);
};
}(sector)
);
Related
I use the following to get the json for each member and create an element with a click listener.
$.getJSON('/api/members', function (membersJson) {
$(membersJson).each(function (i, item) {
$('#contacts').append('<div class="membercard">' + item.Name + '</div>');
.click(() => show_details(item));
})
});
function show_details(item) {
$('#memberName').val(item.Name);
$('#memberOcc').val(item.Occupation);
}
When a membercard is clicked it is meant to send its info to a more detailed div. However, when clicking on any of the dynamically created divs, only the item data from the last json in the loop is sent to the detailed view. Why is this and how can I fix it?
you are binding and iterating inside a loop, this is to avoid in general, because the scope of the function will in the click, will take only the last element of the loop
Try refactoring like this:
$('#contacts').on('click', '.membercard', function() {
show_details($(this).data('item'));
});
$.getJSON('/api/members', function (membersJson) {
$(membersJson).each(function (i, item) {
var div = $('<div class="membercard">' + item.Name + '</div>');
div.data('item', item);
$('#contacts').append(div)
})
});
function show_details(item) {
$('#memberName').val(item.Name);
$('#memberOcc').val(item.Occupation);
}
This is a common problem with JavaScript. Essentially, because the click event happens after the loop, the “item” variable is equal to the last item. To fix this, simply change:
$(“#contacts”).click(() => show_details(item));
To:
let _item = item;
$(“#contacts”).click(() => show_details(_item));
This creates a copy of the variable that will have the same value even after the loop completes.
I'd like to get the id of the pressed element.
I know it's a very common question but I do not want a solution in jquery (I'm trying to use it as few as possible)
I cannot modify the html, supposing is something like:
<p id='123' class='my_class'>x</p>
I cannot create something like:
<p id='123' class='my_class' onclick='console.log(this.id + " pressed");'>x</p>
This is my personal attempt:
$(document).on('click', '.my_class', this.id, function (id) {
console.log( id + " pressed!");
});
But I keep obtaining this:
[object Object] pressed!
var elems = document.getElementsByClassName("my_class");
for (var i = 0; i < elems.length; i++) {
elems[i].addEventListener('click', function() {
console.log(this.id + " pressed");
});
}
Here is solution without jquery.
You are almost right. Use the below code:
$(document).on('click', '.my_class', function () {
console.log( this.id + " pressed!");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id='123' class='my_class'>x</p>
Explanation
You should not be sending the third argument with this as it is not contextual. The syntax of the .on is:
.on( events [, selector ] [, data ], handler )
You can completely ignore the third option and that doesn't matter. Always, inside the function, the this will point to the current element, the event is triggered on.
And in your code, you are passing id as the parameter to the call back function, which is the event (EventObject). That's why you got [Object object].
Hope it makes sense.
id in the arguments points to the event object, make it
$(document).on('click', '.my_class', this.id, function (id) {
console.log( id.target.id + " pressed!");
});
or
$(document).on('click', '.my_class', this.id, function (e) {
console.log( e.target.id + " pressed!");
});
This question already has answers here:
How can I pass arguments to event handlers in jQuery?
(6 answers)
Closed 6 years ago.
When I click on the button it will say Bruce Wayne is Batman. In the last jQuery line, for the 'click' function, if I pass the parameter 'guy', the jQuery wont run, but if I don't pass in a parameter I get undefined. What am I doing wrong? Thanks in advance.
$("div").on('click', 'button', click(guy));
The jsFiddle link, HTML and JS are below.
https://jsfiddle.net/wrj5w1Lk/
<div>
<button>
Click Me! Click Me!
</button>
<p>Hello</p>
</div>
$(document).ready(function() {
var Person = function(first, last, secret) {
this.first = first;
this.last = last;
this.secret = secret;
}
var guy = new Person("Bruce", "Wayne", "Batman");
var click = function(person) {
$(this).closest('div').find('p').text(person.first + " " + person.last + " is " + person.secret);
};
$("div").on('click', 'button', click(guy));
});
You have two issues, firstly you need to wrap the call to click() in an anonymous function. Secondly you need to pass the reference of the current button element to your click() function. Try this:
var click = function($element, person) {
$element.closest('div').find('p').text(person.first + " " + person.last + " is " + person.secret);
};
$("div").on('click', 'button', function() {
click($(this), guy);
});
Updated fiddle
I think what you're looking for is how to pass custom data to the event handler using .on(). The link references the optional [ data ] object of the .on() function. This allows you to pass custom data outside of the normal event data to your event handler for further processing. An example is shown below:
$(document).ready(function() {
var Person = function(first, last, secret) {
this.first = first;
this.last = last;
this.secret = secret;
}
var guy = new Person("Bruce", "Wayne", "Batman");
var click = function(event) {
$(this).closest('div').find('p').text(event.data.first + " " + event.data.last + " is " + event.data.secret);
};
$("div").on('click', 'button', guy, click);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<button>
Click Me! Click Me!
</button>
<p>Hello</p>
</div>
I've edited your snippet from your question to use this method of passing data. Your custom object is passed in the event.data object of the callback. By accessing, event.data.[first,last,secret] you can reference the properties of the Person(...) object from within your callback.
To prepare dropdown elements and their behavior i bind click event handlers to show/hide the appropriate shallow divs (e.g. the dropdown menu lists) with jQuery .each().
// Excerpt, this is just for debugging purpose, s. below also
$('.m-dropdown-select__trigger').each(function() {
var triggerElem = $(this);
triggerElem.on('click', function(e) {
if(e) e.stopPropagation();
if(e) e.preventDefault();
if(triggerElem.hasClass('is-open')) {
// debug if we are in the original or cloned object
alert('hide this, ' + triggerElem.closest('dl').attr('id') + ', parent: ' + triggerElem.parents('.m-form-elements--accompanied__item').attr('id'));
triggerElem.removeClass('is-open');
} else {
// debug if we are in the original or cloned object
alert('show this, ' + triggerElem.closest('dl').attr('id') + ', parent: ' + triggerElem.parents('.m-form-elements--accompanied__item').attr('id'));
triggerElem.addClass('is-open');
}
});
});
When i deep copy (with .clone(true, true)) a div which comprises one of those dropdowns, the event handler are not bound to the dropdown within the cloned div but on the original object.
E.g. i have a link somewhere in the page which copies additional accompanies form inputs (and a dropdown for salutation) and insert them after the last item.
// Clone first hardcoded container, adapt some attributes and insert it
var accompaniesCount = 2;
$('.js-add-accompanies').on('click', function(e) {
if(e) e.preventDefault();
var count = accompaniesCount++;
// Grap the first (hardcoded) item and copy it
var cont = $('#accompanied-item-1').clone(true, true);
// change clone attributes
cont.attr('id', 'accompanied-item-' + count );
cont.find('.m-form-elements--accompanied__heading span').text(count);
cont.find('.m-dropdown-select__select')
.attr('id', function(index, attr) {
return attr.replace(1, count);
})
.attr('name', function(index, attr) {
return attr.replace(1, count);
})
cont.find('.m-dropdown-select__definitionlist')
.attr('id', function(index, attr) {
return attr.replace(1, count);
})
cont.find('input').val('');
cont.insertAfter($('[id^=accompanied-item-]:last'));
});
Demo: http://jsfiddle.net/felic/L98jzkko/18/
Info for the fiddle example: Click on "Anrede" twice to get debugged output. Then click on "Add accompanies" and toggle "Anrede" there. Parent is always the first entry (e.g. the original object).
What i am missing here? Thx in advance.
You are dealing with dynamic elements, so the way to register event handlers is to use event delegation
$(document).on('click', '.m-dropdown-select__trigger', function(e) {
var triggerElem = $(this);
if(e) e.stopPropagation();
if(e) e.preventDefault();
if(triggerElem.hasClass('is-open')) {
// debug if we are in the original or cloned object
alert('hide this, ' + triggerElem.closest('dl').attr('id') + ', parent: ' + triggerElem.parents('.m-form-elements--accompanied__item').attr('id'));
triggerElem.removeClass('is-open');
} else {
// debug if we are in the original or cloned object
alert('show this, ' + triggerElem.closest('dl').attr('id') + ', parent: ' + triggerElem.parents('.m-form-elements--accompanied__item').attr('id'));
triggerElem.addClass('is-open');
}
});
Demo: Fiddle
I'm just getting into Javascript and I've run into the same problem a number of times with different pieces of code: I have a function that creates a type of element and a function that does something with that type of element. It seems obvious to me that I need to call the "do something" function after the element has been created, but when I do, it ends up running more times than I'd like.
Here's an example of my problem:
function rightClick(){
$(".element").mousedown(function(e){
switch (e.which){case 3: alert( $(this).attr("id") )};
});
};
function doubleClick(){
var counter = 0;
$(document).dblclick(function(e){
counter++;
elementId = "element" + counter;
$("#new_elements").append("<div class='element'" +
"id='" + elementId + "'" +
"style='position:absolute;" +
"top:" + e.pageY + ";" +
"left:" + e.pageX + ";'>" +
elementId+ "</div>");
rightClick();
});
In this example, if I create 4 elements and I right-click on the first one I created, I end up getting 4 alert boxes instead of one. If I right-click on the second element I created, I get three alerts; the third: 2 alerts; the fourth: one alert.
Can anyone explain to me why this is happening, and how to fix it so that I only get one alert each time I right-click on an element?
Binding is the act of associating an event with a DOM element. The .mousedown and similar events only bind on elements that already exist.
Each time you call rightClick() you bind a new event to all current .element elements.
You can bind functions to the same element as much as you'd like, which is why you see the function being called many times.
For dynamic elements should checkout .on or .delegate which work like this:
Example of jQuery.fn.on
$(document.body).on("mousedown", ".element", function(e) {
if (e.which === 3) alert($(this).attr("id"));
});
Example of jQuery.fn.delegate
$(document.body).delegate(".element", "mousedown", function(e) {
if (e.which === 3) alert($(this).attr("id"));
});
Only call this once and you should be pretty much okay. If you're not using jQuery 1.7 or higher you will want to use .delegate() instead of .on.
You do not need to bind the event everytime you insert and element into the DOM. You can use .on to attach event handlers for elements that are dynamically inserted.
$(document).on('mousedown','.element', (function(e){
switch (e.which){
case 3: alert( $(this).attr("id") );
break;
};
});
var counter = 0;
$(document).dblclick(function(e){
counter++;
elementId = "element" + counter;
$("#new_elements").append("<div class='element'" +
"id='" + elementId + "'" +
"style='position:absolute;" +
"top:" + e.pageY + ";" +
"left:" + e.pageX + ";'>" +
elementId+ "</div>");
});
I believe you are adding the same handler several times, meaning that when you click a button you are re-binding the action to the same function.
You've bound your event handler to the class '.element'. This means that every element with the class '.element' on your page will fire that event when the right click occurs.