简体   繁体   中英

Custom KnockoutJS bindingHandler for dynamic Bootstrap Tooltips

I've found a couple other questions and resources here on using Bootstrap tooltips with a custom knockout binding handler. However, I haven't found is a cohesive solution that 1) involves using a dynamic knockout template 2) one where the tooltip can change when data it is bound to changes.

I'm also away of knockout-bootstrap on GitHub, but the tooltip title in that is rendered only once,

I've created a NEW JSFiddle with the following new dynamicTooltip that's based on the prior JSFiddle .

The new DynamicTooltip data binder looks like:

ko.renderTemplateHtml = function (templateId, data) {
    var node = $("<div />")[0];
    ko.renderTemplate(templateId, data, {}, node);
    return $(node).html();
};

ko.bindingHandlers.tooltip = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
    var local = ko.utils.unwrapObservable(valueAccessor()),
        options = {};

    ko.utils.extend(options, ko.bindingHandlers.tooltip.options);
    ko.utils.extend(options, local);

    var tmplId = options.kotemplate;

    ko.utils.extend(options, {
        title: ko.renderTemplateHtml(tmplId, viewModel)
    });

    $(element).tooltip(options);

    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
        $(element).tooltip("destroy");
    });
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
    var local = ko.utils.unwrapObservable(valueAccessor()),
        options = {};

    ko.utils.extend(options, ko.bindingHandlers.tooltip.options);
    ko.utils.extend(options, local);

    var tmplId = options.kotemplate;
    var forceRefresh = options.forceRefresh;
    var newdata =  ko.renderTemplateHtml(tmplId, viewModel); 
    $(element).data('bs.tooltip').options.title = newdata

},
options: {
    placement: "top",
    trigger: "hover",
    html: true
}};

It's not complete, as I'm manually triggering the update call manually by passing in a dummy databinding property on the view Model, in this case, it's called renderTooltip():

<a data-bind="tooltip: { title: firstName, placement: 'bottom', kotemplate: 'tile-tooltip-template', forceRefresh: renderTooltip() }">Hover on me</a>

I'd like to be able to trigger the tooltip to refresh itself when data changes.

I'm thinking I should be using createChildContext() and maybe controlsDescendantBindings, but I'm not really sure.

Any thoughts? I'll continue to update this, because it seems like dynamic bootstrap tooltips would be a common idea.

The root of the problem is that the update binding isn't firing, because it doesn't have a dependency on the properties that you are trying to update (ie firstName and address);

Normally you can delegate these properties to a new binding and let knockout automatically handle the dependency tracking. However, in this case, you're actually returning a string, so the element's automatic binding can't be used. A string is necessary, because that's how the tooltip works. If it could dynamically read from a DOM element, then the automatic bindings would work, but because it requires a HTML string, there's no way for the bindings to affect that.

Couple of options that I see:

1. Automatically create a dependency on the properties used by the template. This can be done by isolating the template view model (data) as seen in this fiddle: http://jsfiddle.net/tMbs5/13/

//create a dependency for each observable property in the data object
for(var prop in templateData)
    if( templateData.hasOwnProperty(prop) && ko.isObservable(templateData[prop]))
        templateData[prop]();

2. Instead of using an DOM-based template, use ko.computed to generate the template inside your view model. This will automatically create the dependencies as needed. See this fiddle for an example of that: http://jsfiddle.net/tMbs5/12/

var vm = {
    firstName: ko.observable('Initial Name'),
    address: ko.observable('55 Walnut Street, #3'),
    changeTooltip: function () {
        this.firstName('New Name');
    }
};

vm.tooltipHtml = ko.computed(function () {
    return "<h2>" + vm.firstName() + "</h2>" +
        "<span>" + vm.address() + "</span>";
});

ko.applyBindings(vm);

note: In both fiddles, I have refactored things a tiny bit - mostly for simplification

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM