web-dev-qa-db-ja.com

オートコンプリートコンボボックスを作成する方法は?

Knockout JSテンプレートでオートコンプリートコンボボックスを作成する最良の方法を知っている人はいますか?

次のテンプレートがあります。

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

時々、このリストは長く、KnockoutをjQueryのオートコンプリートやJavaScriptコードでうまく再生したいのですが、ほとんど成功していません。

さらに、jQuery.Autocompleteには入力フィールドが必要です。何か案は?

56
Craig Bruce

これが、私が書いたjQuery UI Autocompleteバインディングです。 optionsoptionsTextoptionsValuevalueバインディングパラダイムを選択要素でミラーリングするためのもので、いくつかの追加があります(オプションを照会できます) 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

120
RP Niemeyer

私の解決策は次のとおりです。

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と比較すると、非常に基本的ですが、おそらくニーズを満たすことができます。

44
Epstone

廃棄が必要です...

これらのソリューションはどちらも優れています(Niemeyerの方がはるかにきめ細かい)が、どちらも廃棄処理を忘れています!

次のようにして、jqueryオートコンプリートを破壊する(メモリリークを防ぐ)ことにより、廃棄を処理する必要があります。

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

マイナーな改善、

まず、これらは非常に役立つヒントです。共有してくれてありがとう。

私はEpstoneによって投稿されたバージョンを使用していますが、次の改善が行われています。

  1. 上または下に押したときに(値の代わりに)ラベルを表示します-明らかにこれはフォーカスイベントを処理することで実行できます

  2. (配列の代わりに)観察可能な配列をデータソースとして使用する

  3. Georgeが示唆するように、使い捨てハンドラーを追加しました

http://jsfiddle.net/PpSfR/

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

ところで、minLengthを0に指定すると、テキストを入力せずに矢印キーを移動するだけで代替を表示できます。

4
Antonio Inacio

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);
}
2
cakefactory

Niemeyer's solution をJQuery UI 1.10.xで試しましたが、いくつかの検索の後、オートコンプリートボックスが表示されませんでした。簡単な回避策が見つかりました here 。 jquery-ui.cssファイルの最後に次のルールを追加すると、問題が修正されます。

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

Knockout-3.1.0も使用したため、ko.dependentObservable(...)をko.computed(...)に置き換える必要がありました。

さらに、KO Viewモデルに数値が含まれている場合は、比較演算子を===から==および!==から!=に変更して、型変換が実行されるようにしてください。

これが他の人に役立つことを願っています

2
chomba

私はこの質問が古いことを知っていますが、これをフォームで使用するチームのための本当にシンプルなソリューションを探していましたが、 jQuery autocompleteは '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イベントをビューモデルにアタッチしないため、気に入っています。これは、ソースとしてURLの代わりに配列を使用したフィドルです。これは、テキストボックスをクリックしてEnterキーを押すと機能します。

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

0
avid

Niemeyerのソリューションは優れていますが、モーダル内でオートコンプリートを使用しようとすると問題が発生します。モーダルクローズイベントでオートコンプリートが破壊されました(キャッチされないエラー:初期化前にオートコンプリートでメソッドを呼び出せません;メソッド 'option'を呼び出そうとしました)バインディングのサブスクライブメソッドに2行追加して修正しました:

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

Epstoneの元のソリューションの別のバリエーション。

私はそれを使用しようとしましたが、値が手動で入力されたときにのみビューモデルが更新されていることもわかりました。オートコンプリートエントリを選択すると、ビューモデルに古い値が残ります。これは、検証がまだパスするため少し心配です。データベースを調べるときにのみ問題が発生します。

私が使用した方法は、ノックアウトバインディングinitでjquery UIコンポーネントの選択ハンドラーをフックすることです。これは、値が選択されたときにノックアウトモデルを更新するだけです。このコードには、上記のジョージの有用な回答からの廃棄配管も組み込まれています。

_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を照会するのではなく、ページにプリロードされた値の配列に対して機能することを目的としています。

0