Search code examples
jquerytwitter-bootstrapknockout.jsdurandal-2.0twitter-typeahead

Twitter Typeahead, Knockout JS, and Twitter Bootstrap 3 not playing nicely


I'm attempting to get the knockout-bootstrap custom binding for typeahead jQuery working with Bootstrap 3 so that I can use it with Durandal 2.0, but it isn't working quite yet. The original binding is the following:

koObject.bindingHandlers.typeahead = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var $element = $(element);
        var allBindings = allBindingsAccessor();
        var typeaheadOpts = { source: koObject.utils.unwrapObservable(valueAccessor()) };

        if (allBindings.typeaheadOptions) {
            $.each(allBindings.typeaheadOptions, function(optionName, optionValue) {
                typeaheadOpts[optionName] = koObject.utils.unwrapObservable(optionValue);
            });
        }

        $element.attr("autocomplete", "off").typeahead(typeaheadOpts);
    }
};

Since Bootstrap 3, typeahead is a separate plugin so I needed to make a few changes. I've modified the binding to look like this:

koObject.bindingHandlers.typeahead = {
    init: function (element, valueAccessor, bindingAccessor) {
        var $e = $(element),
            options = koObject.utils.unwrapObservable(valueAccessor());

        console.dir(options.source());

        console.dir($e);

        // passing in `null` for the `options` arguments will result in the default
        // options being used
        $e.typeahead({ 
            highlight: true,
            minLength: 2,           
        },
        {             
            source: options.source()
        }).on('typeahead:selected', function (el, datum) {
            console.dir(datum);
        }).on('typeahead:autocompleted', function (el, datum) {
            console.dir(datum);
        });

    }
};

I've simplified the knockout-bootstrap example HTML to only demonstrate the typeahead binding. The issue I'm having is that when typeahead tries to provide suggestions, it breaks on line 1184 throwing an Uncaught TypeError: object is not a function exception. I've attempted to create a jsFiddle for this, but it is not functional at the moment.

What am I missing to get Twitter typeahead 0.10.2 jQuery, knockout 3.1.0, Bootstrap 3.1.1, and Durandal 2.0?


Solution

  • Here is an updated, working version of your fiddle.. All I did was use the substringMatcher function with the jsFrameworks array passed into it as the typeahead source - exactly how it is used in the typeahead.js first example.

    So the source option inside your binding handler when you initiate typeahead becomes this:

    source: substringMatcher(options.source())

    where options.source() is the underlying array of your jsFrameworks observable array.

    Here is the binding handler in full

    ko.bindingHandlers.typeahead = {
            init: function (element, valueAccessor, bindingAccessor) {
                var substringMatcher = function (strs) {
                    return function findMatches(q, cb) {
                        var matches, substringRegex;
    
                        // an array that will be populated with substring matches
                        matches = [];
    
                        // regex used to determine if a string contains the substring `q`
                        substrRegex = new RegExp(q, 'i');
    
                        // iterate through the pool of strings and for any string that
                        // contains the substring `q`, add it to the `matches` array
                        $.each(strs, function (i, str) {
                            // console.log(str);
                            if (substrRegex.test(str)) {
                                // the typeahead jQuery plugin expects suggestions to a
                                // JavaScript object, refer to typeahead docs for more info
                                matches.push({
                                    value: str
                                });
                            }
                        });
    
                        cb(matches);
                    };
                };
                var $e = $(element),
                    options = valueAccessor();
    
                console.dir(options.source());
    
                console.dir($e);
    
                // passing in `null` for the `options` arguments will result in the default
                // options being used
                $e.typeahead({
                    highlight: true,
                    minLength: 2
                }, {
                    source: substringMatcher(options.source())
                }).on('typeahead:selected', function (el, datum) {
                    console.dir(datum);
                }).on('typeahead:autocompleted', function (el, datum) {
                    console.dir(datum);
                });
    
            }
        };