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
函数。
我为 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
函数。