bazzel
bazzel

Reputation: 833

createRecord called w/o params does not add object to collection

Using:

Please have a look at this jsFiddle illustrating the described problem.

I have a list of items that are displayed in a template. The template contain a linkTo helper that let's the controller add an item to the collection and is shown as a text input on the page.

Adding the item to the collection is done by the controller:

App.TodoItem = DS.Model.extend({
    title: DS.attr('string', { defaultValue: "unknown" })
});

App.Router.map(function () {
    this.resource('todo_items')
});

App.TodoItemsRoute = Em.Route.extend({
    model: function () {
        return App.TodoItem.find();
    }
});

App.TodoItemsController = Em.ArrayController.extend({
    addTodoItem: function () {
        App.TodoItem.createRecord();
    }
});

If I want the new item to be shown is the list, I have to pass params to createRecord, otherwise the item is not visible. The same behaviour can be reproduced by using Chrome's inspector and then the item can be made visible as follows:

// Open the jsFiddle http://jsfiddle.net/bazzel/BkFYd/ and select 'result(fiddle.jshell.net) in the inspector, then:
var item = App.TodoItem.createRecord();
// Nothing visible yet.

item.set('title', 'Whatever');
// Now the text input appear with the title as its value.

Is this expected behaviour and if so, what am I missing here?

Upvotes: 2

Views: 1273

Answers (2)

bazzel
bazzel

Reputation: 833

Thank you Ken for answering my question. It indeed feels like a more proper of way of doing this in Ember. However, I still think it's difficult to get the hang of which objects are accessible from where...

Your example inspired me to do a rewrite of my code. I also made some changes to your approach:

  • I'm not sure if it's the best practice, my I don't create a store instance. Instead I define a Store class.
  • The content for the TodoItemsNewController is set by calling the model property on the corresponding route.
  • renderTemplate in the TodoItemsNewRoute only needs the outlet key.

    <script type="text/x-handlebars">
        {{outlet}}
    </script>
    
    <script type="text/x-handlebars" data-template-name="todo_items">
        {{#linkTo 'todo_items.new'}}Add Todo Item{{/linkTo}}
        <ul>
        {{outlet "addItem"}}
        {{#each controller}}
            <li>
            {{#unless isNew}}
                {{title}}
            {{/unless}}
            </li>
        {{/each}}
        </ul>
    </script>
    
    <script type="text/x-handlebars" data-template-name="todo_items/new">
        {{view Ember.TextField valueBinding="title" placeholder="Enter title"}}
    

    window.App = Em.Application.create();
    
    App.TodoItem = DS.Model.extend({
        title: DS.attr('string', {
            defaultValue: "unknown"
        })
    });
    
    App.TodoItem.FIXTURES = [{
        id: 1,
        title: 'Lorem'
    }, {
        id: 2,
        title: 'Ipsum'
    }];
    
    App.Store = DS.Store.extend({
        revision: 11,
        adapter: DS.FixtureAdapter.create()
    });
    
    App.Router.map(function() {
        this.resource('todo_items', function() {
            this.route('new');
        });
    });
    
    App.IndexRoute = Em.Route.extend({
        redirect: function() {
            this.transitionTo('todo_items');
        }
    });
    
    App.TodoItemsRoute = Em.Route.extend({
        model: function() {
            return App.TodoItem.find();
        }
    });
    
    App.TodoItemsNewRoute = Em.Route.extend({
        model: function() {
            return App.TodoItem.createRecord();
        },
        renderTemplate: function() {
            this.render({
                outlet: 'addItem'
            });
        }
    });
    
    App.TodoItemsNewView = Em.View.extend({
        tagName: 'li'
    });
    

The updated example is on jsFiddle.

Any reviews are welcome.

Upvotes: 0

ken
ken

Reputation: 3745

I took time to redo your example the way i feel things should be done properly with Emberjs. You should rather make sure of transaction and properly define your views and then all your issues get taken care of. So here's how i think you should do this

  • Define a view for the textfield to capture the value being entered or just bind it to the model property.

  • Listing items and adding a new item to the list should be done in two different views and should not be mixed together

<script type="text/x-handlebars">
    {{outlet}}
    <div>
        {{outlet 'addItem'}}
    </div>
</script>

<script type="text/x-handlebars" data-template-name="todo_items">
    {{#linkTo 'todo_items.new'}}Add Todo Item{{/linkTo}}
  <ul>
    {{#each item in controller}}
        <li>
            {{#unless item.isNew}}
              {{item.title}}
            {{/unless}}
        </li>
    {{/each}}
    </ul>
</script>
  • Define different states for listing items and adding a new one

  • To benefit from automatic binding of your text field value to the model property, you need to associate an ObjectController to the TodoItemsNew route

  • Finally, make use of transaction to create and commit records to the store
window.App = Em.Application.create();

App.TodoItem = DS.Model.extend({
    title: DS.attr('string')
});

App.TodoItem.FIXTURES = [{
    id: 1,
    title: 'Lorem'
}, {
    id: 2,
    title: 'Ipsum'
}];

App.store = DS.Store.create({
    revision: 11,
    adapter: DS.FixtureAdapter.create()
});

App.Router.map(function () {
    this.resource('todo_items',function(){
      this.route('new');
    })
});

App.IndexRoute = Em.Route.extend({
    redirect: function () {
        this.transitionTo('todo_items');
    }
});

App.TodoItemsRoute = Em.Route.extend({
    model: function () {
        return App.TodoItem.find();
    }
});

App.TodoItemsNewRoute = Em.Route.extend({
    transaction: App.store.transaction(),

    setupController:function(controller) {
        console.info(controller.toString());
        controller.set('content',this.transaction.createRecord(App.TodoItem));
    },

    renderTemplate: function() {
        this.render('addItem',{
            into:'application',
            outlet:'addItem',
        })
    },
    events: {
      addItem: function() {
          this.transaction.commit();
            this.transitionTo('todo_items');
      }
    }
});


App.TodoItemsController = Em.ArrayController.extend();
App.TodoItemsNewController = Em.ObjectController.extend();
App.TextField = Ember.TextField.extend({
    insertNewline: function () {
      this.get('controller').send('addItem')
    }
});

Here' is a working version of the example on jsfiddle. Hopefully, i helped with this example clarify some of your issues.

Upvotes: 2

Related Questions