Parent-child controller relationships without using routes


#1

I’ve been struggling with finding the best design for what seems like a fairly common use case. I’m creating a standard Facebook-like feed with posts and comments. The problem I’m encountering is that most of the responsibility for setting relationships between controllers seems to fall on the routing layer. But how do you establish parent-child relationships between controllers when all posts, nested comments and new comment forms are visible at the same time?

The controller structure I’m working with is: PostsController -> PostController - > CommentsController - > CommentController / CommentNewController

I use itemController on the each helper to create non-singleton controllers for posts and comments, and this seems to work:

{{each controller itemController="comment" itemViewClass="Cultivate.CommentView"}}

But how do I access the parent controller from within the child? I tried to use needs but that doesn’t seem to work. In the guides (Managing dependencies between controllers) the parent-child relationship that needs depends on is a result of using a nested route, but posts and comments are not part of the routing structure if you’re displaying a full feed at the same time.

I also use the render helper to create child controllers, but I can’t use needs to access the parent controller in there either.

  {{render "comments" comments}}
  {{render "comments/new"}}

Am I missing something? How would other people approach a structure like this?


#2

Can you tell us what went wrong? :slight_smile:

It would also be a lot easier for people to help you if you made a small JSBin or JSFiddle with your example that doesn’t work.


#3

What happens if I specify needs: ‘post’ is that controllers.post = null. It does work if I’m referring to a singleton controller.

I tried constructing a JSBin example, but there doesn’t seem to be a build for the latest master version of Ember. In my original code I use render with a model parameter, but for the example I’ve switched to using the experimental control helper. (This seems to be the commit that added support for a model parameter to render: https://github.com/emberjs/ember.js/pull/2274)

Maybe I’m misunderstanding the recommended application structure, but it seems to me non-singleton controllers are fairly common and accessing parent controllers is often needed. Am I overusing controllers? How do other people tackle designs like this?

Example


#4

I could get your example working like this: http://jsbin.com/igawaq/126/edit

I changed the App.CommentsNewController to be an ObjectController with the post as content.

But how you do get in contact with parent controllers I actually don’t know. The idea with the {{control}} helper AFAIK is to have a 100% separate control (i.e. with it’s own container and all that). But with the {{render}} helper it should be possible. Maybe someone else has a good answer?

There is an open pull request for allowing bindings to be set on a {{render}} helper: https://github.com/emberjs/ember.js/pull/2225. With this you could easily pass on the post to child controllers as deep as you’d like. I hope this gets included in 1.0.


#5

Thanks for taking the time to look into this! That is a great workaround for now.

Setting controller bindings on render would definitely help, but might (in more complex cases) end up pushing too much logic to the templates. If you use routing it would be the responsibility of the route to set up controllers and decide what should be rendered where. I don’t know how to think about this yet, but I feel those responsibilities are too tightly coupled to routing in the current structure.

Especially with the ability to use non-singleton controllers added to render I’m not sure why I would want to use control. And it seems confusing to have two separate helpers. Couldn’t using an isolated container be set as an option on the helper? What would some of the use cases for control as opposed to render be?


#6

One extra thing: needs only works with singleton controllers.

I’m not sure about that.


#7

If only the router used “real” nested resource objects (instances of Resource or RouteResource?), the resources created could be made to link up the child controllers of child resources/routes with their parent controllers automatically (I ould think!). Then in your RouteHandler (yes handler!) you could add whichever other controllers you need, including having access to the whole controller tree, and configure it as you like before rendering. Wouldn’t that be awesome!?


#9

I am also having some difficulty with parent-child relationsionship. Initially I tried doing Parent-child controller relationships without using routes and had lots of problem and quickly switched back to doing it via routes and I am still having experiencing problems.

All I am trying to do is implement a comment-feature, where a post has many comments. The post feature works but the comment feature has issues. I want to be able to display a list of comments that belongs to a single post. Then click edit on a selected comments from the many comments that belong to a single post.

Take a look at this fiddle: http://jsfiddle.net/RFLbV/11/

Clicking on either the link to comments.edit or comments.new templates returns the error:

Uncaught TypeError: Cannot read property ‘router’ of undefined

Also the CommentsEdit route is returning null.

I don’t know if you have thoughts on what I am doing wrong.


#10

Hi Mr. y,

Your example is pretty large, so it’s a little hard to get around in. And you have several things that are not used at all, so it’s even harder to figure out how you meant the app to work. Try to clean up a little, and hopefully you’ll be able to solve the problem.

Have you read some good tutorials? Check out the peepcode screencast: https://peepcode.com/products/emberjs. Or watch Tom Dale’s screencast (https://www.youtube.com/watch?v=Ga99hMi7wfY) where he basically builds the same thing as you.


#11

Hi Seilund,

Thanks for checking the fiddle. I bought the peepcode screencast and have watched Tom’s youtube video. Both did not address the dynamic segment of a nested route when using a child record.

I have cleaned up: http://jsfiddle.net/VrR2T/2/. I deleted all the controllers. There is now only 5 routes which are needed in this case and the fixtureAdapter for post and comment.

The problem is that i unable to edit a child record or the comment record. This is what the editcomment url looks like: posts/2/comments/undefined/edit.

I suspect it is because the two controllers shown below are returning null despite using modelFor to get parent record and filtering as necessary. I also tried controllerFor in the setupController but both router still returned null.

EmBlog.PostCommentRoute = Ember.Route.extend({
   model: function(params){  
     comment = this.modelFor('post').get('comments');
      return comment.get(params.comment_id);
    },

   setupController: function(controller, model) {
     controller.set('content', model);
   }
});

 EmBlog.PostEditCommentRoute = Ember.Route.extend({
     model: function(params) {
       var commentEdit = this.modelFor('post').get('comments');
       return commentEdit.get(params.comment_id); 
    },

   setupcontroller: function( controller, model) {
    controller.set('content', model);    
   }
});

This is the important part of the router. The post is nested under posts resources not shown here for brevity and everything else is nested under the post resources as shown below.

this.resource('post', {path: '/:post_id/'}, function(){
    this.route('edit', {path: '/edit'});
   
    this.route('comments', {path:  '/comments'});
    this.route('newComment');
    this.route('comment', {path: '/comments/:comment_id'});    
    this.route('editComment', {path: '/comments/:comment_id/edit'});            
});

Looking at this bit what do you think is wrong.

Thanks.


#12

Hi Seilund,

It is now fixed. Thanks and enjoy the weekend.

Cheers


#13

Nice! Could you please post your fixed solution on JsFiddle :wink:


#14

Here is the working jsfiddle: http://jsfiddle.net/VrR2T/5/

The problem was that I wasn’t passing in the correct context to #linkTo edit helper. Once I moved the {#linkTo “post.editComment” body}} into the #each block it worked.

   {{#each body in controller}}
      {{body}} <br/> 
     <p>{{#linkTo "post.editComment" body}} Edit Comment: {{body.id}} {{/linkTo}}</p>
   {{/each}}

There is a second jsfiddle which someone else helped me with. He also moved the {{#linkTo “post.editComment” body}} into the #each block but then his form his different : http://jsfiddle.net/VrR2T/4/

Both jsfiddle work but in the forms I used valueBinding=“body” , while he uses valueBinding=“content.body” as seen below. I think his form his the better one, hence my pasting it here.

     <script type="text/x-handlebars" data-template- name="post/_commentForm">
        <form {{action save on='submit'}}>
           {{view Ember.TextArea valueBinding="content.body" placeholder="body"}}
           <button type="submit"> save comment </button> 
           <button {{action 'cancel' content}}> Cancel</button>
     </form>
 </script>

Cheers


#15

Thanks @yyy :slight_smile:

Btw, the {{view Ember.TextArea valueBinding="content.body" placeholder="body"}} looks really ugly to me. Why not introduce some Handlebars helpers to encapsulate this, so you could write something like:

`{{#textarea valueBinding=“content.body” placeholder=“body”}}``

There are several form builder helpers out there already… would be nice with some more :wink: Perhaps check out the ember-bootstrap project and help introduce helpers to wrap the views. Cheers!


#16

Just chiming in in case it helps: I had similar issues in an app. I could not use the router (nesting was too deep and the relationships too complex), so I spent hours trying to combine the itemController and needs API until - as pointed at by @seilund - I realized that “needs” only works for singleton controllers.

So I ended up refactoring and putting things in the models directly (for now). It is not ideal, but if you end up having a higher “depth” of nesting, it seems to me it is the only way. Maybe that could be an option for you too? Cf here as well (the comments):

And a stripped down JSFiddle showing the needs current limitation: http://jsfiddle.net/8V9xQ/5/


#17

It looks to me like you really shouldn’t nest resources if you can avoid it!!! The current router is simply not built for that (it seems to me) :stuck_out_tongue: Perhaps it was a conscious decision? I really do think it is an anti-pattern if you have resources nested more than 2 levels. There are always ways to get around that. Think deeply about your routes. They are the core of any app. From there on, it should be “smooth sailing”, except for authentication, authorization and other “externalities” that fall outside CRUD operations.


#18

Thanks I will check out ember-bootstrap and try to introduce handlebars helper.


#19

Thanks for chiming in, I looked at the the stackoverflow link and the fiddle. they are useful and contribute to this discussion.

Do yo want to show an example of a few models demonstrating this deep nesting approach via the model. Both links demonstrate the problem but non shows an example of the solution.

I think adding some examples here will make this page very resourceful, since we have some examples here and 2 fiddles, one using itemcontroller and another fiddle using a different approach. Yours will provide the 3rd approach.

Cheers


#20

That was actually added a couple of weeks ago, but it’s nowhere in the docs yet.

See: https://github.com/emberjs/ember.js/blob/master/packages/ember-handlebars/lib/controls.js

So instead of:

{{view Ember.TextArea valueBinding="content.body" placeholder="body"}}

You can do:

{{textarea valueBinding="content.body" placeholder="body"}}

#21

Nice! Thanks :slight_smile: Yeah, the documentation needs continual updates… :stuck_out_tongue: