Integrating maps and Formtastic forms with Geous.js

The fabulous Formtastic gem provides a tidy DSL for presenting Rails models in user input forms. One particularly nice feature is the presentation of different model attribute types as appropriate form elements. Dates, for instance, are rendered using a date selector. But what about those attributes that simply don’t lend well to an input alone?

Latitude, longitude: I’m looking at you.

In a location-based model, a Formtastic form might render a coordinate pair along these lines:

f.inputs "Location" do
  f.input :lat
  f.input :lng
end

The form will now produce a text input for each value, and that’s where the trouble begins. Cryptic decimals are a precise way to store locations, but they aren’t so good for telling humans where their locations actually are. Wouldn’t it be nice if they came marked on a map?

The good news is that just a few lines of javascript and a generous sprinkling of third-party code can have one up and running in moments.

Getting started

First, there are a few required dependencies.

We’ll also use geous’ jQuery and Google Maps plugins (included in the main repo) to speed up development. Assuming that jQuery and Google maps are already included somewhere in the project, pull down a copy of geous:

$ cd /path/to/javascripts
$ git clone https://github.com/rjz/geous.js geous

Because the plugins aren’t compiled into geous by default, the formtastic page will need to include them separately from the core library:

<script src="/javascripts/geous/geous.js"></script>
<script src="/javascripts/geous/plugins/googlemaps/geous.maps.js"></script>
<script src="/javascripts/geous/plugins/jquery/geousable.js"></script>

Drawing a map

Next up, let’s make sure the location fields generated by Formtastic are recognizable to the script we’re about to write. There are plenty of ways to do this, but for the sake of simplicity let’s just add a custom data attribute to the fieldset wrapping the two locations.

f.inputs "Location", :'data-geousable' => 'yup' do
  f.input :lat
  f.input :lng
end

This is also a good time to update the input fields generated by formtastic to conform to the geousable plugin’s default, class-based nomenclature. An appropriate attribute map could stand in for the explicit classes, but it may be easier to just use the default:

f.inputs "Location", :'data-geousable' => 'yup' do
  f.input :lat, :input_html => { :class => 'lat' }
  f.input :lng, :input_html => { :class => 'lng' }
end

Great! Now the form is ready for a healthy dollop of javascript magic. Producing a map of the location in question is a three step process:

  1. Select target form
  2. Parse the form’s location
  3. Create a map using the form location

Or, in code,

jQuery(document).ready(function ($) {

  // select target fieldset
  $('[data-geousable]').each(function () {

    var $fields = $(this),
        $map = $('<div class="geous-map" style="width:100%;height:100px;"></div>');

    // initialize the geousable plugin and container element
    $fields.geousable({ overwrite: true });
    $fields.prepend($map);

    // build a map with the fields' location
    geous.gmap.create({ 
      el: $map,
      locations: [ $fields.geousable('getLocation') ]
    });
  }); 
});

With this snippet added, the original form generated by formtastic will now include a map centered on its current location.

The view from ActiveAdmin

Refining

Just seeing a map is nice, but what if we could interact with it? Instead of simply initializing the map and forgetting about it, consider a function that would simply assign the form’s location to the map.

var setMapLocation = function ($fields, geousMap) {
  var location = $fields.geousable('getLocation');
  geousMap.locations.empty();
  geousMap.locations.add(location, { draggable: true });
  geousMap.centerOnLocation(location);
};

This allows the map’s location to be easily reset. Inside data-geousable.each, sometime after gmap has been created, a quick event handler bound to the input fields can be used to update the map’s location:

$fields.find('input').change(function() {
  setMapLocation($fields, gmap);
});

Now, changes to either coordinate will be reflected by the marker’s position on the map.

Wrap-up

Geous makes it easy to put a pin on a map, but it’s a task of dubious value. In the next episode, we’ll take a look at harnessing Geous to do some actual geocoding. Stay tuned!

Featured