Vertebrae provides AMD structure and additional objects for extending Backbone.js as an application framework.
- Github source : https://github.com/hautelook/vertebrae
The plan: build an application, mostly with open source libraries
Following a review of many MV* front-end frameworks/libraries, such as Ember, JavaScriptMVC, Spine, Knockout, and Backbone.js our team decided to begin building a framework with Backbone.js, RequireJS, Underscore.js, jQuery, Mustache.js with code organized in packages of (AMD) modules, see previous post: Optimize and Build a Backbone.js JavaScript application with Require.JS using Packages…
We needed to add some logic on top of the chosen stack of libraries
The libraries we selected provide: module loading, dependency management, application structure (for models, collections, views and routes), asynchronous interactions with API, various utilities and objects to manage asynchronous behaviors, e.g. (Promises) Deferreds, Callbacks. The remaining logic needed to complete the application include:
- An object (collection) to manage state(s) in the single-page application;
- A layout manager to present, arrange/transition and clear views, and
- Controllers which respond to routes, get/set application state, and hand off views and models/collections to layout manager.
The project goals include:
- Modules for code organization (a module should have a single responsibility)
- MVC structure for separation of concerns
- Dependency management using a script loader
- Build tools for optimization in packages which should support various experiences in the app
- Well documented and actively used open source libraries
- Framework code should have unit tests, build framework by writing unit tests.
- Solutions for managing asynchronous behaviors, e.g. Promises
We are using: RequireJS, Underscore.js, jQuery, Backbone.js, Mustache.js; and for BDD… Jasmine, Sinon, jQuery-Jasmine.
Solutions
Application state manager
The application manager stores data in memory (collection) and also persists data in browser storage to provide a resource for storing and retrieving data/metadata. The application states collection also provides data (state) to reconstruct a page (layout view) based on previous interactions (e.g. selected tab, active views, etc.). The application state manager provides a strategy for resources to retrieve state for a common interface for localStorage, sessionStorage, or a cookie; also sets an expires
property.
Also, data used to bootstrap a page can be stored as well, e.g. user/visitor information (items in cart, etc.), data that is reused for many pages, interactions on a page that should be recalled when a user revisits the page.
Layout manager
The layout manager has one or many views as well as document (DOM) destinations
for each (rendered) view. A page may transition between views, so the layout manager keeps track of view states, e.g. rendered, not-rendered, displayed, not-displayed. The layout manager can load and render (detached) views that a user/visitor is very likely to request, e.g. tab changes on page. The transition between view states is managed by this object. An entire layout may be cleared so that view objects and their bindings are removed, preparing these objects for garbage collection (preventing memory leaks). The layout manager also communicates view state with controller(s).
Controller
A controller object is called by a route handler function, is responsible for getting relevant state (application models), and does the work to construct a page (layout); also responsible for setting state when routes change. The controller passes dependent data (models/collections) and constructed view objects for a requested page to the layout manager. As a side-effect the use of controllers prevents the routes object from becoming bloated and tangled. A route should map to a controller which then kicks off the page view, keeping the route handling functions lean.
Behavior Driven Development -
We made the decision to develop the framework using behavior driven development. Jamsine is the test framework we selected, below are the specs that were used to discover the solutions for our need to have the additional logic we wanted to add on top of the open source stack of libraries…
Specs
Application state manager specs
(an model object to manage state within the single-page application)
should use options for persistent storage of JSON data
should reference stored data with string name, e.g. route name
should have expiration for data objects that are persisted in localStorage
should store (in memory) reference to model/collection objects for reuse
should be a singleton object
should provide strategy for data retrieval (memory/storage/api)
should store view state of page layouts
Layout manager specs
(presents, arranges, transitions and clears views)
should use ‘destination’ property for location on dom to show each view should keep track of current state for view objects, e.g. not-rendered, shown, hidden should lazy/load or render hidden (detached) views within a layout should have show and close methods to render and display a view should manage transition between views within a layout scheme should close (destroy) managed views/bindings within a layout (e.g. on transition to new layout) should have access to view’s deferred object (returned on render) for asynchronous display should have option to display layout when all views’ deferreds are resolved or as ready
Controller specs
(gets/sets application state, and delegates work to a layout manager object, used within route handlers)
should get data from application state manager object
should initialize views/models with relevant data received from application state manager
should call layout manager with arguments including relevant views/data
should send data to manager when route changes to store view state or dependent data
should receive data from view objects which publish change in view state
Views:
We studied many posts on the topic of Backbone.js and came up with a set of views that will support all the work we needed and provide some patterns to reuse in our application…
BaseView
, CollectionView
, SectionView
, LayoutView
(Manages Sections)
All the views extend the BaseView which extends the Backbone.View object.
Base View
A view object to construct a standard view with common properties and utilties The base view extends Backbone.View adding methods for resolving deferreds, rendering, decorating data just in time for rendering, adding child views to form a composite of views under one view object, add a destroy method.
Collection View
Manages rendering many views with a collection. The CollectionView extends BaseView and is intended for rendering a collection. A item view is required for rendering within each iteration over the models. This was a great source for the CollectionView: http://liquidmedia.ca/blog/2011/02/lib-js-part-3/
Section View
View object to track view’s state ‘not-rendered’, ‘rendered’, ‘not-displayed’, ‘displayed’; can be mixed in to another view type, e.g. CollectionView.
A section view is the required view object for a layout view which expects views to track their own state. This view may be extended as need. to use in a layout, perhaps adding the Section prototype properties to another view.
Layout Manager View
Presents, arranges, transitions and clears views
The layout manager has one or many views as well as document (DOM) destinations for each (rendered) view. A page may transition between many views, so the layout manager keeps track of view states, e.g. ‘not-rendered’, ‘rendered’, ‘not-displayed’, ‘displayed’.
The layout manager can lazy load and render (detached) views that a member is very likely to request, e.g. tab changes on events page. The transition between view states is managed by this object. An entire layout may be cleared so that view objects and their bindings are removed, preparing these objects for garbage collection (preventing memory leaks). The layout manager also communicates view state with controller(s).
Models:
BaseModel
, ApplicationState
Application State Model
A model object to manage state within the single-page application Attributes: {String} name
, {Object} data
, {String} storage
, {Date} expires
All the models extend the BaseModel which extends the Backbone.Model object.
Collections
BaseCollection
, ApplicationStates
Application State Collection
A collection object to reference various states in the application.
The application manager stores data in memory and also persists data in browser storage to provide a resource for common data/metadata. Also provides data (state) to reconstruct the page views based on previous interactions (e.g. selected tab, applied filters). The application state manager provides a strategy for resources to retrieve state.
All the collections extend the BaseCollection which extends the Backbone.Collection object.
Syncs:
syncFactory, application-state, storageFactory
We have a sync to use localStorage, sessionStorage, or a cookie using the same interface. The application state manager (collection) uses a specific sync object for the browser storage options.
Utils:
ajax-options, docCookies, debug, storage, shims, lib [checkType, duckTypeCheck, Channel (pub/sub), loadCss, formatCase, formatMoney]
We put any of our utilities into a library module.
Controller
A controller object should called within a route handler function, and may be responsible for getting relevant state (application models) to generate a page (layout), (also responsible for setting state when routes change). The controller passes dependent data (models/collections) and constructed view objects for a requested page to the layout manager. As a side-effect the use of controllers prevents the routes object from becoming bloated and tangled. A route should map to a controller which then kicks off the page view, keeping the route handling functions lean.
Facade
Vendor libraries and specific methods used in the framework are required in the facade, and referenced from the facade module in the views, models, collections, lib and other objects in the framework.
AMD - Asynchronous Module Definition
The the examples below show how we are using facade module as a dependency. RequireJS has various options for the syntax you can use to manage dependencies, below are a couple we use:
define(['facade','utils'], function (facade, utils) {
var ModuleName,
// References to objects nested in dependencies
Backbone = facade.Backbone,
$ = facade.$,
_ = facade._,
lib = utils.lib;
ModuleName = DO SOMETHING HERE
return ModuleName;
});
define(['require','facade','utils'], function (require) {
var ModuleName,
// Dependencies
facade = require('facade'),
utils = require('utils'),
// References to objects nested in dependencies
Backbone = facade.Backbone,
$ = facade.$,
_ = facade._,
lib = utils.lib;
ModuleName = DO SOMETHING HERE
return ModuleName;
});
References:
* AMD spec
* RequireJS why AMD
* RequireJS AMD
Docs
View docs on the demo site, hosted on Heroku at:
http://vertebrae-framework.herokuapp.com/docs/
http://vertebrae-framework.herokuapp.com/models/docs/
http://vertebrae-framework.herokuapp.com/collections/docs/
http://vertebrae-framework.herokuapp.com/syncs/docs/
http://vertebrae-framework.herokuapp.com/utils/docs/
http://vertebrae-framework.herokuapp.com/views/docs/