0

I'm having a hard time understanding why this.$map and this.markers are undefined. Here's the code I'm working with and I have added comments where I expect these variables to supply a value:

(function($) {
    'use strict';

    var A = {

        /**
         * Initialize the A object
         */
        init: function() {
            this.$map = this.renderMap();
            this.markers = this.getMarkers();

            this.renderMarkers();
        },

        renderMap: function() {

            var url = 'http://a.tiles.mapbox.com/v3/delewis.map-i3eukewg.jsonp';

            // Get metadata about the map from MapBox
            wax.tilejson(url, function(tilejson) {
                var map = new L.Map('map', {zoomControl: false});

                var ma = new L.LatLng(42.2625, -71.8028);

                map.setView(ma, 8);

                // Add MapBox Streets as a base layer
                map.addLayer(new wax.leaf.connector(tilejson));

                return function() {
                    return map;
                };
            });
        },

        renderMarkers: function() {
            var geojsonLayer = new L.GeoJSON(null, {
                pointToLayer: function (latlng){
                    return new L.CircleMarker(latlng, {
                        radius: 8,
                        fillColor: "#ff7800",
                        color: "#000",
                        weight: 1,
                        opacity: 1,
                        fillOpacity: 0.8
                    });
                }
            });

            geojsonLayer.addGeoJSON(this.markers); // this.markers is undefined
            this.$map.addLayer(geojsonLayer); // this.$map is undefined

        },

        getMarkers: function() {
            $.getJSON("/geojson/", function (data) {
                return data;
            });
        }
    };

    /**
     * A interactions
     */
    $(document).ready(function() {
        A.init()
    });

})(jQuery.noConflict());

I have spent much of the day searching and I think I'm missing something fundamental here, but I don't get it.

Mark Lewis
  • 157
  • 2
  • 13
  • 5
    Because you cannot (lets say should not) return values from an Ajax call... Ajax is **asynchronous** by default. If you could return the value directly, why would you have to pass a callback? Please read [jQuery: Return data after ajax call success](http://stackoverflow.com/questions/5316697/jquery-return-data-after-ajax-call-success). It's partly a scope issue, but mostly a timing issue. – Felix Kling May 10 '12 at 01:30
  • 3
    Look at `getMarkers`. The function does not return a value. The callback you pass to `$.getJSON` does, but that callback is executed by (inside) `$.getJSON`, so it returns the value to `$.getJSON` and not the function that calls `getMarkers`. Furthermore, the callback is executed when the response was received and at that time `getMarkers` already finished (returned `undefined`). There are literally (I assume) thousands of questions here which are about the same problem. – Felix Kling May 10 '12 at 01:37
  • There must be thousands, easily. One day I counted 8 new "async thing doesn't return a value" questions in a few hours. Assuming there are about 8 of these posted a day, that would be about thirty thousand over the lifespan of the site so far. – Dagg Nabbit May 10 '12 at 02:08
  • @GGG, I didn't realize both scenarios were an ajax call. Stupid on my part? Yes, and it had me looking in the wrong place. – Mark Lewis May 10 '12 at 02:13
  • @MarkLewis the fact that you're passing callbacks to functions with "json" in the name should be a pretty big hint that the functions you are calling are ascynchonous, and the fact that you're returning things from callbacks should be a red flag. – Dagg Nabbit May 10 '12 at 02:18

2 Answers2

3

Neither the renderMap, nor getMarkers methods return any value, consequently their return value is undefined.

It looks like you are trying to initialize these fields from an ajax request, which is not necessarily a good idea.

What you probably ought to do is something like:

getMarkers: function(callback){
    var result = this;
    $.getJSON("url", function(jsonData){ 
        result.markers = jsonData; 
        if(callback) callback()
     });
},

which will lazily initialize the fields of the object as they become available.

NB: AJAX is asynchronous you cannot rely on this callback setting the member quickly, or indeed ever (it could fail). This suggests you need to think a bit more about your design, and try to use callbacks more.

e.g. modify the getMarkers and renderMap functions as above to take a callback that is called after the data is stored then change init to:

init: function(){
    var res = this;
    var post_init_callback = function(){
        if(res.$map != undefined && res.markers!=undefined){
            //this will only run after both ajax calls are complete
            res.renderMarkers();  
        }
    };
    this.getMarkers(post_init_callback);
    this.renderMap(post_init_callback);
},
tobyodavies
  • 27,347
  • 5
  • 42
  • 57
  • So instead of trying to return the data to a variable, I should utilize a callback to process the data? In the case of `renderMap()` how what I return `map`? – Mark Lewis May 10 '12 at 01:56
  • Add `var result = this;` at the top of render map, and change `return map` to `result.$map = map;` also, see my edit – tobyodavies May 10 '12 at 01:58
  • WOW, your comment just made me realize that `wax.tilejson` is an ajax call. Now I understand, I thought I had two different situations. Thank you! – Mark Lewis May 10 '12 at 02:04
1

The problem here is that you call return inside another function. What you're essentially doing is defining getMarkers (the simplest example) as this:

getMarkers: function() {
    $.getJSON('/geojson/', some_random_func);
}

At which point it becomes obious that getMarkers doesn't actually return anything (ergo undefined). The same goes for your renderMap function. Also in this case your "some_random_func" is defined as function(data) { return data; } but what does it return it to? Truth is that the some_random_func is called by jQuery itself, and AFAIK jQuery doesn't care at all for the return-value of it's success-function.

Alxandr
  • 12,345
  • 10
  • 59
  • 95
  • Doesn't it return the data to `this.markers` in `A.init()` (i.e. `this.markers = this.getMarkers();`)? – Mark Lewis May 10 '12 at 01:43
  • 1
    @Mark: The function `getMarkers` does not have a `return` statement, how can it return anything? Maybe you are confused by the function you define inside `getMarkers`... the value that that function returns is not magically returned by `getMarkers` (there might be languages where this is the case, but not in JavaScript). You can make a simple test: What does `function foo() { function bar() { return 'bar';} bar(); }; alert(foo());` give you? – Felix Kling May 10 '12 at 01:50
  • @Felix, your comments help, thank you. Now I just need to understand the solution. – Mark Lewis May 10 '12 at 01:59