0

我为 KO 做了一个 jQuery AutoComplete 绑定。它允许按术语执行服务器端搜索建议。它还允许使用通过 ID 从服务器检索的单个值填充文本框或非输入 html 元素。到目前为止,它工作正常。

当我将相同的绑定放入 ContentTemplate 时,在 GridView 的第一次渲染期间,所有绑定工作正常,从服务器检索项目中每个 id 的数据,并将正确的名称注入到 span 中。

如果我试图移动到网格的第二页,则从服务器检索主要数据,我为每个行项获取新的 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”是一个填充 api url,它通过 Id (ReviewObjectId) 提供实体。

提供 TextItem 和 ValueItem 用于将接收到的 JSON 映射到 viewModel。

我在 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

1

I think that you are ignoring the update event in your binding handler. When the page is changed, only the view model is refetched and bindings are updated, the page is not refetched. Have a look at the knockout.js documentation how to use the update function http://knockoutjs.com/documentation/custom-bindings.html:

ko.bindingHandlers.yourBindingName = {
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        // This will be called when the binding is first applied to an element
        // Set up any initial state, event handlers, etc. here
    },
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        // This will be called once when the binding is first applied to an element,
        // and again whenever any observables/computeds that are accessed change
        // Update the DOM element based on the supplied values here.
    }
};

Knockout basically watches any changes of observables touched when the binding is initialized and then invokes the update function when any of these changes.

于 2018-06-19T08:21:23.310 回答