Building Applications Using the backbone.khs Framework Extension

John Boardman BackboneJS, JavaScript, Node.js, Technology Snapshot Leave a Comment

Backbone is a very powerful application development framework. However, it can be a little “close to the metal” in terms of how much work is needed to produce a working application with it. I see Backbone as a low level framework that could use some help in making it a bit easier and faster to use.

Keyhole has released an extension to help! The backbone.khs framework extension npm module (available by clicking the link) does its best to minimize the work necessary to get a Backbone application up and running.

The extension makes it easier to deal with:

  • browser history
  • root level non-Model Object implementation
  • caching
  • session support
  • regions (which break pages up into more workable segments)
  • a top-level Application object to manage the application
  • modules to help with page and URL routing
  • a Backbone View extension to seamlessly integrate Backbone Stickit and make Marionette templates easier
  • a Collection View to enhance working with groups of items.

In this blog, I’ll describe these enhancements with some code examples. Look for a fully working example solution that replaces the (overly) simple plain JavaScript Whirlpool UI from my previous blog soon!

Browser History

The backbone.khs extension overrides the loadUrl() function in Backbone’s History object. This function loops through all available handlers, finding the route with the highest score, and uses that instead of simply picking the first handler that returns any positive result. The extension is called just like the normal Backbone.History.loadUrl(fragment) call.

Root Level Object

The class Object provides a non-Model top level class. This class supports Backbone’s “extend” functionality, and includes support for the optional initialize() function when an object is created. All of the other classes in the framework extension extend from this class.

Caching

The backbone.khs extension provides a global Cache for keeping track of data. The cache can expire data at the end of a session, add, remove, and find data in the cache. Cache keys are simple strings. The following example places sampleObj into the cache with the key “sample” that expires when the session ends, retrieves the data, then manually removes it from the cache.

Backbone.cache.put('sample', sampleObj, {expire: 'session:end'});
var sampleObj = Backbone.cache.get('sample');
Backbone.cache.remove('sample');

Session Support

The backbone.khs extension adds a Session class that provides some basic structure around the session. It keeps track of whether or not the user is authenticated, the principle (key) of the user, and what roles the user has. It also provides helper functions for invalidating the session, finding out if a user has a role, adding roles to a user, and authenticating the user.

Regions

A region is a section of a page that is rendered. A region manager handles all the regions that are present on a page. This additional layer of hierarchy helps in situations like common nav bars and other areas where portions of a page show up repeatedly. The Application class uses regions to tear down and rebuild pages during routing.

Application

Application is the top-level manager of the page. It should be created and invoked when the page is finished loading.

Backbone.$(document).ready(function (event) {
 window.myApp = new Application(event);
});

This is all it takes to kick off your app. Application should set up caching, the initial rendering, and handle redirecting the user to the login page when there is no session.

Here’s an example Application.js. This is meant more for perusing than copy/pasting and trying to run, as it would require more code than just what you see here.

require('bootstrap');
var Backbone = require('backbone.khs');
// need to load overrides
require('./backbone.iris');
var _ = require('underscore');
var UnauthenticatedModule = require('./UnauthenticatedModule');
var AuthenticatedModule = require('./AuthenticatedModule');

var Application = Backbone.Application.extend({
  initialize: function () {
    var session = new Backbone.Session();

    this.on("all", function (eventName) {
      Backbone.cache.trigger(eventName);
    }, this);

    this.addRegions({
      body: '#body'
    });

    window.location.hash = '';
    Backbone.history.start({pushState: true, silent: true});

    this.unauthenticatedModule = new UnauthenticatedModule({path: '', regionManager: this.regions.body});
    this.authenticatedModule = new AuthenticatedModule({path: '', regionManager: this.regions.body});

    session.comply('authenticated:success', this.authenticated, this);
    session.comply('authenticated:fail', this.notAuthenticated, this);
    session.checkAuthentication();

    this.on('session:start', function () {
      console.log('session started event');
    });

    this.on('session:end', function () {
      Backbone.history.navigate('/login', {trigger: true});
      console.log('session ended event');
    });
  },
 
  command: function (name) {
    // make sure we pass all application event to the cache
    Backbone.cache.command.apply(Backbone.cache, arguments);
    return Backbone.Application.prototype.command.apply(this, arguments);
  },

  notAuthenticated: function () {
    // make sure we have a clean session
    this.session.invalidate();
    this.unauthenticatedModule.start();
    this.authenticatedModule.stop();
    Utility.forceNavigate('/login');
    Backbone.history.loadUrl();
  },

  authenticated: function (session) {
    this.unauthenticatedModule.stop();
    Backbone.cache.put('_session', session);
    // setup event for logout - only want this once.
    this.session.complyOnce('authenticated:invalidated', this.notAuthenticated, this);
    this.doAuthenticatedRedirect(session);
  },

  doAuthenticatedRedirect: function (session) {
    this.authenticatedModule.start();
    var redirect = this.redirect;

    if (Backbone.cache.has('session')) {
      var theSession = Backbone.cache.get('session'),
      url = theSession.get('url');
      if (url.indexOf('login') > -1) {
        window.Iris.trigger('session:end');
      } else {
        Backbone.history.loadUrl(theSession.get('url'));
        window.Iris.trigger('session:start', theSession);
      }
    } else if (redirect) {
      Backbone.history.loadUrl(redirect, {trigger: true}));
    } else {
      Backbone.history.loadUrl();
    }
  }
});

Backbone.$(document).ready(function (event) {
 window.myApp = new Application(event);
});

Modules

Modules process routes and create Views to display data. A super minimal module is shown below.

The important piece is the routes object, which is processed when a URL is being loaded. The route below only has the default since it only displays one page. Routes are hierarchical, which each layer of a URL (a/b/c) being processed by a Module at that folder level in the file system. All possible routes must be accounted for.

Modules also have the opportunity to be called when a session starts, session ends, before a route is processed, and after a route is processed. This provides a lot of flexibility for verification, validation, and security.

'use strict';
var Backbone = require('backbone.khs');
var Region = require('./RegionView');
var EditView = require('./EditView');

module.exports = Backbone.Module.extend({
  region: Region,
  routes: {
    '': 'show'
  },

  show: function () {
    var model = new Backbone.Model(),
    view = new EditView({model: model});
    this.region.regions.body.show(view);
  }
});

Backbone View Extension

The extension to the Backbone View adds a Backbone.Radio channel name and Backbone.Stickit bindings to the Backbone View. It also provides calls for beforeRender() and afterRender(), beforeShow() and afterShow(), checking to see if the view is rendered, rendering templates, and building the data that the template will use.

CollectionView

A CollectionView handles displaying sets of data, typically results from a query. Displaying this data can be as easy as what is shown below, but it can also handle very complex cases too. This example uses a table to render, but divs can be used just as easily.

ResultsView.js

'use strict';
var Backbone = require('backbone.khs');
var template = require('./results.ejs');
var ChildView = require('./ResultView.js');

module.exports = Backbone.CollectionView.extend({
  tagName: 'table',
  template: template,
  childSelector: 'tbody',
  childView: ChildView
});

results.ejs

<thead>
<tr>
  <th>Foo</th>
</tr>
</thead>
<tbody>
</tbody>

ResultView.js

module.exports = Backbone.ItemView.extend({
  events: {},
  tagName: "tr",
  template: template,

  initialize: function() {
    this.model.on('change', this.render, this);
  },
 
  _getTemplateData: function () {
    return {
      foo: "foo"
    };
  },

  remove: function() {
    this.model.off('change', this.render, this);
    Backbone.View.prototype.remove.call(this);
  }
});

result.ejs

<td><%- foo %></td>

Conclusion

This has been a super quick overview of the capabilities that the backbone.khs framework extension brings to the table for enhancing development using the excellent Backbone framework. Again, a full working example is coming soon!


About the Author
John Boardman

John Boardman

Twitter

John is a Sr. Keyhole Consultant with 20+ years of experience in C, C++, Java, and IoT enterprise software design and development. Also currently writing a multi-platform, multi-user game in Unity3d (and server in Java) and have written custom graphical game engine clients in C and C++ on several platforms.


Share this Post

Leave a Reply