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
...