A Simple OpenLayers App with Yeoman, Sinatra, MongoDB and Backbone - Part 1

Introduction

This post is essentially a learners journey into a few new (at least for me) technologies. I’ve been wanting to try out Backbone and Yeoman for a while and I recently had to develop some OpenLayers functionality at my day job. The thought came to me combine these with Sinatra and MongoDB, to see if I could hack out some-real time display and peristence of locations on a map.

It became apparent that this would become a lengthy affair for a single post, so I decided to break it into parts. Part one deals with getting Sinatra and Yeoman to play nicely together, then getting Backbone and OpenLayers ready to use.

Setting up the Environment

I put this all together on my netbook, which runs Lubuntu 12.04.

Following these instructions, I was able to set up Yeoman. The instructions also include setting up Ruby via RVM, which we need for Sinatra; so it’s two birds with one stone if you don’t already have Ruby installed. Just make sure to read the comments - there are some corrections to instructions in the post.

Setting up the Sinatra Project

First let’s get the Sinatra side of things up and running.

$ mkdir ol-blog-post
$ cd ol-blog-post
$ touch app.rb config.ru Gemfile Procfile

Next we add some dependencies to Gemfile. For a web server, I normally use Unicorn, but to keep it simple I’m going with Thin this time. The mongoid gem will be utilised when we start talking with MongoDB in the future parts.

source 'http://rubygems.org'
ruby '1.9.3'

gem 'bundler', '>= 1.2.1'
gem 'thin'
gem 'sinatra'
gem 'mongoid'

Now we create our Sinatra application in app.rb. We’ll test the base route in a moment to check that it works.

require 'sinatra/base'

class App < Sinatra::Base
  configure do
    enable :logging
  end

  get '/' do
    'Hello World!'
  end
end

Our rackup file is config.ru, which should look like this. It just points at app.rb as our Sinatra application.

require './app.rb'
run App

I like to use Foreman by default, because it allows us to start our application according to the processes defined in Procfile. Heroku utilises procfiles too, so it’s just a way of making deployment to the cloud one less step away. If you don’t already have it, install the gem via the following command.

$ gem install foreman

Procfile just has a single web process that starts serving the application via the rackup file. It will run on port 4567.

web: bundle exec thin start -R config.ru -e development -p 4567

Now we install the dependencies from our gemfile and start the application using foreman, which detects and uses our procfile automatically.

$ bundle
$ foreman start

Now we can either navigate to localhost:4567 via a browser or cURL. If we see the greeting, we’re good to go.

Creating a Project with Yeoman

I haven’t seen much in the way of how one is supposed to use Yeoman alongside something like Sinatra, but the method below seems to work well enough. It could probably be more elegant though, so go ahead and comment if you’ve got ideas.

Yeoman provides inbuilt generators that can scaffold a backbone application automatically. However, we don’t need all of the assets that it creates by default; so let’s set up a vanilla application to start with. In the root folder of the Sinatra application, run this command and answer no to the prompts.

$ yeoman init

Now we can see that Yeoman has generated some files for us, notably the application content within a sub-folder called app. Note: I used version 0.9.5, which left out the bootstrap CSS link from index.html, so I added it manually.

Yeoman includes a build command that does things like image optimisation, CoffeeScript and SASS compilation, JavaScript and CSS minification etc. Check the help docs for more information. Let’s test the default behaviour by using this command.

$ yeoman build

This creates a folder called dist where the optimised, deployable files for the application go. This folder is configurable in Gruntfile.js, but we’re going to bend Sinatra to Yeoman instead of vice versa.

The Sinatra Public Folder

By default, Sinatra serves static files from a folder named public if it exists. This folder is configurable, but we don’t just want to point it at app where Yeoman generated our files - we wan’t to use the dist folder with our optimised content when we run in production.

So let’s change app.rb so that the default route shows index.html from the public folder, which is set based on the environment in which we’re running. It turns out to be quite simple.

require 'sinatra/base'

class App < Sinatra::Base
  configure do
    enable :logging
    set :public_folder, ENV['RACK_ENV'] == 'production' ? 'dist' : 'app'
  end

  get '/' do
    send_file File.join(settings.public_folder, 'index.html')
  end
end

If we restart the application and navigate to the base URL, the content of index.html appears as we expect. To test that the public folder is set to dist when running in production, change the environment from development to production in the procfile. Now if we navigate to the base URL and view the source, we should see that our JavaScript and CSS files are prefixed with what looks like randomly generated strings. These are the optimised files generated by Yeoman - it’s working like a charm.

Setting up OpenLayers

To use OpenLayers, all we do is add a script tag linking to this file and create a div for our map container. The file below has had some of the default yeoman-generated content removed for brevity.

<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title></title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width">
    <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->

    <link rel="stylesheet" href="styles/bootstrap.css">
    <link rel="stylesheet" href="styles/main.css">
    <script src="scripts/vendor/modernizr.min.js"></script>
  </head>
  <body>
    <div class="container" style="margin-top:50px">
      <div id="mapdiv"></div>
    </div>

    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
    <script>window.jQuery || document.write('<script src="scripts/vendor/jquery.min.js"><\/script>')</script>
    <script src="http://openlayers.org/api/OpenLayers.js"></script>
    <script src="scripts/main.js"></script>
  </body>
</html>

This is what main.css looks like to begin with. Bootstrap has a style for img that has a max-width: 100% attribute. This causes the map tiles to display in distorted vertical strips. Our style below rectifies this.

#mapdiv {
  border: 1px solid gray;
  width: 100%;
  height: 420px;
}

img { max-width: none; }

The last thing to get us started is some code to initialise the map. I’m using Open Street Map for the base layer. I’ll tidy this up in the next part, but for now it’s enough to ensure that we have OpenLayers working correctly.

;(function() {
  $(function() {
    var map = new OpenLayers.Map('mapdiv');
    var osm = new OpenLayers.Layer.OSM();
    map.addLayer(osm);

    var lonlat = new OpenLayers.LonLat(-1.788, 53.571).transform(
      new OpenLayers.Projection("EPSG:4326"), // WGS 84
      new OpenLayers.Projection("EPSG:900913") // Spherical Mercator
    );

    map.setCenter(lonlat, 13);
  });
})();

Installing Backbone

As stated above, we haven’t used the Backbone generator to scaffold our application, but Yeoman integrates with Bower, so we’ve got a package manager that can install and manage our dependencies. Let’s use it to install Backbone.

$ yeoman install backbone

packages installed via this method are placed in app/components. The components folder is deployed to the dist folder when we invoke the Yeoman build command.

In the next part we’ll set up our API to get data in and out of MongoDB, as well as writing the JavaScript that uses OpenLayers and Backbone to show spatial features and communicate with the API.