1

dear all. Please, don't judge me hard, I am new in working with JavaScript and OpenLayers.

I am applying the same action of click functionality to multiple buttons that actually perform the same task but for different values.

I have six buttons that are placed in HTML section:

<button id="pan-to-kiev">Kiev</button>
<button id="pan-to-munich">Munich</button>
<button id="pan-to-vienna">Vienna</button>
<button id="pan-to-dresden">Dresden</button>
<button id="pan-to-enschede">Enschede</button>
<button id="pan-to-bonn">Bonn</button>

I also have the object that is placed in JS sheet:

var cities = [
  {name: "Kiev", lonlat: [30.523400, 50.450100], color: '#006699', id:"pan-to-kiev"},
  {name: "Munich", lonlat: [11.581980, 48.135125], color: '#AB4450', id:"pan-to-munich"},
  {name: "Vienna", lonlat: [16.373819, 48.208174], color: '#93648D', id:"pan-to-vienna"},
  {name: "Dresden", lonlat: [13.737262, 51.050409], color: '#FFC65D', id:"pan-to-dresden"},
  {name: "Enschede", lonlat: [6.893662, 52.221537], color: '#F16745', id:"pan-to-enschede"},
  {name: "Bonn", lonlat: [7.0954900, 50.7343800], color: '#4CC3D9', id:"pan-to-bonn"}
];

This is a code that I wrote and which is executable, but I know that is bullshit since my code have to be more explicit and short because some variables repeat.

function onClick(id, callback) {
  document.getElementById(id).addEventListener('click', callback);
}

onClick(cities[0]["id"], function() {
    view.animate({
      center: ol.proj.fromLonLat(cities[0]["lonlat"]),
      duration: 1500
    });
  });

onClick(cities[1]["id"], function() {
    view.animate({
      center: ol.proj.fromLonLat(cities[1]["lonlat"]),
      duration: 1500
    });
  });

onClick(cities[2]["id"], function() {
  view.animate({
    center: ol.proj.fromLonLat(cities[2]["lonlat"]),
    duration: 1500
  });
});

onClick(cities[3]["id"], function() {
  view.animate({
    center: ol.proj.fromLonLat(cities[3]["lonlat"]),
    duration: 1500
  });
});

onClick(cities[4]["id"], function() {
  view.animate({
    center: ol.proj.fromLonLat(cities[4]["lonlat"]),
    duration: 1500
  });
});

onClick(cities[5]["id"], function() {
  view.animate({
    center: ol.proj.fromLonLat(cities[5]["lonlat"]),
    duration: 1500
  });
});

And my idea does not work

 for (var city in cities) {
   onClick(cities[city]["id"], function() {
     view.animate({
       center: ol.proj.fromLonLat(cities[city]["lonlat"]),
       duration: 1500
     });
   });
   console.log(cities[city]["id"]);
}

Can you be so kind and help or at least explain to me what should I change here to be able to get better code with a for-loop?

Thank you so much, dear experts.

5
  • You can use this.text etc. Commented Jan 20, 2018 at 17:54
  • Learn about JavaScript closure: Kyle Simpson, You Don't Know JS Commented Jan 20, 2018 at 18:04
  • It seems your problem is that you are losing the context of your city var in the for loop, what is exactly that doesn’t work ? Commented Jan 20, 2018 at 18:04
  • @JohuderGonzalez when I click each button it animates only to the last city in the cities array, i.e. Bonn. Commented Jan 20, 2018 at 18:10
  • @TarasSara yeah your problem is just for context, you need a closure to make it work, I just posted my answer right below, that resolves your problem, please check out the closure javascript doc to understand what happened. Commented Jan 20, 2018 at 18:25

2 Answers 2

1

In for (var city in cities) city is already an object, there is no point in using cities inside the loop, just use the object you have.

However, when iterating over an Array, you should be using for...of or even better forEach(), as suggested in the MDN documentation for for...in.

Here is the forEach implementation of the same loop with lambdas to avoid any problems with this:

cities.forEach(city => { 
  onClick(city.id, () => {
    view.animate({
      center: ol.proj.fromLonLat(city["lonlat"]),
      duration: 1500
    });
  });
  console.log(city.id); 
});
Sign up to request clarification or add additional context in comments.

4 Comments

Excuse me please, but when I use commands 'console.log(city.name); ' either 'console.log(city["name"]); ' it won't work. Am I correctly thinking?
sorry, I shouldn't have used the for...in loop without testing it - I removed that code in this edit, always use for...of or forEach for Arrays.
Thank you so much, dear @5ar. Can you be so kind and suggest me the topic that I should read to be able to understand the background of my problem? Many Thanks!
@TarasSara glad to be of help :) As many others have suggested, take a look into JS Closures - especially take note of the this keyword (I always use Arrow functions if I can to avoid any accidental issues) - and in using for...in vs for...of (the short answer is that the first one is for pure objects and somewhat unpredictable without practice, and the second is for arrays and behaves as you would expect).
1

Your code is good for your purpose, the only problem is that your are loosing your context in the for loop, so you need to make a function closure to avoid this behavior, so you only need to change this part of your code to make it work.

for (var city in cities) {
   onClick(city.id, function(cityPassed){
     return function() {
       view.animate({
         center: ol.proj.fromLonLat(cityPassed["lonlat"]),
         duration: 1500
       });
     }
   // pass the city variable
   }(city));
}

as 5ar said, you don't need to access cities[city]["lonlat"] again because city is already the object inside the array, so you just need to use it, please check above Javascript Closures here: https://developer.mozilla.org/es/docs/Web/JavaScript/Closures

I hope it can help you.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.