Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Twitter Bootstrap typeahead selection not bound by KnockoutJS

Ok, I've fought with this issue for hours now and narrowed the issue to a very simple Fiddle

Problem is, that when I'm using twitter bootstrap's typeahead plugin on a text input and make a selection, the value doesn't update in the KnockoutJS ViewModel. I know I could hack it to work but there must be something that I'm missing here.

Basically what I have is:

Knockout Binding

// Bind twitter typeahead
ko.bindingHandlers.typeahead = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var $element = $(element);
        var allBindings = allBindingsAccessor();
        var typeaheadArr = ko.utils.unwrapObservable(valueAccessor());

        $element.attr("autocomplete", "off")
                .typeahead({
                    'source': typeaheadArr,
                    'minLength': allBindings.minLength,
                    'items': allBindings.items,
                    'updater': allBindings.updater
                });
    }
};

Knockout ViewModel

function MyModel(){
    var self = this;
    self.productName = ko.observable();
    self.availableProducts = ['One', 'Two', 'Three'];
}

ko.applyBindings(new MyModel());

HTML

<input type="text" data-bind="typeahead:availableProducts, value:productName"/>

The rest of the stuff is simply coming from Twitter Bootstrap.

like image 988
Jani Hyytiäinen Avatar asked Jul 10 '13 08:07

Jani Hyytiäinen


3 Answers

One solution is to modify your updater function where you need to grab the observable used in the value binding and update it with the function paramter:

'updater': function(item) {
    allBindings.value(item);
    return item;
}

Demo JSFiddle.

If you are not the author of the binding you than you can use the updater option to specify the updater function

data-bind="typeahead:availableProducts, 
           updater: function(item) { productName(item); return item; }"

Because the updater should returns the selected item the syntax of this is not so nice.

Demo JSFiddle.

like image 154
nemesv Avatar answered Oct 27 '22 17:10

nemesv


I prefer to keep my autocomplete suggestions list separate from Knockout entirely. I only want Knockout to know once a user has actually entered a value.

This is closer to user2576666's technique, since it uses Typeahead's custom events to force an update in the Knockout model when there is a selection or an autocompletion. However, it doesn't require a typeahead custom binding, and nor does it require the values to be stored in the Knockout ViewModel. This opens up the scope for more customisable completion down the line (such as using Bloodhound), which would get needlessly fiddly if we tried to store it in the Knockout model. My ViewModel is definitely not the right place to store the autocompletion options for my use case (and, I would suggest, many others – particularly if you have potentially large list which needs dynamic population). IMO this version is also easier to understand:

var availableProducts = ['One', 'Two', 'Three'];

var substringMatcher = function(strs) {
  return function findMatches(q, cb) {
    var matches, substrRegex;
    matches = [];
    substrRegex = new RegExp(q, 'i');
    $.each(strs, function(i, str) {
      if (substrRegex.test(str)) {
        matches.push({ value: str });
      }
    });
    cb(matches);
  };
};


function MyModel(){
    var self = this;
    self.productName = ko.observable();
}

var myModel = new MyModel();

ko.applyBindings(myModel);

var onUpdated = function($e, datum) {
    myModel.productName(datum.value);
};

$(".typeahead")
    .typeahead(
       {hint: true, minLength: 1, highlight: true},
       {displayKey: 'value', source: substringMatcher(availableProducts)})
    .on('typeahead:autocompleted', onUpdated)
    .on('typeahead:selected', onUpdated); // for knockoutJS 

I have, of course, saved this as a JSFiddle:

like image 4
Andy MacKinlay Avatar answered Oct 27 '22 19:10

Andy MacKinlay


This updater stuff didn't work for me, here is what did

ko.bindingHandlers.typeahead = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var $element = $(element);
        var allBindings = allBindingsAccessor();
        var typeaheadArr = ko.utils.unwrapObservable(valueAccessor());

        var updateValues = function (val) {
            allBindings.value(val);
        };

        $element.attr("autocomplete", "off")
                .typeahead({
                    'local': typeaheadArr,
                    'minLength': allBindings.minLength,
                    'items': allBindings.items,
                }).on('typeahead:selected', function (el, item) {
                    updateValues(item.value);
                }).on('typeahead:autocompleted', function (el, item) {
                    updateValues(item.value);
                });
    }
};
like image 3
user2576666 Avatar answered Oct 27 '22 19:10

user2576666