Advertisement
JavaScript & AJAX

Getting Started with Spine Mobile

by

With the growing complexity of JavaScript applications, frameworks are an absolute must if you need to meet real world deadlines. In this article, we're going to take a look at a new framework called Spine Mobile which you can use to create awesome mobile applications in CoffeeScript and HTML, without sacrificing the great user experience of native apps.

Interested? Let's get started!


What Exactly is Spine?

Spine is a lightweight JavaScript MVC framework that you can use to create awesome client-side web applications. Spine Mobile is an extension to Spine, specifically designed for creating native-feeling mobile web applications.

Task lists and contact managers are a dime a dozen, so let's do something different in this tutorial and create a workout recorder. Users are going to be able to record workouts, including their type, time and duration. Then we're going to have a simple list showing all recorded workouts. There's also a lot of scope for further development, such as social features and graphs.

You can checkout a live demo of the finished application here, as well as all the example's source code on GitHub. I highly recommend you follow along this tutorial using the source code, at least initially, as it'll help you get started if you're new to Spine.

If you ever need more details about Spine Mobile, then hit up the comprehensive docs or the mailing list. For a short introduction to CoffeeScript, take a look at The Little Book on CoffeeScript.


Step 1: Setup

First things first, we need to install some npm modules, namely spine.app and hem. The former generates Spine apps, whilst the latter acts as a dependency manager. If you haven't got them installed already, you'll need to download Node and npm (both sites have excellent installation guides). Then run:

npm install -g spine.app hem

Now to actually generate our Spine Mobile application:

spine mobile spine.workout
cd spine.workout

Have a browse round the the directory structure and initial files that Spine has created for you.

$ ls -la
.gitignore
Procfile
app
css
package.json
public
slug.json

The app directory is where all the application's logic lives, such as its models and controllers. The public directory is just full of static assets, and is where our application will ultimately be compiled to. It's the public directory that gets served up as our mobile application.

Our new application also has some local dependencies (specified in package.json), so let's go ahead and install those now:

npm install .

These will download and install the local dependencies in a folder called node_modules (which shouldn't be in your source control).

The last thing we need to do, is to run Spine's development server, Hem.

hem server

Hem compiles CoffeeScript files, resolves dependencies, wraps source into CommonJS modules and concatenates everything into one JavaScript file, application.js.

Now that the server is running, we can navigate to our initial application on http://localhost:9294.


Step 2: Models

In MVC frameworks, models store your application's data, and any logic associated with that data. That's it - models shouldn't know anything else about the rest of your application; they should be completely de-coupled.

Our application needs to track workouts, recording the type of workout, how long it took, and when it took place.

So let's go ahead and create a new model by running the following:

spine model workout

That'll generate a model named: app/models/workout.coffee. Let's open up that file and implement our Workout model by replacing the contents with this:

Spine = require('spine')

class Workout extends Spine.Model
  @configure 'Workout', 'type', 'minutes', 'date'

  @extend Spine.Model.Local

  load: ->
    super
    @date = new Date(Date.parse(@date))

  validate: ->    
    return 'type required' unless @type
    return 'minutes required' unless @minutes
    return 'date required' unless @date

module.exports = Workout

Ok, so that's a lot of code without any explanation; let's drill down into it and look at the details.

First off, we're creating a Workout class inheriting from Spine.Model, calling @configure() to set the model's name and attributes:

class Workout extends Spine.Model
  @configure 'Workout', 'type', 'minutes', 'date'

So far so good. Now we're going to extend the model with a module named Spine.Model.Local. This ensures that the model data is persisted between page reloads using HTML5 Local Storage.

@extend Spine.Model.Local

Now the next function, load(), needs a bit of an explanation. load() gets called multiple times internally in Spine, especially when records are serialized and de-serialized. Our issue is that we're serializing the records to JSON when persisting them with HTML5 Local Storage. However, JSON doesn't have a native 'Date' type, and just serializes it to a string. This is a problem, as we want to date attribute to always be a JavaScript date. Overriding load(), making sure the date attribute is a JavaScript Date, will solve this problem.

load: ->
  super
  @date = new Date(Date.parse(@date))

Lastly, we have a fairly straightforward validate() function. In Spine, a model's validation fails if the validate() function returns anything 'truthy' - i.e. a string. Here we're returning "type required" unless the type attribute exists. In other words, we're validating the presence of the type, minutes and date attributes.

validate: ->    
  return 'type required' unless @type
  return 'minutes required' unless @minutes
  return 'date required' unless @date

You'll notice that the final line in the model is a module.exports assignment. This exposes the Workout class, so other files can require it. Spine applications use CommonJS modules, which requires explicit module requiring and property exporting.

WorkoutType model

The only other model we'll need is a WorkoutType model. This is just going to be a basic class, and contains a list of valid workout types. As before, we need to generate the model first:

spine model workout_type

And then its contents is a simple class, containing an array of valid workout types:

class WorkoutType
  @types: [
    'running'
    'jogging'
    'walking'
    'swimming'
    'tennis'
    'squash'
    'handstands'
    'skipping'
    'aerobics'
    'biking'
    'weights'
  ]

  @all: ->
    @types

module.exports = WorkoutType

For more information about models, please see the Spine models guide.


Step 3: Main Controllers

In Spine applications, controllers are the glue between models and views. They add event listeners to the view, pull data out the model and render JavaScript templates.

The key thing you need to know about Spine's controllers, is that they're all scoped by a single element, the el property. Everything a controller does in its lifetime is scoped by that element; whether it's adding event listeners, responding to event callbacks, updating the element's HTML, or pulling out form data.

Spine Mobile applications have one global Stage controller, which encompasses the whole screen. Our generated application already includes a Stage in app/index.coffee, let's replace it with the following:

require('lib/setup')

Spine    = require('spine')
{Stage}  = require('spine.mobile')
Workouts = require('controllers/workouts')

class App extends Stage.Global
  constructor: ->
    super

    # Instantiate our Workouts controller
    new Workouts

    # Setup some Route stuff
    Spine.Route.setup(shim: true)
    @navigate '/workouts'

module.exports = App

Our App Stage is going to be the first controller instantiated, and in charge of setting up the rest of the application. You can see, it's requiring an as-yet undefined controller named Workouts, and instantiating Workouts in the class' constructor function.

In other words, when our application first runs, the App stage is going to be instantiated. That will in turn instantiate our Workouts controller, where all the action is going to be. You can ignore all the route stuff for the time being.

Now let's setup the aforementioned Workouts controller:

spine controller workouts

The new Workouts controller is located under app/controllers/workouts.coffee. This controller is going to be where most of our application lives, so let's start filling it out by replacing its contents with the following:

Spine   = require('spine')
{Panel} = require('spine.mobile')

# Require models
Workout     = require('models/workout')
WorkoutType = require('models/workout_type')

# To be implemented:
class WorkoutsList extends Panel
class WorkoutsCreate extends Panel

class Workouts extends Spine.Controller
  constructor: ->
    super

    # Our application's two Panels
    @list   = new WorkoutsList
    @create = new WorkoutsCreate

    # Setup some route stuff
    @routes
      '/workouts':        (params) -> @list.active(params)
      '/workouts/create': (params) -> @create.active(params)

    # Fetch the initial workouts from local storage
    Workout.fetch()

module.exports = Workouts

Again, let's drill down into that and explain what's going on. Firstly, we're requiring our application's two models, Workout and WorkoutType:

# Require models
Workout     = require('models/workout')
WorkoutType = require('models/workout_type')

Then Workouts constructor is setting up a few Panels, as yet unimplemented, and then some routes which we can ignore for the time being. Finally, Workout.fetch() is being called, retrieving all the stored data from local storage.


Step 4: Listing Workouts

Ok, now we've done a fair bit of setting up with our App and Workouts controllers, but now comes the fun part, the panels.

So our application has two Panel controllers, a list view, and a create view. These two panels belong to the main stage which ensures they transition in and out properly, only showing one panel at any one time.

So let's first define our WorkoutsList controller in app/controllers/workouts.coffee, which, you guessed it, will list the workouts. Add the following code after the require statements in workouts.coffee, before the Workouts controller definition:

class WorkoutsList extends Panel
  title: 'Workouts'

  constructor: ->
    super
    # Add a button to the header
    @addButton('Add', @add)

    # Bind the model to the view
    Workout.bind('refresh change', @render)

  render: =>
    # Fetch all workout records from the model
    workouts = Workout.all()

    # Render a template with the workout array
    template = require('views/workouts')(workouts)

    # Replace the current element's HTML with the template
    @html(template)

  add: ->
    # Navigate to the 'create' controller, with a  
    # swipe transition out to the left
    @navigate('/workouts/create', trans: 'right')

The first thing you'll notice is that WorkoutsList extends Panel, a class defined in the spine.mobile package. This ensures that it inherits Panel's properties, so the application's Stage can work with it.

The template uses a great library called Eco. Check out the view guide for more information on its syntax. Suffice to say, it's CoffeeScript syntax, using the <%= %> notation to render template variables to the page.

Then we've got a property called title. This is an optional setting, and will be the title of our panel.

In the constructor function, we're adding a button to the panel header by calling @addButton(title, callback). When tapped, this will invoke the class' add() function.

Lastly, we're adding binding to two events, refresh and change on the Workout model. Whenever the model is changed, these events will be fired, and our callback render() function invoked. render() first pulls out all the Workout records from the database, then renders a template, replacing the panel's contents with the result.

So this template merely acts as a function. All we're doing is executing that function, passing in a template context, the result being the rendered DOM element. For more information on how this works, please see the views guide, otherwise let's press on and define the template.

In app/views, create a folder called workouts which will contain all our templates associated with the Workouts controller. Then let's create a file under app/views/workouts/index.jeco containing:

  <div class="item">
    <span class="type"><%= @type %></span>
    <span class="minutes">for <%= @minutes %> mins</span>
    <span class="date">on <%= @date.toDateString() %></span>
  </div>

The template's .jeco extension isn't a typo, it's a jQuery extension to the Eco templating library provided by Hem. Amongst other things, it allows us to associate elements with the original template data, which will be useful later.

The end result is a list of workouts looking like this:

List

Obviously, if you haven't created any workouts, then the list will be empty. We can create a workout programmatically, using the command line inside the Web Inspector console:

var Workout = require('models/workout');
Workout.create({type: 'handstands', minutes: 5, date: Date.now()});

Step 5: Creating New Workouts

Now the last panel to define is WorkoutsCreate, which will contain the form for creating new workouts. This is going to be our largest controller, but it should be fairly straightforward now you're familiar with the API and terminology.

The only new addition here is the addition of an elements property, which is a convenience helper to match DOM elements to instance variables. In the example below, the elements property is set to {'form': 'form'}, which maps the

element to the @form variable.

class WorkoutsCreate extends Panel
  title: 'Add Workout'

  elements:
    'form': 'form'

  constructor: ->
    super
    @addButton('Back', @back)
    @addButton('Create', @create)

    # Render the view whenever this panel is activated,
    # resetting the form
    @bind 'active', @render()

  render: ->
    # Fetch all workout types
    types = WorkoutType.all()

    # Render the template, replacing the HTML
    @html require('views/workouts/form')(types: types)

  create: ->
    # Create new workout from form data
    item = Workout.create(@formData())

    # Navigate back to the list, if validation passed
    @back() if item

  # Navigate back to the list
  back: ->
    @form.blur()
    @navigate('/workouts', trans: 'left')

  # Retrive form data as a object literal
  formData: ->
    type    = @form.find('[name=type]').val()
    minutes = parseInt(@form.find('[name=minutes]').val())
    date    = @form.find('[name=date]')[0].valueAsDate
    {type: type, minutes: minutes, date: date}

So let's take that apart piece by piece. Firstly, in the WorkoutsCreate constructor, we're adding two buttons to the panel, 'Create' and 'Back'. You can probably guess what these are going to do.

Next, we're binding to the panel's active event, triggered whenever the panel is shown. When the event is triggered, the render() function is called, replacing the controller element's HTML with a rendered template. By attaching the render() invocation to the active event, rather than directly in the constructor, we're making sure that the form is reset whenever the panel is navigated to.

The last part to the panel is the create() function, where our Workout record is actually going to be created. We are using formData() to retrieve the user's input, passing it to Workout.create().

Now onto defining the app/views/workouts/form.eco template used in the render() function:

<form>
  <label>
    <span>Select type</span>

    <select name="type" size="1" required>
      <% for type in @types: %>
        <option value="<%= type %>"><%= type %></option>
      <% end %>
    </select>
  </label>

  <label>
    <span>Select minutes</span>

    <select name="minutes" size="1" required>
      <option value="5">5 minutes</option>
      <!-- ... -->
    </select>
  </label>

  <label>
    <span>Select date</span>
    <input name="date" type="date" required>
  </label>
</form>

That's it for our application. Give it a whirl, and create a few workouts.


Step 6: Build and Deploy

The last step is to build our application to disk, and deploy it. We can do that using Hem:

hem build

This will serialize all your JavaScript/CoffeeScript to one file (public/application.js), and all your CSS/Stylus (public/application.css). You'll need to do this before pushing your site to a remote server, so it can be served statically.

We're going to use Heroku to serve our application, a great option for serving Node.js and Rails applications, and they have a generous free plan. You'll need to signup for an account with them if you haven't got one already, as well as install the Heroku gem.

Now, all we need to deploy our app is run a few Heroku commands to get our application deployed.

heroku create my-spine-app --stack cedar
git add .
git commit -m "first commit"
git push heroku master
heroku open

Voila! You've now got a slick mobile application written in CoffeeScript, HTML5 and CSS3. We've got tons of possibilities now, such as wrapping it PhoneGap to access the phone's APIs, customizing the theme for Android phones or adding offline support.


Next Steps

It may feel like a lot to learn, but we've actually covered most of Spine's API in this tutorial. Why not check out the extensive documentation, and learn a bit more about the framework?

I'm sure you have lots of questions so feel free to ask away in the comments and thank you so much for reading! Otherwise, be sure to refer to our sister-site, Mobiletuts+, for the best Mobile-focused tutorials on the web!

Related Posts
  • Code
    Theme Development
    Custom Controls in the Theme CustomizerTheme customizer custom control 400
    In the last article, we explored the advanced controls available in the Theme Customizer, and how to implement them. We’re going to look at how to create our own custom control, allowing you to choose which Category of Posts are displayed on the home page. To get started, download version 0.6.0 of our Theme Customizer Example.Read More…
  • Code
    JavaScript & AJAX
    Ember Components: A Deep DiveEmber components retina preview
    Ember.js is a JavaScript MVC framework that allows developers to create ambitious web applications. Although pure MVC allows a developer to separate concerns, it does not provide you with all the tools and your application will need other constructs. Today, I'm going to talk about one of those constructs. Ember components are essentially sandboxed re-usable chunks of UI. If you are not familiar with Ember, please check out Getting Started With Ember.js or the Let's Learn Ember Course. In this tutorial, we will cover the Web Components specification, learn how to write a component in Ember, talk about composition, explain the difference between an Ember view and an Ember component, and practice integrating plugins with Ember components.Read More…
  • Code
    JavaScript & AJAX
    Working With IndexedDB - Part 2Indexeddb retina preview
    Welcome to the second part of my IndexedDB article. I strongly recommend reading the first article in this series, as I'll be assuming you are familiar with all the concepts covered so far. In this article, we're going to wrap up the CRUD aspects we didn't finish before (specifically updating and deleting content), and then demonstrate a real world application that we will use to demonstrate other concepts in the final article.Read More…
  • Code
    Scala
    Building Ribbit in ScalaRibbit scala retina preview
    In this tutorial we will implement the Ribbit application in Scala. We'll be covering how to install the Play web framework, a NetBeans plugin for it, and finally the code in Scala. If you are new to Scala, check out this previous tutorial which will help you set up your environment and provides you with a general platform that you can build upon. Even though the essence of Ribbit is to create/send/read Ribbits (our version of tweets), we will spend a large part of this tutorial explaining how Play works, authentication, and persistence. After these are in place, the rest becomes much easier. We will also implement ribbit creation, submission and listing out all ribbits. Following someone, advanced user settings, and direct messages will be an extra assignment for you to complete on your own. I am sure if you manage to follow along with this tutorial and create Ribbit as explained below, these three functionalities will be easily accomplished as homework.Read More…
  • Code
    Mobile Web Apps
    Create a Location-Aware Site with Sencha Touch - Displaying LocationsLogo
    This tutorial will guide you through the development of a Location-based mobile website using the Google Place search engine and Sencha Touch 2.1 . This is the second in a two-part series, and today we'll learn how to display map markers and location details.Read More…
  • Code
    JavaScript & AJAX
    Building Ribbit in MeteorRibbit meteor preview retina
    This is a continuation of the Twitter clone series with building Ribbit from scratch, this time using Meteor.Read More…