Пользовательские привязки Knockout и JQuery UI не распознают виджет при обновлении наблюдаемого массива

0

Всем привет,

Я использую 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>
Теги:
knockout.js

2 ответа

0

Документация 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/

0

Если вы пытаетесь обновить всплывающую подсказку, когда значение в вашем массиве изменится, вам придется немного изменить это значение, чтобы вы могли наблюдать за значениями объекта в вашем массиве.

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/

  • 0
    Почему if ($(element) != undefined) ? С одной стороны, использование if (element) короче и не требует одноразового вызова jQuery, но даже более важно: element просто никогда не будет неопределенным. (И, $(anything) того, $(anything) никогда не будет неопределенным для начала, это всегда будет объект jQuery)
  • 0
    @ Томалак Я не пытался пересмотреть существующий код. Конечно, if(element) имеет больше смысла, однако, это не был вопрос ОП.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню