1

Ok, using Leaflet, JQuery GeoJSON layers, and JavaScript.

I have a simple map with an HTML checkbox, that should turn on a Leaflet layer. The html input has an onClick function that it passes a layer name to.

I use JQuery $.getJSON to read the GeoJSON data. with the layers defined inside the getJSON function. I need to place the updateLayerVis outside this function so the html part can see it. However now I can't use layerName.addTo(map); as layerName is now undefined.

I've looked at JavaScript namespace examples, and basically it's call the outsidefunction.insidefunction() but how do you do this with $.getJSON to get the layers? Is there a better way? My code simplified.

<input id="larynx_chk" type="checkbox" class="list_item" onClick='updateLayerVis("larynx");'/>

var larynx;

function style(feature) {
    return {
        -----
    };
}

function theOnEachFeature(feature, layer){
    -----
}

$.getJSON(url, function(data) {
    larynx = new L.GeoJSON(data,{ 
    style: style,
    onEachFeature: theOnEachFeature, 
    filter: function(feature, layer) {
        return feature.properties.CANCERTYPE =="larynx";}
    });

    larynx.addTo(map); // will turn layer on.
});

function updateLayerVis(layer){
    ----

    larynx.addTo(map);  // fails larynx undefined.
}
  • you may have a typo on line 3, where you define larynZ rather than larynX. – Stephen Lead Nov 25 '16 at 1:19
  • notwithstanding that, you could use the Layer Control to toggle the layer on/off – Stephen Lead Nov 25 '16 at 1:20
  • The Layer Control is not really an option. I'm replacing a mash up of Java, Google, ArcGIS map with Leaflet and I need to replicate the look they have in the old app. Thanks for the editing, I was having trouble with the formatting. – Bill Chappell Nov 25 '16 at 3:35
0

This is not a namespace/scope issue. This is an asynchronicity issue. larynx has a value of undefined until some (potentially very slow) network operation has finished, so any operations on larynx before it's initialized will fail.

The variable scopes look good. You need larynx to be accessible from several parts of the code, so you've put it in the scope that is common to all the parts that need a reference to it. That's a sane thing to do.

It's common for a beginner-intermediate JS programmer to have trouble with JS doing asynchronous things. How to overcome issues related to async initialization?

Add sanity checks

Whenever you handle a variable that is potentially not initialized, do a sanity check, to see if it's in fact initialized. e.g.:

function updateLayerVis(layer){
    if (larynx !== undefined) {
        larynx.addTo(map);
    }
}

Do not let the user do things before it's time to do things

In other words: do not let the user activate the visibility of larynx until it has been loaded. e.g. something like:

<input id="larynx_chk" type="checkbox" disabled />

$.getJSON(url, function(data) {
    ...

    larynx.addTo(map);
    var checkbox = document.getElementById('larynx_chk');
    checkbox.disabled = false;
    checkbox.addEventListener('click', function(){ updateLayerVis('larynx')});
}

That way, the user will not be able to click on that checkbox until the data has been loaded. Makes sense, right?


I'm sure there are more ways to work around your specific problem. But it's more important that you understand the concept of "do not let your code assume that things are in a ready state".

  • Well you answer was helpful but not the right answer. Now if I pass the function the parameter which is the layer name, it doesn't work unless the layer name is hard coded. function updateLayerVis(a){ console.log(a); // Shows larynx a.addTo(map); // fails a.addTo is not a function larynx.addTo(map); //works }; – Bill Chappell Nov 26 '16 at 3:14
  • Sounds like a variable scope issue. Read up about javascript closures – IvanSanchez Nov 28 '16 at 8:53
0

I would suggest changing the approach slightly, by defining the larynx layer and populating it on map instantiation - this way, you only need to run one AJAX query to retrieve the records. Then toggle its visibility using the checkbox:

Within your HTML:

<label><input id="larynx_chk" type="checkbox">Toggle larynx layer</label>

Within your JS:

// Define the empty larynx layer
var larynx = new L.GeoJSON(null,{ 
    style: style,
    onEachFeature: theOnEachFeature, 
    filter: function(feature, layer) {
        return feature.properties.CANCERTYPE =="larynx";
    }
});

// Populate the larynx layer with the data retrieved via AJAX
$.getJSON(url, function(data) {
    larynx.addData(data);
});

// Toggle the layer on/off using the checkbox and a jQuery listener
$("#larynx_chk").on("click", function(){
    if(larynx._map == undefined || larynx._map == null) {
        larynx.addTo(map);
    } else {
        map.removeLayer(larynx);
    }
})
  • Steven, I tried your suggestion while it worked with 15 layers I had 15 separate on click events. I liked your example.. – Bill Chappell Nov 28 '16 at 16:28
0

At first I thought it was a scope issue, after working on it I found it was a string vs. layer issue. While I had the layer name as a string and passed it to my updateLayerVis function, the function received a string value not the layer, the quick fix was a switch statement for my 15 layers.

function updateLayerVis(a){
  // a.addTo(map);  // a.addTo is not a function

  switch(a) {
    case ("larynx"):
        larynx.addTo(map);
        break;
    case ("oral"):
        oral.addTo(map);
        break;
  }
}

If you know a better way please let me know for next time. Thanks...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.