Undo/Redo framework
https://groups.google.com/d/topic/knockoutjs/Xsj2xJrmnyM/discussion
by rniemeyer
HTML
<script src="https://github.com/jquery/jquery-tmpl/raw/master/jquery.tmpl.js"></script>
<script src="http://knockoutjs.com/downloads/knockout-2.2.1.js"></script>
<table>
<tr>
<th>Name</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 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 class="buttons">
<button data-bind="enable: name.undoable, click: name.undo">Undo</button>
<button data-bind="enable: name.redoable, click: name.redo">Redo</button>
</td>
</tr>
</script>
CSS
input { width: 75px; }
td { width: 75px; }
th { color: #666; font-size: .8em; }
.buttons { width: 150px; }
JavaScript
$(function() {
ko.applyBindings(viewModel);
});
//wrapper for an observable that supports undo/redo
ko.undoableObservable = function(initialValue) {
//private variables
var _actual = ko.observable(initialValue);
var _undos = ko.observableArray([]);
var _redos = ko.observableArray([]);
var result = ko.dependentObservable({
read: function() {
return _actual();
},
write: function(newValue) {
_redos([]);
_undos.push(_actual());
_actual(newValue);
}
});
//expose these flags to allow enabling/disabling buttons
result.undoable = ko.dependentObservable(function() {
return _undos().length > 0;
});
result.redoable = ko.dependentObservable(function() {
return _redos().length > 0;
});
//go back to the latest undo value
result.undo = function() {
if (_undos().length > 0) {
var last = _undos.pop();
_redos.push(_actual());
_actual(last);
}
};
//redo the latest change
result.redo = function() {
if (_redos().length > 0) {
var next = _redos.pop();
_undos.push(_actual());
_actual(next);
}
};
return result;
};
var viewModel = {
items: ko.observableArray([
{
name: ko.undoableObservable("one")},
{
name: ko.undoableObservable("two")},
{
name: ko.undoableObservable("three")}
]),
selectedItem: ko.observable(),
addItem: function() {
var newItem = { name: ko.undoableObservable("new") };
this.items.push(newItem);
this.selectedItem(newItem);
},
deleteItem: function(itemToDelete) {
this.items.remove(itemToDelete);
this.selectedItem(null);
},
editItem: function(item) {
this.selectedItem(item);
},
templateToUse: function(item) {
return viewModel.selectedItem() === item ? "editTmpl"...