Tad Thorley Avatar

@phaedryx

Posts tagged spinejs

8 Notes

For SpineJS With Rails Use spine-rails

I’ve been learning how to use spinejs with rails. When I’m learning I like to forgo code generators as much as possible and write the necessary code myself. I’ve done that with the past few projects I’ve posted here.

However, for most of my future projects I’m going to use the spine-rails gem. The gem is written by Alex MacCaw, the author of spinejs which is much nicer than the situation of backbone with rails.

The gem’s README already documents it pretty well, so I won’t repeat it here.

3 Notes

Rails With SpineJS and Google Maps

I wanted to create a simple project that used ruby on rails, spinejs, and google maps together. I came up with the following features:

  • Dragging a marker icon from outside of a map onto a map creates a marker on the map and in the database
  • Dragging a marker from one position to another updates the marker on the map and updates its position in the database
  • Right-clicking on a map marker removes it from the map and the database

The end result can be seen here and the source code is on github.

Let’s get started.

First, create a new rails project. I prefer to use rails 3.1 because the asset pipeline makes it easy to use coffeescript with your application.

rails new dragmarkers --database=postgresql

I used PostgreSQL because it’s awesome and I wanted to put an example up on Heroku. For this project, the database doesn’t matter. ignore the database option if you’d like. Remember you’ll need to edit config/database.yml accordingly.

Take care of the usual cleanup.

cd dragmarkers
rm public/index.html

Now that we have our rails project, let’s add spinejs. I think that the spine-rails gem is great (I’m even a minor contributor), but I’d like to write everything from scratch for this project. Instead, download the latest version of spine from the spinejs website. There will be several directories. Create a directory in your project for the spine files

mkdir -p vendor/assets/javascripts/spine

and copy all of the files from the lib directory of your spinejs download into the spine directory you created.

Now let’s add our standard libraries. For this project I decided to use CDN’s for the standard javascript libraries, rather than the jquery-rails gem. Open the file app/views/layouts/application.html.erb and add the libraries to the <head> section of your layout. The file should look like this:

Note that we’ll be using jquery-ui to make the marker icons draggable from outside of the map onto the map. I also added a div for page styling.

The foundation is in place, let’s start writing our own code. First we want to scaffold a Marker.

rails generate scaffold Marker

Edit config/routes.rb to make generated route the root route, for convenience. The file should look like this when you’re done:

Now let’s set up the marker model on the rails side. Edit the migration file db/migrate/[datestamp]_create_markers.rb to add columns for latitude, longitude, and icon. The file should look like this when you’re done:

Run the migration.

rake db:migrate

The rails model is done. Let’s turn to the views for a bit. Most of the scaffolded views are unnecessary and you can remove them.

rm app/assets/stylesheets/scaffolds.css.scss
rm app/views/markers/_form.html.erb
rm app/views/markers/edit.html.erb
rm app/views/markers/new.html.erb
rm app/views/markers/show.html.erb

Only the index.html.erb will be used.

We don’t need to touch the controller. Spine requires a REST interface and json, which our controller is already doing beautifully.

The rails part is done, so now for the spinejs portion. I like to do everything with coffeescript so let’s rename the application.js file to application.js.coffee (I wish this were a default).

mv app/assets/javascripts/application.js app/assets/javascripts/application.js.coffee

Open the application.js.coffee file for editing. We aren’t using the jquery gem, so you can remove the jquery-related require statements at the top. One of the nice features of spinejs is its modularity. For this project we’re using the core spinejs module and the ajax module (to work with the rails REST).

#= require spine/spine #= require spine/ajax

I like to create my directory structure for spinejs similar to what I’d find in rails application (or any MVC) and require them accordingly:

#= require_tree ./lib #= require_self #= require_tree ./models #= require_tree ./controllers #= require_tree ./views #= require_tree .

Create the corresponding directories:

mkdir app/assets/javascripts/lib
mkdir app/assets/javascripts/models
mdkir app/assets/javascripts/controllers
mkdir app/assets/javascripts/views

Let’s create a base spinejs controller and expose it. So far, your application.js.coffee file should look like this:

We want our index file to set up html for the base controller to hook in to and create the base controller. Edit app/views/markers/index.html.erb by taking out the scaffolding and making it look like this:

A div with an “app” id and create a new App and pass that div in as the root element. At this point you could actually run your rails application and have a single-page javascript application, even though you’d only see a blank page.

Let’s create a spinejs model to correspond to our rails model. First let’s create the file

touch app/assets/javascripts/models/marker.js.coffee

Open it up and create a Marker model that inherits from Spine.Model, is configured with the same fields as our rails model, and extends the Spine.Model.Ajax functions. The file should look like this for now:

At this point, if you ran the rails application, you’d be able to do CRUD operations with the database through the javascript console in your browser with commands like:
App.Marker.create({latitude: 0, longitude: 0}) (create a marker entry in the database), marker = App.Marker.find(id) (get the marker from the database that was just created, using its id), marker.updateAttributes({latitude: 1, longitude: 2}) (update the marker’s fields in the database), marker.destroy() (delete the marker from the database)

Things are shaping up pretty well, but we still have a blank-page application. Let’s remedy that. We need to show a map a marker images that can be dragged onto the map. Let’s make a javascript template (JST) view for it. I like to use eco, but there are other nice options like jQuery templates and mustache. to use eco, let’s add the line gem 'eco' to our Gemfile and install it.

bundle install

Now let’s create a new directory for our marker views and create the view file.

mkdir app/assets/javascripts/views/markers
touch app/assets/javascripts/views/markers/index.jst.eco

Note the jst and eco extensions. Edit the file to contain a div with a “map” id and a bunch of image tags. Your file should look like this when you’re done.

Notice that it references 0.png through 9.png in the app/assets/images directory. You can copy the images I used into that directory or use your own. I found this website to be a great resource for map marker images. Just make sure that the images you reference in the javascript template are there.

Now let’s create a spinejs controller for the template and actually display it. I like to alias $ for jQuery and Marker for the App.Marker model. Also, we want references to the elements in our view and to display our view. Create the new spine controller file.

touch app/assets/javascripts/controllers/markers_controllers.js.coffee

Edit markers_controller.js.coffee to include that code. The file should look like this so far:

Remember in our rails view index, we’re actually creating a new App so we need to add our spinejs markers controller to that one. Edit the app/assets/javascripts/application.js.coffee file and have the constructor append our new controller. When you’re done the file should look like this.

This is the last time we’ll edit that file. If you were to run your rails application now, you would see it working because the marker icons would show up, but there still isn’t a map yet. We still need to create our google map. First, however, let’s add some style to the div that will hold the map and a bit of CSS for the marker icons too. Edit your app/assets/stylesheets/application.css file to look something like this

and edit your app/assets/stylesheets/markers.css.scss file to look something like this

If you run your rails application again, you’ll see a border around the div where the map will go and spacing for the marker icons.

Let’s return to our markers controller (app/assets/javascripts/controllers/markers_controller.js.coffee) and add the rest of the code to it. We want it to create our google map, create an overlay for the map (so that we can calculate the drag position on the map), make our marker icons draggable (and droppable), and, lastly, add any existing markers to the map. Let’s write the code for it in our controller’s constructor, then create each function in turn. Edit the file to look like this.

The first function is to create the map. I initialize it to the middle of the United States at a reasonable zoom level and use the roadmap view. Our elements declaration for our map div returns a jQuery wrapped set, the same as $('#map') and we need to get the first (and only) member to pass to the google map constructor. The function for creating the map is pretty straightforward:

The second function is to create an overlay for the map. It is simple. Note that we’re using the variable @overlay because we’ll be using it later.

The third function uses jquery-ui to make the icons draggable. We need another function to handle the icon when it is dropped (we’ll call it placeNewMarker). Note that I’m using the draggable option clone to make a copy of the image and containment of parent to keep it within the map area. Placing a new marker takes some calculations to figure out where the dropped location is within the page and uses our overlay to figure out what the equivalent longitude and latitude on the map would be. Finally, we create the new marker and put it on the map (the setMap function doesn’t exist yet, we’ll get to that in a bit). The two functions look like this

Lastly, we want to make sure the existing markers show up on the map. A fetch will retrieve the marker information from rails which triggers a refresh event. Let’s bind that even to display our markers, which will be triggered when we fetch. Our last function looks like this

Altogether, our markers_controller.js.coffee file should look like this.

Our controller is done, but we have a few more things to do to finish up. Our spinejs model for markers has no concept of a map to set a map to, and, for that matter, has no concept of a google marker for the map. Lastly, we need to make the markers draggable once they are on the map, and provide a means to remove the marker from the map.

Let’s edit our spinejs marker model (app/assets/javascripts/models/marker.js.coffee) and add these finishing touches. First, let’s have our constructor create a corresponding google map marker

add add the setMap function we were using in our controller. It’s a simple 1-liner

When a marker has been dragged on the map we want it to save its new position to the database

and right-click will remove the marker. We want to remove it from the map and the database

If we add the code to listen to the events and respond with those functions, we’re done. The file should look like this when you’re done.

The project is now complete and does everything I set out to do. However, I think there is still some room for improvement. This is nice for a small set of points, but if you were dealing with a large number of markers you’d want to fetch only the markers within the map’s currently viewable area. You may want to use a GIS system (like postgis). Instead of having right-click delete the marker, perhaps you could have a context menu with delete as one of the options.

Acknowledgements:

Thanks to Alex MacCaw for creating spinejs and all of the great documentation to go with it.

Thanks to kwicher who’s example code helped me debug some frustrating bugs.

4 Notes

SpineJS With Rails Screencast Transcribed

I transcribed the “Spine and Rails” screencast by Alex MacCaw and put it together with the source code on github.

4 Notes

Simple SpineJS and Ruby on Rails Integration

I’ve recently been learning an MVC javascript framework called Spine. It is similar to backbone (hence the name), but I’ve found it to be a bit more rails friendly and closer to what I’m familiar with. The source is also written in coffeescript, which I think has a better syntax.

What’s the first step when learning something new? “Hello World!” of course! I’ll take you step by step through a simple application using Rails and Spine. The source code is on github.

First, create your rails (3.1) application:

rails new hello-spine

Next you’ll want to download spine.js and copy the lib directory into vendor/assets/javascripts/spine of your new rails application (the directories won’t exist, you’ll have to create them).

Now you’ll need to configure Rails to use Spine. Edit the application.js file in app/assets/javascripts to include a line to require spine (spine.js in the spine directory). I love coffeescript, so I renamed the file to application.js.coffee and this is what it should look like:

#= require jquery
#= require jquery_ujs
#= require spine/spine
#= require_tree .

Rails needs to supply the basic HTML that Spine hooks into, so you’ll need to generate a controller for it

rails generate controller Hello index

and set it as the root route. Your config/routes.rb file should look like this:

HelloSpine::Application.routes.draw do
  get "hello/index"
  root :to => "hello#index"
end

Now for the Spine part. Because this is so simple, there are no views or templates; there is no routing logic. The only logic you’ll need is a Spine.Controller that you can put straight in to your application.js.coffee file (for more complex applications you’d want to separate controllers, models, and views into separate directories and include them from there). The first step is to create your new class and expose it by assigning it to the javascript “window” object. Your file should look like this:

#= require jquery
#= require jquery_ujs
#= require spine/spine
#= require_self
#= require_tree .

class App extends Spine.Controller
  constructor: ->
    super


window.App = App

Note also the “require_self” line right after requiring spine.

Next edit views/hello/index.html.erb to provide a div for your Spine app to work with. Followed by some javascript to create your Spine app and pass it the div as it’s base DOM element. Your file could look something like this:

<div id="hello"></div>

<script type="text/javascript" charset="utf-8">
  jQuery(function() { 
    new App({el: $("#hello")});
  });
</script>

The last step is to have your controller append the greeting to it’s base element. The addition of one more line to application.js.coffee should do it:

...

class App extends Spine.Controller
  constructor: ->
    super
    @append("<p>Hello from Spine!</p>")

...

Start up your rails app to see the message:

rails server

So what’s the next step? Alex MacCaw, the author of Spine has done a great series of screencasts and the spine gem for rails makes it easy to use spine with rails.