At the heart of Coherent is the process of binding values in your data model to views in your UI. But this can be rather confusing, since it’s an entirely different style from traditional web development.
There are two ways to establish bindings and each has its place. The first is straight programatic binding via calling Bindable#bindNameToObjectWithKeyPath. The second method relies on the parameters dictionary passed to the constructor of a Bindable object.
Programatic Bindings
The programatic method of establishing bindings is probably the simplest to understand. Call bindNameToObjectWithKeyPath on a view or controller to establish a binding. You’ll need to provide values for the name of the binding you wish to create, the object you wish to which you want to bind, and the keypath on the object that will be bound.
For example:
var view= new coherent.View('node-id');
var obj= new coherent.KVO();
view.bindNameToObjectWithKeyPath('text', obj, 'title');
This works pretty much as you’d expect: The implementation of bindNameToObjectWithKeyPath observes the key path title on obj and calls the setText method on view whenever a change occurs. The view could update the title property of obj, but that’s a topic for another time.
There’s nothing wrong with creating bindings programatically, but I prefer to establish them when I create the view.
Declarative Bindings
The constructor of all Bindable objects accepts a dictionary containing additional parameters for the newly created object. Bindings are a special case of parameters, any keys that end in Binding are assumed to be a binding definition. So the previous example can be rewritten as:
var view= new coherent.View('node-id', {
textBinding: 'obj.title'
});
But wait! There’s more! You can also specify options for the binding by changing the string key path into a dictionary1:
var view= new coherent.View('node-id', {
textBinding: {
keypath: 'obj.title',
transformer: 'truncated',
nullValuePlaceholder: 'No Title',
noSelectionPlaceholder: 'No Image',
multipleValuesPlaceholder: 'Multiple Images'
}
});
All entries in the dictionary are optional — except the keypath of course.
The placeholder entries provide values for the binding when the value of the key path represents a special marker value: null value (coherent.NullValueMarkerType), no selection (coherent.NoSelectionMarkerType), or multiple values (coherent.MultipleValuesMarkerType). These are typically only used when binding to the selection property of an ObjectController or ArrayController. The value of these entries may be of any type that will make sense to the view; no value need be provided if the default value specified by the view is acceptable.
The transformer entry is either the registered name of a ValueTransformer or an instance of a ValueTransformer. For example, if you’re not comfortable with the default truncating transformer, which truncates at 50 characters, you could create your own:
var view= new coherent.View('node-id', {
textBinding: {
keypath: 'obj.title',
transformer: new coherent.transformer.Truncate(20)
}
});
Sometimes you need to transform model values somewhat ad hoc, like when converting a model value into a class name. In those cases, you can specify a transformation method directly in the binding definition:
var view= new coherent.View('node-id', {
textBinding: {
keypath: 'obj.title',
transformedValue: function(value)
{
if ('string'!==typeof(value))
return value;
return value.replace(/[^aeiou]+/gi, '');
}
}
});
All About Context
There’s one important thing missing in the code samples from the previous section: the object value for bindNameToObjectWithKeyPath. The declarative method takes the value of object from the currently active context. There is always an active context. In many cases, it’s the global context, but there are also contexts for NIBs and the internal structure of views.
First, let’s make the example work with the global context:
var obj= new coherent.KVO();
coherent.registerModelWithName(obj, 'obj');
var view= new coherent.View('node-id', {
textBinding: 'obj.title'
});
The call to registerModelWithName wires up an object to the global context with a given name. Behind the scenes this is nothing more than a call to setValueForKey(obj, 'obj') on the global context.
NIB Context
The global context isn’t usually where bindings will be evaluated, however. Most bindings get evaluated in the context of a NIB. A NIB represents a collection of views and controllers for a specific chunk of your application. Each entry in the NIB is exposed in the active context.
In the following example, the NIB context will contain entries for gallery, data, and controller:
NIB({
'gallery': VIEW(NIB.asset('gallery.html'), {
':root': coherent.View({
visibleBinding: 'controller.arrangedObjects.@count'
}),
'img': coherent.Image({
srcBinding: 'controller.selection.href'
}),
'p': coherent.View({
textBinding: 'controller.selection.caption'
}),
'a.next': coherent.Anchor({
enabledBinding: 'controller.canSelectNext',
target: 'controller',
action: 'selectNext'
}),
'a.prev': coherent.Anchor({
enabledBinding: 'controller.canSelectPrevious',
target: 'controller',
action: 'selectPrevious'
})
}),
'data': NIB.asset('gallery.json'),
'controller': coherent.ArrayController({
contentBinding: 'data.photos'
}),
'owner': {
view: 'gallery'
}
});
In addition to the entries defined in the NIB, there are a number of special entries that represent objects external to the context: owner and application. The owner entry is usually an instance of coherent.ViewController and application is the shared coherent.Application instance. You can connect views and data objects to the owner or application by specifying the linkages in a dictionary associated with those keys.
View Context
In addition to NIB contexts, if you create subclasses of coherent.View, you’ll probably encounter View contexts. During initialisation of a view and its children, the context is set to the containing view. This allows child views to access properties of their parent.
For example:
views.MyView= Class.create(coherent.View, {
title: coherent.View({
textBinding: 'representedObject.title'
})
});
Then later, you can do the following:
var view= new views.MyView('my-view-id');
var obj= new coherent.KVO();
view.setValueForKey(obj, 'representedObject');
Template Context
The final context you’re likely to encounter is the template context created for each item in coherent.CollectionView#newItemForRepresentedObject. This is something of a special case, because unlike the other contexts, the template context contains all the entries from the context used to create the CollectionView plus an entry (representedObject) for the current item in the collection.
-
Actually, you may pass a dictionary as the
keypathparameter to bindNameToObjectWithKeyPath. That’s exactly what’s happening under the covers with the declarative syntax. ↩