//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: _actual, write: function(newValue) { _temp = newValue; } }).extend({ notify: "always" }); //needed in KO 3.0+ for reset, as computeds no longer notify when value is the same //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 var Item = function(name, quantity) { this.name = ko.protectedObservable(name); this.quantity = ko.protectedObservable(quantity); } var ViewModel = function(items) { var self = this; this.items = ko.observableArray(items); this.selectedItem = ko.observable(); this.addItem = function() { var newItem = new Item("new item", 0); self.items.push(newItem); self.selectedItem(newItem); }; this.deleteItem = function(itemToDelete) { self.items.remove(itemToDelete); self.selectedItem(null); }; this.editItem = function(item) { self.selectedItem(item); }; this.acceptItemEdit = function() { self.selectedItem().name.commit(); self.selectedItem().quantity.commit(); self.selectedItem(null); }; this.cancelItemEdit = function() { self.selectedItem().name.reset(); self.selectedItem().quantity.reset(); self.selectedItem(null); }; this.templateToUse = function(item) { return self.selectedItem() === item ? "editTmpl" : "itemTmpl"; }; }; ko.applyBindings(new ViewModel([ new Item("one", 1), new Item("two", 2), new Item("three", 3) ]));
<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: $root.editItem">Edit</button> <button data-bind="click: $root.deleteItem">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: $root.acceptItemEdit">Accept</button> <button data-bind="click: $root.cancelItemEdit">Cancel</button> </td> </tr> </script>
input { width: 75px; } td { width: 75px; } th { color: #666; font-size: .8em; } .buttons { width: 150px; }