Over the past few years there has been a surge in the number of client-side JavaScript frameworks. Currently, Backbone gets a lot of the attention, and rightly so, it's a great framework. In this post though, I want to demonstrate a use case where I believe SproutCore 2 excels — building interactive web apps.
Let's start by describing what I mean when I say 'interactive' web apps. I'm talking about applications where user interaction repeatedly causes lots of elements to be updated, where multiple different elements interact and change the state of each other, and where keeping track of all the necessary view updates becomes a pain.
The reason that SproutCore excels in this use case is because of the way it handles view updates. When any data changes, it is the responsibility of the framework to update the views and it does this automatically. I find that this encourages a real data driven approach to building apps — you take care of updating the data and the framework takes care of updating the views.
SproutCore achieves this view magic by utilising the concept of bindings.
Bindings
Bindings are a way of keeping data synchronised. Simply put, when two properties are bound together, they are connected; a change to either one of the properties will automatically be reflected in the other. While this method of data synchronisation is not present in JavaScript by default, SproutCore introduces it with its object model.
SproutCore introduces a new base object SC.Object which includes a number of new features such as bindings. The example below shows two SC.Objects that have a bound property.
var App = SC.Application.create()
App.mr = SC.Object.create({
name: 'Mr X',
maritalStatus: 'Married'
})
App.mrs = SC.Object.create({
name: 'Mrs X',
// This is how bindings are created in SproutCore,
// pass the property name suffixed with 'Binding'
// and a string that references the property you
// are binding to
maritalStatusBinding: 'App.mr.maritalStatus'
})
App.mrs.get('maritalStatus') // => Married
// The property has been synced
App.mr.set('maritalStatus','Single') // Set Mr to single
App.mrs.get('maritalStatus') // => Single
// The property has been synced again
The example shows how you can bind properties together and in effect build relationships between objects. Building relationships using bindings in an incredibly useful way to propagate data changes throughout your application.
SproutCore view system
The SproutCore view system is a good demonstration of the power of bindings. Each view has a binding to an underlying data object (which in turn can have bindings to other objects). The binding means that whenever the underlying data changes, the view automatically updates to reflect the change.
When you build an application using the SproutCore view system, the responsibility for triggering view updates is moved up to the framework level. I find that this encourages a data driven approach to building applications.
Data driven applications
The example below is taken from our vehicle tracking product, Sensor. It shows a summary of the movement of a vehicle for a particular day.
In this demo, there are three main sections:
- A graphical representation of the speed of the vehicle throughout the day.
- A textual list of the journeys and stops that the vehicle made.
- A map showing each of the journeys and stops on the map.
Each of these sections is a different view of the same underlying data stored in the client side data model. The data model is essentially just a list of the journeys and stops that the vehicle has made.
Each of the three views in the demo are independent and have no knowledge of one another. However, if you mouse over the demo, you'll see that whenever a journey is highlighted in one representation, it is highlighted in the other two representations.
This state synchronisation is achieved because each of these views have bindings to the same underlying data and we use the interaction events to trigger changes in this data. Whenever the data changes, each of the views are updated.
For example, when an hovering over an item in the textual journey list, we set the hover property to true on the corresponding journey object.
Sensor.Views.JourneyListItem = SC.View.extend({
mouseEnter: function(){
// Get the journey object associated with the
// view and set it's hover property
this.get('journey').set('hover',true);
},
mouseEnter: function(){
this.get('journey').set('hover',false);
}
//... Rest of the view code omitted
})
The textual journey list does not need to know about the speed graph view or the map view. The speed graph and the map have bindings to the same underlying journey object that the textual view is manipulating. Whenever the journey object is changed, all the views update. The result is three views that appear to all interact with each other but, in fact, are each just responding to the underlying object they are bound to.
Using this technique, adding a new interacting view of the data is trivial. You can just create a new view, bind it to the same object and it will automatically interact with the other views, highlighting the correct journeys on hover.
Live updates
In Sensor all of our screens update live as the vehicles are being driven. We use websockets to stream the live updates to the browser where the underlying data is updated. As this live data is streamed in, all of the views update accordingly — the paths on the map update in real time, the speed graph updates and new journeys are added to the list.
Developing in SproutCore takes away the need to update and re-render views, and the focus shifts entirely to managing and updating the client side data model. This is why I call it a data driven approach to building applications.
