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 gurkavcu

HTML

<script src="https://github.com/jquery/jquery-tmpl/raw/master/jquery.tmpl.js"></script>
<script src="https://github.com/SteveSanderson/knockout/raw/master/build/output/knockout-latest.debug.js"></script>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.11/themes/base/jquery-ui.css">
<table>
    <tr>
        <th>Id</th>
        <th>Name</th>
        <th></th>
        <th></th>
    </tr>
    <tbody data-bind="template: { name: 'productsTmpl', foreach: productList }"></tbody>
</table>
<button data-bind="click: addProduct">Add Product</button>

<script id="productsTmpl" type="text/html">
    <tr>
        <td data-bind="text: id"></td>
        <td data-bind="text: name"></td> 
        <td>
            <button data-bind="click: function() { viewModel.editProduct($data) }">Edit</button>
        </td>
        <td>
            <button data-bind="click: function() { viewModel.removeProduct($data) }">Delete</button>
        </td>
    </tr>
</script>

<hr />

<div id="details" data-bind="template: { name: 'editTmpl', data: 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>        
</script>

CSS

td { padding: 5px; }
input { width: 100px; margin: 10px;  }
label: { width: 100px; }

JavaScript

$(function() {
    //set up dialog
    $("#details").dialog({
        autoOpen: false,
        resizable: false,
        modal: true,
        buttons: {
            "Accept": function() {
                viewModel.accept();
                $(this).dialog("close");
            },
            Cancel: function() {
                viewModel.cancel();
                $(this).dialog("close");
            }
        }
    });
});

function Product(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 = {
    productList: ko.observableArray([
        new Product(1, "one"),
        new Product(2, "two"),
        new Product(3, "three"),
        new Product(4, "four")
        ]),
    selectedProduct: ko.observable(),
    editProduct: function(productToEdit) {
        viewModel.selectedProduct(productToEdit);
        //$("#details").dialog("open");  //let's put this in a custom binding handler called "openDialog"
    },
    addProduct: function() {
        viewModel.selectedProduct(new Product(0, "", true));
    },
    removeProduct: function(product) {
        viewModel.productList.remove(product);   
    },
    accept: function() {
        var selected = viewModel.selectedProduct();
        selected.accept();
        
        if (selected.isNew) {
             viewModel.productList.push(new Product(selected.id(), selected.name()));   
        }

        viewModel.selectedProduct("");
    },
    cancel: function() {
        viewModel.selectedProduct().cancel();
        viewModel.selectedProduct("");
    }
};

//custom binding handler that...