demo
demo

Reputation: 6237

Knockout autocomplete with jquery doesn't allow to select custom value

I've read this topic how to implement autocomplete with knockout.

I've made it to work, but notice that I couldn't select my own-typed value.


More description: I type any string into input that doesn't exists in autocomplete source. As result this string isn't setted as value of that input.


ko.bindingHandlers.autoComplete = {
    // Only using init event because the Jquery.UI.AutoComplete widget will take care of the update callbacks
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        // { selected: mySelectedOptionObservable, options: myArrayOfLabelValuePairs }
        var settings = valueAccessor();

        var selectedOption = settings.selected;
        var options = settings.options;
        var elVal = $(element).val();

        var updateElementValueWithLabel = function (event, ui) {
            // Stop the default behavior
            event.preventDefault();

            // Update the value of the html element with the label 
            // of the activated option in the list (ui.item)
            $(element).val(ui.item !== null ? ui.item.label : elVal);

            // Update our SelectedOption observable
            if(typeof ui.item !== "undefined") {
                // ui.item - label|value|...
                selectedOption(ui.item);
            }
        };

        $(element).autocomplete({
            source: options,
            select: function (event, ui) {
                updateElementValueWithLabel(event, ui);
            },
            focus: function (event, ui) {
                updateElementValueWithLabel(event, ui);
            },
            change: function (event, ui) {
                updateElementValueWithLabel(event, ui);
            }
        });
    }
};

// Array with original data
var remoteData = [{
    name: 'Ernie',
    id: 1
}, {
    name: 'Bert',
    id: 2
}, {
    name: 'Germaine',
    id: 3
}, {
    name: 'Sally',
    id: 4
}, {
    name: 'Daisy',
    id: 5
}, {
    name: 'Peaches',
    id: 6
}];

function ViewModel() {
    var self = this;
    
    self.users = remoteData;
    
    self.selectedOption = ko.observable('');
    self.options = self.users.map(function (element) {
        // JQuery.UI.AutoComplete expects label & value properties, but we can add our own
        return {
            label: element.name,
            value: element.id,
            // This way we still have acess to the original object
            object: element
        };
    });
}

$(function () {
    ko.applyBindings(new ViewModel());
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" rel="stylesheet"/>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://rawgit.com/rniemeyer/knockout-jqAutocomplete/master/build/knockout-jqAutocomplete.js"></script>

<input type="text" data-bind="autoComplete: { selected: selectedOption, options: options }" />

<!-- Debugging -->
<p data-bind="text: ko.toJSON(selectedOption())"></p>

Upvotes: 0

Views: 847

Answers (1)

Jason Spake
Jason Spake

Reputation: 4304

If you want to modify the binding to accept values that aren't in the options list you'll need to adjust updateElementValueWithLabel to not reset the element value when an item isn't selected.

ko.bindingHandlers.autoComplete = {
    // Only using init event because the Jquery.UI.AutoComplete widget will take care of the update callbacks
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        // { selected: mySelectedOptionObservable, options: myArrayOfLabelValuePairs }
        var settings = valueAccessor();

        var selectedOption = settings.selected;
        var options = settings.options;
        var elVal = $(element).val();

        var updateElementValueWithLabel = function (event, ui) {
            // Stop the default behavior
            event.preventDefault();

            // Update our SelectedOption observable
            if(ui.item) {
                // Update the value of the html element with the label 
                // of the activated option in the list (ui.item)
                // ui.item - label|value|...
                $(element).val(ui.item.label);
                selectedOption(ui.item.label);
            }else{
            		selectedOption($(element).val());
            }
        };

        $(element).autocomplete({
            source: options,
            select: function (event, ui) {
                updateElementValueWithLabel(event, ui);
            },
            change: function (event, ui) {
                updateElementValueWithLabel(event, ui);
            }
        });
    }
};

// Array with original data
var remoteData = [{
    name: 'Ernie',
    id: 1
}, {
    name: 'Bert',
    id: 2
}, {
    name: 'Germaine',
    id: 3
}, {
    name: 'Sally',
    id: 4
}, {
    name: 'Daisy',
    id: 5
}, {
    name: 'Peaches',
    id: 6
}];

function ViewModel() {
    var self = this;
    
    self.users = remoteData;
    
    self.selectedOption = ko.observable('');
    self.options = self.users.map(function (element) {
        // JQuery.UI.AutoComplete expects label & value properties, but we can add our own
        return {
            label: element.name,
            value: element.id,
            // This way we still have acess to the original object
            object: element
        };
    });
}

$(function () {
    ko.applyBindings(new ViewModel());
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" rel="stylesheet"/>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://rawgit.com/rniemeyer/knockout-jqAutocomplete/master/build/knockout-jqAutocomplete.js"></script>

<input type="text" data-bind="autoComplete: { selected: selectedOption, options: options }" />

<!-- Debugging -->
<p data-bind="text: ko.toJSON(selectedOption())"></p>

Upvotes: 1

Related Questions