This is undoubtedly due to my ignorance of JavaScript. I have two equisized arrays of objects and I loop through both of them simultaneously adding an action handler so that when the n:th object in the first array is hovered over, some magic occurs to the n:th object in the second array.
That's my intention.
In reality, the computer "forgets" the previous assignments and only executes the last magic declared, independently of which of the invoking objects gets hovered over. I know what the problem is but I can't think of a good way to solve it. There might be an answer to this already (seems like a common gotcha to me) but since I, obviously, can't word the problem correctly I get nada.
The code is like this.
for (index in pushpins) {
map.entities.push(pushpins[index]);
map.entities.push(infoboxes[index]);
Microsoft.Maps.Events.addHandler(pushpins[index], "mouseover", function (d) {
infoboxes[index].setOptions({ visible: true });
});
Please note that the problem most likely isn't specific to Microsoft.Maps but to the fact that JavaScript has a different scoping rules for variables. When I statically add a few instances and call them some1, some2 etc., I get the behavior intended. I believe it's index that somehow retains its value.
The best solution here is move
Microsoft.Maps.Events.addHandler(pushpins[index], "mouseover", function (d) {
infoboxes[index].setOptions({ visible: true });
});
to the other function like:
function addEvent(element, i) {
Microsoft.Maps.Events.addHandler(element[i], "mouseover", function (d) {
element[i].setOptions({ visible: true });
});
}
See: Javascript scope problem or What is the scope of variables in JavaScript?
index is not bound to each item. Your loop will attach a handler to each item, but they all refer to the same index which end up as n after your loop. Thus they all trigger with n indexes.
A common solution is to use an IIFE for each iteration which creates a local scope per iteration. That way, the handler will refer to that local index rather than the index outside the loop. Not optimal (JSHint screams "Don't create functions in loops"), but does the job.
for (index in pushpins) {
(function(index){
map.entities.push(pushpins[index]);
map.entities.push(infoboxes[index]);
Microsoft.Maps.Events.addHandler(pushpins[index], "mouseover", function (d) {
infoboxes[index].setOptions({ visible: true });
});
}(index));
}
Related
I'm in the process of reviewing a fairly straightforward ES6 implementation of a clickable, double-sided flashcard on Codepen.
I'm able to parse 99% of the code, but this toggleTurn function has left me scratching my head.
Specifically, I'm wondering:
(1) What is the purpose of setting the turned value to to its logical opposite (!turned) at the beginning of this function?
(2) Why is it necessary to pass the event object to this function as a parameter? Wouldn't it be simpler & easier to just invoke a toggleTurn() function within an event listener?
function toggleTurn(e) {
turned = !turned;
if (turned) {
this.classList.add("turned");
} else {
this.classList.remove("turned");
}
}
I'm sure the answers here are blindly obvious, so many thanks in advance for the time & patience!
What is the purpose of setting the turned value to to its logical
opposite (!turned) at the beginning of this function?
If the "turned" class is added on the html element, then that means turned is true. Negating its value will give false which will execute the else block.
Similarly, if the "turned" class is not added on the html element, then that means turned is false. Negating its value will give true which will execute the if block.
You could simplify the function using the toggle method:
function toggleTurn() {
this.classList.toggle("turned");
}
Why is it necessary to pass the event object to this function as a
parameter? Wouldn't it be simpler & easier to just invoke a
toggleTurn() function within an event listener?
If it's not used in the function, then it's not needed. You can remove it if you want to. It does however serve as a reminder that the function is used as an event listener.
In this code it makes no sense to have the turned variable, because it's only being used to toggle the class name. In fact, the toggleTurn() function could just be a one-liner:
function toggleTurn(e) {
this.classList.toggle("turned");
}
And as for the e, as you can see it's not in use. Event listeners automatically get the event parameter and there are different philosophies of whether you should include unused parameters in your function definition or not. There's nothing wrong with keeping it in there.
I have something wrong with the following code. I can't understand what is wrong with it.
function some(){
for (var i=0;i<....;i++)
{
var oneObject;
...some logic where this object is set
oneObject.watch(property,function(id, oldval, newval){
globalFunction(oneObject,id,newval);
return newval;
});
}
}
If I have for example three cycles and set three different objects I have the following result. Three different objects (for example oneObject can be equal some={},some.foo={}, some.boo={}) are set. Every of them has its own watch handler (I change the object and the handler is called). The problem is that when globalFunction is called oneObject that is passed as argument is always equal to the last object of for loop.
I can't understand why it happers as for every new cycle I redeclare oneObject variable using var. Please, explain.
EDIT
Also I tried:
function some(){
for (var i=0;i<....;i++)
{
var oneObject;
...some logic where this object is set
oneObject.watch(property,function(id, oldval, newval){
(function(obj) {
globalFunction(obj,id,newval);
}(oneObject))
return newval;
});
}
}
Since oneObject refers to an object, changing it will also change other references to that object. You can solve this with a closure.
(function(obj) {
globalFunction(obj,id,newval);
}(oneObject))
This way, each time you call globalFunction it will receive a unique copy of oneObject.
You need to create a closure for the entire reference to oneObject:
(function(obj) {
obj.watch(property,function(id, oldval, newval){
globalFunction(obj,id,newval);
return newval;
});
}(oneObject));
(I'm curious what that return is expected to do in a callback, but that's a separate issue.)
It is a little hard to tell from the abstracted code you provided but this looks like a problem caused by using an asynchronous event-loop callback (i.e. the function in watch). What typically happens in situations like this: The main loop sets up a callback. The value changes, triggering the event that is being listened to (i.e. the watch). The callback is queued in the event-loop, which is different from the main executing loop. The callback doesn't get fired until the next open cycle, which might mean the main loop has been executing in the meantime, changing the value more.
It is a little hard to explain here, but here is a link to a wonderful video that will walk you through the details of what might be happening: https://www.youtube.com/watch?v=8aGhZQkoFbQ
I don't think that oneObject persists outside of the scope. You might try using an array of oneObjects so that your oneObject variable doesn't get re-assigned each iteration. It tends to be precarious to declare variables inside of a for loop.
Consider this small snippet of JavaScript:
for(var i in map.maps)
{
buttons.push($("<button>").html(i).click(function() { alert(i); }));
}
It creates one button for each of the fields in the map.maps object (It's an assoc array). I set the index as the button's text and set it to alert the index as well. Obviously one would expect all the buttons to alert it's own text when clicked, but instead all the buttons alert the text of the final index in the map.maps object when clicked.
I assume this behavior is caused by the neat way JavaScript handles closures, going back and executing functions from the closures in which they were created.
The only way I can imagine getting around this is setting the index as data on the button object and using it from the click callback. I could also mimic the map.maps indices in my buttons object and find the correct index on click using indexOf, but I prefer the former method.
What I'm looking for in answers is confirmation that I'm doing it the right way, or a suggestion as to how I should do it.
Embrace the closures, don't work around them.
for(var i in map.maps)
{
(function(i){
buttons.push($("<button>").html(i).click(function() { alert(i); }));
})(i);
}
You need to wrap the code that uses your var i so that it ends up in a separate closure and the value is kept in a local var/param for that closure.
Using a separate function like in lonesomeday's answer hides this closure behaviour a little, but is at the same time much clearer.
If you pass the changing value to another function as a parameter, the value will be locked in:
function createButton(name) {
return $("<button>").html(name).click(function() { alert(name); });
}
for (var i in map.maps) {
buttons.push(createButton(i));
}
for(var i in map.maps){
(function(i){
buttons.push($("<button>").html(i).click(function() { alert(i); }))
})(i);
}
The cause why the closure failed in your case is that it's value still updated even after the function is bound, which is in this case is the event handler. This due to the fact that closure only remember references to variables and not the actual value when they were bound.
With executed anonymous function you can enforce the correct value, this achieved by passing i to the anonymous function, so then inside the scope of anonymous function i is defined anew.
This is the most elegant way to do what you're trying to do:
var buttons = myCharts.map(function(chart,i) {
return $("<button>").html(chart).click(function(event){
alert(chart);
});
}
You need closures to code elegantly in javascript, and shouldn't work around them. Or else you can't do things like nested for loops (without terribly hacks). When you need a closure, use a closure. Don't be afraid of defining new functions inside functions.
I'm currently in the process of building out a VERY simple Observer class for a project I'm working on. I have successfully implemented the subscribe, unsubscribe, and notify methods. Everything works exactly as expected when using "regular" functions (i.e: var f = function()).
However, when I pass an anonymous function to the subscribe method and then try to unsubscribe passing the "same" anonymous function it (as expected) doesn't remove the function from my array (they are different, after all).
Here's my subscribe and unsubscribe methods:
this._subscribers = {};
subscribe: function(type, callback) {
if ( isUndefined(this._subscribers[type]) ) {
this._subscribers[type] = [];
}
this._subscribers[type].push(callback);
},
unsubscribe: function(type, callback) {
if ( this._subscribers[type] instanceof Array ) {
var index = this._subscribers[type].indexOf(callback);
if ( index >= 0 ) {
this._subscribers[type].splice(index, 1);
}
}
},
And here's the code I'm testing with:
var o = new gaf.events.Observable();
o.subscribe('testEvent', function(event) { alert('Got It!'); });
o.notify('testEvent');
// Correct alerts 'Got It!'
o.unsubscribe('testEvent', function(event) { alert('Got It!'); });
o.notify('testEvent')
// Incorrectly alerts 'Got It!'
I know I could using an object (i.e.: _subscribers[event] = {}) and then when something subscribes I could add a new property equal to the callback and the value equal to the callback. This will cause Javascript to convert the callback to the string. I could then look it up (provided the methods passed in sub/unsub are exactly the same) using that string.
However, this is a mobile project and I'm very leery about storing strings that could be hundreds of characters long as properties as we could end up with a lot of subscribers.
Are there any other ways of doing this? Are there any SMALL (tiny, even) hashing libraries I can use to maybe hash the string value of the function and use that as the property? Would it be better to store the string value of the callback (so I can compare against it) in the array (rather then the actual callback) and use eval() on it?
EDIT
First, thanks all for the replies!
Per all the questions about "Why even pass anonymous" functions -
There really is no reason one COULDN'T use named functions. In fact, I agree with everyone that named functions are going to be the better solution. I'm simply gathering information and looking for a solution so that I can build out an implementation that handles the most scenarios as best as possible.
The other reason for this is what happens if a user (co-worker) of this Observable class passes it an anonymous function and then unsubscribes. That function won't actually be unsubscribed and therefore won't be cleaned up. I have a thing against orphaned data :)
Maybe another question I should as is, is it possible to test if the callback is anonymous or not? I'm going to assume no but doesn't hurt to ask.
There is nothing wrong with storing the entire string; premature optimization is evil.
However, this sounds like an incredibly bad idea.
If someone changes the function, but forgets to change the unsubscribed copy, the code will be subtly broken with no warning whatsoever.
Instead, you can require the user to store the anonymous function in a variable if they want to unsubscribe from it.
Alternatively, you can pass an optional name with each subscriber, then unsubscribe by that name.
the clients that use the Observer should store the reference to the function.
var obsCallback = function() {
}
o.subscribe('test', obsCallback);
....
o.unsubscribe('test', obsCallback);
in other words, keep a reference to the function around...
Perhaps a better solution is to modify the code using your library
var f = function() { alert('Got It!'); };
o.subscribe('testEvent', f);
o.notify('testEvent');
o.unsubscribe('testEvent', f);
o.notify('testEvent');
You could even return the function from the subscribe method
var f = o.subscribe('testEvent', function() { alert('Got It!'); });
// ...
then if you want to store a hash or some other identifier for subscribed functions, it is opaque to the calling code meaning that you just use the returned value to unsubscribe and the library hides the implementation detail.
What is the reason for passing in anonymous functions rather than named ones, or keeping references that you can use for unsubscribing later?
Alternatively you could allow for an optional 'id' argument but this would require unnecessarily complex bookkeeping to avoid duplicates.
I'm using mootools:
I can't figure out how to use a variable when using an addEvent.
I want to use a for next loop to set values in a loop:
for (x=0;x<num;x++){
var onclickText = 'function (){onclick="addPageMoveEvent('+x+'"); }';
$('pageNum'+x).addEvent('click', onclickText);
}
>
I've search forums but not found any help.
Any help would be great.
Thanks
The addEvent method in MooTools accepts two arguments:
myElement.addEvent(type, fn);
Arguments:
type - (string) The event name to monitor ('click', 'load', etc) without the prefix 'on'.
fn - (function) The function to execute.
It does not take a string and passing a string such as "myFunction()" or "function() { myFunction(); }" will not work.
Since you are inside a loop, and the variable x will share the environment, you need to wrap its value inside another closure. One way is to use an additional closure:
$("pagenum" + x).addEvent("click", (function(value) {
return function() { addPageMoveEvent(value); }
})(x));
See all questions on StackOverflow regarding this particular problem of creating closures within loops.
Also worth checking out is this MDC article - Creating closures in loops: A common mistake
Warning: this first example will not work! Read on for an explanation.
You are confusing onclick HTML syntax with the MooTools addEvent. Try
for (var x=0;x<num;x++){
$('pageNum'+x).addEvent('click', 'addPageMoveEvent('+x+');');
}
This is simpler and cleaner, but might still not do what you want. This code will call the function addPageMoveEvent every time the link is clicked... is that what you want?
Since MooTools doesn't allow the above method, you must use the following:
A programmatically more interesting and less hazardous way to do the same would be:
factory = function (x) { return function() { addPageMoveEvent(x); }; };
for (var x=0;x<num;x++){
$('pageNum'+x).addEvent('click', factory(x));
}
This uses a factory for creating closures that hold your values of x... rather complex code, but it's the purist way. It also avoids using the scary eval that occurs because you feed addEvent a string. (It seems that MooTools doesn't like the other option anyway.)
That a use case for mootools pass method.
for (x=0;x<num;x++){
$('pageNum'+x).addEvent('click', addPageMoveEvent.pass(x));
}
Pass internally creates a closure that holds x in the his scope, so when the click event is fired it has the right value cause its not the same from the for loop.