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

DotVVM:在GridViewTemplateColumn ContentTemplate中使用自定义绑定,gridview,knockout.js,data-binding,dotvvm,contenttemplate,Gridview,Knockout.js,Data Binding,Dotvvm,Contenttemplate,我为KO做了一个jQuery自动完成绑定。它允许在服务器端按术语搜索建议。它还允许用ID从服务器检索到的单个值填充文本框或非输入html元素 当我将相同的绑定放置到ContentTemplate中时,在GridView的第一次呈现期间,所有绑定都可以正常工作,条目中每个id的数据都从服务器中检索,正确的名称被注入到范围中 如果我试图移动到网格的第二个页面,则会从服务器检索主数据,我会为每个行项目获取新的ReviewObjectId-s,但不会请求服务器(chrome debugger的“网络”选

我为KO做了一个jQuery自动完成绑定。它允许在服务器端按术语搜索建议。它还允许用ID从服务器检索到的单个值填充文本框或非输入html元素

当我将相同的绑定放置到ContentTemplate中时,在GridView的第一次呈现期间,所有绑定都可以正常工作,条目中每个id的数据都从服务器中检索,正确的名称被注入到范围中

如果我试图移动到网格的第二个页面,则会从服务器检索主数据,我会为每个行项目获取新的ReviewObjectId-s,但不会请求服务器(chrome debugger的“网络”选项卡中没有请求),而且绑定根本没有初始化,名称显示与前一页完全相同。在我转到寻呼机中的最后一页或在寻呼机中呈现更多的页码之前,通常都会发生相同的行为。有时单击下一页可以完成此任务

筛选数据源以显示每行的相同名称(每个项具有相同的 target ReviewObjectId)通常显示相同的结果

自定义绑定如下所示

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

我认为您正在忽略绑定处理程序中的更新事件。更改页面时,仅会重新蚀刻视图模型并更新绑定,而不会重新蚀刻页面。查看knockout.js文档,了解如何使用
update
功能:


Knockout基本上会在绑定初始化时观察所接触的可观察对象的任何变化,然后在这些变化发生时调用
update
函数。

实际上,autocomplete是在
select function
中设置绑定属性的值,并更新文本,现在,它不应该对绑定属性的更改做出反应。重点是,当您更改GridView的页面时,其中的绑定只会更新,页面不会每次都重新蚀刻。更新甚至不会触发,我的意思是,在分页期间,仅当调用init时,才会调用更新。但是如果页面的大小与前一页不同,那么一切都正常。例如:页面大小为30。我们有6页,最后一页只有3项。1页绑定有效,2-3-4-5页的organization name字段值相同,且未输入绑定的初始化或更新函数,6页正常,绑定有效,值更改。开始分页回到5-绑定工作,4-3-2-1-都一样。可能是因为页面大小改变了,所以整个表都重新初始化了。但是当页面大小不变时,它只会更新。实际上你是对的。KO足够聪明,可以忽略空的更新函数。为更新做了一些重构&一切都很好。非常感谢。
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: {}
}
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.
    }
};