Products with knockout and typeahead

by Farzad Cyrus

HTML

<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/typeahead.js/0.9.2/typeahead.min.js"></script>
<div class="container">
     <h2>Products</h2>

    <div data-bind="foreach: products">
        <div class="product">
            <label>Enter Product Name:</label>
            <input type="text" class="text-input product-search" data-bind="typeahead: productName, value: productName, productNameVal: productName, productIDVal: productID, valueUpdate: 'afterkeydown'" /> <a class="btn btn-danger" data-bind="click: $root.removeProduct" title="remove">remove</a>

        </div>
    </div> <a class="btn btn-primary" data-bind="click: addProduct">Add another product</a>

    <div class="row">
        <div class="span4"> <small>Results</small>
 <pre data-bind="text: ko.toJSON($root.products, null, 2)" style="background:#eee;" title="results"></pre>

        </div>
        <div class="span4"> <small>Errors</small>
 <pre id="printError" style="background:pink;" title="errors"></pre>

        </div>
    </div>
</div>

CSS

@import url('http://twitter.github.com/bootstrap/assets/css/bootstrap.css');
 .container {
    margin: 25px 40px;
}
.product {
    margin: 5px 0;
}
.tt-dropdown-menu {
    width: 320px;
    background: #fff;
    border: solid 1px #aaa;
    border-radius: 4px;
}
.tt-dropdown-menu p {
    margin: 0;
    padding: 10px 15px;
}
.tt-dropdown-menu .tt-is-under-cursor p {
    color: darkblue;
    background: #f5f5f5;
}
pre {
    min-height: 260px;
}
.row {
    margin-top: 20px;
}

JavaScript

var my = {}, i;

// Create two initial entry boxes
my.initialData = [];
for (i = 2; i !== 0; i -= 1) {
    my.initialData.push({
        productName: "",
        productID: 0
    });
}

// Knockout model
my.ProductsModel = function (products) {
    var self = this;
    self.products = ko.observableArray(ko.utils.arrayMap(products, function (product) {
        return {
            productName: ko.observable(product.productName),
            productID: ko.observable(product.productID)
        };
    }));

    self.addProduct = function () {
        self.products.push({
            productName: ko.observable(""),
            productID: ko.observable(0)
        });
    };

    self.removeProduct = function (product) {
        self.products.remove(product);
    };

};

// List of product options
my.productsList = [{
    value: 'alpha',
    productID: 1
}, {
    value: 'apple',
    productID: 2
}, {
    value: 'beta',
    productID: 3
}, {
    value: 'bannana',
    productID: 4
}, {
    value: 'gamma',
    productID: 5
}, {
    value: 'grape',
    productID: 6
}, {
    value: 'delta',
    productID: 7
}, {
    value: 'dragonfruit',
    productID: 8
}, {
    value: 'diamond',
    productID: 9
}];

// Typeahead handler
ko.bindingHandlers.typeahead = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        var $e = $(element),
            productNameVal = allBindingsAccessor().productNameVal,
            productIDVal = allBindingsAccessor().productIDVal;

        var updateValues = function (datum) {
            try {
                productNameVal(datum.value);
                productIDVal(datum.productID);
            } catch (err) {
                var errorBox = $("#printError"),
                    d = new Date();
                errorBox.text(d.toTimeString('hh:mm:ss') + '\n' + errorBox.text() + err + '\n\n');
            }
        };

        $e.typeahead({
            name: 'products',
            local: my.productsList
       ...