Knockout.js is a rich JavaScript MVVM framework for data binding HTML elements to JavaScript view models, auto UI updates, dependency tracking and templating.
For a Silverlight or WPF developer this is just what we’ve wanted to be able to unit test our presentation layer (the View Model) outside of the UI.
MVVM
Overview
Example of an implementation of MVVM
Knockout.js
A simple Knockout.js example
Live demo: http://c9.io/stevenh77/knockoutdemo/workspace/demo1.html
In this example we will be:
Diagram | Steps |
| 1. Creating a view model in JavaScript called viewModel (see diagram). |
2. Three of the properties will be ko.observables which enables data binding: |
firstName, familyName and fullNameVisible |
3. firstname and familyName bound to textboxes. |
4. Enforce the update of the viewModel properties after every key press by adding: |
valueUpdate: 'afterkeydown' to the data-bind section of the textboxes |
5. Binding the checked property of the checkbox to fullNameVisible along with the label.visible property. |
6. Create a fourth dependentObservable property called fullName, which is dependent on firstName |
and familyName and databound to “Hello {fullName}” label. |
7. Bind the changeName function to the Change Name button, which updates firstName and familyName. |
<!DOCTYPE html>
<html>
<head>
<script src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.0.0rc.debug.js" type="text/javascript"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
var viewModel = {
firstName: ko.observable("Bob"),
familyName: ko.observable("Smith"),
changeName: function () {
this.firstName("Joe");
this.familyName("Bloggs");
},
fullNameVisible: ko.observable(true),
};
viewModel.fullName = ko.dependentObservable(function() {
return this.firstName() + " " + this.familyName();
}, viewModel);
ko.applyBindings(viewModel);
});
</script>
</head>
<body>
<div>Stockbroker:
<input type="text" data-bind="value: firstName, valueUpdate: 'afterkeydown'" />
<input type="text" data-bind="value: familyName, valueUpdate: 'afterkeydown'" />
<input type="checkbox" data-bind="checked: fullNameVisible" />
<span>Hello </span><span data-bind="text: fullName, visible: fullNameVisible"></span>
<button data-bind="click: changeName">Change name</button>
</div>
</body>
</html>
Observable Arrays
Here is an example showing lists of data with validating input controls
Live demo: http://c9.io/stevenh77/knockoutdemo/workspace/demo2.html
<!DOCTYPE html>
<html>
<head>
<script src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.0.0rc.debug.js" type="text/javascript"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js" type="text/javascript"></script>
<script src="http://ajax.microsoft.com/ajax/jQuery.Validate/1.6/jQuery.Validate.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
var TradeModel = function(trades) {
var self = this;
self.trades = ko.observableArray(trades);
self.addTrade = function() {
self.trades.push({
ticker: "",
price: ""
});
};
self.removeTrade = function(trade) {
self.trades.remove(trade);
};
self.save = function(form) {
alert("Could now transmit to server: " + ko.utils.stringifyJson(self.trades));
// To actually transmit to server as a regular form post, write this: ko.utils.postJson($("form")[0], self.gifts);
};
};
var viewModel = new TradeModel([
{ ticker: "MSFT", price: "39.95"},
{ ticker: "AAPL", price: "120.00"}
]);
ko.applyBindings(viewModel);
// Activate jQuery Validation
$("form").validate({ submitHandler: viewModel.save });
});
</script>
</head>
<body>
<form action='/someServerSideHandler'>
<p>You have entered <span data-bind='text: trades().length'> </span> trade(s)</p>
<table data-bind='visible: trades().length > 0'>
<thead>
<tr>
<th>Ticker</th>
<th>Price</th>
<th />
</tr>
</thead>
<tbody data-bind='foreach: trades'>
<tr>
<td><input placeholder='Ticker' class='required' data-bind='value: ticker, uniqueName: true' /></td>
<td><input placeholder='Price' class='required number' data-bind='value: price, uniqueName: true' /></td>
<td><a href='#' data-bind='click: $root.removeTrade'>Delete</a></td>
</tr>
</tbody>
</table>
<button data-bind='click: addTrade'>Add Trade</button>
<button data-bind='enable: trades().length > 0' type='submit'>Submit</button>
</form>
</body>
</html>
Bindings
Built in binding
These bindings come out of the box
Text and Appearance |
visible, text, html, css, style, attr |
Forms |
Click, Event, Submit, Enable, Disable, Value, Checked, Options/Selected options (for dropdown or multi select) |
Control Flow |
if, ifnot, foreach, with |
Template |
Old: Traditional JavaScript template (<script>) New: Inline, template less, comment based syntax |
Custom bindings
Sometimes it’s preferred to use custom bindings over the built in bindings as it can keep your code more reusable and succinct.
ko.bindingHandlers.yourBindingName = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
// This will be called when the binding is first applied to an element
// Set up any initial state, event handlers, etc. here
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
// This will be called once when the binding is first applied to an element,
// and again whenever the associated observable changes value.
// Update the DOM element based on the supplied values here.
}
};
KO Utilities
Knockout protected observable: Allows you to commit or undo changes to your observable data item, sweet!
Data features
You can use the built in Knockout functions to convert to |JavaScript and JSON: ko.ToJS() and ko.ToJSON()
var viewModel = {
firstName : ko.observable("Bert"),
lastName : ko.observable("Smith"),
pets : ko.observableArray(["Cat", "Dog", "Fish"]),
type : "Customer"
};
viewModel.hasALotOfPets = ko.computed(function() {
return this.pets().length > 2
}, viewModel)
var jsonData = ko.toJSON(viewModel);
// Result: jsonData is now a string equal to the following value
// '{"firstName":"Bert","lastName":"Smith","pets":["Cat","Dog","Fish"],"type":"Customer","hasALotOfPets":true}'
var plainJs = ko.toJS(viewModel);
// Result: plainJS is now a plain JavaScript object in which nothing is observable. It's just data.
// The object is equivalent to the following:
// {
// firstName: "Bert",
// lastName: "Smith",
// pets: ["Cat","Dog","Fish"],
// type: "Customer",
// hasALotOfPets: true
// }
Loading and Saving JSON Data
$.getJSON("/some/url", function(data) {
// Now use this data to update your view models,
// and Knockout will update your UI automatically
})
var data = /* Your data in JSON format - see below */;
$.post("/some/url", data, function(returnedData) {
// This callback is executed if the post was successful
})
See the knockout documentation for examples on loading and saving JSON data.
http://knockoutjs.com/documentation/json-data.html