nicholas
nicholas

Reputation: 14593

attaching behavior to elements created by knockoutjs

Take, for example, the situation using knockoutjs:

<div id="container" data-bind="foreach:containers">
    <li class="complex-element">...</li>
</div>

Now I want to attach some complex behavior to complex-element. How do I do that? I can't just attach events directly to complex-element, as they're created and removed dynamically at runtime to match the view model. So, as I see it:

I can riddle my html with data-bind="click:..."s and data-bind="mouseenter:..."s but I would rather avoid this if I could. Maybe I'm too rooted in my old MVC ways, but adding open(), select(), or dragStart() functions and isOpen, isSelected or isDragging observable flags to my view model just makes a mess and my intuition tells me that as the app gets bigger that view model is going to become unmanageable. I'd rather keep my data and my presentation separate if possible.

Or I can use jquery delegation to attach events to something that stays fixed. Something like:

$("#container").on('click', '.complex-element button.open', function(e) {
    var elem = $(e.target).parents('.complex-element');
    ...
});

But this wouldn't work as the app gets more complex because what happens if the container is itself wrapped in an element only shown on login (<div id="wrapper" data-bind="if:isLoggedIn">...</div>). I might as well just bind all events to the body and that's a recipe for disaster.

I found a very cool article on knockmeout.net (http://www.knockmeout.net/2011/07/another-look-at-custom-bindings-for.html) that advocates using custom knockout bindings to drive complex behavior, and this seems to be an awesome solution for widget-like behaviors like autocompletes or date-pickers, but what about just simple old fashioned controllers... would this work?

I guess, after all that, my question is pretty simple: Has anyone used Knockoutjs on a really large web application? And how did you go about it?

Upvotes: 0

Views: 412

Answers (2)

madcapnmckay
madcapnmckay

Reputation: 15984

If you need to execute some arbitrary js on some rendered elements from the foreach you could use the afterAdd callback

<div id="container" data-bind="foreach: { data: containers, 
               afterAdd: somefunction }">
    <li class="complex-element">...</li>
</div>

I have used KO on a really large web app and the way I went about managing complexity was

  1. Split out viewmodels into separate using namespaces within a single master namespace.
  2. Write custom bindings for complex reusable behavior.
  3. Created entity model classes that encapsulated most of the important business logic, akin to backbone models.
  4. Ensured that the viewModels were entirely view specific and view related (helped by point 3)
  5. Kept all jquery selectors to a bare minimum, I feel dirty writing any global delegates but sometimes they are needed.

Hope this helps.

Upvotes: 2

soniiic
soniiic

Reputation: 2685

You could use a different jquery bind:

$(document).on('click', '#container>.complex-element button.open', function(e) {
    var elem = $(e.target).parents('.complex-element');
    ...
});

This would work if #container does not exist.

There is some work going on in the community to make the knockout bindings unobtrusive and easy to write. For now, knockout offers you dataFor() which can be seen here: Using unobtrusive event handlers or you can use a little library like this: Introducing the Knockout.Unobtrusive Plugin

Upvotes: 2

Related Questions