The JS model layer

This post was originally published on the New Bamboo blog, before New Bamboo joined thoughtbot in London.


Adding a Model layer to your Javascript

To build the best possible JS RIA, we need to build a data or model layer. This needs to be handled in a way which goes beyond simply embedding the data in the HTML, and also beyond relying on simple collections of hashes and arrays.

A web server needs to send data to the client, as part of an initial “platform” which the client can build up the UI and behaviour from. A client therefore holds onto and manages this snapshot of “state” which it has received from the server.

The lifecycle of these data objects need to be managed on the client end, persisting the state (when necessary) as part of the process. A “state manager” needs to work with collections of objects, which can be manipulated locally and bound to the UI.

js-model is our solution to this. It should feel straightforward and familiar to you - particularly if you’re used to ActiveRecord and its ilk.

Not another REST proxy

A really important point to note at the outset is that this is not a simple “proxy” to the “real” models on the server. It is not a glorified REST-client, and hence, retrieving objects is done from the constantly updated local collection, and not over HTTP.

JS-model is a library for dealing with models in your Javascript - a subtle difference from being a REST-client, and one that’s easy to overlook the importance of.

To put it another way, we need to appoint it responsibility for maintaining the state of objects for the duration of the page view.

Moving Beyond Arrays

Getting data into the browser isn’t difficult, especially with the prevalence of JSON (how did we live without it?!). But having the data itself is just the first step and beyond the most simple of applications things start to get increasingly unmanageable.

Persistence

By default JS model only persists objects within the context of the page being viewed. Perhaps you’re displaying data from a Twitter feed or maybe you’re sending and receiving data through a WebSocket (chat perhaps). In both cases you might not care about keeping the data beyond the page view but having the ability to work with models and collections is a convenience at the very least.

REST

As a design constraint, to persist using REST means explicitly adding a REST adaptor at the time of class creation.


var Project = Model("project", {
  persistence: Model.RestPersistence("/projects")
})

In the most common usage, you read data, manipulate it and send it back to the server using Ajax and JSON. In the old days we’d be tempted to send back snippets of rendered HTML back to an essentially dumb client (RJS) but nowadays that’s not good enough. We need data.

Our websites are evolving towards the level of desktop apps where an ever-increasing amount of functionality is performed in the browser. Our client-side code operates on this data reflecting changes in the view.

It would be equally possible to add local storage to your models instead. You can even mix and match between some data which need to only persist locally, and others which need to go via REST.

Convention-based event-triggering

There are common patterns to the lifecycle of data objects on the client side and these can be listened to by your UI. Objects are created, updated and destroyed, and these actions need to be reflected in the UI.

JS-model gives you hooks to link to adding and removing from a collection, and others linked to changes in state in individual objects. This becomes immensely powerful as a means of structuring UI behaviour.

Updating state from elsewhere

If state is held on the browser, there is a possibility that another user is making this state stale by interacting independently with the server. This highlights the increased complexity a manager of client objects needs to have.

We can alleviate the problem by adding websockets into the mix, and using an event based convention to update our JS models whenever necessary.

RTFM

Much of the detail has been glossed over in this post to allow for some bigger picture discussion. However, the documentation on Github has some more in-depth examples, which we will be expanding further going forward.