Getting Started with Spine Mobile

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:

Now to actually generate our Spine Mobile application:

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

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:

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 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:

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:

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:

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.

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.

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.

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:

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

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:

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:

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:

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:

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:

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:

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:


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.

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:

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:

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.

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!

Tags:

Comments

Related Articles