simple unstackable one-way Cocoa-like bindings for Javascript

It’s funny how everyday English terms are co-opted to mean specific things in various specialties. You wind up saying bizarre yet perfectly justifiable things like “unstackable Cocoa-like bindings.” Cocoa? Java? Beverages? Snowboards? Huh? I guess if your bindings are like cocoa, they wouldn’t stack very well now would they?

So using closures and Javascript’s Object.watch method, it’s pretty easy to set up Cocoa-style property bindings in Javascript.

The general idea—the model-view or model-view-controller design pattern—is that you have a data model, and you have an independent view object that represents a property in the data model. When the property in the data model changes, you generally always want the view object to redraw itself. If you’re willing to let your data model know about your specific views, you can just have the data model tickle the view directly, but it’s better to isolate the model and views. Models shouldn’t care how they’re being presented, and views shouldn’t care where their data is coming from.

Cocoa has some nifty binding technology to help with this. In short, Cocoa bindings give all NSObjects the ability to bind the value of one object’s member to the value of another object’s member. This is particularly nice in interface design, since Interface Builder allows you to set up bindings in the view layout itself. There is also a programmatic way to set up bindings in the code, using the message:

[ bind:toObject:withKeyPath:options: ]

So yesterday I was working on version 2 of my iPhone tip calculator app and decided, for the sake of exercising Javascript, to make it as traditionally app-like as possible, with a model, and views, and reducing its operation to just the result of a set of objects talking to each other; the whole thing.

Anyway, here’s how to get simple, unstackable, one-way Cocoa-like bindings for your Javascript objects:

Object.prototype.bind = function(myProperty, toObject, withProperty, callback) {
	if (!this.bindings) {
		this.bindings = new Object;
	}

	var boundObj = this;
	this.bindings[myProperty] = {
		object: toObject,
		property: withProperty,
		enforce: function(prop, oldValue, newValue) {
			if (myProperty != ”) {
				boundObj[myProperty] = newValue;
			}
			if (callback) {
				callback(prop, oldValue, newValue);
			}
			return newValue;
		}
	}
	toObject.watch(withProperty, this.bindings[myProperty].enforce);
}

Object.prototype.unbind = function(myProperty) {
	this.bindings[myProperty].object.unwatch(this.bindings[myProperty].property);
}

So let’s say you have a model object with a “bill total” property, model.mBillTotal, and you have a view object of class CurrencyDisplay, which formats and renders a number as currency. The view object, call it billTotalDisplay, keeps track of its own value in a property mValue. You can now do this in some appropriate place (ie, a “controller”):

billTotalDisplay.bind('mValue', model, 'mBillTotal');

When anything causes the model’s bill total to change, the view displaying that total will be updated automagically. OK, actually, that’s not true: the view’s mValue property will be updated automagically, but without further action, the view won’t be redrawn onscreen.

Let’s say that CurrencyDisplay views have a method called render which draws the display to the screen. You could use the binding’s callback argument to trigger the view’s redraw like this:

billTotalDisplay.bind('mValue', model, 'mBillTotal', billTotalDisplay.render);

But I would prefer to keep that sort of thing in-house with respect to the view. The controller shouldn’t necessarily care what the display does with its value; it only needs to know that this particular view’s value depends on this particular part of the data model. We can use Object.watch to trigger render() when the view’s value changes. In CurrencyDisplay’s constructor:

CurrencyDisplay = function() {
	...
	var self = this;
	this.watch('mValue', function(property, oldValue, newValue) {
		self.render();
		return newValue;
	});
	...
}

Now, anytime a CurrencyDisplay’s mValue changes, its render() will be invoked automagically. Anytime the model’s bill total changes, the right CurrencyDisplay’s mValue will be updated automagically. So, there you have it: update the model, the view redraws, and all you have to do is set up the binding in the controller. Simple one-way Cocoa-like bindings.

Note the line var self = this; above and var boundObj = this; in Object.prototype.bind. These are necessary to access the CurrencyDisplay or bound object from within the anonymous functions we pass to watch. When those functions are invoked, this refers to the functions themselves, and not to the CurrencyDisplay or bound object. On the other hand, the functions create closures around the scope of their creation, so self and boundObj will retain their value within the anonymous function, even when they’re only called much later, at runtime. Sexy.

Also note the big limitation on this: You can’t stack bindings. Calling watch() on a property replaces any previous “watchpoint.” In this example, only one display can depend on the value of model.mBillTotal at a time. Bummer.

Leave a Reply

You must be logged in to post a comment.