I realise a similar issue has been asked here Javascript - Dynamically assign onclick event in the loop but I think I was a bit confused and couldn't add a comment.
I'm making a series of divs dynamically, in an array "newdiv".
I want to create an onClick function which will expand the offsetHeight to the scrollHeight.
I'm trying to do this in my for loop as such:
newdiv[i].onclick = function() {expandThis(message_id) };
Where
message_id = message_array[i][0];
(the id column in the array, at message 'i')
The problem is familiar - all the made onClicks refer to the last message_id.
Is there an easy way to make the onClick for newdiv[i] refer to message_array[i][0]?
You could use an anonymous function to create a closure to contain the value to be referred to.
function(message_id) {
newdiv[i].onclick = function() {expandThis(message_id) };
}(message_array[i][0]);
JavaScript is a functional programming language without a let statement. So you have to write the equivalent closure, as ugly as it looks.
You need to break the closure with message_id:
newdiv[i].onclick = (function(id) {
return function() {expandThis(id) };
}(message_id));
There are a million questions like this one, e.g. Do while javascript Problem.
you could create the javascript code dynamically and store in in a var and then use the evalute() function and assign the result to your onclick callback.
Related
I'm creating a simple jQuery plugin and am having trouble making it modular.
For example:
$.fn.testMethod = function(option1, option2) {
var something1 = option1;
var something2 = option2;
};
My problem starts to occur when I have more than one element using this method. For example:
$('.element1').testMethod(1, 2);
$('.element2').testMethod(3, 4);
The second element ends up using the variable of the first later down the line if I'm changing stuff around. What would be a better way of doing this and locking the variables to the specific element they're being used with? If this is too vague, I can paste in my full code but it is a bit complex.
When working with jQuery plugins, you generally want to do an each loop and return the collection.
That also means you can't use just simple variables, but that's where jQuery's data comes in handy.
$.fn.testMethod = function(option1, option2) {
return this.each(function() {
$(this).data('something1', option1);
$(this).data('something2', option2);
// do stuff
var something = $(this).data('something1'); // etc
});
};
On the other hand, the arguments and variables inside the function will be unique to each function call, so there's no way the second call to the function would use the variables from the first call unless you're doing something else strange.
I'm trying to move away from jQuery for my everyday site functionality, and I'm having a little bit of trouble with the onclick event. I'd like to put together a function like jQuery's .click(), but simply using document.getElementsByTagName and adding a func onclick won't work.
The question then is how would one add a single function to fire onclick to all elements in the list object returned by querying document.getElementsByTagName('h4')
EDIT: Just in case someone finds this and would like some code, here's what I did:
var headings = document.getElementsByTagName('h4')
for (var g in headings) {
headings[g].onclick = function() {
//code
}
}
You need to loop through the list and pass the event to each item.
I think there is no simpler way to do this, expect you need a library like jQuery or you write your own eventManager...
This is my first question, probably very silly indeed :)
I have a selection of values in an array, returned from GM_listValues().
As I loop over the collection, I want to dynamically create buttons that call a function to delete the stored value, and reload the page.
deleteB.addEventListener("click", function() {deleteTrip(names[i]);pageSelect();}, false);
Above is the line I am using to attach the event to the button (deleteB). However, when I press the button, javascript tries to access the array of listValues (names) with the count variable (i). Naturally, this will not exist, as the loop is now done, and names is not global anyway.
What I want to know is if there is a way to copy the string value of names[i] while I am creating the function in the button, so as to not need a reference to names[i] in the code.
I know this is probably a really simple answer, but its got me stumped, this is some of my first work with javascript.
Thanks in advance.
Use a closure to remember the value;
function createDeleteFunc(name) {
return function(){deleteTrip(name);pageSelect();}
}
for() {
...
deleteB.addEventListener("click", createDeleteFunc(names[i]), false);
...
}
The problem is that all functions you create reference the same i variable. When they are called, they try to delete names[i], but i is now equal to names.length so it doesn't work.
The solution is to make a separate reference to names[i] for each function. This is usually done with a closure (à-la Paul's answer)
The Objective
I want to dynamically assign event handlers to some divs on pages throughout a site.
My Method
Im using jQuery to bind anonymous functions as handlers for selected div events.
The Problem
The code iterates an array of div names and associated urls. The div name is used to set the binding target i.e. attach this event handler to this div event.
While the event handlers are successfully bound to each of the div events, the actions triggered by those event handlers only ever target the last item in the array.
So the idea is that if the user mouses over a given div, it should run a slide-out animation for that div. But instead, mousing over div1 (rangeTabAll) triggers a slide-out animation for div4 (rangeTabThm). The same is true for divs 2, 3, etc. The order is unimportant. Change the array elements around and events will always target the last element in the array, div4.
My Code - (Uses jQuery)
var curTab, curDiv;
var inlineRangeNavUrls=[['rangeTabAll','range_all.html'],['rangeTabRem','range_remedial.html'],
['rangeTabGym','range_gym.html'],['rangeTabThm','range_thermal.html']];
for (var i=0;i<inlineRangeNavUrls.length;i++)
{
curTab=(inlineRangeNavUrls[i][0]).toString();
curDiv='#' + curTab;
if ($(curDiv).length)
{
$(curDiv).bind("mouseover", function(){showHideRangeSlidingTabs(curTab, true);} );
$(curDiv).bind("mouseout", function(){showHideRangeSlidingTabs(curTab, false);} );
}
}
My Theory
I'm either not seeing a blindingly obvious syntax error or its a pass by reference problem.
Initially i had the following statement to set the value of curTab:
curTab=inlineRangeNavUrls[i][0];
So when the problem occured i figured that as i changed (via for loop iteration) the reference to curTab, i was in fact changing the reference for all previous anonymous function event handlers to the new curTab value as well.... which is why event handlers always targeted the last div.
So what i really needed to do was pass the curTab value to the anonymous function event handlers not the curTab object reference.
So i thought:
curTab=(inlineRangeNavUrls[i][0]).toString();
would fix the problem, but it doesn't. Same deal. So clearly im missing some key, and probably very basic, knowledge regarding the problem. Thanks.
You need to create a new variable on each pass through the loop, so that it'll get captured in the closures you're creating for the event handlers.
However, merely moving the variable declaration into the loop won't accomplish this, because JavaScript doesn't introduce a new scope for arbitrary blocks.
One easy way to force the introduction of a new scope is to use another anonymous function:
for (var i=0;i<inlineRangeNavUrls.length;i++)
{
curDiv='#' + inlineRangeNavUrls[i][1];
if ($(curDiv).length)
{
(function(curTab)
{
$(curDiv).bind("mouseover", function(){showHideRangeSlidingTabs(curTab, true);} );
$(curDiv).bind("mouseout", function(){showHideRangeSlidingTabs(curTab, false);} );
})(inlineRangeNavUrls[i][0]); // pass as argument to anonymous function - this will introduce a new scope
}
}
As Jason suggests, you can actually clean this up quite a bit using jQuery's built-in hover() function:
for (var i=0;i<inlineRangeNavUrls.length;i++)
{
(function(curTab) // introduce a new scope
{
$('#' + inlineRangeNavUrls[i][1])
.hover(
function(){showHideRangeSlidingTabs(curTab, true);},
function(){showHideRangeSlidingTabs(curTab, false);}
);
// establish per-loop variable by passsing as argument to anonymous function
})(inlineRangeNavUrls[i][0]);
}
what's going on here is that your anonmymous functions are forming a closure, and taking their outer scope with them. That means that when you reference curTab inside your anomymous function, when the event handler runs that function, it's going to look up the current value of curTab in your outer scope. That will be whatever you last assigned to curTab. (not what was assigned at the time you binded the function)
what you need to do is change this:
$(curDiv).bind("mouseover", function(){showHideRangeSlidingTabs(curTab, true);} );
to this:
$(curDiv).bind("mouseover",
(function (mylocalvariable) {
return function(){
showHideRangeSlidingTabs(mylocalvariable, true);
}
})(curTab)
);
this will copy the value of curTab into the scope of the outer function, which the inner function will take with it. This copying happens at the same time that you're binding the inner function to the event handler, so "mylocalvariable" reflects the value of curTab at that time. Then next time around the loop, a new outer function, with a new scope will be created, and the next value of curTab copied into it.
shog9's answer accomplishes basically the same thing, but his code is a little more austere.
it's kinda complicated, but it makes sense if you think about it. Closures are weird.
edit: oops, forgot to return the inner function. Fixed.
I think you're making this more complicated than it needs to be. If all you're doing is assigning a sliding effect on mouseover/out then try the hover effect with jquery.
$("#mytab").hover(function(){
$(this).next("div").slideDown("fast");},
function(){
$(this).next("div").slideUp("fast");
});
If you posted your full HTML I could tell you exactly how to do it :)
You can put your variable's value into a non existing tag, and later you can read them from there. This snippet is part of a loop body:
s = introduction.introductions[page * 6 + i][0]; //The variables content
$('#intro_img_'+i).attr('tag' , s); //Store them in a tag named tag
$('#intro_img_'+i).click( function() {introduction.selectTemplate(this, $(this).attr('tag'));} ); //retrieve the stored data
I'm generating an unordered list through javascript (using jQuery). Each listitem must receive its own event listener for the 'click'-event. However, I'm having trouble getting the right callback attached to the right item. A (stripped) code sample might clear things up a bit:
for(class_id in classes) {
callback = function() { this.selectClass(class_id) };
li_item = jQuery('<li></li>')
.click(callback);
}
Actually, more is going on in this iteration, but I didn't think it was very relevant to the question. In any case, what's happening is that the callback function seems to be referenced rather than stored (& copied). End result? When a user clicks any of the list items, it will always execute the action for the last class_id in the classes array, as it uses the function stored in callback at that specific point.
I found dirty workarounds (such as parsing the href attribute in an enclosed a element), but I was wondering whether there is a way to achieve my goals in a 'clean' way. If my approach is horrifying, please say so, as long as you tell me why :-) Thanks!
This is a classic "you need a closure" problem. Here's how it usually plays out.
Iterate over some values
Define/assign a function in that iteration that uses iterated variables
You learn that every function uses only values from the last iteration.
WTF?
Again, when you see this pattern, it should immediately make you think "closure"
Extending your example, here's how you'd put in a closure
for ( class_id in classes )
{
callback = function( cid )
{
return function()
{
$(this).selectClass( cid );
}
}( class_id );
li_item = jQuery('<li></li>').click(callback);
}
However, in this specific instance of jQuery, you shouldn't need a closure - but I have to ask about the nature of your variable classes - is that an object? Because you iterate over with a for-in loop, which suggest object. And for me it begs the question, why aren't you storing this in an array? Because if you were, your code could just be this.
jQuery('<li></li>').click(function()
{
$(this).addClass( classes.join( ' ' ) );
});
Your code:
for(class_id in classes) {
callback = function() { this.selectClass(class_id) };
li_item = jQuery('<li></li>')
.click(callback);
}
This is mostly ok, just one problem. The variable callback is global; so every time you loop, you are overwriting it. Put the var keyword in front of it to scope it locally and you should be fine.
EDIT for comments: It might not be global as you say, but it's outside the scope of the for-loop. So the variable is the same reference each time round the loop. Putting var in the loop scopes it to the loop, making a new reference each time.
This is a better cleaner way of doing what you want.
Add the class_id info onto the element using .data().
Then use .live() to add a click handler to all the new elements, this avoids having x * click functions.
for(class_id in classes) {
li_item = jQuery('<li></li>').data('class_id', class_id).addClass('someClass');
}
//setup click handler on new li's
$('li.someClass').live('click', myFunction )
function myFunction(){
//get class_id
var classId = $(this).data('class_id');
//do something
}
My javascript fu is pretty weak but as I understand it closures reference local variables on the stack (and that stack frame is passed around with the function, again, very sketchy). Your example indeed doesn't work because each function keeps a reference to the same variable. Try instead creating a different function that creates the closure i.e.:
function createClosure(class_id) {
callback = function() { this.selectClass(class_id) };
return callback;
}
and then:
for(class_id in classes) {
callback = createClosure(class_id);
li_item = jQuery('<li></li>').click(callback);
}
It's a bit of a kludge of course, there's probably better ways.
why can't you generate them all and then call something like
$(".li_class").click(function(){ this.whatever() };
EDIT:
If you need to add more classes, just create a string in your loop with all the class names and use that as your selector.
$(".li_class1, .li_class2, etc").click(function(){ this.whatever() };
Or you can attach the class_id to the .data() of those list items.
$("<li />").data("class_id", class_id).click(function(){
alert("This item has class_id "+$(this).data("class_id"));
});
Be careful, though: You're creating the callback function anew for every $("<li />") call. I'm not sure about JavaScript implementation details, but this might be memory expensive.
Instead, you could do
function listItemCallback(){
alert("This item has class_id "+$(this).data("class_id"));
}
$("<li />").data("class_id", class_id).click(listItemCallback);