DotVVM:在 GridViewTemplateColumn ContentTemplate 中使用自定义绑定

DotVVM: using custom bindings inside the GridViewTemplateColumn ContentTemplate

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

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

如果我试图移动到网格的第二页,主要数据是从服务器检索的,我正在为每个行项目获取新的 ReviewObjectId-s,但没有请求服务器(没有请求chrome 调试器的网络选项卡),而且绑定根本没有初始化,名称与上一页完全一样。在我转到寻呼机的最后一页或在寻呼机中呈现更多寻呼号码之前,大部分情况都会发生相同的行为。有时点击下一页就可以了

过滤数据源以显示每行相同的名称(每个项目具有相同的 target ReviewObjectId) 经常显示相同的结果。

自定义绑定如下所示

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

"find" 是自动完成的 api url,它会生成一个建议列表。

"get"是一个填充apiurl,它通过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: {}
}

我认为您忽略了绑定处理程序中的更新事件。页面更改时,仅重新获取视图模型并更新绑定,不会重新获取页面。查看 knockout.js 文档如何使用 update 函数 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 基本上会监视初始化绑定时触及的可观察对象的任何变化,然后在发生任何这些变化时调用 update 函数。