Knockout pre-rendered binding handlers
init
: initialize an observable to a value retrieved from existing HTML content.
foreachInit
: wraps theforeach
binding, with the observable array's elements bound to existing HTML elements.
Init binding
Suppose we have the following view model: ```javascript function ViewModel() { this.name = ko.observable(); } ``` Normally, you'd bind thename
observable to an HTML element as follows:
```html
Michael Jordan
```
Once the binding has been applied however, the text within the <span>
element will be cleared, as the bound observable did not have a value (existing HTML content is ignored).
We can fix this by specifying the init
binding handler before the text
binding handler:
```html
Michael Jordan
```
Now, the text within the <span>
element is left unchanged. This is due to the init
binding handler setting the observable's value to the text content of the bound element. As Knockout binding handlers are executed left-to-right, when the text
binding executes the init
binding will already have initialized the observable.
You can combine the init
handler with any binding, as long as you ensure that it is listed before the other bindings:
```html
Michael Jordan
```
Converting
By default, theinit
binding will set the observable's value to a string. If you want to convert to a different type, you can specify a convert function:
```html
198
```
Now, the observable's value will be set to what the convert
function, with the innerText
value as its parameter, returns.
Custom conversion
It is also possible to use your own, custom conversion function. You could, for example, define it in your view model: ```javascript function CustomConvertViewModel() { this.dateOfBirth = ko.observable(); this.parseDate = function(innerText) {return new Date(Date.parse(innerText));
};
}
```
You can then use this custom convert function as follows:
```html
February 17, 1963
```
Virtual elements
You can also use theinit
binding handler as a virtual element:
```html
Michael Jordan
```
Converting works the same as before:
```html
198
```
Note that we now need to explicitly specify the field
parameter, which points to the observable to initialize. In our previous examples, the init
binding was able to infer this due to it being combined with the text
, textInput
, value
or checked
binding and using the observable they were pointing to. As a consequence, the following bindings are equivalent:
```html
Michael Jordan
Michael Jordan
```
Explicit value
If you provide avalue
parameter to the init
binding, that value will be used to initialize the observable instead:
```html
Michael Jordan
```
This would result in the observable's value being set to "Larry Bird"
, and thus the element's content is changed once the text
binding is applied.
Multiple values
If you want to initialize multiple observable's at once, you just specify them as key/value pairs: ```html ``` This would set thecity
observable to "London"
and the year
observable to 230
.
Note: the keys cannot be equal to the "value"
, "convert"
or "field"
strings.
Supported bindings
Theinit
binding can be used with the following bindings:
text
: the value is set to the bound element's text contents.
textInput
: the value is set to the bound element's text contents.
value
: the value is set to the bound element's value.
html
: the value is set to the bound element's inner HTML value.
attr
: the bound attribute properties are set to their respective attribute values.
checked
: the value is set to the bound element's checked value.
visible
: the value is set totrue
orfalse
depending on the bound element's visibility.
enable
: the value is set totrue
if the bound element does not have adisabled
attribute; otherwise, it is set tofalse
.
disable
: the value is set totrue
if the bound element has adisabled
attribute; otherwise, it is set totrue
.
Foreach init binding
This binding handler wraps theforeach
binding, but instead of creating new HTML elements, it binds to existing HTML elements. Consider the following view model:
```javascript
function PersonViewModel() {
this.name = ko.observable();
}
function ForeachViewModel() {
this.persons = ko.observableArray();
this.persons.push(new PersonViewModel());
this.persons.push(new PersonViewModel());
this.persons.push(new PersonViewModel());
}
```
We can bind the elements in the persons
observable array to existing HTML elements by using the foreachInit
binding handler:
```html
- Michael Jordan
- Larry Bird
- Magic Johnson
- There must be one child element with the
data-template
attribute. This element will be used as the template when new items are added to the array.
- Elements to be bound to array items must have the
data-init
attribute.
- You can use the
init
binding handler to initialize the array items themselves.
Template
You can also use a template that is defined elsewhere on the page: ```html- Michael Jordan
- Larry Bird
- Magic Johnson
data-init
and data-template
can be omitted.
Create missing array elements
Up until now, we assumed that the observable array already contained an item for each HTML element bound inside theforeachInit
section. However, you can also have the foreachInit
binding handler dynamically add an element for each bound element.
Take the following view model:
```javascript
function ForeachDynamicViewModel() {
this.persons = ko.observableArray();
this.addPerson = function() {
this.persons.push(new PersonViewModel());
};
}
```
Note that the persons
observable array does not contain any elements, but that it does have a function to add a new element to the array.
We can use the foreachInit
binding handler as follows:
```html
- Michael Jordan
- Larry Bird
- Magic Johnson
data-init
attribute, the function specified in the createElement
parameter is called. Our modified code now looks as follows:
```javascript
function ForeachDynamicViewModel() {
this.persons = ko.observableArray();
this.createPerson = function() {
return new PersonViewModel();
};
}
```
Processing data changes
TheforeachInit
binding does not immediately process changes. Instead, it queues all changes, which it then later processes all at once. If you want to do additional processing before or after each queue processing round, you can use the dataChanged
, beforeQueueFlush
and afterQueueFlush
attributes:
```html
index
: the index of the item in the underlying array.
status
: indicates the status of the change item, eitherexisting
,added
orremoved
.
value
: the array item being processed.
console.log(changes.added.length + " items have been added");
console.log(changes.existing.length + " items were modified");
console.log(changes.deleted.length + " items were deleted");
};
this.beforeQueue = function(changeQueue) {
console.log(changeQueue.length + " queued items will be processed");
};
this.afterQueue = function(changeQueue) {
console.log(changeQueue.length + " queued items have been processed");
};
}
```
There are two main differences between the dataChanged
and beforeQueue
callbacks:
- The
dataChanged
callback is also called upon initialization, when the input array may be empty.
- The
dataChanged
callback on receives the new changes that will be added to the queue, whereasbeforeQueue
will receive the complete queue.
Installation
The best way to install this library is using Bower:bower install knockout-pre-rendered
You can also install the library using NPM:
npm install knockout-pre-rendered --save-dev
The library is also available from a CDN.
Demos
There is a JSBin demo for each of the binding handlers:History
Acknowledgements
Many thanks go out to Brian M Hunt, whichfastForEach
binding handler formed the basis of the foreachInit
binding handler.