Как создать авто-полный combobox?



кто-нибудь знает лучший способ создать автозаполнение combobox с нокаутом JS шаблонов?



У меня есть следующий шаблон:



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


иногда этот список длинный, и я хотел бы, чтобы нокаут играл красиво, возможно, с автозаполнением jQuery или каким-то прямым кодом JavaScript, но имел мало успеха.



кроме того, jQuery.Для автозаполнения требуется поле ввода. Есть идеи?

454   9  

9 ответов:

вот это jQuery автозаполнения пользовательского интерфейса привязки, что я написал. Он предназначен для отражения options,optionsText,optionsValue,value парадигма привязки, используемая с выбранными элементами с несколькими добавлениями (вы можете запрашивать параметры через AJAX, и вы можете различать, что отображается в поле ввода, а что отображается в поле выбора, которое появляется.

вам не нужно предоставлять все варианты. Он будет выбирать значения по умолчанию для вас.

вот пример без функциональность AJAX:http://jsfiddle.net/rniemeyer/YNCTY/

вот тот же пример с кнопкой, которая заставляет его вести себя больше как поле со списком: http://jsfiddle.net/rniemeyer/PPsRC/

вот пример с опциями, полученными через AJAX: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());    
    }
};

вы бы использовали его как:

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

обновление: я поддерживаю версию этой привязки здесь: https://github.com/rniemeyer/knockout-jqAutocomplete

вот мое решение:

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

использование:

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

http://jsfiddle.net/7bRVH/214/ По сравнению с RP это очень простой, но, возможно, заполняет ваши потребности.

утилизация нужны....

оба эти решения великолепны (с Нимейером гораздо более мелкозернистым), но они оба забывают об утилизации!

они должны обрабатывать удаление, уничтожая автозаполнение jquery (предотвращение утечек памяти) с помощью этого:

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

мелкие улучшения,

прежде всего, это некоторые очень полезные советы, спасибо всем за обмен.

Я использую версию написал Epstone со следующими улучшениями:

  1. отображение метки (вместо значения) при нажатии вверх или вниз-по-видимому, это можно сделать, обработав событие focus

  2. использование наблюдаемого массива в качестве источника данных (вместо массив)

  3. добавлен одноразовый обработчик, как предложено Джордж

http://jsfiddle.net/PpSfR/

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

кстати, задание minLength как 0 позволяет отображать альтернативы, просто перемещая клавиши со стрелками без необходимости ввода текста.

пробовал решение Нимейера С jQuery UI 1.10.x, но окно автозаполнения просто не появилось, после некоторого поиска я нашел простой обходной путь здесь. Добавление следующего правила в конец вашего jQuery-ui.css файл устраняет проблему:

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

Я также использовал нокаут-3.1.0, поэтому мне пришлось заменить ko.dependentObservable(...) с Ко.вычисленный.(..)

кроме того, если ваша модель представления KO содержит некоторое числовое значение, убедитесь, что вы измените операторы сравнения: От === До == и != = to != , так что преобразование типа выполняется.

Я надеюсь, что это помогает другим

исправлена очистка ввода на проблему нагрузки для решения RP. Хотя это своего рода косвенное решение, я изменил это в конце функции:

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

для этого:

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

решение Нимейера отлично, однако я сталкиваюсь с проблемой при попытке использовать автозаполнение внутри модального. Автозаполнение было уничтожено при модальном закрытии события (Неперехваченная ошибка: не удается вызвать методы автозаполнения до инициализации; попытка вызвать метод "option") я исправил его, добавив две строки в метод subscribe привязки:

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

Я знаю, что этот вопрос старый, но я также искал действительно простое решение для нашей команды, используя это в форме, и узнал, что автозаполнение jQuery вызывает событие "autocompleteselect".

это дало мне эту идею.

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

с обработчиком просто:

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

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

мне понравился этот подход, потому что он сохраняет обработчик простым, и он не прикрепляет события jQuery в мою viewmodel. Вот скрипка с массивом вместо этого url-адреса в качестве источника. Это работает, если вы нажмете на текстовое поле, а также если вы нажмете enter.

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

еще один вариант оригинального решения Эпстона.

Я попытался использовать его, но также обнаружил, что модель представления обновляется только при вводе значения вручную. Выбор записи автозаполнения оставил модель представления со старым значением, что немного беспокоит, потому что проверка все еще проходит - это только когда вы смотрите в базе данных, вы видите проблему!

метод я использовал для подключения выберите обработчик компонента пользовательского интерфейса jQuery в нокаут привязки init, который просто обновляет модель нокаута при выборе значения. Этот код также включает в себя утилизацию сантехники из полезного ответа Джорджа выше.

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() }" />

теперь это работает довольно хорошо. Он предназначен для работы с предварительно загруженным массивом значений на странице, а не для запроса api.

Comments

    Ничего не найдено.