Esri Map Service Loading & Error Events in Leaflet.js

screen shot of codepen demo map

Over the summer, we were having a recurring issue where our map services on ArcGIS Server were down.  It seemed to happen at random times.  I even received an e-mail while at a picnic about a service being down.  While this was annoying, more concerning was that my web maps built using Leaflet.js and consuming the ArcGIS Server map services did not inform the users about the services being down.  These people would open a map showing a basemap and nothing else.

I decided that at some point I was going to look into the API and learn how to deal with map services not loading.  I want to share the solution I plan on implementing.  I’ve built a CodePen to demo these concepts.  I don’t cover everything in this post, so the CodePen is definitely worth checking out.

My solution comprises two parts: 1) Remove a loading screen after all layers have loaded; 2) Adding a warning message if any layers do not load (loading screen is removed after closing the message).  I’m hoping to implement these changes as I perform other enhancements/upgrades to existing maps.

Esri Leaflet Service Class

My solution is tied to events within the L.esri.Service class. This class is “[a] generic class representing a hosted resource on ArcGIS Online or ArcGIS Server.”  It extends Leaflet’s L.Evented, which is “[a] set of methods shared between event-powered classes.”  There are five events tied to this class:

  1. requeststart – fires when a request to the service begins
  2. requestend – fires when a request to the service ends
  3. requestsuccess – fires when a request to the service was successful
  4. requesterror – fires when a request to the service responds with an error
  5. authenticationrequired – fires when a request to the service fails and requires authentication (password protected services)

In addition to these events for the L.esri.Service class, the L.esri.DynamicMapLayer and L.esri.FeatureLayer classes both contain the Loading and Load events.  The Loading event fires when new features start loading, whereas the Load event fires when all the features in the current map bounds have loaded.  My solution connects with the Load event, as my layers are typically one of these two classes. However, I am guessing you could also utilize the generic requestsuccess event in place of the load event.

Loaded Status Property

I had such a struggle figuring out this solution I had to take a break for a few weeks.  Fortunately, during my break, an idea popped into my mind – an idea that worked!  This idea was to add the isLoaded property to the feature and dynamic map layer constructor.  This property is initially set to false, representing that the layer is not loaded to the map.  I then created a function that handles the load and requesterror events for a provided service.  Within the load event, the isLoaded property is set to true, representing the layer has been loaded to the map.  Within the requesterror event, the isLoaded property is set to false (it should already be false, but this is an extra precaution).

While I usually add a layer to the map when it’s constructed, I have to wait until after I call the function to handle the loading-related events.  This is because the documentation states that the following procedure should be used when using the load event:

  1. Create layer object
  2. Add the load event listener
  3. Add the layer object to the map

As I wrote above, the requesterror event fires when a request to the service responds with an error.  In my experience, an error usually occurs because the service is down (typically a 500 error code; the ArcGIS Server probably needs restarted), or a 404 error (the URL has changed for that service or the service is no longer published).  Below is the code sample for implementing this solution:

 

// feature layer
var featureLayerObject = L.esri.featureLayer({
   url: '//services.somedomain.com/arcgis/rest/MapService/MapServer/0',
   isLoaded: false
});

// dynamic map layer
var dynamicMapObject = L.esri.dynamicMapLayer({
   url: '//services.somedomain.com/arcgis/rest/MapService2/MapServer',
   isLoaded: false
});

// function handles load & requesterror events
// argument is a esri map/feature service object
function processLoadEvent(service) {
   // service load event
   service.on('load', function(e) {
      console.log('The map service ' + service.options.url + ' has been added to the map');
      // set isLoaded property to true
      service.options.isLoaded = true;
   });   

   // request error event
   service.on('requesterror', function(e) {
      // if the error url matches the url for the map service, display error messages
      // without this logic, various urls related to the service appear
      if (e.url == service.options.url) {
         // add messages to console
         console.log('There was an error adding ' + e.url + 'to the map');
         console.log('Error code:' + e.code + '; Message: ' + e.message);
         // set isLoaded property to false
         service.options.isLoaded = false;
      }
   });
}

Connecting isLoaded Value to Loading Screen

My maps currently remove a loading screen after so many seconds.  And while using the setTimeout function is debated, I prefer that when users see the map, they see the basemap and operational layers.  I’m still trying to fine-tune this approach, so any tips are appreciated.

My solution now utilizes an interval function (setInterval) that tests the loaded status of the layers every so many seconds.  If the isLoaded property for all layers is true, then I use a setTimeout function to slowly remove the loading screen.  I also clear the interval function.  Below is the code sample (note I use Bootstrap and jQuery in my maps):

 

// loading screen element
var $backCover = $('#back-cover');
// display style property
var $backCoverDisplay = $backCover.css('display');

// test if isLoaded property for both layers is true every 2 seconds
// if it is true for both layers, remove loading screen and remove interval
var loadScreenTimer = window.setInterval(function() {
   // isLoaded property
   var ftLayerLoaded = featureLayerObject.options.isLoaded;
   var dyLayerLoaded= dynamicMapObject.options.isLoaded;

   if (ftLayerLoaded && dyLayerLoaded) {
      // remove loading screen after 3 seconds
      window.setTimeout(function() {
            $backCover.fadeOut('slow');
      }, 3000); 

      // clear timer
      window.clearInterval(loadScreenTimer);
   }
}, 2000);

 

If one or more layers fails to load, the loading screen will persist.  You can add code within the requesterror event of the function processLoadEvent to add a message to an existing html element.  I would encourage you to visit my CodePen demo, as it shows you one approach to this, and includes working and error service URLs so you can test various scenarios.

Hopefully you’ve found this article helpful. If you have any thoughts, suggestions, or other feedback, please leave a comment.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s