Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

KnockoutJS custom binding of typeahead.js losing allBindingsAccessor function

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));
});
like image 257
Justin Anderson Avatar asked Jul 18 '13 06:07

Justin Anderson


1 Answers

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.

like image 83
nemesv Avatar answered Jan 02 '23 11:01

nemesv