0

I've been trying to create an autocomplete dropdown based on the accepted response in this post but the autocomplete dropdown simply isn't showing up. It could be because the response is 9 years old or perhaps I'm doing something wrong. I have tried all of the suggestions that I've come across. Is there an updated way to create this combobox using jquery version 1.12.3, jquery-ui version 1.12.1, and knockoutjs version 3.4.1?

To me is seems like the bindings aren't really taking place because I could rename the custom binding to "jqAuto1" instead of "jqAuto" and there would be no errors, even though "jqAuto1" isn't defined anywhere. Why isn't that being picked up?

Here is my code. Note that the JS script is in a separate, parent solution from the CSHTML and TS files. The browser still finds and executes the JS script.

CSHTML

<input class="form-control form-control-xs" data-bind="value: companyName, jqAuto: { autoFocus: true }, jqAutoSource: myComp, jqAutoValue: mySelectedGuid, jqAutoSourceLabel: 'displayName', jqAutoSourceInputValue: 'coname', jqAutoSourceValue: 'id'" placeholder="Type Company Name and select from list" />

TS

// For list of Companies
class Comp {
    _id: KnockoutObservable<string>;
    _coname: KnockoutObservable<string>;
    _coid: KnockoutObservable<string>;

    constructor(id: string, coname: string, coid: string) {
        this._id = ko.observable(id);
        this._coname = ko.observable(coname);
        this._coid = ko.observable(coid);
    }
}

myComp: KnockoutObservableArray<Comp>;
mySelectedGuid: KnockoutObservable<string>;
displayName: KnockoutComputed<string>;

...

this.myComp = ko.observableArray([
            new Comp("1", "Company 1", "CO1"),
            new Comp("2", "Company 2", "CO2"),
            new Comp("3", "Company 3", "CO3"),
            new Comp("4", "Company 4", "CO4"),
            new Comp("5", "Company 5", "CO5")
        ]);

this.companyName = ko.validatedObservable<string>("");
this.displayName = ko.computed(function () {
            return this.myComp.coname + " [" + this.myComp.coid + "]";
        }, this);
this.mySelectedGuid = ko.observable("5");

JS Pretty much what's in the linked post

(function () {
    var global = this || (0, eval)('this'),
        document = global['document'],
        moduleName = 'knockout-binding-jqauto',
        dependencies = ['knockout', 'jquery'];

    var moduleDance = function (factory) {
        // Module systems magic dance.

        if (typeof define === "function" && define["amd"]) {
            define(moduleName, dependencies.concat('exports'), factory);
        } else {
            // using explicit <script> tags with no loader
            global.CPU = global.CPU || {};
            factory(global.ko, global.Globalize);
        }
    };

    var factory = function (ko, $) {

        ko.bindingHandlers.jqauto = {
            init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
                var options = valueAccessor() || {},
                    allBindings = allBindingsAccessor(),
                    unwrap = ko.utils.unwrapObservable,
                    modelValue = allBindings.jqAutoValue,
                    source = allBindings.jqAutoSource,
                    valueProp = allBindings.jqAutoSourceValue,
                    inputValueProp = allBindings.jqAutoSourceInputValue || valueProp,
                    labelProp = allBindings.jqAutoSourceLabel || valueProp;

                //function that is shared by both select and change event handlers
                function writeValueToModel(valueToWrite) {
                    if (ko.isWriteableObservable(modelValue)) {
                        modelValue(valueToWrite);
                    } else {  //write to non-observable
                        if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['jqAutoValue'])
                            allBindings['_ko_property_writers']['jqAutoValue'](valueToWrite);
                    }
                }
                //on a selection write the proper value to the model
                options.select = function (event, ui) {
                    writeValueToModel(ui.item ? ui.item.actualValue : null);
                };

                //on a change, make sure that it is a valid value or clear out the model value
                options.change = function (event, ui) {
                    var currentValue = $(element).val();
                    alert(currentValue);
                    var matchingItem = ko.utils.arrayFirst(unwrap(source), function (item) {
                        return unwrap(inputValueProp ? item[inputValueProp] : item) === currentValue;
                    });

                    if (!matchingItem) {
                        writeValueToModel(null);
                    }
                }


                //handle the choices being updated in a DO, to decouple value updates from source (options) updates
                var mappedSource = ko.dependentObservable(function () {
                    mapped = ko.utils.arrayMap(unwrap(source), function (item) {
                        var result = {};
                        result.label = labelProp ? unwrap(item[labelProp]) : unwrap(item).toString();  //show in pop-up choices
                        result.value = inputValueProp ? unwrap(item[inputValueProp]) : unwrap(item).toString();  //show in input box
                        result.actualValue = valueProp ? unwrap(item[valueProp]) : item;  //store in model
                        return result;
                    });
                    return mapped;
                }, null, { disposeWhenNodeIsRemoved: element });

                //whenever the items that make up the source are updated, make sure that autocomplete knows it
                mappedSource.subscribe(function (newValue) {
                    $(element).autocomplete("option", "source", newValue);
                });

                options.source = mappedSource();

                //initialize autocomplete
                $(element).autocomplete(options);

            },

            update: function (element, valueAccessor, allBindings, viewModel) {
                //update value based on a model change
                var allBindings = allBindingsAccessor(),
                    unwrap = ko.utils.unwrapObservable,
                    modelValue = unwrap(allBindings.jqAutoValue) || '',
                    valueProp = allBindings.jqAutoSourceValue,
                    inputValueProp = allBindings.jqAutoSourceInputValue || valueProp;

                //if we are writing a different property to the input than we are writing to the model, then locate the object
                if (valueProp && inputValueProp !== valueProp) {
                    var source = unwrap(allBindings.jqAutoSource) || [];
                    var modelValue = ko.utils.arrayFirst(source, function (item) {
                        return unwrap(item[valueProp]) === modelValue;
                    }) || {};  //probably don't need the || {}, but just protect against a bad value          
                }

                //update the element with the value that should be shown in the input
                $(element).val(modelValue && inputValueProp !== valueProp ? unwrap(modelValue[inputValueProp]) : modelValue.toString());
            }

        }
    };

    moduleDance(factory);
})();
SecretAgentMan
  • 2,856
  • 7
  • 21
  • 41
Wayward Entity
  • 89
  • 1
  • 10
  • What about this? https://jqueryui.com/autocomplete/ – panoskarajohn Dec 10 '19 at 14:16
  • @panoskarajohn knockout needs to connect to the autocomplete via custom bindings. I tried: `ko.bindingHandlers.jqAutoComplete = { update: function (element, valueAccessor, allBindingsAccessor, viewModel) { var companies = valueAccessor(); $(element).autocomplete({ source: valueAccessor() }); } };` – Wayward Entity Dec 10 '19 at 16:48
  • @panoskarajohn Then I tried to use it like this: ``. It still doesn't work – Wayward Entity Dec 10 '19 at 16:52
  • I will post an answer. I have not full understood your question. But why do you even need knockout? – panoskarajohn Dec 10 '19 at 16:57
  • @panoskarajohn the team who worked on this project before me integrated knockout and structured the project in such a way that i need to use custom bindings or else deconstruct everything that was done before me – Wayward Entity Dec 10 '19 at 19:22
  • Take a look at this https://stackoverflow.com/questions/15867569/knockout-js-autocomplete-bindinghandler – panoskarajohn Dec 10 '19 at 19:29
  • @panoskarajohn none of the suggestions work. The autocomplete widget simply does not show up at all – Wayward Entity Dec 11 '19 at 19:28

1 Answers1

0

I have not fully understood your question. But knockout is not relevant to UIComplete. Please see a simple example using UI complete.

async function autocomplete() {
    const sthings= await getSthings(); //gets json array, or ajax call, this is a promise
    $("#sthHighlightSearch").autocomplete({
        source: sthings
    });
    //This is an extension method for autocomplete
    //Should filter the list with starts with characters written in the autocomplete
    $.ui.autocomplete.filter = function (array, term) {
        var matcher = new RegExp("^" + $.ui.autocomplete.escapeRegex(term), "i");
        return $.grep(array, function (value) {
            return matcher.test(value.label || value.value || value);
        });
    };

}
panoskarajohn
  • 1,846
  • 1
  • 21
  • 37