varvet / serenade.js

Client side MVC framework
http://serenadejs.org
Other
524 stars 27 forks source link

Controller.loaded doesn't work as expected for non-trivial templates #107

Open wojtha opened 10 years ago

wojtha commented 10 years ago

Controller.loaded doesn't work for templates with multiple root nodes, especially when there are some DynamicNodes.

template.serenade:

form[class="form-inline" event:submit=search!]
  ...
- if @searchTopicsCount
  ...
- else
    "No results were found."
- in @pager
  ...

controller.coffee:

class SearchController
  loaded: (view, model) ->
    console.log(view, model)

Expected input to loaded is view, model, however the argument list for this template is:

<form>
undefined
undefined
model

The related code in View.prototype.render is following:

      if (typeof controller.loaded === "function") {
        controller.loaded.apply(controller, __slice.call(nodes.map(function(node) {
          return node.element;
        })).concat([model]));
      }

So first three arguments are product of node.element and this property is not defined for DynamicNodes build for @searchTopicsCount or @pager, thats why the second and third argument is undefined.

How this is suppose to work? I expected compiled view as the first argument but instead of that I got node list in first three arguments for this particular case. I need to hookup after view is rendered to initialize jQuery UI Sortable on the result set since I'm preloading the result set when the app is initialized. Is there a better way? Something like "view rendered" event?

jnicklas commented 10 years ago

Hmm, that's a good question. I think when we added this feature we didn't really consider what would happen if root nodes were dynamic nodes. So far we aren't really exposing Node objects to the user, so sending those in isn't great either. I suppose we could simply remove all undefined items before we send in to the callback, but that's not very intuitive either.

wojtha commented 10 years ago

What about moving loaded callback later in the execution flow or introducing new rendered callback?

I'm experimenting with this now:

 if (!skipCallback) {
      if (typeof controller.rendered === "function") {
        controller.rendered.apply(controller, [fragment, model]);
      }
    }
    return fragment;
  };

  return View;

and in controller:

class MyController
  rendered: (fragment, model) ->
    console.log(fragment, model)

I'm trying also to use fragment = Serenade.document.createElement('div'); since DocumentFragment can't be used as jQuery argument since its API is too basic and lacks needed selectors.

What I'm trying to achieve is something like:

class MyController
  rendered: (fragment, model) ->
    $('tbody', fragment).sortable
jnicklas commented 10 years ago

I think you might have an easier time adding a tbody[on:load=someControllerCallback] in your view. It's not super-well documented that this is possible right now, but it's easier for cases like this.

wojtha commented 10 years ago

Oh, yeah, thats it! Thank you Jonas!