0

For some reason the below code makes the links' click event fire twice. I'm relatively new to knockout and I think I might have done my custom binding wrong. Can anyone tell me what I've done wrong? (Btw, the reason I didn't post a fiddle is that I can't include the mapping plugin from github on jsfiddle.)

JS:

ko.bindingHandlers.activityContent = {
    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
        var content = document.createElement("p");
        content.innerHTML = '<a href="javascript:void(0)" data-bind="text: user_name, click: $parent.NavigatePage.bind($data, \'profile\', user_id)"></a>';
        element.appendChild(content);
        ko.applyBindings(bindingContext, content);
    },
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        // This will be called once when the binding is first applied to an element,
        // and again whenever the associated observable changes value.
        // Update the DOM element based on the supplied values here.
    }
};

var activities = ko.mapping.fromJS({Activities: [{
    "user_id": "52b5042d572b94ceadf6asdf1a2a5bc",
    "user_name": "Sean Templeton"
}, {
    "user_id": "52b5042d57asfda2b94ce61a2a5bc",
    "user_name": "Sean Templeton"
}, {
    "user_id": "52b5042d572b94ce61a2a5bc",
    "user_name": "Sean Templeton"
}, {
    "user_id": "52b5042d5asdfasdf72b94ce61a2a5bc",
    "user_name": "Sean Templeton"
}, {
    "user_id": "52basdf5042d572b94ce6asdf1a2a5bc",
    "user_name": "Sean Templeton"
}], NavigatePage: function(page, userId) { console.log(this); console.log(page); console.log(userId()); }});

ko.applyBindings(activities);

html:

<Ul data-bind="foreach: Activities">
    <li data-bind="activityContent: $data"></li>
</ul>
LordZardeck
  • 7,953
  • 19
  • 62
  • 119
  • Which version of KO are you using? Why do you put together html in your handler? Why don't you just inline or have a separate template (http://jsfiddle.net/eTXQJ/)? – nemesv Mar 18 '14 at 20:04
  • Because the innerHTML is dynamic, and Knockout v3.x – LordZardeck Mar 18 '14 at 20:26
  • Are you sure about the KO version? Because your code throws the "Message: You cannot apply bindings multiple times to the same element." error when using KO3.0 http://jsfiddle.net/hLpzB/ If I use some quite old KO version like 2.2.1 only then I can repro this double executed click behavior... – nemesv Mar 18 '14 at 20:32
  • yeah, sorry, for KO.3.x you need to stopBinding on the parent for that to work – LordZardeck Mar 19 '14 at 14:35

1 Answers1

0

Remove the ko.applyBindings(bindingContext, content); line in your init, and remember that update runs once on init, and then on each update.

Also, you have a larger problem. In the fiddle given above by @nemesv, by not removing the ko.applyBindings() from the custom binding, the list isn't even iterated the full 5 times.

A DIFFERENT APPROACH

Unless you have great plans for the activityContent binding beyond this example, you could dispense with the custom binding and simply register a delegate event on your <li> element in your viewModel. You could then use Knockout's ko.dataFor() to determine what's been clicked on, and then call NavigatePage(). This approach is far more performant and does not require a custom binding.

You can read about delegate events here.

  • the issue with that is we don't use jQuery where at all possible, as we are phasing it out of our application completely. In addition, this breaks our application flow as the only place html elements are located are in our templates/views. – LordZardeck Mar 19 '14 at 14:37
  • but that is an interesting look on it, and maybe another similar approach can be found later – LordZardeck Mar 19 '14 at 14:38