Introductory examples

Detailed examples

Cart editor example

This example shows how computed observables can be chained together. Each cart line has a ko.pureComputed property for its own subtotal, and these in turn are combined in a further ko.pureComputed property for the grand total. When you change the data, your changes ripple out through this chain of computed properties, and all associated UI is updated.

This example also demonstrates a simple way to create cascading dropdowns.

Live example

Category Product Price Quantity Subtotal
Remove

Total value:

Source code: View

<table width='100%'>
    <thead>
        <tr>
            <th width='25%'>Category</th>
            <th width='25%'>Product</th>
            <th class='price' width='15%'>Price</th>
            <th class='quantity' width='10%'>Quantity</th>
            <th class='price' width='15%'>Subtotal</th>
            <th width='10%'> </th>
        </tr>
    </thead>
    <tbody data-bind='foreach: lines'>
        <tr>
            <td>
                <select data-bind='options: sampleProductCategories, optionsText: "name", optionsCaption: "Select...", value: category'> </select>
            </td>
            <td data-bind="with: category">
                <select data-bind='options: products, optionsText: "name", optionsCaption: "Select...", value: $parent.product'> </select>
            </td>
            <td class='price' data-bind='with: product'>
                <span data-bind='text: formatCurrency(price)'> </span>
            </td>
            <td class='quantity'>
                <input data-bind='visible: product, value: quantity, valueUpdate: "afterkeydown"' />
            </td>
            <td class='price'>
                <span data-bind='visible: product, text: formatCurrency(subtotal())' > </span>
            </td>
            <td>
                <a href='#' data-bind='click: $parent.removeLine'>Remove</a>
            </td>
        </tr>
    </tbody>
</table>
<p class='grandTotal'>
    Total value: <span data-bind='text: formatCurrency(grandTotal())'> </span>
</p>
<button data-bind='click: addLine'>Add product</button>
<button data-bind='click: save'>Submit order</button>

Source code: View model

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

    var CartLine = function() {
        var self = this;
        self.category = ko.observable();
        self.product = ko.observable();
        self.quantity = ko.observable(1);
        self.subtotal = ko.pureComputed(function() {
            return self.product() ? self.product().price * parseInt("0" + self.quantity(), 10) : 0;
        });

        // Whenever the category changes, reset the product selection
        self.category.subscribe(function() {
            self.product(undefined);
        });
    };
    
    var Cart = function() {
        // Stores an array of lines, and from these, can work out the grandTotal
        var self = this;
        self.lines = ko.observableArray([new CartLine()]); // Put one line in by default
        self.grandTotal = ko.pureComputed(function() {
            var total = 0;
            $.each(self.lines(), function() { total += this.subtotal() })
            return total;
        });

        // Operations
        self.addLine = function() { self.lines.push(new CartLine()) };
        self.removeLine = function(line) { self.lines.remove(line) };
        self.save = function() {
            var dataToSave = $.map(self.lines(), function(line) {
                return line.product() ? {
                    productName: line.product().name,
                    quantity: line.quantity()
                } : undefined
            });
            alert("Could now send this to server: " + JSON.stringify(dataToSave));
        };
    };

    ko.applyBindings(new Cart());

Try it in jsFiddle