permalink

27

Building JavaScript Web Apps With MVC & Spine.js

JavaScript developers wishing to add more structure to their code often look for simple ways to apply the popular MVC architecture pattern to their applications. Utilizing a client-side MVC framework can be useful for such code organization and today we’ll be looking at Spine.js, a recently released solution that assists with this task.

You may be familiar with some existing projects that provide a similar solution – for example Justin Meyer’s JavascriptMVC – which is perfect for larger projects (and addresses more concerns), SproutCore, Rebecca Murphey’s dojo MVC template and Jeremy Ashkenas's Backbone.js – a worthy solution to achieving this for small to middleweight projects, in particular when creating SPAs (single-page applications).

I've been quite an avid fan of JMVC and Backbone for quite some time and for any developers that have yet to try Backbone out, I recommend it or new alternative Spine.js, which we'll be looking at in more detail today. I'll be taking you through an introduction to Spine.js, followed by an interview with it's developer, Alex MacCaw and then wrapping up with a tutorial on how you can create your own multi-view Bit.ly API client using Spine.

 

 

 

 

A Short Re-Introduction To Backbone.js

To give you a quick summary on Backbone which will help us compare it with Spine: data in Backbone is represented through models (which can be created, destroyed and saved). Changes to the front-end/UI trigger a model to change and all of the views in your application that display the model's data are notified of the event, causing them to re-render. It is also quite agnostic in the JavaScript libraries it can be used with and is regularly combined with jQuery or zepto for DOM interaction.

The benefit tools like Spine and Backbone bring to the table is that the 'backbone' to your application's structure is all taken care of for you meaning that you can focus on improving the overall application logic with your time. If you wish to explore Backbone further, feel free to read my article 'Building Single Page Applications with jQuery's Best Friends' or check-out some of the excellent resources on their new wiki for more information. The section under secondary resources is particularly worth a look.

Let us now acquaint ourselves with Spine.js – a new alternative to Backbone that's been getting quite a lot of interest lately.

 

An Introduction to Spine.js

Back-story

A few months ago, I was approached by a rather passionate JavaScript developer named Alex MacCaw to help provide guidance for a modern JaveScript web applications book he was working on. Much of the focus of the book ended up being on MVC, application architecture and dependancy management (which really need a solid title on) but as it turns out, he ended up investing lot of the lessons he learned into creating Spine.js - a lightweight framework for building JavaScript web applications.

When Spine was first released, it received quite a number of mixed reviews, however I believe in giving every new solution deserves a fair evaluation, which is why I wanted to write this post on Spine to answer questions that both I and other developers had about it.

So, what is Spine.js?

Spine is a lightweight framework that provides an MVC structure to your JavaScript applications with a focus on providing a more traditional literal inheritence model through classes and extension. It's based in many ways on Backbone's API so developers who have used Backbone before may find getting started a little easier than expected (however see the notes below for some fundamental differences). Spine also comes with baked in support for HTML5 localStorage and asynchronous server communication.

Spine's classes, models and views.

The official Spine documentation contains the most comprehensive break-down of it's features, which include support for validation, serialization, persistance, proxying and more.For the purpose of this post however, we're going to focus on the three big ones – namely: classes, models and views.

Classes

At the heart of Spine it's it's class implementation which uses en emulated version of Object.create to ensure that it's both dynamic and that properties can be resolved at runtime. Classes are relatively straight-forward to create as can be demonstrated in the following example:

var twitterClient = Spine.Class.create();
//or
var twitterClientWithArgs = Spine.Class.create({
testMessage: "If it weren't for WebMD I would have never known what symptoms to mimic so I could get all these prescriptions from my doctor."
});

Instantiating classes is then achieved using init(). Spine makes a decision to not use constructor functions as employing the "new" keyword can cause issues if left out when creating instances.

var twitterClient = Spine.Class.create({
 testMessage: "Hello world"
});
var myclient = twitterClient.init();

Arguments that you pass to this are then passed to init(), the class's instantiation callback. Eg.

var twitterClient = Spine.Class.create({
 init:function(testMessage){
 this.testMessage = testMessage;
 }
});

Models

In Spine, models are used for your application's data storage (models) as well as any logic associated with this data. This falls in line with the traditional idea of models in the MVC pattern and is also quite straight-forward to wrap your head around. Data that's associated with models is stored in memory under Model.records and models themselves can be created with the help of the Spine setup() function.

In the following example, we pass a model name and set of attributes to setup():

var Tweets = Spine.Model.setup("Tweet", ["username","tweet_message"]);

Models are effectively also Spine classes so you're also able to extend them and include properties as follows:

Tweets.include({
 toTweetString: function(){
 return("@" + this.username + " " + this.tweet_message);
 }
});

To create a model instance is then as trivial as using .init():

var mytweets = Tweets.init({username: "addyosmani", tweet_message: "hai twitter"});

Spine and Backbone both have a viewing layer which consist of rendering templates to the DOM and this can be done using a variety of different templating solutions, which Alex will expand on shortly, along with other clarifications on his solution.

Controllers

Think of controllers as the glue that holds everything together inside of your application. Similar to models, Spine's controllers extend Spine.Class and also inherit it's properties. By now you'll begin to see a pattern with the Spine syntax, but creating a controller is as simple as:

var TweetController = Spine.Controller.create({
 init:function(){
 //initial logic on instantiation
 }
})

Typically, you'll only add instance properties onto controllers and so you can easily pass them as the first argument to create(). Instantiation of controllers (as with other classes) is done by:

var myTweetController = TweetController.init();

Each controller also has an element associated with it, which can be passed down through an instance property called 'el'. This can also be set by passing it through via instantiation as follows:

var myTweetController = TweetController.init({el: $('#tweets')});

There are some interesting additional parameters that can be passed through to controllers, but the explanation above should suffice for understanding the basic workings of Spine's controllers.

Documentation Links

Feel free to read more about classes, models and controllers in the official Spine docs.

 

What are the main differences between Spine and Backbone?

Developers reading through the Backbone and Spine documentation may initially struggle to distinguish them, however after you get stuck in to using Spine in an actual project, you'll find that they do have some quite subtle differences which we'll go through below:

1) Backbone's views are what developers usually look at as traditional controllers and Backbone controllers assist with handling URL routing. Spine has recently added support for routing (something much needed) , but it's controllers are effectively the same concept as Backbone's views.

2) In terms of inheritance, Backbone uses constructor functions and prototypes whilst Spine uses an emulated version of Object.create and a simulated class system for it's version – the idea of class, extension and inheritence is very much at the core of the Spine experience and this is something I personally found to be an interesting change from Backbone. Both approaches have properties which are correctly resolved at runtime but Alex is going to explain why he thinks his approach might be easier to understand in the next section.

3) I've noticed a few developers reading up on Spine cite the file-size differences as a reason to try it out, but just to comment on this further: In the Hacker news thread, Jeremy points out that the main difference is because Spine doesn't include the collection mapping, filtering, and aggregation functions Backbone comes with. Whilst this is true (again, I love Backbone dearly), the dependency on underscore is probably what Jeremy is referring to in his overall comparison. When it comes down to it, it will be your call as to whether you feel the KBs worth of difference is relevant to your application's load time or not.

 

An interview with Spine developer Alex MacCaw

Could you tell us what inspired you to start writing Spine.js?

Well, I'd been playing around with own application frameworks for a while, specifically Super, and had some idea of the real-word problems in web application design, and how to address them in a library.

Whilst writing a chapter for my O'Reilly JavaScript book, I explored Backbone in more depth, and really liked what I saw. There were a few caveats to that which I'll address later, but all in all, Backbone's an excellent library. Spine was an endeavor to combine all I'd learnt so far with some of Backbone's concepts.

When developers first look at Spine, they may be surprised at the number of similarities between it and Backbone.js. Could you take us through the advantages Spine has to offer?

Absolutely, and with good reason. Backbone's View (in Spine it's called a Controller) API is hard to improve on, so Spine implements something very similar. However, the similarities stop there. Spine has some rather different ideas concerning classes and models, although these may not be obvious from a quick glance.

Before I go any further, I want to preface this with the message that I definitely don't want to turn this into an altercation between the two libraries, such as who's got what features and which is superior. Jeremy is very graceful when it comes to competition and I'm hoping, like Merb and Rails, both libraries can inspire each other and improve the ecosystem as a whole. With that said, I'll go through the things that, in my opinion, are Spine's strengths, and where the library differs with Backbone.

I've tried to keep Spine as slim as possible, and as a result the library's only about 500 lines, that's about 2k minified and compressed. However, Spine is easily extendable via a module interface, which is how features like routing are added, for example. This means you can choose exactly what you want to include in the page, keeping your application size down to an absolute minimum. Additionally, Spine doesn't require any third party libraries.

Spine includes a class library, which is really useful for encapsulating your own code and modules. The library uses a pure prototypal inheritance based system, rather than copying properties, which means property lookup is dynamic.

Spine does away with Collections, and puts records straight under models. This is in an effort to reduce code and simplify things. In fact, this is the message throughout the library. Simplification and clean code is at the core of Spine's philosophy.

Spine includes lots controller shortcuts, such as element lookup and function proxying, as I found there was usually a fair amount of repetition there.

Spine has different ideas when it comes to communicating with the server, namely de-coupling. What this means, is that servers are detached from the client, and aren't necessarily needed for clients to function properly. Apart from GET requests, clients are never waiting for response from the server. When a record is updated, for example, the client interface is updated, and then a request is send of to the server. All record ID generation is is done client side.

This has the advantage that users have a completely non-blocking interface, they're never waiting for a slow server response for further interaction with the application. After they've updated a record, for example, they can go and update it again and again, without worrying about background requests. Traditionally, clients would block further input until any Ajax requests had been successfully propagated to the server; not so with Spine. This gives your clients a desktop like experience, every interaction is instantaneous.

Lastly, I've done lots of work on the documentation. Using a new framework can be overwhelming at first, and I want to make it as easy as possible for developers to get started. Documentation is often neglected by developers, and I don't intend to make that mistake with Spine. In addition to the documentation, I've also put together some example applications, such as a simple todo list, a contact manager, and a full-on realtime group chat application.

I notice that Spine doesn't use the same concept of 'collections' that can be found in Backbone – how should developers who are used to Backbone's notion of models and collections approach this change?

I found that in the vast majority of cases, there was a direct one-to-one match between models and collections in my Backbone apps. It was quite the exception to have two collections to one model. It's for that reason I decided to forsake collections, and put records directly under models.

That said, if you do need the equivalent of collections, it's as simple as subclassing the model.

You mention that Spine uses a 'class' system. As you know, JavaScript has no native sense of traditional 'class', so how does your implementation work behind the scenes? Do you think client-side developers will be able to adjust to working with this easily?

I personally find some sort of class system in JavaScript very useful, and I therefore added one as a core component to Spine. JavaScript doesn't have a native class system, but it does have native inheritance through prototypes.

Traditionally, JavaScript class libraries have copied properties on sub-classes during inheritance. Even if they use some sort of prototypal inheritance, it'll only work for instance properties when using constructor functions. Static properties need to be copied manually to appear on sub-classes. This has the drawbacks that static properties aren't dynamic, and there's some overhead when first creating classes.

Spine approaches the problem differently, using pure prototypal inheritance. This has only been possible recently due to browsers adding the `Object.create()` function to JavaScript.

Both static and instance properties are inherited prototypically, and are therefore dynamic. It's pretty neat, from an object you can see all the way up it's prototype tree, finally ending up at `Object`.

The caveat to this approach is that instances can't be created with the `new` keyword, as classes aren't constructor functions. Instead, Spine has the `inst()` function. From a practical point of view, that's the only difference you'd make between traditional class implementations and Spine.

I've been looking through the official Spine documentation and I could be wrong, but is routing something Spine currently has support for (or will be trying to solve)?. If not, do you recommend integrating it with a solution like history.js or Ben Alman's hashchange plugin?

I've taken this opportunity to add routing to Spine. You'll find the module under lib/spine.route.js

I have also explained it in the documentation (note that the demo we'll be looking at today uses Spine's new routing features).

What kind of templating support does Spine have to offer? Backbone supports micro-templating via Underscore and a few other templating solutions (jQuery-tmpl etc). Is the same level of compatibility available?

Spine leaves templating decisions up to you, it doesn't make any decisions when it comes to views. Personally, and in Spine's examples, I've used jQuery.tmpl, but there are other good templating libraries out there you can use, like Mustache or Underscore's micro templating.

I see that the project has already racked up quite some interest on GitHub. For those wondering how long you plan on maintaining spine, could you give us some insights on that? Is it a personal project or something you feel has the potential to grow into what Backbone has matured to since it got released?

Absolutely, the library is generating lots of interest, and my inbox is getting overloaded with feedback and suggestions. I'm definitely going to spend a lot of time supporting and extending Spine, particularly as it's the basis of a lot of the examples in my book, and the core for a startup idea I'm working on. Additionally there's some cool features in the pipeline for Spine, such as simple offline sync.

If developers are looking to get started with Spine today and would like to be pointed in the direction of some good examples of it's usage, what would you recommend they check out?

Well, you could certainly point people to the three main examples so far, Spine Todos, Spine Contacts and Holla. Other than that, some examples I can think of: Twitter client, CMS, CRM, RSS reader.

Great. By the way, is there a channel where developers wishing to have their questions about Spine answered can go check out? At the moment I think a lot of answers are being derived from looking at the source rather than asking you directly.

Yes of course!. If it's a question, ask it on the Google Groups. Otherwise, if it's a bug, open a ticket. I'll try and respond to either as soon as I can. 

I was lucky enough to get a chance to see some of your book during some tech/content review sessions. Could you tell readers what they can expect to see in it and how Spine factors into your book's content for anyone wishing to learn more?

The aim of the book is to help you build the next generation of JavaScript web applications. The running theme throughout is structure, and the MVC pattern. The book will take you through all the steps involved in moving state to the client side, from templating and data binding to the actual deployment. A lot of the new HTML5 and CSS3 APIs are also covered, such as drag & drop file uploads and WebSockets. Finally, there's a full introduction to the Backbone, Spine and JavaScriptMVC frameworks.

As you alluded to, a lot of the examples and concepts, especially in the chapters explaining models and controllers, are based on Spine. However, they'll be useful regardless of whether or not you end up using the library.

Although the title hasn't quite been finalized yet (next day or so), the book will be on O'Reilly's Safari Rough Cuts in the next week or two. In the meantime you can check out the un-official website, see the full table of contents, and sign up to be notified when the book is released.

Finally, what do you hope to add to Spine in the next few iterations? Are there any clear areas that you would like to put more work into now that you've had an opportunity to sit back and review the first release?.

Well, currently I'm building a mobile client with Spine – so expect to see some features in that area. I'm thinking of adding support for some simple transitions (via CSS) in the controller manager, and 'touch' support to the list module. Lastly, I'm also working on a simple offline data synchronization library for Spine, which I'll open source when complete. All of these extensions will be implemented as modules, like jQuery plugins. I'm trying to keep the core library as small and simple as possible. Readers can 'watch' the repo on GitHub to find out as soon as updates are available.

Tutorial: Building A Bit.ly Client With Spine.js

Now that we've covered the basics, let's work on building something useful with Spine.

A lot of the time, when you're developing an SPA, you'll be consuming some external data (either your own or data supplied by an external API). You also might want to use routing to enable saving application state/bookmarking, possibly use localStorage and will of course need to handle the ajax requests to query whatever service is outputting your data.

With this in mind, today we're going to build a bit.ly client that allows you to easily:

  • create shortened URLs from inside your browser
  • archive your bit.ly URLs so you can easily access them at any time
  • provide you click statistics (just an additional 'view' so we can demonstrate routing)

Pre-requisites

Create a bit.ly plugin

Before we begin, we're going to need a convenient way to access bit.ly's services to first 1) shorten URLs and 2) access click statistics. Rather than keeping the ajax calls for this inline to our Spine code, I decided that a jQuery plugin would be a neater way of maintaining the logic outside of the app so it can easily be updated or re-used. Here's the bit.ly plugin that we put together:

 

Add support for store.js

By default, Spine targets modern browsers and so it's currently implementation of localStorage doesn't work cross-browser if you wish to support those with different implements or which are older.

This however can be easily tackled by bringing in store.js (and it's dependancy json2.js). Below is the Spine file spine.model.local.js, which you can update to use store by commenting out the lines I've noted below and replacing them with the line that follows.

 

Spine.Model.Local = {
  extended: function(){
    this.sync(this.proxy(this.saveLocal));
    this.fetch(this.proxy(this.loadLocal));
  },
  saveLocal: function(){
    var result = JSON.stringify(this);
    //localStorage[this.name] = result;
    store.set(this.name, result);
  },
  loadLocal: function(){
    //var result = localStorage[this.name];
    var result = store.get(this.name);
    if ( !result ) return;
    var result = JSON.parse(result);
    this.refresh(result);
  }
};

jQuery templating

Deciding which templating solution to use is often one of the nicest parts of using either Spine or Backbone as they both support a multiude of different solutions (underscore's micro-templating, mustache.js and so on). Today we're going to use jQuery's tmpl plugin to render our shortened URL entries and click statistics using templates.

 

Writing our application

Alex was kind enough to refactor the app to follow some of Spine's best practices so let's summarize the structure of our application beyond the pre-requisites. We'll need:

  • A model to represent the data being stored in each shortened URL entry (Url model)
  • A controller to represent individual entries and the actions that can be done with them (exports.Urls)
  • A controller to represent the view for adding in new shortened bit.ly entries (exports.UrlsList)
  • A controller to represent the view for click statistics of a given entry (exports.Stats)
  • A generic application controller that will amongst other things, handle general routing (exports.UrlApp)

We've opted to use jQuery in today's tutorial application as we're using it for both templating and plugin structure, however, Spine works just as well with Zepto or vanilla JavaScript. Let's now take a look at the code for our app:

 

Initial caching

var exports = this;

 

Simple jQuery plugin for toggling the display of content

$.fn.toggleDisplay = function(bool){
    if ( typeof bool == "undefined" ) {
      bool = !$(this).filter(":first:visible")[0];
    }
    return $(this)[bool ? "show" : "hide"]();
};


Url model:

var Url = Spine.Model.setup("Url", ["short_url", "long_url", "stats"]);
Url.extend(Spine.Model.Local);
Url.include({
  validate: function(){
    if ( !this.long_url )
      return "long_url required"
    if ( !this.long_url.match(/:\/\//))
      this.long_url = "http://" + this.long_url
  },
  fetchUrl: function(){
    if ( !this.short_url )
      $.bitly(this.long_url, this.proxy(function(result){
        this.updateAttributes({short_url: result});
      }));
  },
  fetchStats: function(){
    if ( !this.short_url ) return;
    $.bitly.stats(this.short_url, this.proxy(function(result){
      this.updateAttributes({stats: result});
    }));
  }
});
Url.bind("create", function(rec){
  rec.fetchUrl();
});

 

exports.Urls controller:

  exports.Urls = Spine.Controller.create({
    events: {
      "click .destroy": "destroy",
      "click .toggleStats": "toggleStats"
    },
    proxied: ["render", "remove"],
    template: function(items){
      return $("#urlTemplate").tmpl(items);
    },
    init: function(){
      this.item.bind("update",  this.render);
      this.item.bind("destroy", this.remove);
    },
    render: function(){
      this.el.html(this.template(this.item));
      return this;
    },
    toggleStats: function(){
      this.navigate("/stats", this.item.id, true);
    },
    remove: function(){
      this.el.remove();
    },
    destroy: function(){
      this.item.destroy();
    }
  });


exports.UrlsList controller:

exports.UrlsList = Spine.Controller.create({
    elements: {
      ".items": "items",
      "form":   "form",
      "input":  "input"
    },
    events: {
      "submit form": "create",
    },
    proxied: ["render", "addAll", "addOne"],
    init: function(){
      Url.bind("create",  this.addOne);
      Url.bind("refresh", this.addAll);
    },
    addOne: function(url){
      var view = Urls.init({item: url});
      this.items.append(view.render().el);
    },
    addAll: function(){
      Url.each(this.addOne);
    },
    create: function(e){
      e.preventDefault();
      var value = this.input.val();
      if (value)
        Url.create({long_url: value});
      this.input.val("");
      this.input.focus();
    }
  });


exports.Stats controller:

  exports.Stats = Spine.Controller.create({
    events: {
      "click .back": "back"
    },
    proxied: ["change", "render"],
    init: function(){
      Url.bind("update", this.render);
    },
    template: function(items){
      return $("#statsTemplate").tmpl(items);
    },
    render: function(){
      if ( !this.item ) return;
      this.el.html(this.template(this.item));
    },
    change: function(item){
      this.item = item;
      this.navigate("/stats", item.id);
      this.item.fetchStats();
      this.render();
      this.active();
    },
    back: function(){
      this.navigate("/list", true);
    }
  });


exports.UrlApp controller:

  exports.UrlApp = Spine.Controller.create({
    el: $("body"),
    elements: {
      "#urls": "urlsEl",
      "#stats": "statsEl"
    },
    init: function(){
      this.list = UrlsList.init({el: this.urlsEl});
      this.stats = Stats.init({el: this.statsEl});
      this.manager = Spine.Controller.Manager.init();
      this.manager.addAll(this.list, this.stats);
      this.routes({
        "": function(){ this.list.active() },
        "/list": function(){ this.list.active() },
        "/stats/:id": function(id){ this.stats.change(Url.find(id)) }
      });
      Url.fetch();
      Spine.Route.setup();
    }
  });

Finally, we call the following to complete the initialization of our 'app' controller:

exports.App = UrlApp.init();

Our Bit.ly snippets for URL shortening and click statistics

(function($){
  var defaults = {
    version:    "3.0",
    login:      "legacye",
    apiKey:     "R_32f60d09cccde1f266bcba8c242bfb5a",
    history:    "0",
    format:     "json"
  };
  $.bitly = function( url, callback, params ) {
    if ( !url || !callback ) throw("url and callback required");
    var params = $.extend( defaults, params );
    params.longUrl = url;
    return $.getJSON("http://api.bit.ly/shorten?callback=?", params, function(data, status, xhr){
      callback(data.results[params.longUrl].shortUrl, data.results[params.longUrl], data);
    });
  };
  $.bitly.stats = function( url, callback, params ) {
    if ( !url || !callback ) throw("url and callback required");
    var params = $.extend( defaults, params );
    params.shortUrl = url;
    return $.getJSON("http://api.bitly.com/v3/clicks?callback=?", params, function(data, status, xhr){
      callback(data.data.clicks[0], data);
    });
  };
})(jQuery);

Application Index/HTML:

Below I've opted to use LABjs for dependency management, however you can easily switch this out for whatever script loader you personally prefer using.

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="css/application.css" type="text/css" charset="utf-8">
  <script src="lib/LAB.min.js" type="text/javascript" charset="utf-8"></script>
  <script type="text/javascript">
	$LAB
	.script("lib/json.js")
	.script("lib/jquery.js")
	.script("lib/jquery.tmpl.js")
	.script("lib/jquery.bitly.js")
	.script("lib/store.min.js")
	.script("lib/spine.js")
	.script("lib/spine.model.local.js")
	.script("lib/spine.controller.manager.js")
	.script("lib/spine.route.js")
	.script("app/models/url.js")
	.script("app/application.js");
  </script>
  <script type="text/x-jquery-tmpl" id="urlTemplate">
    <div class="item">
      <div class="show">
        <span class="short">
          ${long_url}
        </span>
        <span class="long">
          {{if short_url}}
            <a href="${short_url}">${short_url}</a>
          {{else}}
            Generating...
          {{/if}}
        </span>
        <a class="toggleStats"></a>
        <a class="destroy"></a>
      </div>
    </div>
  </script>
  <script type="text/x-jquery-tmpl" id="statsTemplate">
    <div class="stats">
      <a class="back">Back</a>
      <h1>Click Statistics</h1>
      <h1 class="longUrl">${long_url}</h1>
      <p>Short URL:
        {{if short_url}}
          <a href="${short_url}">${short_url}</a>
        {{else}}
          Generating...
        {{/if}}
      </p>
      {{if stats}}
        <p>Global clicks: ${stats.global_clicks}</p>
        <p>User clicks: ${stats.user_clicks}</p>
      {{else}}
        Fetching...
      {{/if}}
    </div>
  </script>
</head>
<body>
  <div id="views">
    <div id="urls">
      <h1>Bit.ly Client</h1>
      <form>
        <input type="text" placeholder="Enter a URL">
      </form>
      <div class="items"></div>
    </div>
    <div id="stats">
    </div>
  </div>
</body>
</html>

Notes:

  • For optimal cross-browser compatibility, the demo should be run from a live or local server. Use MAMP/WAMP if necessary.
  • If testing for click statistics, I recommend pasting in URLs that are more common rather than those that are obscure. Eg. http://www.google.com likely has existing Bit.ly click data
  • Note that the demo is using my own Bit.ly API key details and should be changed to your own. If for any reason you notice the demo linked to this post ceases to work, let me know and I’ll acquire another key.
  • Pie charts are generated dynamically with the aid of the Google Chart API. To keep things simple I opted for the image chart variation, however you can easily switch to the Visualization API if you require more granular control over the data being displayed
  • How you structure your application directory is entirely up to you. Some developers prefer the common models/view/controllers literal folder structure whilst others prefer having a generic app folder where their app may use the MVC pattern but be based in a single file. In our demo app we’re using Alex’s preferred folder structure.
  • When approaching bookmarking and routing in Spine, remember that if you wish to preserve unique 'views' for content (eg. one view for #ui/dashboard and another completely different one for #ui/stats), it's worth taking a look at spine.controller.manager.js as this contains a good way of approaching the problem.

and that's it!. To download the sources for today's tutorial or try our a live demo, please click on one of the buttons below.

Demo, download or fork

 

 

Conclusions

I found working with Spine a nice alternative experience to what I'm used to with Backbone. The documentation is relatively straight-forward to study and if you're interested in playing around with Spine further, I recommend forking one of the sample apps listed to see just how each piece correlates with something already functional and complete.

Thanks for reading. If you found this post useful, please help spread the knowledge by clicking on the retweet or like buttons below.

27 Comments

  1. Pingback: あとで読む:Building JavaScript Web Apps With MVC & Spine.js | iida note

  2. Spine doesn't seem to run in IE6 or IE7… Maybe even other Internet Explorers. Shouldn't this be pointed out? Backbone runs fine with these. Am I just missing something?

    • Maybe because of json? "If you're using an older browser which doesn't have native JSON support (i.e. IE 7), you'll need to include json2.js which adds legacy support." (from the docs)

  3. Pingback: jQuery应用程序架构设计工具(PPT) | 百锐网

  4. Pingback: Link del día: Spine.js y cómo utilizarlo | Alpha's Manifesto

  5. Pingback: JavaScript e MVC | Alberto Monteverdi

  6. There's a mistake on the text here:
    "the dependency on underscore is probably what Jeremy is referring to in his overall comparison. "

    Surely you mean:
    "the dependency on underscore is probably what Alex is referring to in his overall comparison. "

    Not "Jeremy", but "Alex". :)

  7. Pingback: Javascript Resources « Kooljoy.com Blog

  8. Pingback: 20 JavaScript Frameworks Worth Checking Out | linuxin.ro

  9. Pingback: Tutorial - 20 JavaScript Frameworks Worth Checking Out | Tutorials and Guides

  10. Pingback: 20 JavaScript Frameworks Worth Checking Out | Web Design Northamptonshire

  11. Pingback: Web Development articles, tutorials, help » Blog Archive » 20 JavaScript Frameworks Worth Checking Out

  12. Pingback: 20 JavaScript Frameworks Worth Checking Out – blog

  13. Pingback: 20 JavaScript Frameworks Worth Checking Out | Shadowtek | Hosting and Design Solutions

  14. Pingback: 20 JavaScript Frameworks Worth Checking Out « Fast Ninja Blog by Freelanceful – Web Design | Coding | Freelancing

  15. Pingback: 20 个值得一试的JavaScript 框架 | 在路上

  16. Pingback: 傻子日志

  17. Pingback: 20个值得一试的JavaScript框架 - 博客 - 伯乐在线

  18. Pingback: 20个值得一试的JavaScript框架 - phpsir - 致力于php开发

  19. Pingback: Написание сложных интерфейсов с Spine.js « Максим Денисов — портфолио

  20. This is a great post!

    A few links to github repo could be updated since it has moved from github.com/maccman/spine to github.com/spine/spine

Leave a Reply

Required fields are marked *.