Ember.js sorting and filtering children of a hasMany relationship in parent route
Asked Answered
T

3

22

Update #2

I found that when I refactored the filtering logic to take place in a compound computed property within the PostController instead of within individual routes, I was able to get it working. The solution was ultimately dependent upon a single dynamic variable set by the specific #linkTo route action that triggered filtering changes within a PostController computed property. I have a lot of work to catch up on so I can't post the solution to this specific question now, but when I can I will detail an explanation of the solution below. For now I have marked @twinturbo's answer as correct for the partial but incredibly helpful guidance he gave below. Thanks again man!! Much appreciated!!

Update #1

The latest fiddle is at http://jsfiddle.net/aZNRu/14/ with @twinturbo's help, sorting the "rank" attribute of Comments in its Post parent controller is working, along with basic filtering. Still having the problem of not getting auto updating views when in a filtered route and a new comment is created.

Original Question

I see that there is talk of combining the sortable mixin with filtering functionality, but for now, as you can see in my jsfiddle example, I'm having issues with both sorting and filtering:

1) I can't figure out how to sort by a specific child attribute in the controller of its parent. If we have:

App.Post = DS.Model.extend({
    title: DS.attr('string'),
    post: DS.attr('string'),
    comments: DS.hasMany('App.Comment')
});

App.Comment = DS.Model.extend({
    post: DS.belongsTo('App.Post'),
    description: DS.attr('string'),
    isActive: DS.attr('boolean'),
    rank: DS.attr('number')
});

App.Router.map(function() {
  this.resource("posts", { path: "/" }, function() {
    this.resource('post', { path: ':post_id' }, function() {
        this.route('active');
        this.route('inactive');
     });
   });
});

I want to be able to sort each post's comments in ascending order by it's "rank" attribute. I want to do something like:

App.PostController = Ember.ObjectController.extend({
    sortProperties: ['comments.rank']

but for one, I think sortProperties only works on arrayControllers, and I don't think it can work more than one level deep. How could I achieve this?

2) The second problem is not getting auto-updating views when in a filtered route. For example, if you view the jsfiddle and go into the active filtered route by clicking "Active Comments" you get a nice filtering effect on the current data. But if you remain in the active route and create a new record that is active by clicking "Add New Comment," the record does not automatically render under "Active," and only appears if you click on another route and then return to it.

Am I setting up the route filtering incorrectly in the route or referencing it wrong in the template?

App.PostActiveRoute = Ember.Route.extend({
  setupController: function() {
    var post = this.controllerFor('post').get('model'),
    comments = post.get('comments');

    var activeComments = comments.filter(function(comment) {
      if (comment.get('isActive')) { return true; }
    });

    this.controllerFor('post').set('filteredComments', activeComments);
  }
});


<ul>
    {{#each comment in filteredComments}}
        <li>{{comment.rank}} {{comment.description}} - isActive: {{comment.isActive}}</li>
    {{/each}}
</ul>

Any insight you could give on these issues would be greatly appreciated!

Tema answered 30/1, 2013 at 9:57 Comment(1)
It's been a while, but any chance you can show us what you got in the end? (Will this code even still work any more?)Dishwater
K
39

but for one, I think sortProperties only works on arrayControllers, and I don't think it can work more than one level deep. How could I achieve this?

You are correct that sortProperties only works on Ember.ArrayController.

You really don't need to do anything fancy to achieve this. Simply wrap the comments array in a new ArrayProxy that includes the sortable mixin. Then you can sort the comments. Then you don't need a nest property because you're sorting an array of comments.

Please don't extend DS.ManyArray. There is no need for that.

As for sorting and filtering, you need to use composition here. That means creating something like filteredContent and sortedContent. Then you can have sortedContent use filteredContent.

Update:

PostController = Ember.ObjectController.extend({
  comments: (function() {
    return Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, {
      sortProperties: ['rank'],
      content: this.get('content.comments')
    });
  }).property('content.comments')
Kamat answered 11/2, 2013 at 21:40 Comment(5)
Hi twinturbo, thanks for the response! By "wrapping the comments array in a new ArrayProxy that includes the sortable mixin" do you mean creating this arrayProxy from within the PostController or PostRoute, or defining a new Comments array/controller that is bound to the value of this.controllerFor('post').get('comments') and changing the #each comments' rendering context to the new commentsController?Tema
It's easiest to just override the comments property on the ObjectController since it's just a proxy anyway. See my updated response.Kamat
Awesome, thanks so much man! That's great how simple it is once you know the syntax. The only error in your code was not making it: return Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, { instead of return ArrayProxy.createWithMixins(Ember.SortableMixin, { . Updated fiddle is at jsfiddle.net/aZNRu/12 . Now I need to take a shot at making that comments PostController arrayProxy filterable by clicked route. Really appreciate the help!!Tema
Works perfect! In the template, we had to access the sorted comments through "controller.comments", not "content.comments", as it would appear to an Ember n00b such as myself.Lazare
One thing I wanted to point out is that while this may have been true when the answer was posted, you can sort more than one layer deep in the sortProperties argument, even if it's async. example shownUsers: function(){ return Em.ArrayProxy.createWithMixins(Em.SortableMixin, { content: this.get('shownUser.userItems'), sortProperties: ['item.startTime', 'item.endTime'], sortAscending: true }); }.property("shownUser"), In my case item is a property of userItem. Hope that helpsTso
H
2

This can be done with a computed property macro, too.

PostController = Ember.ObjectController.extend({
 commentSortProperties: ['rank:desc', 'createdAt:asc'],
 comments: Em.computed.sort('model.comments', 'commentSortProperties')
});

Take a look at the code here.

Harrisonharrod answered 30/10, 2014 at 21:28 Comment(2)
I think it should be 'commentSortProperties' instead of commentSortProperties - because second version doesn't work for me.Recusancy
this worked for me in the same situation as described in this post.Vaivode
E
0

Perhaps you can make your many-arrays sortable by extending the store and add the sortable mixin in the createManyArray method? (I did not test if it works)

App.store = DS.Store.create({
  createManyArray: function (type, clientIds) {
    var array = DS.ManyArray.create(Ember.SortableMixin, { type: type, content: clientIds, store: this });

    clientIds.forEach(function (clientId) {
        var recordArrays = this.recordArraysForClientId(clientId);
        recordArrays.add(array);
    }, this);

    return array;
  }
});

then you can bind to the arrangedContent of the array

{#each comment in filteredComments.arrangedContent}}
Episcopalism answered 30/1, 2013 at 11:49 Comment(1)
Thanks for the response! I tried implementing this solution by adding it to the App.Store in the fiddle example but was unable to get it working. As an aside, this seems like a pretty common use case, and I suspect that this could be solved by moving the comments to their own commentsController and commentsRoute, and setting up relationships between controllers via needs or controllerFor, but my attempts to do so have all ended up fruitless.Tema

© 2022 - 2024 — McMap. All rights reserved.