The data-bind syntax
Knockout takes advantage of the HTML5 data-*
attribute specification to define its data-bind
attribute. Though all HTML attributes are necessarily strings, Knockout parses them as name:value pairs. The name refers to the binding handler to be used and the value refers to the value the binding will use:
<button data-bind="enable: canSave">Save</button>
The data-bind
attribute can also contain multiple bindings separated by commas. This allows multiple properties to be bound on an element:
<input data-bind="value: firstName, enable: canEdit" />
In the preceding example, the enable binding uses canEdit
as a value. The binding will set the disabled
attribute on the button element when canEdit
is false
, and remove the disabled
attribute when canEdit
is true
. If canEdit
is an observable, the enable binding will update whenever canEdit
is updated. If canEdit
is a literal value, such as true
, it will only use the value to set the initial state.
Enable is a one-way binding; it will update the element with changes from the value but it will not update the value with changes from the element. This is because when enable is being used to control the element, Knockout assumes that nothing will be programmatically updating the element. Updates should happen in the viewmodel, and binding handlers should be responsible for ensuring the view is kept in sync.
When users update the UI of data-bound input elements, those changes need to be synced to the viewmodel. This is done with two-way bindings,
such as the value
binding:
<input data-bind="value: firstName" />
This binding will set the initial value of the input
element to the current value of the firstName
property, and after that, it will ensure that any changes to either the element's value or the property cause the other to update. If the user types something into the input, the firstName
property will receive the value. If the firstName
property is updated programmatically, the input's value will be updated.
These are both examples of binding against a simple property on the viewmodel. This is the most common case, but Knockout supports more complex scenarios as well.
Note
For a complete list of the standard Knockout binding handlers, see the Knockout documentation (http://knockoutjs.com/documentation/introduction.html).
Binding with nested properties
In the previous example, Knockout parsed the binding value for the name of a property and looked for that property on the current viewmodel. You can also provide deep property references. Consider the following object:
var viewmodel = { user: { firstName: ko.observable('Tim'), age: ko.observable(27) } };
We can bind directly against the firstName
property of the viewmodel's user by using standard dot notation:
<input data-bind="value: user.firstName" />
Binding against functions
If you are using the click
or event
bindings to bind some UI event, the binding expects the property to be a function. Functions will receive the current model (the binding context) as their first parameter, and the JavaScript event as the second parameter (though you shouldn't need to do this very often).
In this example, the parent viewmodel receives the contact to be removed from the click
binding because the foreach
loop creates a nested binding context for each contact. The parent reference in the binding moves the context up to the parent viewmodel to get access to the remove function:
<ul data-bind="foreach: contacts"> <li> <span data-bind="text: name"></span> <button data-bind="click: $parent.remove">Remove</button> </li> </ul> var ViewModel = function() { var self = this; self.contacts = ko.observableArray([{ name: 'Tim' }, { name: 'Bob' }]); self.remove = function (contact) { self.contacts.remove(contact); }; };
Binding with expressions
In addition to property references, Knockout also supports the use of JavaScript expressions as binding values. For bindings that expect true or false values, such as enable, we can use Boolean expressions to set them:
<button data-bind="enable: age > 18">Approve</button>
We can also use ternary expressions to control the result of the expression. This is useful in cases where Booleans are not expected, such as text bindings:
Old enough to Drink in the U.S. <span data-bind="text: age > 18 ? 'Yes' : 'No'"></span>
Now the span
will have Yes
as content.
Both forms of expressions will use dependency tracking to rerun if they read from an observable the first time they are run. If age
was an observable value, we could update it and the element's binding would re-evaluate the expression, changing the text or enabled state if the result changed.
Binding with function expressions
The last method to set binding values is by using functions. You can call a function by referencing it in the binding:
<button data-bind="enable: canApprove(age)">Approve</button>
You can also write an anonymous function as a string directly in the binding. When creating a function for the click
binding, the parameters are the binding context (viewmodel) and the JavaScript click
event. If you bind against a viewmodel function using its property name, it would receive the same parameters:
<button data-bind="text: function(data) { console.log(data.age) }">Log Age</button>
Though this is possible, I wouldn't encourage it. It places logic directly in the view instead of in the viewmodel where it belongs. You should only use this last method in very special cases. It's much better to place the method on the viewmodel and just use a property reference.
Using parentheses in bindings
It can be confusing trying to figure out when to use parentheses in bindings to use an observable as a value. Knockout tries to be helpful by not requiring the parentheses in simple binding expressions like this one:
<input data-bind="value: firstName" />
In this example, the firstName
property could be either an observable or a literal value, and it would work just fine. However, there are two cases when the parentheses are needed in bindings: when binding against a nested property and when binding with an expression. Consider the following viewmodel:
var viewmodel = { user: ko.observable({ firstName: ko.observable('Tim'), age: ko.observable(27) }) };
The user object here is an observable property, as are each of its properties. If we wanted to write the same binding now, it would need to include parentheses on the user
function but still not on the firstName
property:
<input data-bind="value: user().firstName" />
In cases where we are binding directly against a property, the parentheses of that property are never needed. This is because Knockout is smart enough to understand how to access the value of the observable that it is given in bindings.
However, if we are binding against an expression, they are always needed:
<button data-bind="enable: user().age > 18">Approve</button> <button data-bind="enable: user().age() > 18">Approve</button>
Neither of these bindings will cause errors, but the first one will not work as expected. This is because the first expression will try to evaluate on the age
observable itself (which is a function, not a number) instead of the observable's value. The second one correctly compares the value of the observable to 18
, producing the expected result.
Debugging with ko.toJSON
Because ko.toJSON
accepts the spaces argument for JSON.stringify
, you can use it in a text binding to get a live copy of your viewmodel with nice, readable formatting:
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
The cp1-databind
branch has an interactive example of each of these bindings.