Guard your model
http://www.knockmeout.net/2011/03/guard-your-model-accept-or-cancel-edits.html
by smichelotti
HTML
<script src="http://rniemeyer.github.com/KnockMeOut/Scripts/jquery.tmpl.js"></script>
<script src="http://rniemeyer.github.com/KnockMeOut/Scripts/knockout-latest.debug.js"></script>
First:
<ul>
<!-- ko foreach: items -->
<li>
<span data-bind="text: name"></span>
</li>
<!-- /ko -->
</ul>
Second:
<ul data-bind="template: { name: 'itemTempl2', foreach: items }">
</ul>
<script id="itemTempl2" type="text/html">
<li>
<span data-bind="text: name"></span>
</li>
</script>
<table>
<tr>
<th>Name</th>
<th>Quantity</th>
<th></th>
<th></th>
</tr>
<tbody data-bind="template: { name: templateToUse, foreach: items}"></tbody>
</table>
<script id="itemTmpl" type="text/html">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: quantity"></td>
<td class="buttons">
<button data-bind="click: function() { viewModel.editItem($data); }">Edit</button>
<button data-bind="click: function() { viewModel.deleteItem($data); }">Delete</button>
</td>
</tr>
</script>
<button data-bind="click: addItem">New Item</button>
<script id="editTmpl" type="text/html">
<tr>
<td>
<input data-bind="value: name" />
</td>
<td>
<input data-bind="value: quantity" />
</td>
<td class="buttons">
<button data-bind="click: function() { viewModel.acceptItemEdit() }">Accept</button>
<button data-bind="click: function() { viewModel.cancelItemEdit() }">Cancel</button>
</td>
</tr>
</script>
CSS
input { width: 75px; }
td { width: 75px; }
th { color: #666; font-size: .8em; }
.buttons { width: 150px; }
JavaScript
//wrapper for an observable that protects value until committed
ko.protectedObservable = function(initialValue) {
//private variables
var _temp = initialValue;
var _actual = ko.observable(initialValue);
var result = ko.dependentObservable({
read: function() {
return _actual();
},
write: function(newValue) {
_temp = newValue;
}
});
//commit the temporary value to our observable, if it is different
result.commit = function() {
if (_temp !== _actual()) {
_actual(_temp);
}
};
//notify subscribers to update their value with the original
result.reset = function() {
_actual.valueHasMutated();
_temp = _actual();
};
return result;
};
//construct an Item
function Item(name, quantity) {
this.name = ko.protectedObservable(name);
this.quantity = ko.protectedObservable(quantity);
}
var viewModel = {
selectedItem: ko.observable(),
addItem: function() {
var newItem = new Item("new item", 0);
this.items.push(newItem);
this.selectedItem(newItem);
},
deleteItem: function(itemToDelete) {
this.items.remove(itemToDelete);
this.selectedItem(null);
},
editItem: function(item) {
this.selectedItem(item);
},
acceptItemEdit: function() {
this.selectedItem().name.commit();
this.selectedItem().quantity.commit();
this.selectedItem(null);
},
cancelItemEdit: function() {
this.selectedItem().name.reset();
this.selectedItem().quantity.reset();
this.selectedItem(null);
},
templateToUse: function(item) {
return viewModel.selectedItem() === item ? "editTmpl" : "itemTmpl";
}
};
viewModel.items = ko.observableArray([
new Item("one", 1),
new Item("two", 2),
new Item("three", 3)
]);
ko.applyBindings(viewModel);