Tuesday, December 6, 2011

Converting a Backbone.js App to Require.js

‹prev | My Chain | next›

Today, I hope to build on my recent success with require.js optimization of Backbone.js applications. Specifically, I would like to see if I can move beyond using require.js with simple Backbone applications. Instead, I hope to convert my Funky Backbone Calendar to require.js.

First up, I remove the <script> tag references to various supporting libraries from my jade layout:
!!!
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
    link(rel='stylesheet', href='/stylesheets/blitzer/jquery-ui.css')
    script(src='/javascripts/jquery.min.js')
    script(src='/javascripts/jquery-ui.min.js')
    script(src='/javascripts/underscore.js')
    script(src='/javascripts/backbone.js')
    script(src='/javascripts/calendar.js')
  body!= body
In my application page, I add a require.js script tag, which loads a new main.js script:
h1
  = title
  span.year-and-month

#calendar

script(data-main="javascripts/main", src="javascripts/require.js")
That script line evaluates to:
<script data-main="javascripts/main" src="javascripts/require.js"></script>
That main.js file (the ".js" is implied in the data-main attribute) is new. Similar to the main.js that I used in my simple Backbone app, it contains configuration to tell require.js where to find certain libraries and initializes the application itself:
require.config({
  paths: {
    'jquery': 'jquery.min',
    'jquery-ui': 'jquery-ui.min'
  }
});

require(['calendar'], function(calendar){
  calendar.initialize();
});
The library mapping in this case instructs require.js to use the minified versions of jquery and jquery-ui whenever they are referenced / required elsewhere. With that, I then require() my calendar.js Backbone applications (again, the ".js" is implied with require.js) and initialize it. That is not quite the normal initialization that I need to do—no matter, I do not expect this to work just yet. And sure enough, it does fail:
Uncaught TypeError: Cannot call method 'initialize' of null  main.js:9
I expected that failure because my main.js script is requiring a non-require.js library: calendar.js.

Currently, I am encapsulating that calendar.js as:
window.Cal = function(root_el) {
  var Models = (function() { /* ... /* })();
  var Collections = (function() { /* ... */ })();
  var Views = (function() { /* ... /* })();

  var Routes = Backbone.Router.extend({ /* ... */ })

  // Initialize the app (collection, application view, router)
}
I need to convert that to a require.js defined module:
define(['jquery',
        'underscore',
        'backbone'],
  function($, _, Backbone, jqueryUi) {
    return function(root_el) { /* ... */ };
  }
);
If that works, then I ought to be able to initialize it in main.js as:
require.config({ /* ... */ });

require(['calendar'], function(Calendar){
  var calendar = new Calendar($('#calendar'));
});
Unfortunately, when I load that up, I see Javascript errors along the lines of:
Uncaught TypeError: Cannot read property 'Model' of null
Cannot read the "Model" property on "Backbone"? What on earth?!

D'oh! I forgot. I am trying to use Backbone in a require.js kind of way, so I need a fork of require.js that is compatible with require.js (like the one in James Burke's optamd3 branch). With that, I... still get an error:
Uncaught TypeError: Object [object Object] has no method 'dialog'
Hrm... that is because I not loading the jquery-ui library which contains the dialog method. But how to include that? It cannot be as easy as adding jquery-ui to the list of libraries upon which my Backbone application depends, can it?
define(['jquery',
        'underscore',
        'backbone',
        'jquery-ui'],
  function($, _, Backbone, jqueryUi) {
    return function(root_el) { /* ... */ };
  }
);
In fact it can be that easy because now my Backbone application is back without errors:


I call it a night here. I am not close to realizing the power of require.js yet, but this is a start. Tomorrow, I plan to break up the application into smaller files that can then be require'd together.

Day #227

4 comments:

  1. I assume you do not need to jqueryUI to your parameter list, because it returns no object. it just works, because it´s loaded.

    define(['jquery',
    'underscore',
    'backbone',
    'jquery-ui'],
    function($, _, Backbone) {
    return function(root_el) { /* ... */ };
    }
    );

    ReplyDelete
  2. James Burke made jqueryUI AMD compatible https://github.com/jrburke/jqueryui-amd

    so you can just use dialog.js and it should get its dependencies 'jquery','jqueryui/core','jqueryui/widget','jqueryui/button','jqueryui/draggable','jqueryui/mouse','jqueryui/position','jqueryui/resizable')

    ReplyDelete
  3. Yah, you're correct about not needing to include jquery-ui as a parameter on the callback. I've dropped it in subsequent posts. Thanks for the tip :)

    Thanks also for the tip on jquery-ui AMD compatibility. I don't know that I'll end up making use of that, but it is good to know that it is out there.

    Thanks yet again for the many pointers!

    ReplyDelete
  4. SUPER helpful series of blog posts on actually using Require.js along with Backbone.js. I could put this same comment on EVERY post you've made in December, but that seems excessive, so consider this one comment a response to all of them. Thank You.

    ReplyDelete