IT:AD:Knockout:HowTo:ClientSide View Markup
Summary
One of the value propositions of Knockout is that the binding is elegantly done using markup, rather than a whole bunch of js/jquery wiring.
It works by publishing a JS ViewModel (initially embedded/rendered in the page, subsequently updated by JSON and or binding from form values), and then binding that to DOM elements.
Once you have control over the ViewModel's shape you can control the UI layout precisely, only having to work out:
- rendering display/edit values to conditionally switch back and forth
- client side validation
- data postback
- server server validation
Process
Use html markup to bind the properties of UI elements back to the ViewModel properties.
Value-Binding: Basic Syntax
Use the data-bind attribute, containing the current element's property (text, value, background-color, etc), a colon, then the ViewModel's property:
//Binding the span's text property to the model's first property: <p>First name: <span data-bind="text:first"/> </p> //Or an `input` element's `value` property: <p>First name: <input data-bind="value:first"/></p>
Value-Binding: ko.observable
Changing the input's value is updating the ViewModel property – but unless the ViewModel property is a ko.objservable, it's just a string, and cannot re-broadcast the chanages back to the UI.
<p>First name: <input data-bind="value:first"/></p> <p>Result: <span data-bind="value:first"/></p>
bound to:
var viewModel {
...
first:ko.observable("smith")
}
causes changes to the input value to update the model, which in turn immediately broadcasts/updates the the span… Nice.
Value-Binding: SubProperties
The data-bind property is parsed…so using dot syntax to get sub properties of the target viewModel property is valid as well:
<span data-bind="text:firstName.length"/>
Checked-Binding & Checkboxes
As usual with HTML, checkboxes are different, and you don't work with value, but checked:
<input type="checkbox" data-bind="checked:isRich"/>
Note that changes set the viewModel to true/false (I don't see anything handing tri-state values).
Checked-Binding & Radio Buttons
You use Checked-Binding with Radio buttons as well, but instead of binding to a boolean, you bind to a string, that ties back to the radio element's value:
<input type="radio" name="SOM" data-bind="checked:stateOfMind" value="sober"/> <input type="radio" name="SOM" data-bind="checked:stateOfMind" value="drunk"/>
Binding back to:
var ViewModel {
isRich:false
stateOfMind:ko.observable("drunk")
}
Note:
- It's so easy to get frustrated by not recognizing that you are not setting checked to a boolean, but a string that matches the
valueon theinputelement.
Binding-Modifiers: valueUpdate
The basic binding syntax is data-bind:"value:first", and that updates by watching the input element's change event. To get it to update on every keystroke, use valueUpdate modifier:
<input type="text" data-bind="value:firstName, valueUpdate: 'afterkeydown'" />
Binding Modifiers: enable & disable
Another aspect of IT:AD:Knockout.JS that is very useful is enabling form elements due to other conditions.
The following only enables the input box when the checkbox is checked:
<p>
<input type='checkbox' data-bind="checked: hasCellphone" />
I have a cellphone
</p>
<p>
Your cellphone number:
<input type='text' data-bind="value: cellphoneNumber, enable: hasCellphone" />
</p>
<script type="text/javascript">
var viewModel = {
hasCellphone : ko.observable(false),
cellphoneNumber: ""
};
</script>
Binding Modifiers: visible
**Hugely Useful**
You can hide/show hidden fields by binding the visible keyword:
<callout icon="true" type="data-bind="visible:shouldShowXtraFields">
<input type="text" data-bind="enable:shouldShowXtraFields" data-bind="value:someField"/">
</callout>
Or simpler, using the if keyword:
<callout icon="true" type="data-bind="if:shouldShowXtraFields">
<input type="text" data-bind="value:someField"/">
</callout>
See: Markup
An important difference between the two approaches is that `if` removes the elements from the page, whereas visible just makes them not appear.
Binding-Modifiers: isSelected
Can be used to set focus to an element (eg. If 'Have Motorcycle?' is selected, can set focus to 'Have Babe as well?').
See http://knockoutjs.com/documentation/hasfocus-binding.html
Binding to Select Options
Binding to Options using data-bind:options is specific to Options (not the same 'foreach' described below)
//Binding to a simple array: <select data-bind="options:transportOptions"/> //Binding to a complex array for text/value: <select data-bind="options:transportOptions, optionsText: 'name', optionsValue:'id'"/> //Binding to a complex array, with a dummy prefix option: <select data-bind="options:transportOptions, optionsText: 'name', optionsValue:'id', optionsCaption:'Pick us a winner...'" /> //Binding to a complex array, setting a value, with a dummy prefix option: <select data-bind="options:transportOptions, optionsText: 'name', optionsValue:'id',value:transport, optionsCaption:'Pick us a winner...', " />
Binding to Multiple Select Options
Different binding (data-bind:options rather than data-bind:option)
Binding to Arrays: foreach
<ul data-bind='foreach:vehicleTypes'> <li><span data-bind="text:$data"/></li> </ul>
Binding to:
var viewModel {
vehicleType: ko.observable(['skateboard','bike','hoverboard','car','jet-fighter'])
}
* See: Markup
Events: click-binding
Binding to data, and updating the ViewModel's properties is all nice, but there's a time for Action…. Good news: it's very easy.
You can call methods in the ViewModel using click binding:
<button data-bind="click: incrementClickCounter">Click me</button>
* See: Markup
Events: submit-binding
<form data-bind="submit: submitByAjax"> .... <button type="submit"/> </form>
Note:
- It intercepts and cancel's the default postback
- that's good, as you generally want the form to update the ViewModel, not postback and lose the page.
- If you do want to continue with postback, make your eventHandler return
true. - Why not use
click? BecauseSubmitresponds to form-specific keyboard events that one would have to emulate if usingclick.
Markup Examples
Examples:
//Text: <input type="text" data-bind="value:firstName" /> //adding 'valueUpdate:afterkeydown' makes it pick up more than the default 'change': <input type="text" data-bind="value:firstName, valueUpdate: 'afterkeydown'" /> //Password: <input type="password" data-bind="value:password" /> //TextArea: <textarea type="text" data-bind="value:lifeStory"></textarea> //Checkbox (note use checked, not value): <input type="checkbox" data-bind="checked:isRich" /> //Radio (note: all have same 'name', and bind to a string value, even though using 'checked' keyword): <input type="checkbox" name="SOM" data-bind="checked:stateOfMind" value="Sober"/> <input type="checkbox" name="SOM" data-bind="checked:stateOfMind" value="Drunk"/> //Select Options (binding to a complex array, with a dummy prefix option): <select data-bind="options:transportOptions, optionsText: 'name', optionsValue:'id', optionsCaption:'Pick us a winner...'" />
Computed Properties
var viewModel {
var first:ko.observable('John'),
var last:ko.observable('Smith'),
this.fullName = ko.computed(function() {
return this.firstName() + " " + this.lastName();
}, this);
}
Note:
N
ote that the term is ko.computed, not ko.observable
…and – as usual – watch out for 'this' (replace with self)
If you want to pass arguments, you have to do it an alternate way:
model.computedMe2 = function (name) {
return model.FirstName() + " " + model.LastName() + name
}.bind(model);
Noice the bind...see: [http://jsfiddle.net/2pB9Y/41/](http://jsfiddle.net/2pB9Y/41/)
YOu can even have read/write Computed Propertes. See http://knockoutjs.com/documentation/computedObservables.html