18

I'm building an app which has layout like below.

preview

I want to create a new post so I pressed 'new post button' and it took me to 'posts/new' route.

My PostsNewRoute is like below (I followed the method described here)

 App.PostsNewRoute = Ember.Route.extend({
     model: function() {

           // create a separate transaction, 

           var transaction = this.get('store').transaction();

           // create a record using that transaction

           var post = transaction.createRecord(App.Post, {
                title: 'default placeholder title',
                body: 'default placeholder body'
           });

          return post;
     } 
 });

It immediately create a new record, updates the list of the posts, and displays forms for new post.

preview
(source: sunshineunderground.kr)

Now I have two problems.

One is the order of post list.

I expected new post will be on top of the list, but It's on the bottom of the list.

I'm using rails as my backend and I set the Post model order to

 default_scope order('created_at DESC')

so old Post sits below within existing posts. but newly created one is not. (which isn't commited to backend yet)

Another is When I click created post in the list

I can click my newly created post in the posts list. and It took me to a post page with URL

 /posts/null

and It's very weird behavior that I must prevent.

I think there will be two solutions.

  1. When I click 'new post button' create a record and commit to server immediately and when server successfully saved my new record then refresh the posts list and enter the edit mode of newly created post.

  2. or initially set Route's model to null and create a record when I click 'submit' button in a PostsNewView.

  3. or show show only posts whose attribute is

        'isNew' = false, 'isDirty' = false, 
    

in the list..

But sadly, I don't know where to start either way...

for solution 1, I totally get lost.

for solution 2, I don't know how to bind datas in input forms with not yet existing model.

for solution 3, I totally get lost.

Please help me! which will be ember's intended way?

(I heared that Ember is intended to use same solution for every developer)

Update

now I'm using solution 3 and still having ordering issue. Here is my Posts template code.

    <div class="tools">
        {{#linkTo posts.new }}new post button{{/linkTo}}
    </div>
    <ul class="post-list">
        {{#each post in filteredContent}}
        <li>
            {{#linkTo post post }}
                <h3>{{ post.title }}</h3>
                <p class="date">2013/01/13</p>
                <div class="arrow"></div>
            {{/linkTo}}
        </li>
        {{/each}}
    </ul>
    {{outlet}}

Update

I solved this problem by filtering 'arrangedContent' ,not 'content'

 App.PostsController = Ember.ArrayController.extend({
   sortProperties: ['id'],
   sortAscending: false,
   filteredContent: (function() {

     var content = this.get('arrangedContent');

     return content.filter(function(item, index) {
       return !(item.get('isDirty'));
     });
   }).property('content.@each')

 });
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
synthresin
  • 931
  • 1
  • 9
  • 23

3 Answers3

8

We use a variation of solution 3 in several places on our app. IMHO it's the cleanest of the 3 as you don't have to worry about setup/teardown on the server side This is how we implement it:

App.PostsController = Ember.ArrayController.extend({
  sortProperties: ['id'],
  sortAscending: true,
  filteredContent: (function() {
    return this.get('content').filter(function(item, index) {
      return !(item.get('isDirty'));
    });
  }).property('content.@each')
});

Then in your Posts template you loop through controller.filteredContent instead of controller.content.

For solution 1, there are many possibilities. You could define the following event:

  createPost: function() {
    var post,
      _this = this;
    post = App.Post.createRecord({});
    post.one('didCreate', function() {
      return Ember.run.next(_this, function() {
        return this.transitionTo("posts.edit", post);
      });
    });
    return post.get("store").commit();
  }

It creates the post then sets up a promise that will be executed once "didCreate" fires on the post. This promise transitions to the post's route only after it has come back from the server, so it will have the correct ID.

Andre Malan
  • 2,043
  • 12
  • 12
  • Thank you for your solution! but I'm still curious about how to deal with solution 1 when immediate updating list is needed. – synthresin Feb 05 '13 at 13:39
  • and I couldn't find sortProperties, sortAscending, nor filteredContent property on API doc(even in Ember.SortableMixin page), is it my problem or the Documentation's problem? – synthresin Feb 05 '13 at 13:55
  • how do you find these solutions? – synthresin Feb 05 '13 at 14:20
  • 1
    Due to the fact that Ember is so new, a lot of the documentation has not been written yet. The best way to find out if you can do something is to read the actual code. In the API docs it always contains links back to the source. So if you go to "sortable mixing" there is a link back to this: https://github.com/emberjs/ember.js/blob/v1.0.0-pre.4/packages/ember-runtime/lib/mixins/sortable.js#L8. There you can see the where sortProporties etc come from. As for finding the solutions, reading the code, stackOverflow and just guessing are things I use often. – Andre Malan Feb 05 '13 at 14:27
  • so I have to be familiar with actual code.. Thank you for your favor. It was a great support for me :D – synthresin Feb 05 '13 at 14:39
  • My pleasure. As for being familiar with the code, for now, I'm afraid so. That being said it is getting better every day. Also, the fact that the API docs point to the related code means that you don't have to dig too deeply. – Andre Malan Feb 05 '13 at 14:41
  • Ok I got it. another quick question. filteredContent property isn't affected by Controller's sortAscending property when I use filteredContent in my template. Do you know anything about this? It always show newer post at the bottom. – synthresin Feb 05 '13 at 15:44
  • Hmmm, try basing filteredContent off of arrangedContent rather than content. That may work. – Andre Malan Feb 05 '13 at 16:07
  • The isDirty or isSaving were both not firing in my case eventhough the record was not assigned an id yet... So I used a work around by filtering with the items having ids return !(item.get('isDirty')); – Marwa Hack Jul 05 '13 at 10:09
3

Indeed, very nice write up. Thx for that.

Doesn't your filteredContent have to use the isNew state i.o. isDirty, otherwise the Post that is being edited will not be visible. In either case, the filteredContent property does not work in my case. I also noticed that, since I use an image as part of every element, all images will be refreshed when filteredContent is changed. This means that I see a request for every image.

I use a slightly different approach. I loop through the content and decide whether or not to display the Post in the template:

# posts.handlebars
<ul class='posts'>
  {{#each controller}}
    {{#unless isNew}}
      <li>
        <h3>{{#linkTo post this}}{{title}}{{/linkTo}}</h3>
        <img {{bindAttr src="imageUrl"}}/>
        <a {{action deletePost}} class="delete-post tiny button">Delete</a>
      </li>
    {{/unless}}
  {{/each}}
</ul>

This will only show the Post object after it is saved. The url in the H3-tag also contain the id of the newly created object i.o. posts/null.

One other thing I noticed in your question: instead of passing default values to createRecord, you can use the defaultValues property on the model itself:

So, instead of:

# App.PostsNewRoute
var post = transaction.createRecord(App.Post, {
            title: 'default placeholder title',
            body: 'default placeholder body'
       });

you can do this:

# App.Post
App.Post = DS.Model.extend({
  title: DS.attr('string', {
    defaultValue: "default placeholder title"
  }),
  body: DS.attr('string', {
    defaultValue: "default placeholder body"
  })
});

# App.PostsNewRoute
var post = transaction.createRecord(App.Post);
bazzel
  • 833
  • 7
  • 22
  • I also tried template based approach, but failed, because I didn't know how to use template language exactly. I prefer this approach. Thanks! – synthresin Feb 19 '13 at 02:40
  • A couldn't get the accepted answer to work; the list of posts would not update without a screen refresh. This answer (using #isNew in the template) worked great for me. Thanks. – GSP Sep 13 '14 at 21:38
0

I actually wrote a function to reverse the content array a while back:

http://andymatthews.net/read/2012/03/20/Reversing-the-output-of-an-Ember.js-content-array

That's a pretty old article, almost a year old, so it probably won't work as is, but the framework is there...

You can see it in action in this mini-app I wrote:

http://andymatthews.net/code/emberTweets/

Search for users in the input field at the top and watch the left side as they appear in order from newest to oldest (instead of oldest to newest).

commadelimited
  • 5,656
  • 6
  • 41
  • 77