it-swarm.com.de

Wie erstelle ich eine Auto-Complete-Combobox?

Kennt jemand die beste Möglichkeit, eine Autocomplete-Combobox mit Knockout JS-Vorlagen zu erstellen?

Ich habe folgende Vorlage:

<script type="text/html" id="row-template">
<tr>
...
    <td>         
        <select class="list" data-bind="options: SomeViewModelArray, 
                                        value: SelectedItem">
        </select>
    </td>
...        
<tr>
</script>

Manchmal ist diese Liste lang und ich möchte, dass Knockout mit jQuery autocomplete oder mit etwas JavaScript-Code gut gespielt wird, aber bisher wenig Erfolg hatte.

Außerdem erfordert jQuery.Autocomplete ein Eingabefeld. Irgendwelche Ideen?

56
Craig Bruce

Hier ist eine jQuery UI Autocomplete-Bindung, die ich geschrieben habe. Es ist beabsichtigt, das options, optionsText, optionsValue, value Bindungsparadigma zu spiegeln, das mit ausgewählten Elementen mit einigen Ergänzungen verwendet wird (Sie können Optionen über AJAX und Sie können unterscheiden, was im Eingabefeld angezeigt wird und was im daraufhin angezeigten Auswahlfeld angezeigt wird.

Sie müssen nicht alle Optionen angeben. Es werden die Standardeinstellungen für Sie ausgewählt.

Hier ist ein Beispiel ohne die AJAX -Funktionalität: http://jsfiddle.net/rniemeyer/YNCTY/

Hier ist dasselbe Beispiel mit einer Schaltfläche, die das Verhalten eines Kombinationsfelds verstärkt: http://jsfiddle.net/rniemeyer/PPsRC/

Hier ist ein Beispiel mit den über AJAX abgerufenen Optionen: http://jsfiddle.net/rniemeyer/MJQ6g/

//jqAuto -- main binding (should contain additional options to pass to autocomplete)
//jqAutoSource -- the array to populate with choices (needs to be an observableArray)
//jqAutoQuery -- function to return choices (if you need to return via AJAX)
//jqAutoValue -- where to write the selected value
//jqAutoSourceLabel -- the property that should be displayed in the possible choices
//jqAutoSourceInputValue -- the property that should be displayed in the input box
//jqAutoSourceValue -- the property to use for the value
ko.bindingHandlers.jqAuto = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        var options = valueAccessor() || {},
            allBindings = allBindingsAccessor(),
            unwrap = ko.utils.unwrapObservable,
            modelValue = allBindings.jqAutoValue,
            source = allBindings.jqAutoSource,
            query = allBindings.jqAutoQuery,
            valueProp = allBindings.jqAutoSourceValue,
            inputValueProp = allBindings.jqAutoSourceInputValue || valueProp,
            labelProp = allBindings.jqAutoSourceLabel || inputValueProp;

        //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();
            var matchingItem =  ko.utils.arrayFirst(unwrap(source), function(item) {
               return unwrap(item[inputValueProp]) === currentValue;  
            });

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

        //hold the autocomplete current response
        var currentResponse = null;

        //handle the choices being updated in a DO, to decouple value updates from source (options) updates
        var mappedSource = ko.dependentObservable({
            read: 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;                
            },
            write: function(newValue) {
                source(newValue);  //update the source observableArray, so our mapped value (above) is correct
                if (currentResponse) {
                    currentResponse(mappedSource());
                }
            }
        });

        if (query) {
            options.source = function(request, response) {  
                currentResponse = response;
                query.call(this, request.term, mappedSource);
            }
        } else {
            //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();
        }

        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            $(element).autocomplete("destroy");
        });


        //initialize autocomplete
        $(element).autocomplete(options);
    },
    update: function(element, valueAccessor, allBindingsAccessor, 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;
           }) || {};             
       } 

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

Sie würden es wie folgt verwenden:

<input data-bind="jqAuto: { autoFocus: true }, jqAutoSource: myPeople, jqAutoValue: mySelectedGuid, jqAutoSourceLabel: 'displayName', jqAutoSourceInputValue: 'name', jqAutoSourceValue: 'guid'" />

UPDATE: Ich verwalte eine Version dieser Bindung hier: https://github.com/rniemeyer/knockout-jqAutocomplete

120
RP Niemeyer

Hier ist meine Lösung: 

ko.bindingHandlers.ko_autocomplete = {
    init: function (element, params) {
        $(element).autocomplete(params());
    },
    update: function (element, params) {
        $(element).autocomplete("option", "source", params().source);
    }
};

Verwendungszweck:

<input type="text" id="name-search" data-bind="value: langName, 
ko_autocomplete: { source: getLangs(), select: addLang }"/>

http://jsfiddle.net/7bRVH/214/ Verglichen mit RPs ist es sehr einfach, erfüllt aber möglicherweise Ihre Bedürfnisse.

44
Epstone

Entsorgung erforderlich ....

Beide Lösungen sind großartig (wobei Niemeyer viel feinkörniger ist), aber beide vergessen die Entsorgung!

Sie sollten mit Veräußerungen umgehen, indem sie die automatische Vervollständigung von Jquery zerstören (Speicherverluste verhindern):

init: function (element, valueAccessor, allBindingsAccessor) {  
....  
    //handle disposal (if KO removes by the template binding)
    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
        $(element).autocomplete("destroy");
    });
}
13

Kleinere Verbesserungen,

Dies sind vor allem einige nützliche Tipps, danke für das Teilen.

Ich verwende die von Epstone veröffentlichte Version mit den folgenden Verbesserungen:

  1. Zeigen Sie das Etikett (anstelle des Werts) an, wenn Sie nach oben oder unten drücken. Dies kann anscheinend durch Behandeln des Fokusereignisses erfolgen

  2. Verwenden eines beobachtbaren Arrays als Datenquelle (anstelle eines Arrays)

  3. Der Einweg-Handler wurde hinzugefügt, wie von George vorgeschlagen.

http://jsfiddle.net/PpSfR/

...
conf.focus = function (event, ui) {
  $(element).val(ui.item.label);
  return false;
}
...

Übrigens: Durch Angabe von minLength als 0 können Sie die Alternativen anzeigen, indem Sie einfach die Pfeiltasten bewegen, ohne Text eingeben zu müssen.

4
Antonio Inacio

Problem beim Löschen der Eingabe beim Laden der RP-Lösung behoben. Auch wenn es sich um eine indirekte Lösung handelt, habe ich dies am Ende der Funktion geändert:

$(element).val(modelValue && inputValueProp !== valueProp ?
unwrap(modelValue[inputValueProp]) : modelValue.toString());

zu diesem:

var savedValue = $(element).val();
$(element).val(modelValue && inputValueProp !== valueProp ?  unwrap(modelValue[inputValueProp]) : modelValue.toString());
if ($(element).val() == '') {
   $(element).val(savedValue);
}
2
cakefactory

Ich habe versucht, Niemeyers Lösung mit JQuery UI 1.10.x, aber die Autocomplete-Box wurde einfach nicht angezeigt. Nach einiger Suche fand ich eine einfache Problemumgehung hier . Das Hinzufügen der folgenden Regel am Ende Ihrer Datei jquery-ui.css behebt das Problem:

ul.ui-autocomplete.ui-menu {
  z-index: 1000;
}

Ich habe auch Knockout-3.1.0 verwendet, also musste ich ko.dependentObservable (...) durch ko.computed (...) ersetzen. 

Wenn Ihr KO-View-Modell einige numerische Werte enthält, müssen Sie außerdem die Vergleichsoperatoren ändern: von === in == und! == in! =, Damit die Typkonvertierung durchgeführt wird. 

Ich hoffe das hilft anderen

2
chomba

Ich weiß, dass diese Frage alt ist, aber ich habe auch nach einer wirklich einfachen Lösung für unser Team gesucht, die dies in einem Formular verwendet, und habe herausgefunden, dass jQuery autocomplete ein 'autocompleteselect'-Ereignis auslöst

Das gab mir diese Idee.

<input data-bind="value: text, valueUpdate:['blur','autocompleteselect'], jqAutocomplete: autocompleteUrl" />

Mit dem Handler einfach sein:

ko.bindingHandlers.jqAutocomplete = {
   update: function(element, valueAccessor) {
      var value = valueAccessor();

      $(element).autocomplete({
         source: value,
      });
   }    
}

Dieser Ansatz hat mir gefallen, weil er den Handler einfach hält und jQuery-Ereignisse nicht in mein Ansichtsmodell einfügt .. Hier ist eine Geige mit einem Array anstelle einer URL als Quelle. Dies funktioniert, wenn Sie auf das Textfeld klicken und die Eingabetaste drücken.

https://jsfiddle.net/fbt1772L/3/

0
avid

Niemeyers Lösung ist großartig, jedoch stoße ich auf ein Problem, wenn ich versuche, Autocomplete in einem Modal zu verwenden. Autocomplete wurde beim Modal Close-Ereignis zerstört (Uncommail-Fehler: Methoden können vor der Initialisierung nicht auf Autocomplete aufgerufen werden; es wurde versucht, die Methode 'option' aufzurufen.)

mappedSource.subscribe(function (newValue) {
    if (!$(element).hasClass('ui-autocomplete-input'))
         $(element).autocomplete(options);
    $(element).autocomplete("option", "source", newValue);
});
0
Jerry

Eine weitere Variation der ursprünglichen Lösung von Epstone.

Ich habe versucht, es zu verwenden, fand aber auch heraus, dass das Ansichtsmodell nur aktualisiert wurde, wenn ein Wert manuell eingegeben wurde. Wenn Sie einen Eintrag für die automatische Vervollständigung auswählen, wird das Ansichtsmodell mit dem alten Wert angezeigt. Dies ist ein wenig bedenklich, da die Validierung immer noch erfolgreich ist. Das Problem tritt nur auf, wenn Sie in der Datenbank nachsehen.

Die Methode, die ich verwendet habe, besteht darin, den Select-Handler der jquery-UI-Komponente in das Knockout-Bindungs-Init einzuhaken, wodurch das Knockout-Modell einfach aktualisiert wird, wenn ein Wert ausgewählt wird. In diesem Code ist auch die Entsorgungslösung aus Georges nützlicher Antwort oben enthalten.

init: function (element, valueAccessor, allBindingsAccessor) {

        valueAccessor.select = function(event, ui) {
            var va = allBindingsAccessor();
            va.value(ui.item.value);
        }

        $(element).autocomplete(valueAccessor);

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            $(element).autocomplete("destroy");
        });

    }
...
                    <input class="form-control" type="text" data-bind="value: ModelProperty, ko_autocomplete: { source: $root.getAutocompleteValues() }" />

Das funktioniert jetzt ziemlich gut. Es ist beabsichtigt, gegen ein vorinstalliertes Array von Werten auf der Seite vorzugehen, anstatt eine API abzufragen.

0