My goal: Using KnockoutJS I have a collection of input text fields where the user can type in their product names and typeahead.js will give auto-suggestions for each text box. More of these text fields can be added or removed dynamically.
See live example (JS Fiddle): http://jsfiddle.net/justosophy/5Z9We/
The problem is: Typeahead works for the initial text fields, but only works partially for dynamically fields. The knockout custom binding allBindingsAccessor() is undefined so it cannot save the value.
Error:
TypeError: string is not a function
HTML:
<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>
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: "",
productID: 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) {
productNameVal(datum.value);
productIDVal(datum.productID);
};
$e.typeahead({
name: 'products',
local: my.productsList
}).on('typeahead:selected', function (el, datum) {
updateValues(datum);
}).on('typeahead:autocompleted', function (el, datum) {
updateValues(datum);
}).blur(function () {
var el, val, arrayCheck;
el = $(this);
val = el.val();
arrayCheck = ($.grep(my.productsList, function (n) { return n.value === val; }).length !== 0);
if (!arrayCheck) {
el.val('');
source('');
productIDVal(0);
}
});
}
};
// Apply bindings
$(document).ready(function () {
ko.applyBindings(new my.ProductsModel(my.initialData));
});
You need to make the properties of the newly added items also observable.
The initial items are working because in the self.products
self.products =
ko.observableArray(ko.utils.arrayMap(products, function (product) {
return {
productName: ko.observable(product.productName),
productID: ko.observable(product.productID)
};
}));
you have created the items with observable properties.
So change your addProduct
function to:
self.addProduct = function () {
self.products.push({
productName: ko.observable(""),
productID: ko.observable(0)
});
};
Demo JSFiddle.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With