1

I am developing a page using Leaflet to display a map for a clients site. The page first loads the details of the map - name, lat, long etc based on an id. The load is via ajax to a very simple c# page that execs a stored proc and returns the recordset as JSON.

Once the map is loaded there is a second ajax call that clears and plots markers on the map. This is to a similar c# page that returns vehicle details. This ajax call is then set into a setInterval(function()) call to repeat every x seconds.

When I run the page normally, nothing loads properly. Looking through the debugger in firefox I can see that the first ajax call has returned undefined. If I add a breakpoint and step through to watch what happens, it works. If I remove the breakpoint, it fails.

What am I doing wrong here?

Main HTML file

<head>
    <meta http-equiv="x-ua-compatible" content="IE=11">
    <meta charset="utf-8" />
    <meta http-equiv="refresh" content="3600">
    <title>Map Viewer</title>
    <link rel="stylesheet" type="text/css" href="/Content/themes/base/all.css" />
    <link rel="stylesheet" type="text/css" href="/Content/Site.css" />
    <link rel="stylesheet" type="text/css" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />

    <link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon.png">
    <link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32">
    <link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16">

    <script type="text/javascript" src="/Scripts/jquery-3.0.0.min.js"></script>
    <script type="text/javascript" src="/Scripts/jquery-ui-1.11.4.min.js"></script>
    <script type="text/javascript" src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
    <script type="text/javascript" src="/Scripts/leaflet-providers.js"></script>
    <script type="text/javascript" src="/Scripts/leaflet-tracksymbol.js"></script>
    <script type="text/javascript" src="/Scripts/leaflet-tracklayer.js"></script>

    <script type="text/javascript" src="/Scripts/ctrack-clearybros.js"></script>

    <meta name="viewport" content="width=device-width" />
</head>
<body >
<h1 id="pageTitle"></h1>
<div id="errorText"></div>
<div id="map"></div>

<script id="mapScript" type="text/javascript">

Script Block

    var mapDetails;
    var mapId = 1;
    var reloadTime = 15 * 1000;  //15 seconds
    //var w = $(window).width();
    //var h = $(window).height();

    mapDetails = getMapDetails(mapId);
    console.debug("Map details retrieved");
    console.debug(mapDetails);
    //$("#map").width(w);
    //$("#map").height(h);
    $("#pageTitle").text(mapDetails.mapTitle);

    var map = new L.Map("map", {
        center: mapDetails.mapCenter,
        minZoom: 16,
        maxZoom: 16,
        zoom: 16,
        reuseTiles: true,
        unloadInvisibleTiles: true
    });
    console.debug("Map Created");
    //console.debug(map);

    var appId = "snip";
    var appCode = "snip";

    //using plugin
    var tilesLayer = new L.tileLayer.provider("HERE.terrainDay", {
        attribution: "Event Data &copy; <a href=\"http://www.ctrackonline.com.au/\">Ctrack Australia</a> &#151; Map Data &copy; <a href=\"http://www.here.com\">HERE maps</a>",
        app_id: appId,
        app_code: appCode,
        subdomains: '1234',
        mapID: 'newest',
        base: 'aerial',
        maxZoom: 20,
        type: 'maptile',
        language: 'eng',
        format: 'png8',
        size: '256'
    });
    tilesLayer.addTo(map);
    console.debug("tileLayer Created");
    //console.debug(tilesLayer);

    map.removeControl(map.zoomControl);
    map.dragging.disable();  //disable map panning
    map.doubleClickZoom.disable();  //disable click to recenter
    map.touchZoom.disable();
    map.scrollWheelZoom.disable();
    map.boxZoom.disable();
    map.keyboard.disable();
    console.debug("Map options set");

    var unitLayer = new L.FeatureGroup();
    unitLayer.addTo(map);


    populateUnitLayers(mapId) //prime the pump before the setInterval fires
    console.debug("Vehicle layer populated");
    console.debug(unitLayer);
    //setInterval(function() {
    //    populateUnitLayers(mapId) //called every reloadTime seconds
    //}, reloadTime);

</script>


<div id="ajaxLoadingHolder">
    <div id="ajaxLoading"><img src="/Content/images/ajax-loader.gif" /></div>
</div>

Additional JS file

function getMapDetails(mapId) {
    var _mapTitle;
    var _mapLatitude;
    var _mapLongitude;
    var _mapCenter;

    var JsonUrl;
    if (window.location.pathname == "/ClearyBros.cshtml") { JsonUrl = "/JSON/GetDetailForLocation"; }
    if (window.location.pathname == "/CtrackMaps/ClearyBros.cshtml") { JsonUrl = "/CtrackMaps/JSON/GetDetailForLocation"; }

    $.ajax({
        method: "GET",
        url: JsonUrl,
        processData: true, //means data sent as querystring
        dataType: "json", //,"text"
        data: { mapId: mapId },
        timeout: 60000
    })
    .done(function (results) {
        if (jQuery.isEmptyObject(results)) {
            console.error("Map details are blank");
        }
        else {
            console.debug("Map details are not blank");
            console.debug(results);
        }
        $.each(results, function (index, result) {
            _mapTitle = result.Name;
            _mapLatitude = result.Latitude;
            _mapLongitude = result.Longitude;
            _mapCenter = new L.LatLng(_mapLatitude, _mapLongitude);
        });
    })
    .fail(function (xhr, status, error) {
        console.error("Failed to load map details");
        if (status == "timeout") {
            var errorText = "Timeout reached loading map details.";
            displayError(errorText);
        }
        else {
            var errortext = "Error state \"" + status + "\" occured loading map details. \n" + error;
            displayError(errorText);
        }
    });

    var obj = {
        mapTitle: _mapTitle,
        mapLatitude: _mapLatitude,
        mapLongitude: _mapLongitude,
        mapCenter: _mapCenter
    };

    return obj;
}

function populateUnitLayers(mapId) {
    // set all our marker default values here.  This doesnt get added to a layer.
    var trackSymbolDefault = new L.trackSymbol(new L.LatLng(0.0, 0.0), {
        trackId: 0,
        fill: true,
        fillColor: '#ffffff',
        fillOpacity: 1.0,
        stroke: true,
        color: '#000000',
        opacity: 1.0,
        weight: 1.0,
        speed: 0,
        course: 0,
        heading: 0,
        leaderTime: 0
    });

    var JsonUrl;
    if (window.location.pathname == "/ClearyBros.cshtml") { JsonUrl = "/JSON/GetUnitsForLocation"; }
    if (window.location.pathname == "/CtrackMaps/ClearyBros.cshtml") { JsonUrl = "/CtrackMaps/JSON/GetUnitsForLocation"; }

    unitLayer.clearLayers();
    //unit0Layer.clearLayers();
    //unit1Layer.clearLayers();
    //unit2Layer.clearLayers();

    $("#error-text").hide();

    $.ajax({
        method: "GET",
        url: JsonUrl,
        processData: true, //means data sent as querystring
        dataType: "json", //"text",
        data: { mapId: mapId },
        timeout: 60000
    })
    .done(function (results) {

        $.each(results, function (index, result) {
            var marker = createUnitMarker(result, trackSymbolDefault);
            if (marker.options.speed == 0) {
                marker.options.color = '#cccccc';
                //marker.addTo(unit0Layer);
                //unit0Layer.addTrack(marker);
            }
            else if (marker.options.speed > 60) {
                marker.options.color = '#ff0000';
                //marker.addTo(unit1Layer);
                //unit1Layer.addTrack(marker);
            }
            else {
                marker.options.color = '#ffff00';
                //marker.addTo(unit2Layer);
                //unit2Layer.addTrack(marker);
            }
            //marker.addTo(map);
            marker.addTo(unitLayer);
        });
    })
    .fail(function (xhr, status, error) {
        console.error("Failed to load vehicle details");
        displayError("Error getting data while loading vehicle positions");
        if (status == "timeout") {
            //alert("Timeout reached.");
            displayError("Timeout reached loading map details");
        }
        else {
            //alert("Error state \"" + status + "\" occured loading vehicle positions. \n" + error );
            var errorText = "Error state \"" + status + "\" occured loading vehicle positions.";
            displayError(errorText);
        }
    });
}

function createUnitMarker(result, defaults) {
    var _marker;
    const kphToMph = 0.621371;

    var _latlng = new L.LatLng(result.Latitude, result.Longitude);
    var _track = result.NodeId;
    var _speed = result.Speed * kphToMph; // Km/h to m/h
    var _course = result.Heading * Math.PI / 180.0; // Radians from north
    var _heading = result.Heading * Math.PI / 180.0;

    _marker = new L.trackSymbol(_latlng, {
        trackId: _track,
        fill: defaults.options.fill,
        fillColor: defaults.options.fillColor,
        fillOpacity: defaults.options.fillOpacity,
        stroke: defaults.options.stroke,
        color: defaults.options.color,
        opacity: defaults.options.opacity,
        weight: defaults.options.weight,
        speed: _speed,
        course: _course,
        heading: _heading,
        leaderTime: defaults.options.leaderTime
    });

    var _note = "<p><b>Unit Name:</b> " + result.UnitName + "<br /><b>Unit Desc:</b> " + result.UnitDesc + "<br /><b>Last Updated:</b> " + result.AssembledTime + "<br /><b>Speed:</b> " + result.Speed + " km/h</p>"
    _marker.bindPopup(_note);

    console.debug("TrackId " + _marker.options.trackId + " added");

    return _marker;
}

function displayError(message) {
    $("#error-text").text(message);
    $("#error-text").show();
}

$(document).ajaxStart(function () {
    $("#ajaxLoading").show();
}).ajaxStop(function () {
    $("#ajaxLoading").hide();
});
Hecatonchires
  • 1,009
  • 4
  • 13
  • 42
  • Possible duplicate of [How do I return the response from an asynchronous call?](http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Andreas Jul 19 '16 at 06:02
  • When you notice a different behavior when stepping through it's often a result of async requests being processed in a different order. The solution is often promises, as mentioned by @Andreas above. – smoksnes Jul 19 '16 at 06:06
  • My understanding is that using jQuery and the .done() and .fail() events avoids that asynchonous waiting issue. Your code won't fire until it has responded. $.ajax({ //options }) .done(function (results) { }) .fail(function (xhr, status, error) {}) – Hecatonchires Jul 19 '16 at 06:22

2 Answers2

1

I ended up using jQuery's when() and then()

var ajaxOptions = {
    method: "GET",
    url: JsonUrl,
    processData: true, //means data sent as querystring
    dataType: "json", //"text",
    data: { mapId: mapId },
    timeout: 60000,
    cache: false
}

$.when($.ajax(ajaxOptions))
 .then(function (results) {
    $.each(results, function (index, result) {
        // do stuff with each result
    });
});

Putting my ajax in the first calls done did not suit, as the first call happened once on page load, and the 2nd occurred every refresh seconds.

Hecatonchires
  • 1,009
  • 4
  • 13
  • 42
0

To help you out instead of just linking to other questions:

Ajax is asynchronous. This means it starts the call, then gets out of the way while loading to execute other code, and then when it finishes loading comes back and executes the callback.

The second ajax call has to wait for the 1st ajax to complete before executing I'm guessing. What you need to do is either move the call into the 1st ajax's sucess callback, or you need to bind the second call to a ajax:success handler.

gcoreb
  • 181
  • 5
  • I'm using the .done() and .fail() events which is the correct jQuery way I thought – Hecatonchires Jul 19 '16 at 06:20
  • [How should duplicate questions be handled?](http://meta.stackexchange.com/questions/10841/how-should-duplicate-questions-be-handled) – Andreas Jul 19 '16 at 06:22
  • Yes, you have those functions. But in terms of having TWO ajax calls. The second ajax call has to be called inside the first's .done(), or if you don't like the callback hell, use IIFE. – gcoreb Jul 19 '16 at 06:23
  • @Andreas - that question does not apply fully to this person's code. – gcoreb Jul 19 '16 at 06:24
  • I've tried reading that answer Andreas. It makes me cry. What is IIFE? – Hecatonchires Jul 19 '16 at 06:52
  • OK, just for now, try putting your 2nd ajax call inside the first's .done() – gcoreb Jul 19 '16 at 06:52