Всем привет,
Я использую Knockoutjs в объединении с виджетами JQuery UI, чтобы отображать окно автозаполнения с несколькими интервалами для каждого выбранного элемента.
Я следую ниже подхода
1) В viewmodel есть наблюдаемый массив (selecteditems) и привязываем его к декларативному шаблону для отображения SPAN
2) Поле ввода, связанное с виджетами автозаполнения JQUERY UI, чтобы отображать предложения, и для каждого выбора добавьте новый элемент в массив selecteditems, используя CustomBindingHandler.
3) Используйте CustomBindingHandler для отображения виджета JQUERY UI ToolTip для каждого SPAN, которые привязаны к наблюдаемым массивам selecteditems.
Issue-, с которым я столкнулся, виджет JQuery UI ToolTip появляется в загрузке без каких-либо проблем, но всякий раз, когда происходит изменение в массиве selecteditems, виджет Tooltip не распознается в CustomBindingHandler
Любая помощь будет очень признательна.
<div>
<div style="max-height: 105px;" data-bind="foreach: selectedItems">
<span data-bind="text: name, id: id, assignToolTip: id"></span>
<input data-bind="assignAutoComplete: { rootVm: $root }" type="email" value="">
</div>
</div>
<script>
var MyViewModel = function () {
this.selectedItems = ko.observableArray(
[{ name: "eww", id: "ww" },
{ name: "aa", id: "vv" },
{ name: "xx", id: "zz" }]);
};
ko.bindingHandlers.assignToolTip = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
if ($(element) != undefined) {
var currentDataItem = ko.dataFor(element);
$(element).tooltip({
items: 'span',
track: true,
content: function () {
return "<ul><li>" + currentDataItem.name + "</li><li>" + currentDataItem.id + "</li></ul>";
}
});
}
},
};
ko.bindingHandlers.assignAutoComplete = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
if ($(element) != undefined) {
var currentDataItem = ko.dataFor(element);
$(element).autocomplete({
source: function (request, response) {
$.ajax({
url: "http://ws.geonames.org/searchJSON",
dataType: "jsonp",
data: {
featureClass: "P",
style: "full",
maxRows: 12,
name_startsWith: request.term
},
success: function (data) {
response($.map(data.geonames, function (item) {
return {
label: item.name + (item.adminName1 ? ", " + item.adminName1 : "") + ", " + item.countryName,
value: item.name
};
}));
}
});
},
minLength: 2,
select: function (event, ui) {
var settings = valueAccessor();
var rootVm = settings.rootVm;
rootVm.selectedItems.push({ name: ui.item.label, id: ui.item.label });
return false;
},
open: function () {
$(this).removeClass("ui-corner-all").addClass("ui-corner-top");
},
close: function () {
$(this).removeClass("ui-corner-top").addClass("ui-corner-all");
}
});
}
}
};
ko.applyBindings(new MyViewModel());
</script>
<script src="~/Scripts/jquery-ui-1.10.3.js"></script>
Документация API для виджета JQuery UI Tooltip предполагает, что логика всплывающей подсказки предназначена для привязки к контейнеру, а не к отдельным элементам.
Например, чтобы получить всплывающую подсказку для всех <li>
в <ul>
в чистом jQuery, вы сделали бы это:
$("ul").tooltip({
items: "li",
content: function () {
return "tooltip text for this element";
}
});
Основное преимущество заключается в том, что вам не нужно связывать/развязывать/обновлять любую подсказку при изменении дочерних элементов контейнера. Другим преимуществом является то, что это снижает нагрузку на страницу, потому что она регистрирует только одну всплывающую подсказку, а не несколько.
Вы можете (и должны!) Использовать этот подход, потому что он идеально подходит для ваших требований. У вас есть контейнер с переменным числом детей, который должен показать всплывающую подсказку с контентом, созданным по той же логике.
Поскольку мы привязываемся к контейнеру, нам нужна крошечная функция прокси-сервера в модели представления контейнера, чтобы получить для нас отдельный текст всплывающей подсказки.
Шаблон HTML:
<div data-bind="
foreach: items,
tooltip: {items: 'label', content: tooltipContentProxy}
">
<div>
<label data-bind="text: name, attr: {for: id}"></label>
<input data-bind="attr: {id: id}, value: inputVal, valueUpdate: 'keyup'" type="text" />
</div>
</div>
Инструмент пользовательского привязки tooltip
:
ko.bindingHandlers.tooltip = {
init: function (element, valueAccessor) {
var options = ko.unwrap(valueAccessor());
$(element).tooltip(options);
}
};
Обратите внимание, как мы
update
потому что эта настройка отключена от любых изменений данныхИ, наконец, наша модель взгляда:
function Item(data) {
var self = this;
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.inputVal = ko.observable("");
self.tooltipText = ko.computed(function () {
var escapedVal = $("<div>", {text: self.inputVal()}).html();
return "Hi! My value is '" + escapedVal + "'.";
});
}
function ViewModel() {
var self = this;
self.items = ko.observableArray([/* Item objects here ...*/]);
self.tooltipContentProxy = function () {
var targetItem = ko.dataFor(this);
return targetItem.tooltipText();
};
}
Теперь подсказки отображаются корректно, без каких-либо дальнейших проблем. http://jsfiddle.net/7TqpK/
Если вы пытаетесь обновить всплывающую подсказку, когда значение в вашем массиве изменится, вам придется немного изменить это значение, чтобы вы могли наблюдать за значениями объекта в вашем массиве.
var SelectedItem = function(obj){
var self = this;
self.name = ko.observable(obj.name);
self.id = ko.observable(obj.id);
self.tooltipText = ko.computed(function(){
return "<ul><li>" + self.name() + "</li><li>" + self.id() + "</li></ul>";
});
return self;
};
var MyViewModel = function () {
var self = this;
self.selectedItems = ko.observableArray(
[new SelectedItem({ name: "eww", id: "ww" }),
new SelectedItem({ name: "aa", id: "vv" }),
new SelectedItem({ name: "xx", id: "zz" })]);
return self;
};
Когда это будет завершено, вам необходимо обновить customBinding для обработки обновлений:
ko.bindingHandlers.assignToolTip = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
if ($(element) != undefined) {
var currentDataItem = ko.dataFor(element);
$(element).tooltip({
items: 'span',
track: true,
content: function () {
return currentDataItem.tooltipText();
}
});
}
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext){
if ($(element) != undefined) {
$(element).tooltip( "destroy" );
var currentDataItem = ko.dataFor(element);
$(element).tooltip({
items: 'span',
track: true,
content: function () {
return currentDataItem.tooltipText();
}
});
}
}
};
Последнее изменение необходимо, чтобы каждый раз, когда вы нажимаете на наблюдаемый массив, он должен быть экземпляром объекта SelectedItem
:
select: function (event, ui) {
var settings = valueAccessor();
var rootVm = settings.rootVm;
rootVm.selectedItems.push(
new SelectedItem({ name: ui.item.label, id: ui.item.label })
);
return false;
},
Рабочий пример: http://jsfiddle.net/infiniteloops/PLYKk/
if ($(element) != undefined)
? С одной стороны, использованиеif (element)
короче и не требует одноразового вызова jQuery, но даже более важно:element
просто никогда не будет неопределенным. (И,$(anything)
того,$(anything)
никогда не будет неопределенным для начала, это всегда будет объект jQuery)if(element)
имеет больше смысла, однако, это не был вопрос ОП.