3

I'm trying to tie an onClick for each marker to call a function that populates data associated with that marker into an element on the page, but the iterator(i) seems to be overwriting itself (every marker acts as if it's the last one iterated through), but only when used within the onClick function associated with the marker. i works as expected everywhere else. Ignore the console.log lines, I was just trying to track where the variable was breaking. Any help would be appreciated.

var markers = L.markerClusterGroup({maxClusterRadius: 100, disableClusteringAtZoom: 9});

function addMarkers(data, dataset, h) {
    // Create a new marker cluster group
    var popup = '';

    // Loop through data
    for (var i = 0; i < Object.keys(data).length; i++) {
        // Set the data location property to a variable
        var lat = data[i].latitude;
        var lon = data[i].longitude;
        var location = [lat, lon];
        var value = i-1;

        // Check for location property
        if (location) {
            if (dataset.icon == bigfootIcon) {
                popup = `${data[i].date}<br/>
                            ${data[i].county}, ${data[i].state}<br/><br/>
                            ${data[i].title}`;
            } else if (dataset.icon == ufoIcon) {
                popup = `${data[i].date}<br/>
                            ${data[i].city}, ${data[i].state}<br/><br/>
                            ${data[i].title}`;
            } else if (dataset.icon == dogmanIcon) {
                popup = `${data[i].date}<br/>
                            ${data[i].location}, ${data[i].state_abbrev}`;
            } else if (dataset.icon == hauntedIcon) {
                popup = `${data[i].location}<br/>
                            ${data[i].city}, ${data[i].state_abbrev}`
            };
        };

        // Add a new marker to the cluster group and bind a pop-up
        var marker = L.marker(location, {icon: dataset.icon}).bindPopup(popup).on('click', function() {
            // onToggle(dataset);
            // var value = i-1;
            console.log(data[value]);
            onChange(data[value], dataset, h)
        });
        // console.log(marker);
        marker.addTo(markers);
    };

    // Add our marker cluster layer to the map
    map.addLayer(markers);
};

// Called elsewhere to prep for calling the onChange function from a dropdown
function dropdownChange(response, dataset, h) {
    var dropdown = dataset.dropdown;
    var value = dropdown.property('value');

    console.log(value);

    dropdown.on('change', function() {
        onChange(response[value], dataset, h);
    });
};

function onChange(response, dataset, h) {
    var dropdown = dataset.dropdown
    value = dropdown.property('value');
    console.log(value);
    console.log(response)
    document.getElementById('summaryText').style.display = 'block';
    document.getElementById('summaryText').style.maxHeight = `calc(100% - ${h}px)`;

    stats.html('');

    if (dropdown == datasets[0].dropdown) {
        stats.html(`<p>Location: ${response.county}, ${response.state}<br/>
                        Date: ${response.date}<br/>
                        Classification: ${response.classification}<br/>
                        <br/>
                        Incident:</p>`);
    } else if (dropdown == datasets[1].dropdown) {
        stats.html(`<p>Location: ${response.city}, ${response.state}<br/>
                        Date: ${response.date}<br/>
                        Duration: ${response.duration}<br/>
                        Shape: ${response.shape}<br/>
                        <a href='${response.report_link}' target="_blank">Report Link</a><br/>
                        <br/>
                        Incident:</p>`);
    } else if (dropdown == datasets[2].dropdown) {
        stats.html(`<p>Location: ${response.location}, ${response.state_abbrev}<br/>
                        Date: ${response.date}<br/>
                        <br/>
                        Incident:</p>`);
    } else if (dropdown == datasets[3].dropdown) {
        stats.html(`<p>Location: ${response.city}, ${response.state_abbrev}<br/>
                        <br/>
                        Incident:</p>`);
    };

    summaryText.text('');
    summaryText.text(response.summary);
    map.flyTo([response.latitude, response.longitude], 15);
};
Ashlander
  • 33
  • 4

2 Answers2

2

This is a classic JavaScript var scope / closure issue.

TL; DR: just replace var value by const value and the issue should be gone.

In JavaScript, var is function scoped, therefore your value variable is the same throughout all your for loop iterations (but its value is changed in each iteration). At the end of the loop, its value is the last i - 1.

It is then in the closure of all your Markers click event listeners, but they all reference the exact same value variable.

With const keyword, you now have a block scoped variable, much more similar to other programming languages. It is unique to every loop iteration, and each of your Marker click listener has its own value in its closure.

ghybs
  • 47,565
  • 6
  • 74
  • 99
  • This did it, thank you! I've been trying to figure this out for a week, can't believe it was something so simple. Can you explain why the other var variables weren't suffering from this problem? Both `var location` and `var popup` are also based on `i-1` (actually just `i` now in my newly working version), but work as expected in the same function. – Ashlander Feb 23 '21 at 22:09
  • Thank you for your feedback! Your `location` and `popup` variables are used immediately in the loop iteration, whereas the `value` variable is "embedded" in a callback (it is now part of its _closure_), hence it is used later on. If you log the former 2 in the event listener as well, you will see the same effect. That is the main reason for the introduction of `const` (and `let`) keywords in modern JS. You should no longer need `var`. – ghybs Feb 24 '21 at 02:15
  • So would best practice be to use `const` and `let` instead of `var` exclusively, or just for constants/variables within functions? – Ashlander Feb 24 '21 at 06:22
  • Const and let. No more var. https://stackoverflow.com/questions/762011/whats-the-difference-between-using-let-and-var#:~:text=let%20allows%20you%20to%20declare,function%20regardless%20of%20block%20scope. For fun: https://mobile.twitter.com/ireaderinokun/status/776050419684544512 – ghybs Feb 24 '21 at 08:47
1

I'm not sure if this works but try to add the value directly to the marker and then read it out (from the marker) in the click function:

// Add a new marker to the cluster group and bind a pop-up
var marker = L.marker(location, {icon: dataset.icon}).bindPopup(popup);
marker.value = value;
marker.on('click', function(e) {
            var value = e.target.value;
            console.log(data[value]);
            onChange(data[value], dataset, h)
});
marker.addTo(markers);
Falke Design
  • 10,635
  • 3
  • 15
  • 30