One of the hallmarks of a great web experience is the ability to retrace where you’ve been, either through use of the brower’s “back” button or returning to a previously viewed page.  It’s a simple html concept, but it can be challenging to implement in a single-page application as big as ours. At Personal Capital, we use Backbone.js as the foundation for our web application and it’s given us the ability to deliver a ton of feature-rich pages very quickly.  But when it comes to the tracking and retracing view states, Backbone.js only gives us the tools, not a framework to work with.

Routes, Query Params, and Internal Variables

There are three ways in which we can feed the state settings to our Backbone views:

  • Url Paths – Out of the box, Backbone gives us a router class that maps url paths to our client-side page views.  In our single-page application, we’ve segmented our Backbone views into sections, sub-sections, and sub-pages so that they mimic the feeling of a conventional site.  Using url paths helps to inform the user about the organization of our site.  The “portfolio” and “advisor” sections are good examples of where we nest multiple levels of sub-sections and sub-views within a section.
  • Query Params – We use plug-in called “backbone-query-parameters” to abstract query strings into our application.  Many of our pages/views are often data driven by a combination of factors, e.g. selected chart in Account Details with custom date range, and so sometimes it makes sense to expose these values to the url address bar so the state can easily be recreated and bookmarked.  Query params is also a great way to feed multiple state settings to the Backbone view without having to worry about their ordinal position.
  • Internal Variables – Values that we do not want to expose to the address bar and are only good for a single session are stored only in memory.  Our Account Details pages make use of internal variables, such as date range, to track and restore the state of the Backbone view when the user navigates between accounts.

Using any combination of these three options gives us complete flexibility in setting the state of our Backbone views.  But without a framework, it is difficult to implement state management consistently across a large-scale application such as ours.

View State Model Solution

From the standpoint of a Backbone view, it shouldn’t matter how state settings are provided, e.g. url paths, query params, or internal variables.  Wouldn’t it be nice if the settings provided all at once, say in a value object?

State Model

At Personal Capital, we built a state management framework, that stores state settings in a value object, which we call the “state model”, and it persists in memory through out the duration of a session.  The model is segmented into the three kinds of state tracking mechanisms and looks like this:

function CashFlowState () {
this.baseUrl = '/cash-flow';
this.path = ''
this.upStreamPath = '';

this.optionalUrlParams = [];
this.internalStateParams = [‘startDate’, ‘endDate’, ‘userAccountIds’];
this.userAccountIds = ‘all’;

The three aspects of our state model are:

  • Url Path – Comprised of the following properties:
    • this.baseUrl – Used to establish the base hash fragment that corresponds to the top-level sections of our Backbone application.
    • this.path – This is set by router.processRouteParams() when the url address bar changes.
    • this.upStreamPath – Used to give context to sub-views in relation to this.path.
    • this.optionalUrlParams – An array of name that correspond to what queryString variables a section expects.   As indicated in the name, these values are optional and are only defined at runtime as a property within the state model when there’s corresponding query string.
    • this.internalStateParams – An array of property names, which correspond to what state values are being track internally in our Backbone application.

The state model’s role is to be the source of truth for any changes to a section’s state settings, which can be done through either a change to the url address bar or from within our Backbone application.

Other System Actors

There three other main actors that make this whole system work:

  • processRouteParams() in the router – This method takes changes that were made to the url address bar and updates the section’s state model.  The updated state model and then passed into corresponding the section, and if appropriate, is passed downstream into the section’s child sub-sections and sub-views.
  • State datastore – Our state models are kept and referenced in a plain js object, just a like a dictionary. We use an ID convention for the property name in the datastore to reference the state model. As an example, our state model for our Portfolio section would be referenced from the state datastore as “datasource[‘portfolio’]”
  • Backbone.view extensions – Methods added to the prototype provide the following state management functionalities:
    • Backbone.View.prototype.updateView – A public method used to provide a consistent way to send state changes to the view.
    • Backbone.View.prototype.trackViewState – Responsible for storing changes in the views and synchronizing changes between the state of the application and the url address bar.
    • Backbone.View.prototype.saveInternalState – Saves view changes that are meant only for internal storage, which is used as part of the state restoration when the section is viewed.
    • Backbone.View.prototype.pathableSubViews – An array used to register this section’s sub-sections and sub-views that can react to changes to url paths.  Each element in the array is an object with the following properties:
      • nodeName – A string representing the node in the path for the corresponding sub-section or sub-view
      • subView – The Backbone sub-section or sub-view
      • Example – this.pathableSubView[0] = {subviewName: ‘subView1’, subview: SubView}
      • So when the path string is ‘subView1/subSubView1’, we can see that ‘subView1’ has a mapping to SubView
  • Backbone.View.prototype.processPathForSubView – Clones the state model, removes the 1st element in this.path string and places it in this.upstreamPath.

With this system in place, we now have a highly scalable way to manage states amongst our Backbone views.  It’s still a relative young piece of technology for us, so we welcome your thoughts and suggestions.