我为 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: {}
}