Organize Backbone application using Require.js

When I looked into the BigBlueButton HTML5-Client files, I was completely lost because I had no idea about how to organize the code in the BigBlueButton application using modules. The following is the BigBlueButton HTML5-Client Files Structure (for details, please go to BigBlueButton github account):

├── css
│      └── style.css
├── favicon.ico
├── font
│      ├── fontawesome-webfont.eot
├── images
│      ├── bbb-081.png
├── js
│      ├── app.coffee
│      ├── build.js
│      ├── collections
│      │       ├── meetings.coffee
│      │       └── users.coffee
│      ├── lib
│      │       ├── backbone-min.js
│      │       ├── coffee-script.js
│      │       ├── colorwheel.js
│      │       ├── jquery
│      │       ├── raphael
│      │       ├── require
│      │       ├── socket.io.js
│      │       ├── textflow-helper.js
│      │       ├── textflow.js
│      │       └── underscore-min.js
│      ├── main.js
│      ├── models
│      │       ├── connection.coffee
│      │       ├── meeting.coffee
│      │       ├── user.coffee
│      ├── router.coffee
│      ├── utils.coffee
│      └── views
│             ├── app.coffee
│             ├── session.coffee
└── templates
       ├── login.html
       ├── session.html

After reading some tutorials (Introduction to RequireJS, Organizing application using Modules), I have better understanding about AMD (Asynchronous Module Definitions) and the separation between code and design.

Definition of AMD

Asynchronous module definition (AMD) is a JavaScript API for defining modules such that the module and its dependencies can be asynchronously loaded. It is useful in improving the performance of websites by bypassing synchronous loading of modules along with the rest of the site content.

In addition to loading multiple JavaScript files at runtime, AMD can be used during development to keep JavaScript files encapsulated in many different files. This is similar to other programming languages, for example java, which support keywords such as import, package, and class for this purpose. It is then possible to concatenate and minify all the source JavaScript into one small file used for production deployment.

Require.js loader

RequireJS takes a different approach to script loading than traditional script tags. Its goal is to encourage modular code. While it can also run fast and optimize well, the primary goal is to encourage modular code.

Walk through the files starting from build.js

({
  baseUrl: '.',         //path will be relative to current directory, use for all module lookups

  stubModules: ['cs', 'coffee-script'], //This results in all coffee files being compiled to JavaScript and inlined

  name: 'main',        // specify the main module which is main.js
  out: 'main-dist.js', // tell r.js that you want everything in one file

                      //shim--Configure the dependencies and exports for older, traditional "browser globals" scripts
                      //that do not use define() to declare the dependencies and set a module value.
  shim: {
    'colorwheel': ['raphael'],
    'textflow': ['textflow-helper'],
    'backbone': {
      deps: ["underscore"], //These script dependencies should be loaded before loading backbone.js
      exports: "Backbone"   //Once loaded, use the global 'Backbone' as the module value.
    },
    'underscore': {
      exports: "_"
    }
  },

  paths: {          //path mappings for module names not found directly under baseUrl
    'cs': 'lib/require/cs',
     .
     .
     .
    'templates': '../templates',
  }
})

The main.js file :

requirejs.config({
  waitSeconds: 60, //The number of seconds to wait before giving up on loading a script.
  baseUrl: 'js',
  shim: {
    'colorwheel': ['raphael'],
    'textflow': ['textflow-helper'],
    'backbone': {
      deps: ["underscore"],
      exports: "Backbone"
    },
    'underscore': {
      exports: "_"
    }
  },
  paths: { //Require.js allows us to configure shortcut alias

    'coffee-script': 'lib/coffee-script',
     .
     .
     .
    'templates': '../templates',
  }
});

require([ // Load our app module and pass it to our definition function
  'cs!app',
  'coffee-script'
], function(App){// The "app" dependency is passed in as "App"
  App.initialize();
});

The app file:

#load dependencies
define [
  'jquery',
  'underscore',
  'backbone',
    .
    .
    .
  'jquery.ui'
], ($, _, Backbone, Raphael, globals, Router, ConnectionModel) ->

  globals.router = {}
  globals.connection = {}

  initialize = ->
    # Authentication object, set when the user is authorized in
    globals.currentAuth = null

    # An event bus to handle events in the application
    globals.events = _.extend({}, Backbone.Events)

    # Default application router
    globals.router = new Router()

    # Default connection (websocket)
    globals.connection = new ConnectionModel()

    # Start at /login
    globals.router.showLogin()
    Backbone.history.start({silent: true})

  return {
    initialize: initialize
  }

The router module file :

define [
  'jquery',
  'underscore',
  'backbone',
  'globals',
  'cs!views/app',
  'cs!views/login',
  'cs!views/session'
], ($, _, Backbone, globals, AppView, LoginView, SessionView) ->

  Router = Backbone.Router.extend
       #define some url routes
    routes:
      'session': 'showSession', # match http://localhost:3000/#session will fire route : showSession event
      'login': 'showLogin',     # match http://localhost:3000/#login will fire route : showLogin event
      # '*actions': 'defaultAction'

    initialize: ->
      @appView = new AppView()   #create an application view

    showLogin: () ->
      globals.router.navigate "/login", {replace: true}
      @loginView ?= new LoginView()
      @appView.render(@loginView)

    showSession: () ->
      globals.router.navigate "/session", {replace: true}
      @sessionView ?= new SessionView()
      @appView.render(@sessionView)

  Router

Modularizing a Backbone view:
Backbone views usually interact with the DOM. Using new modular system we can load
in JavaScript templates using the Require.js text! plug-in.

define [
  'jquery',
  'underscore',
  'backbone',
  'globals',
  'text!templates/session.html',  // text! plug-in to pass session template
  'cs!views/session_navbar',
  'cs!views/session_chat',
  'cs!views/session_users',
  'cs!views/session_video'
  'cs!views/session_whiteboard'
], ($, _, Backbone, globals, sessionTemplate, SessionNavbarView,
SessionChatView, SessionUsersView, SessionVideoView,
SessionWhiteboardView) ->

  SessionView = Backbone.View.extend
    id: 'session-view'
    initialize: ->
    render: ->
      compiledTemplate = _.template(sessionTemplate) // compile the sessionTemplate
      @$el.html compiledTemplate
      # Connect to the server
      globals.connection.connect()
  SessionView

How to define our Model :

define [
  'underscore',
  'backbone',
  'globals'
], (_, Backbone, globals) ->

  UserModel = Backbone.Model.extend

    initialize: ->

  UserModel

How to define our collection:

define [
  'underscore',
  'backbone',
  'globals',
  'cs!models/user'
], (_, Backbone, globals, UserModel) ->
  UsersCollection = Backbone.Collection.extend

    model: UserModel

    initialize: ->

 UsersCollection

Now we can simply depend on our collection in our view and pass it to our template:

define [
  'jquery',
  'underscore',
  'backbone',
  'globals',
  'cs!collections/users',
  'text!templates/session_users.html',
  'text!templates/user.html'
], ($, _, Backbone, globals, UserCollection, sessionUsersTemplate, userTemplate) ->

  # The users panel in a session
  # The contents are rendered by SessionView, this class is Used to
  # manage the events in the users.
  SessionUsersView = Backbone.View.extend
    model: new UserCollection() // Here is our collection
    events:
      "click #switch-presenter": "_switchPresenter"
      "click .user": "_userClicked"
    initialize: ->
      @userListID = "#current-users"

    render: ->
      compiledTemplate = _.template(sessionUsersTemplate)
      @$el.html compiledTemplate
  SessionUsersView

Conclusion
Take a look at the code in backbone boilerplate, you should get a brief idea of how to organize your code.

Advertisements
This entry was posted in open-source. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s