Passing values from Greasemonkey sandbox into anonymous functions - javascript

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)

Related

Does Javascript temporary object need reference variable and/or closures?

I need short messages disappearing after preset time. Please see the fiddle here: http://jsfiddle.net/X88F9/1/.
It works well, what I am not sure about is the reference for each created object:
function addObject() {
new SomeObj(Math.random() * 1000 + 300);
}
it is not stored in any variable, can I just leave it as it is ? Or do I need to push them in some array ?
I also found this recommendation to put all in closures: https://stackoverflow.com/a/10246262/2969375, but not sure if necessary in my case and if yes, then how.
My answer to the question is: Javascript does not need a reference to the object to work, as proofed by your fiddle. So the question is more about if you need a reference to the object, to do other stuff with it later on. If you, for instance, would like to give the user the ability to click the temporarily display message and stop it from disappearing, than you can put all that code in a closure and do not need a reference, too. But if you would like to display the very same object again after it was removed from the DOM, than you need to store it in an array, other object, or variable, depending on your needs and ways to find it in a list.

Get all Jquery variables beginning with "blank"

all! I've come across a dilemma. I'm creating something of a price-configurator, and basically Jquery is going to be creating a bunch of subtotal variables as the user selects certain options. All of these variables will have names that begin with sub- At the end of the code, I'd like to be able to write a line that gets each one of the sub- variables and adds its value to a grand total. Is there a way to do this, and if so, how?
There is not really a way to do this if the variables are tracked separately. If you stored them in an object, you can loop through the object using
$.each(myObject, function (key, value) {
if (key.indexOf('sub-') === 0) {
// to do
}
});
You can do this with an array, and use
myArray[myArray.length] = value
as you are looping through the form. Then loop through the array at the end to get the values you need.
Without seeing your code or pseudo-code or knowing anything about the scope of your price configurator, is there any reason why you can't create a (relatively) global grand-total variable that is accessible, scope-wise, to every code path that generates a sub- variable; and then add the value of each sub- variable to the grand total as soon as the value of the sub- variable is calculated?

Change eventListener function parameters based on iterator. (Vanilla JS)

What I want:
I have a method which takes an int as as its only parameter, then I'm creating a list where each element will call the other function when clicked, the only thing I want to change is the number that each element passes when clicked.
What I have:
while(++i<e){
(...)
a.addEventListener('click',function(){selectUser(i)},false);
(...)
}
Of course, the obvious problem is that when called, it will always pass the final value of 'i'.
I need that each element passes the value that 'i' had when it was created.
ie; when i=3, I want it to translate into:
a.addEventListener('click',function(){selectUser(3)},false);
I guess it involves some method which returns another method with the actual value, but I'm not really sure how to implement it. I have very little experience with js.
Thanks in advance.
This is a classic problem of closures. You must enclose your event handler inside a closure function which keeps the state of your variable i. Like this...
while(++i<e){
(...)
(function(index){
a.addEventListener('click',function(){selectUser(index)},false);
})(i);
(...)
}
This way every call of the wrapping function will keep your state of the variable i and thereby giving you the right result.
You have to create a new function scope. You could do it the following way (call an anonymous function, which in turn returns a new function with the specified i.
a.addEventListener('click',(function(index) {
return function(){ selectUser(index) };
})(i),false);

Event.observe in a loop and variables

To put things in context, I'm loading a list of items via Ajax, creating a div with main info for each one and want to display details on page when clicking on it. So I have that code in my onSuccess :
items = transport.responseText.evalJSON(); // my list of objects that contains all the details I'll need for that page
for (var itemID in items)
{
newDiv = ... // Creating my div with main infos
$('myDiv').appendChild(newDiv);
// More code to make everything look pretty and that works fine
Event.observe(newDiv, 'click', function() { loadItem(itemID); });
}
loadItem is my function that will display all the item details. And my problem is that itemID isn't replace by its value when creating the observe event, so it always returns the same ID for all items.
Any idea how I can fix that ? I checked bind on prototype doc, that seemed to be made for those cases, but probably didn't get it, since it wouldn't work for me.
For a minimal-impact fix, replace your Event.observe line with this:
Event.observe(newDiv, 'click', loadItem.curry(itemID));
Explanation:
In your original code, the event handler functions you're creating close over (have an enduring reference to) the itemID variable, and so will use the value of that variable at of when the event handler is called, not as of when you assign it to the event. That value will be the last value that itemID has in the loop — for all of the handler functions. More about closures here.
With the minimal-impact revised code, we use Prototype's curry function, which will create a function for you that, when called, will call the underlying function with the arguments you gave curry. (The name is from mathematics; Haskell Curry came up with the technique, though there are arguments he wasn't the first to do so.) We could do the same thing ourselves:
items = transport.responseText.evalJSON(); // my list of objects that contains all the details I'll need for that page
for (var itemID in items)
{
newDiv = ... // Creating my div with main infos
$('myDiv').appendChild(newDiv);
// More code to make everything look pretty and that works fine
Event.observe(newDiv, 'click', prepLoadItem(itemID));
}
function prepLoadItem(id) {
return function() {
loadItem(id);
};
}
...but because Prototype has a general-purpose function for it, we don't have to.
Off-topic: Is items an array? If not, ignore this off-topic comment. If so, don't use for..in to loop through it, or at least, not unless you take some precautions the code above doesn't to do it properly. Details here, but for..in is not for looping through the indexes of an array; it's for looping through the properties of an object. Array objects may well have properties other than array indexes (and in fact, if you're using Prototype, they do.)

How to assign event callbacks iterating an array in javascript (jQuery)

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);

Categories