Simple editor pattern
by rniemeyer
HTML
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css">
<ul data-bind="foreach: items">
<li>
<a href="#" data-bind="click: $parent.items.selectItem, text: name"></a>
(<span data-bind="text: price"></span>)
</li>
</ul>
<hr/>
<div data-bind="with: items.itemForEditing">
<form class="form-horizontal">
<div class="control-group">
<label class="control-label" for="itemName">Name</label>
<div class="controls">
<input type="text" id="itemName" data-bind="value: name" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="itemPrice">Price</label>
<div class="controls">
<input type="number" step=".01" id="itemPrice" data-bind="value: price" />
</div>
</div>
<div class="control-group">
<div class="controls">
<button class="btn" data-bind="click: $parent.items.acceptItem">Accept</button>
<button class="btn" data-bind="click: $parent.items.revertItem">Cancel</button>
</div>
</div>
</form>
</div>
JavaScript
ko.observableArray.fn.editableItems = function(nameOfUpdateFunction) {
nameOfUpdateFunction = nameOfUpdateFunction || "update";
//hold the currently selected item
this.selectedItem = ko.observable();
//make edits to a copy
this.itemForEditing = ko.observable();
//populate the selected item and make a copy for editing
this.selectItem = function(item) {
this.selectedItem(item);
this.itemForEditing(ko.toJS(item));
}.bind(this);
this.acceptItem = function(item) {
var selected = this.selectedItem(),
edited = ko.toJS(this.itemForEditing()); //clean copy of edited
//apply updates from the edited item to the selected item
selected[nameOfUpdateFunction](edited);
//clear selected item
this.selectedItem(null);
this.itemForEditing(null);
}.bind(this);
//just throw away the edited item and clear the selected observables
this.revertItem = function() {
this.selectedItem(null);
this.itemForEditing(null);
}.bind(this);
return this;
};
var Item = function(data) {
this.name = ko.observable();
this.price = ko.observable();
//store latest data behind a function, so it is naturally removed when doing ko.toJS
this.cache = function() {};
//populate our model with the initial data
this.update(data);
};
ko.utils.extend(Item.prototype, {
//can pass fresh data to this function at anytime to apply updates or revert to a prior version
update: function(data) {
this.name(data.name || "new item");
this.price(data.price || 0);
//save off the latest data for later use
this.cache.latestData = data;
}
});
var ViewModel = function(items) {
//turn the raw items into Item objects
this.items = ko.observableArray(ko.utils.arrayMap(items, function(data) {
return new Item(data);
...