odoe

Store Your Selections

Blog Post created by odoe on Jun 24, 2015

geo-storage.jpg

Every once in a while you may get an odd request from your users on features they would like added to an application. They might want to integrate some wacky API or work completely offline, neither of these things might be construed as easy, but could be managed to an extent. Maybe they want to save the last map extent when they close and open their browser back up, you can do that! One thing I've done before is save selections, so that when a user closes the browser, they can come back to the app and the last items they selected are still selected. I don't know man, office people. But hey, you can do that!

 

Storage

I've shown how can use the localStorage API to save map locations and even use other tools like PouchDB to nearly take your application totally offline. We'll refer to this as mostly-offline, so don't get all uppity. Even the offline-editor takes advantage of locaStorage for some bits. Normally I would recommend sing IndexedDB or storing things like graphics locally, but if I'm just concerned about storing the most recently selected features, I would totally just keep it simple with localStorage. Key-value pairs for life.

 

Step By Step

The steps are pretty simple.

  1. Create a unique key to store selections
  2. When a selection is complete, store the selection
  3. When selection is cleared, clear storage
  4. When application starts, check storage or saved selections and reselect

 

Easy-peasy!

 

Generating a unique key is simple. Since localStorage is partitioned out per domains, I just take my latest location and add some unique identifier to it. Something like this:

var key = location.href + '--id-selection';

 

Too easy.

 

Then you just need to store the selection in when it's complete. easy enough.

featureLayer
  .selectFeatures(selectQuery, FeatureLayer.SELECTION_NEW)
  .then(function(results) {
    var data = results.map(function(x) { return x.toJson(); });
    localStorage.setItem(key, JSON.stringify(data));
});

 

So what are we doing here? Well, every Graphic has a toJson method to output a vanilla object representation. As a matter of fact, this method is sprinkled all over the place in the API (hint hint, see what else you do with it).  So we can map over the results and run toJson on them to get the JSON data. They we can save it to localStorage after we stringify it. This is important, localStorage is a string-friendly environment. Plain objects will just look like "[object Object]", which is not useful.

 

Then make sure if there is somewhere in your application where the selection is cleared, you remove the item from localStorage too.

on(dom.byId("clearSelectionButton"), "click", function () {
  featureLayer.clearSelection();
  localStorage.removeItem(key);
});

 

Simple enough.

 

Now the cool part. When an application starts, we want to check the localStorage for saved selections. I like to have a simple helper method to do the actual parsing of graphics, so it could look something like this.

var parseToGraphics = function (str) {
  if (!str) { return undefined; }
  return JSON.parse(str).map(function(x) {
   return new Graphic(x);
  });
};

 

So JSON.parse does the opposite of JSON.stringify. It will attempt to turn string data into vanilla JavaScript objects. Again, another cool feature in the ArcGIS API for JavaScript is that some constructors accept JSON representations of what you are trying to create. Just like the Graphic constructor. This let's you just churn out Graphics like a Graphic maniac. Then you can wait for your FeatureLayer to load and check localStorage.

featureLayer.on('load', function() {
  var items = parseToGraphics(ls.getItem(key));
  if (items) {
   var q = new Query();
   q.objectIds = items.map(function(x) {
     return x.attributes.OBJECTID;
   });
   featureLayer
     .selectFeatures(q, FeatureLayer.SELECTION_NEW);
  }
});

 

And BOOM! You just saved selections and displayed the last selected items when an application loads. You can see a sample of this jsbin. I have to warn ya, jsbin seems a little wonky for this app sometimes, at least on my machine, but this works, trust me.

 

Why even save whole Graphic if you're just using ObjectIds? Hey, I don't tell you how to stir your oatmeal. But good question. If you know for sure, you are only going to select by ObjectIds, then sure, just save the ObjectIds. I like to have the graphics, because then I have options. You may want to do some sort of separate analysis or selection using the saved selection. Or  maybe you want to show to the InfoWindow for the first selected item when the application starts.

featureLayer.on('load', function() {
  var items = parseToGraphics(ls.getItem(key));
  if (items) {
   var q = new Query();
   q.objectIds = items.map(function(x) {
     return x.attributes.OBJECTID;
   });
   featureLayer
     .selectFeatures(q, FeatureLayer.SELECTION_NEW)
     .then(function() {
       items.map(function(x) {
         x.setInfoTemplate(infoTemplate);
       });
       map.infoWindow.setFeatures(items);
       map.infoWindow.show(items[0].geometry.getCentroid());
   });
  }
});

You can see a demo of this on jsbin. This sample needs some work, but you get the idea. I normally use this to display Popup content in some other portion of the application sync it with the map.

 

By saving the Graphic, you at least have some options of stuff you can play with. But if you know you don't need all this, then sure, optimize and only save the ObjectIds.

 

Save The World

So don't be afraid to play around with localStorage or other forms of storage to persist data in your application. Have some fun with it and make cool apps.

 

For more geodev tips and tricks, check out my blog.

Outcomes