Adding click binding to existing view/viewmodel in

2019-08-17 20:17发布

问题:

I'm using a third party charting tool that generates a legend and in that legend I'm making the labels clickable. It uses a callback function to generate the label

function legendLabelFormatter(label, series) {
    var linkHTML = '<a href="#" data-bind="click: drillDown(' + seriesIndex + '), clickBubble: false;">' + label + '</a>';
    seriesIndex += 1;
    return linkHTML;
}

Obviously as the above labelFormatter function is activated after binding as a result of actions the user takes, the link binding on the anchor link is not live and nothing happens when clicked.

The labelFormatter function is called multiple times as there are 5 labels in the legend. Do I have to do an applyBindings(... call on each execution of my function somehow?

All I'm trying to do is get the link to activate a function in which I'll load a new chart basically and the chart/data loaded depends on which of the 5 labels is clicked. The chart is generated by a 3rd party jquery plugin (flot) which does not exist in the dom until the user activates certain functions on my view.

Edit 1:

OK, interesting and thanks for educating me about delegated events. I'm not sure I explained myself entirely in the original post. seriesIndex is a global variable, set to 0 initially.

When the chart is created, the plugin (flot) creates the chart based on a set of data and it has a customizable function (which I've called "legendLabelFormatter") which allows me to format the label. Just setting the label to <class='label' href='#'> will result in an empty label, so I've used:

var linkHTML = '<a href="#" class="legendLabel" seriesIndex="' + seriesIndex + '">' + label + '</a>';

...as this results in a clickable label.

In my "attached" event I have:

    $(view).on('click', 'a.legendLabel', function (e) {
        this.drillDown(this.seriesIndex);
    });

I'm not sure what you were trying to achieve with incrementing seriesIndex in the event handler? It's only there to allow me to identify which label has been clicked. Flot will call the legendLabelFormatter function five times - there are 5 sets of data to be plotted). seriesIndex is only a local global variable I've set up, it's nothing to do with the viewmodel or dataset used in the chart.

Therefore this.seriesIndex simply doesn't exist as this at the point referenced in the attached function is only going to be the clicked link, surely? I tried adding "seriesIndex" as a property in the link, but it still doesn't show when I inspect the this object when the function is hit, so I've not quite got this right yet.

Edit 2 (and answer)

Whereas Eric Taylor's answer was helpful and almost there. Anyone as dim as me happening on this question may be stumped by it, so for completeness:

The jquery chart plugin calls this function as part of its label formatting routine (and does so 5 times in my case as I have 5 lots of data to show on the chart)

function legendLabelFormatter(label, series) {
    var linkHTML = '<a href="#" class="legendLabel" seriesIndex="' + seriesIndex + '">' + label + '</a>';
    seriesIndex += 1;
    return linkHTML;
}

In the "attached" durandal event handler I have this: (my "seriesIndex" property was there after all, just hidden in the attributes)

var attached = function (view) {

    $(view).on('click', 'a.legendLabel', function (e) {
        drillDown(this.attributes.seriesindex.value);
        return false;
    });
};

This successfully calls my "drilldown" function with the index of the label clicked so I can use that to load the correct data array in that function.

回答1:

You're not going to be able to dynamically add a data-bind. But, then, you don't need to.

This is an ideal candidate for delegated events. Take a look at the Knockout documentation here.

Change your linkHTML to the following:

var linkHTML = '<class='label' href='#'>'

In the attached handler of your viewModel that Durandal provides, you would have:

$(".label").click(function () {
    this.drillDown(this.seriesIndex);
    seriesIndex++;
});

or something to that effect.

A better way to express the above is this:

$(view).on('click', 'a.label', function (e) {
    this.drillDown(this.seriesIndex);
    seriesIndex++;
});

I prefer the latter as it leverages "view" available on the attached handler:

attached = function(view) {
    $(view).on('click', 'a.label', function (e) {
        this.drillDown(this.seriesIndex);
        seriesIndex++;
    });
}

What's nice about the delegated approach is that you can dynamically add your labels, and since the event is attached to a tag.class, jQuery will pick up the new label in terms of events.