0

KO 用の jQuery AutoComplete バインディングを作成しました。用語による提案のサーバー側検索を実行できます。また、ID によってサーバーから取得した単一の値をテキスト ボックスまたは非入力 html 要素に入力することもできます。これまでのところ、問題なく動作しています。

同じバインディングを ContentTemplate に配置すると、GridView の最初のレンダリング中にすべてのバインディングが正常に機能し、アイテム内の各 ID のデータがサーバーから取得され、正しい名前がスパンに挿入されます。

グリッドの 2 番目のページに移動しようとすると、メイン データがサーバーから取得され、各行項目の新しい ReviewObjectId-s が取得されますが、サーバーは要求されません (Chrome デバッガーのネットワーク タブに要求はありません) )、さらにバインディングはまったく初期化されていないため、名前は前のページとまったく同じように表示されます。ほとんどの場合、ページャーで最後のページに移動するまで、またはページャーでより多くのページング番号がレンダリングされるまで、同じ動作が発生します。次のページをクリックするとうまくいく場合があります

各行に同じ名前を表示するように DataSource をフィルタリングすると (すべての項目に同じ対象の ReviewObjectId があります)、多くの場合、同じ結果が表示されます。

カスタムバインディングは次のようになります

<span data-bind="autoComplete:{apiOptions:{find:'/find-organization',get:'/get-organization', textItem:'name',valueItem:'id'},selectedValue: ReviewObjectId}"></span>

「検索」は、提案のリストを生成するオートコンプリート API URL です。

「get」は、ID (ReviewObjectId) によってエンティティを提供するフィル API URL です。

受け取ったJSONをviewModelにマッピングするためのTextItemとValueItemが用意されています。

GridView、DataPager、フィルターの両方で DataSource と同じ GridViewDataSet を使用しています。データソースは常にページとフィルターの値に従って正しくフィルター処理されます。

私は何を間違っていますか?どこまで掘ればいい?

UPD: バインディング:

ko.bindingHandlers.autoComplete = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        //initialize autocomplete with some optional options
        var options = valueAccessor().apiOptions;
        if (options && options.find) {
            var value = valueAccessor().selectedValue;
            var data = options.data || { data: {} };
            var searchOptions = { url: options.find, textItem: options.textItem, valueItem: options.valueItem };

            //init text on first load
            if (value() && options.get) {
                var fillOptions = { url: options.get, textItem: options.textItem, valueItem: options.valueItem };
                var fillData = value();
                var fillResult = function (data) {
                    if (data) {
                        if ($(element).is('input')) {
                            $(element).val(data);
                        } else {
                            $(element).html(data);
                        }
                    }
                };

                var promise = new Promise(function(resolve, reject) {
                    fetcher.search(fillOptions, fillData, fillResult);
                });
                if ($(element).is('input')) {
                    promise.then(fetcher.initAutoComplete(element, options, searchOptions, data, value));
                }
            }
            else {
                if ($(element).is('input')) {
                    fetcher.initAutoComplete(element, options, searchOptions, data, value);
                }
            }
        }
    },
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        //var value = ko.utils.unwrapObservable(valueAccessor()) || null;
    }
};

var fetcher = {
    initAutoComplete: function (element, options, searchOptions, data, valueAccessor) {
        $(element).autocomplete({
            delay: options.delay || 300,
            minLength: options.minLength || 3,
            classes: {
                "ui-autocomplete": "dropdown-menu"
            },
            source: function (request, response) {
                //loading values from cache
                if (request.term) {
                    var cacheKey = searchOptions.url + '-' + request.term;
                    if (cacheKey in this.cache) {
                        response(this.cache[cacheKey]);
                        return;
                    }
                }
                //querying server, contract data MUST contain Term
                data.Term = request.term;
                this.search(searchOptions, data, response);
            }.bind(this),
            select: function (e, i) {
                valueAccessor(i.item.val);
                $(element).val(i.item.label);
            }
        });
    },
    search: function(options, data, response) {
        $.ajax({
            url: options.url,
            data: JSON.stringify(data),
            dataType: "json",
            type: "POST",
            contentType: "application/json; charset=utf-8",
            success: function(responseData) {
                var textItemName = options.textItem || "value";
                var valueItemName = options.valueItem || "key";

                //cache results if exist
                var cacheKey = '';
                if (Array.isArray(responseData)) { //search by term
                    var result = $.map(responseData,
                        function (item) {
                            return {
                                label: item[textItemName],
                                val: item[valueItemName]
                            }
                        });
                    if (result.length > 0) {
                        cacheKey = options.url + '-' + data.Term;
                        this.cache[cacheKey] = result;
                    }

                } else { //init by bound value
                    if (responseData[textItemName] && responseData[textItemName].length > 0) {
                        cacheKey = options.url + '-' + responseData[valueItemName];
                        this.cache[cacheKey] = responseData[textItemName];
                    }
                }
                //send result to response
                response(this.cache[cacheKey]);
            }.bind(this),
            error: function (responseData) {
                console.log("error");
            },
            failure: function (responseData) {
                console.log("failure");
            }
        });
    },
    cache: {}
}
4

1 に答える 1