Data binding nested JSON with Knockoutjs

http://stackoverflow.com/questions/9088423/data-binding-nested-json-with-knockoutjs

by Doug Hill

HTML

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
<div>
    <!-- ko foreach:lines -->
        <!-- ko foreach:$parent.menus -->
        <select data-bind='options: categories, optionsText: "name", value: $parent.category' ></select>
    <!-- /ko -->
    
    <!-- ko with: category -->
    <select data-bind='options: products, optionsText: "name", value: $parent.product'></select>   
    <!-- /ko -->
    
    <!-- ko with: product -->
    <span data-bind="text: price"></span> 
    <select data-bind='options: options, optionsText: "name"'></select>                        
    <input data-bind="value: $parent.quantity, valueUpdate: 'keyup'" />
    <!-- /ko -->               
        
    <span data-bind="text: subtotal"></span>
    <a href='#' data-bind='click: $parent.removeLine'>Remove</a><br/> 
    <!-- /ko -->
</div>
 Total value: <span data-bind='text: grandTotal()'> </span>
<button data-bind="click: addLine">Add Product</button>

JavaScript

$(function() {

    function formatCurrency(value) {
        return "$" + value.toFixed(2);
    }

    //single Option with an Observable amount

    function Option(name, quantity) {
        var self = this;
        self.name = name;
        self.quantity = ko.observable(quantity);
    }

    //single Product containing an Observable Array of Options (that contain Observable data)

    function Product(name, price, quantity, options) {
        var self = this;
        self.name = ko.observable(name);
        self.price = price;
        self.quantity = ko.observable(quantity);
        self.options = ko.observableArray(ko.utils.arrayMap(options, function(option) {
            return new Option(option.name, option.quantity);
        }));
        self.addOption = function(name, quantity) {
            self.options.push(new Option(name, quantity));
        };
    }

    //single Category containing an Observable Array of Products (that contain Observable Arrays with Observable data)

    function Category(name, products) {
        var self = this;
        self.name = name;
        self.products = ko.observableArray(ko.utils.arrayMap(products, function(product) {
            return new Product(product.name, product.price, product.quantity, product.options);
        }));
    }

    //single Menu containing an Observable Array of Categories (that contain Observable Arrays with Observable data)

    function Menu(categories) {
        var self = this;
        self.categories = ko.observableArray(ko.utils.arrayMap(categories, function(category) {
            return new Category(category.name, category.products);
        }));

        self.addCategory = function(name) {
            self.categories.push(new Category(name));
        };
    }

    function CartLine() {
        var self = this;
        self.category = ko.observable();
        self.product = ko.observable();
        self.quantity = ko.observable(1);
        self.subtotal = ko.computed(function() {
           ...