FireBind: Knockout bindings for Firebase
Tim Kye
Everybody loves Knockout (or they should, because it's fantastic). I think less people know about Firebase, but its a very cool service. It basically provides a real-time backend for persistent data, along with either client libraries or a REST api for subscribing to the changes in that data.
When I first found out about Firebase, I had already been working on a personal project that had been inspired by Meteor which had a similar cross-client-real-time-data backend. It isn't nearly as clean as Firebase, but then it was something I hand-rolled with NodeJS and Socket.IO. Luckily, the Knockout bindings I had written for this were pretty easily adapted to using Firebase instead, since they worked similarly.
So I made FireBind.
It adds two constructor's to the ko
object. One for array's, which Firebase would call a set, and one for model's, which Firebase would call a location.
FireModels
ko.fireModel(context, propertyMap, firebaseRef)
ko.fireModel
creates an object whose properties are all computed
observables that intercept all writes. The writes are passed to the firebase api. It also has an internal handler for the value
event for each property. Since local changes are sent through the Firebase api, local and remote writes are indistuingishable to the client. Luckily, Firebase implements latency compensation, so local writes will still appear instantly, and be reversed if Firebase rejects them.
The method add's the properties in the propertyMap
object to the context
object, using the values of the propertyMap
as the default values. It is meant to be run on an object while inside of that object's constructor.
The firebase reference is the reference to the collection. If you've used Firebase you will know how to get this, but if not just take a look at the example fiddle at the end of the post.
FireSets
ko.fireSet(firebaseRef, ChildConstructor, config)
ko.fireSet
returns an observableArray
whose methods have all been replaced with ones that instead write to a Firebase set. It also adds handlers for the Firebase collection for the child_added
, child_removed
and child_moved
events. These handlers will write to the underlying array. Just like the FireModels, all local writes are sent to the firebase api, and handlers take care of updating the underlying array.
The ChildConstructor
is a constructor that will be used to create each child item inside the child_added
handler. The constructor will be called with three parameters: the id
(or "name", in firebase lingo) of the child, a data
object representing the other values of the child, and the firebaseRef
for the child.
The config
object has two optional properties that control how the set works. idProperty
, which defauls to 'id'
, tells the set which property on it's children should be used for the firebase location "name".
The orderBy
property controls how the firebase priority will be set. If you leave it out, the priority will be set as an ascending integer. This will keep things ordered chronologically, but there isn't any transactional control at the moment. If you specify a property, that property of the child objects will be used to set the item priorirty. If you do this, the reverse
and move
will throw when you call them, since you cannot try to modify the order when it's set by the value of children's properties. Sort
will no-op in this case.
splice
A note on The current version of Firebind (v0.5.1 at time of writing) does not suppor the splice
method on firesets. If you need to move items inside the set, there is a move
method, that takes the current index of the item you want to move, and the index you want to move it to, as arguments.
Putting them together
This JsFiddle shows a simple example of both of these together, to form a recursively bound tree of Users (don't think to hard about a real use case here, it's a demo).
As you can see, FireModels are a little more complicated to construct that FireSets. One unfortunate consequence of FIrebase's implementation is that empty properties are not given to the client, so local model's must always list them out in the propertyMap
, instead of just passing the data
the constructor gets directly to ko.fireModel
.
While writing this guide, I thought of several ways I could improve Firebind, and I think I will have some time after the holiday season to work on it. I want to simplify the api, and hopefully make it a little more flexible with the constructors.