21

I have an app which at the moment contains a view of objects of the same model. They are retrieved from the server, looped through and added to the list controller using an add method

<script>
App.Controllers.List = Em.ArrayProxy.create({
  content: Ember.A([]),
  add: function(obj){
    this.pushObject(obj);
  }
});
</script>

I am now working on a part where the user creates a new object that (after passing validation) will be added to the list and also sent to the server.

I can't find any examples on the best patter to follow for creating a new object via an input form. I can see a few options, and have semi-implemented a few but nothing feels right.

  • Create a view with appropriate form elements and a method for instantiating the model using various properties retrieved from the form elements using .get()
  • Create a model in the view's content and bind form elements to that. Include a method on the view for adding to the controller array / saving to the server
  • Create a model, add it to the controller array and open it for editing

I can kind of fight out the functionality I want, but I'd prefer make sure I am aware of best practice.

I currently have something like this (which is the second bullet on my list)

<script>
App.Views.ItemCreate = Em.View.extend({
  content: App.Models.Item.create({}),
  templateName: 'create',
  createButton: function(){

    var itemObj = this.get('content');
    var item = {};
    item.title = this.get('content').get('title');

    $.ajax({
      type: 'POST',
      url: '/test/data.json',
      data: item,
      dataType: 'json',
      success: function(responseData, textStatus, jqXHR) {
        App.Controllers.List.add(itemObj);
      }
    });
  }
});
</script>

<script type="text/x-handlebars" data-template-name="create">
  {{view Em.TextField id="create-title" valueBinding="content.title"}}
  <a href="#" {{action "createButton" }}>Create</a>
</script>

Any help greatly appreciated

NOTES

I've changed the correct answer to pangratz's. Although the other responses directly answered my question, I believe those who find this via Google should refer to the answer Pangratz provided as not only is it good MVC, but it is more Ember-y :o)

joevallender
  • 4,293
  • 3
  • 27
  • 35

2 Answers2

23

Communicating with the server is definitely something which shouldn't be done in the view. This is something a controller is for. To separate the different parts of the application further, I would even consider implementing a DataSource which handles the AJAX requests. This split makes your application more testable and each components reusable. The concrete connections of view, controller and data source are then realized via bindings.

The workflow for your case could be the following:

  • The view shows your edit form, which' values are bound to a controller
  • The view handles a save action which tells the controller to save the created object
  • The controller delegates the saving to the DataSource which then finally starts the AJAX request
  • The controller is notified when the object has been saved

You should also look at ember-data which is a client side data storage and handles all the boilerplate for you. Also have a look at The architecture of Ember.js apps - data and EmberJS: Good separation of concerns for Models, Stores, Controllers, Views in a rather complex application?


View:

App.View.ItemCreate = Ember.View.extend({
  templateName: 'create',
  createObject: function(evt) {
    var object = this.get('content');
    var controller = this.get('controller');
    controller.createObject(object);
  }
});

Controller:

App.Controllers.List = Ember.ArrayProxy.extend({
  content: [],
  createObject: function(hash) {
    var dataSource = this.get('dataSource');
    dataSource.createObject(hash, this, this.createdObject);
  },
  createdObject: function(object) {
    this.pushObject(object);
  }
});

DataSource:

App.DataSource = Ember.Object.extend({
  createObject: function(hash, target, callback) {
    Ember.$.ajax({
      success: function(data) {
        callback.apply(target, data);
      }
    });
  }
});

Glue everything together:

App.dataSource = App.DataSource.create();
App.listController = App.Controllers.List.create({
  dataSourceBinding: 'App.dataSource'
});

App.View.ItemCreate.create({
  controllerBinding: 'App.listController'
}).append();
Community
  • 1
  • 1
pangratz
  • 15,875
  • 7
  • 50
  • 75
  • 1
    Thanks too for this. I've noticed I started rewriting ember-data a bit when implementing the data source, I have already used that in a slightly different context and had some unepexted behaviour, I'll give it another go I think! – joevallender Apr 22 '12 at 14:30
  • 2
    Also, just noticed for anyone else that comes across this: in the data source createObject function, if apply is used it should be callback.apply(target,[data]) or callback.call(target,data) and you'd probably be using a self/_this/that you've passed into the ajax function instead of the raw data returned – joevallender May 02 '12 at 14:39
2

If you want to follow a strict MVC model then the model should never be created on the view but on the controller. Ember is still very young and still haven't any defined patterns, what I would do is have your model set as the content of the view (as you have already done) with all the inputs bind to the different model attributes. Then when the button is pushed:

createButton: function(){
  App.Controllers.List.create(this.get('content')); 
}

On the controller:

create: function(model) {
  if model.valid() { //validates the model
    model.save({
      onSuccess: function(response) { // callback
        var item = App.Models.Item.create(response.item)
        App.controllers.List.add(item)
      }
    })
  }

And finally the model:

save: function(options) {
  $.ajax({
    type: 'POST',
    url: '/test/data.json',
    data: item,
    dataType: 'json',
    success: options.onsuccess
  });
}

This is the way other js frameworks expect you to work. It feels a little more verbose and complex, but it keeps the things in place

Fernando Diaz Garrido
  • 3,995
  • 19
  • 22
  • Thanks for the reply. Before you replied, I ended up adding an object to my controller array and invoking its editMode whilst instantiating it. It works in my case since everything would normally be edited in place. I didn't quite have it written as nice as that (and my ajax POST was in the view!) so I'll work it closer to your version. Very clear, thanks. – joevallender Apr 20 '12 at 13:08