How can I manage a master/detail UI using nested classes within a view model
https://groups.google.com/d/topic/knockoutjs/hU7DMwikCEc/discussion
by Jon Kittell
HTML
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.11/themes/base/jquery-ui.css">
<script src="http://knockoutjs.com/downloads/knockout-2.3.0.js"></script>
<table>
<tr>
<th>Id</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody data-bind="foreach: productList">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: name"></td>
<td>
<button data-bind="click: $root.editProduct">Edit</button>
</td>
<td>
<button data-bind="click: $root.removeProduct">Delete</button>
</td>
</tr>
</tbody>
</table>
<button data-bind="click: addProduct">Add Product</button>
<hr />
<div id="details" data-bind="jqDialog: { autoOpen: false, resizable: false, modal: true }, template: { name: 'editTmpl', data: selectedProduct, if: selectedProduct }, openDialog: selectedProduct">
</div>
<script id="editTmpl" type="text/html">
<p>
<label>ID: </label>
<input data-bind="value: editId" />
</p>
<p>
<label>Name: </label>
<input data-bind="value: editName" />
</p>
<button data-bind="jqButton: {}, click: $root.accept">Accept</button>
<button data-bind="jqButton: {}, click: $root.cancel">Cancel</button>
</script>
CSS
td { padding: 5px; }
input { width: 100px; margin: 10px; }
label: { width: 100px; }
JavaScript
//custom binding to initialize a jQuery UI dialog
ko.bindingHandlers.jqDialog = {
init: function(element, valueAccessor) {
var options = ko.utils.unwrapObservable(valueAccessor()) || {};
//handle disposal
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).dialog("destroy");
});
//dialog is moved to the bottom of the page by jQuery UI. Prevent initial pass of ko.applyBindings from hitting it
setTimeout(function() {
$(element).dialog(options);
}, 0);
}
};
//custom binding handler that opens/closes the dialog
ko.bindingHandlers.openDialog = {
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
$(element).dialog("open");
} else {
$(element).dialog("close");
}
}
}
//custom binding to initialize a jQuery UI button
ko.bindingHandlers.jqButton = {
init: function(element, valueAccessor) {
var options = ko.utils.unwrapObservable(valueAccessor()) || {};
//handle disposal
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).button("destroy");
});
$(element).button(options);
}
};
var Product = function(id, name, isNew) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.isNew = isNew;
this.editId = ko.observable(id);
this.editName = ko.observable(name);
//persist edits to real values on accept
this.accept = function() {
this.id(this.editId()).name(this.editName());
}.bind(this);
//reset to originals on cancel
this.cancel = function() {
this.editId(this.id()).editName(this.name());
}.bind(this);
}
var ViewModel = function() {
var self = this;
this.productList = ko.observableArray([
new Product(1, "one"),
new...