Programing

자동 완성 콤보 박스를 만드는 방법은 무엇입니까?

lottogame 2020. 12. 28. 07:44
반응형

자동 완성 콤보 박스를 만드는 방법은 무엇입니까?


Knockout 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 코드로 Knockout이 멋지게 재생되기를 원하지만 거의 성공하지 못했습니다.

또한 jQuery.Autocomplete에는 입력 필드가 필요합니다. 어떤 아이디어?


다음은 내가 작성한 jQuery UI 자동 완성 바인딩입니다. 거울하기위한 것입니다 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와 비교할 때 매우 기본적이지만 필요를 채울 수 있습니다.


폐기가 필요합니다 ....

두 솔루션 모두 훌륭하지만 (Niemeyer가 훨씬 더 세밀 해짐) 둘 다 폐기 처리를 잊어 버립니다!

They should handle disposals by destroying jquery autocomplete (prevent memory leakages) with this:

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

Minor improvements,

First of all these are some very useful tips, thank you all for sharing.

I'm using the version posted by Epstone with the following improvements:

  1. Display the label (instead of the value) when pressing up or down - apparently this can be done by handling the focus event

  2. Using an observable array as the data source (instead of an array)

  3. Added the disposable handler as suggested by George

http://jsfiddle.net/PpSfR/

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

Btw, specifying minLength as 0 allows displaying the alternatives by just moving the arrow keys without having to enter any text.


I tried Niemeyer's solution with JQuery UI 1.10.x, but the autocomplete box simply didn't show up, after some searching i found a simple workaround here. Adding the following rule to the end of your jquery-ui.css file fixes the problem:

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

I also used Knockout-3.1.0, so I had to replace ko.dependentObservable(...) with ko.computed(...)

In addition, if your KO View model contains some numeric value make sure you change the comparison operators: from === to == and !== to != , so that type conversion is performed.

I hope this helps others


Fixed the clearing of input on load problem for RP's Solution. Even though it's kind of an indirect solution, I changed this at the end of the function:

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

to this:

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

Niemeyer's solution is great, however I run into an issue when trying to use autocomplete inside a modal. Autocomplete was destroyed on modal close event (Uncaught Error: cannot call methods on autocomplete prior to initialization; attempted to call method 'option' ) I fixed it by adding two lines to the binding's subscribe method:

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

I know this question is old, but I was also looking for a really simple solution for our team using this in a form, and found out that jQuery autocomplete raises an 'autocompleteselect' event.

This gave me this idea.

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

With the handler simply being:

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

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

I liked this approach because it keeps the handler simple, and it doesn't attach jQuery events into my viewmodel. Here is a fiddle with an array instead of a url as the source. This works if you click the textbox and also if you press enter.

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


Another variation on Epstone's original solution.

I tried to use it but also found that the view model was only being updated when a value was typed manually. Selecting an autocomplete entry left the view model with the old value, which is a bit of a worry because validation still passes - it's only when you look in the database you see the problem!

The method I used is to hook the select handler of the jquery UI component in the knockout binding init, which simply updates the knockout model when a value is chosen. This code also incorporates the dispose plumbing from George's useful answer above.

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

This is now working pretty well. It is intended to work against a preloaded array of values on the page rather than querying an api.

ReferenceURL : https://stackoverflow.com/questions/7537002/how-to-create-an-auto-complete-combobox

반응형