可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm using an ArrayController in my application that is fed from a Ember Data REST call via the application's Router:
postsController.connectOutlet('comment', App.Comment.find({post_id: post_id}));
For the Post UI, I have the ability to add/remove Comments. When I do this, I'd like to be able to update the contentArray of the postsController by deleting or adding the same element to give the user visual feedback, but Ember Data is no fun:
Uncaught Error: The result of a server query (on App.Comment) is immutable.
Per sly7_7's comment below, I just noticed that the result is indeed DS.RecordArray when there is no query (App.Comment.find()), but in the case where there is a query (App.Comment.find({post_id: post_id}), a DS.AdapterPopulatedRecordArray is returned.
Do I have to .observes('contentArray') and create a mutable copy? Or is there a better way of doing this?
回答1:
Here is what I ended up implementing to solve this. As proposed in the question, the only solution I know about is to create a mutable copy of the content that I maintain through add and deletes:
contentChanged: function() { var mutableComments = []; this.get('content').forEach(function(comment) { mutableComments.pushObject(comment); }); this.set('currentComments', mutableComments); }.observes('content', 'content.isLoaded'), addComment: function(comment) { var i; var currentComments = this.get('currentComments'); for (i = 0; i < this.get('currentComments.length'); i++) { if (currentComments[i].get('date') < comment.get('date')) { this.get('currentComments').insertAt(i, comment); return; } } // fell through --> add it to the end. this.get('currentComments').pushObject(comment); }, removeComment: function(comment) { this.get('currentComments').forEach(function(item, i, currentComments) { if (item.get('id') == comment.get('id')) { currentComments.removeAt(i, 1); } }); }
Then in the template, bind to the this computed property:
{{#each comment in currentComments}} ... {{/each}}
I'm not satisfied with this solution - if there is a better way to do it, I'd love to hear about it.
回答2:
A comment will be too long...
I don't know how do you try to add a record, but you can try to do this: App.Comment.createRecord({}). If all goes right, it will update automatically your controller content. (I think the result of App.Comment.find() works as a 'live' array, and when creating a record, it's automatically updated) Here is how we do this in our app:
App.ProjectsRoute = Ember.Route.extend({ route: 'projects', collection: Ember.Route.extend({ route: '/', connectOutlets: function (router) { router.get('applicationController').connectOutlet({ name: 'projects', context: App.Project.find() }); } })
and then, the handler of creating a project (in the router):
createProject: function (router) { App.Project.createRecord({ name: 'new project name'.loc() }); router.get('store').commit(); },
回答3:
Just for the record: as of today (using Ember Data 1.0.0-beta), the library takes this situation into account. When a record in an array gets deleted, the array will be updated.
If you try to delete an element on that array manually, for example by using .removeObject(object_you_just_deleted) on the model of the containing controller (which is an ArrayController, hence its model an array of records), you'll get an error like:
"The result of a server query (on XXXXX - the model you try to update manually) is immutable".
So there is no need anymore to code by hand the deletion of the record from the array to which it belonged. Which is great news because I felt like using ED and working it around all the time... :)
回答4:
Foreword
I had a similar problem and found a little tricky solution. Running through the Ember-Data source code and API docs cleared for me the fact that AdapterPopulatedRecordArray returns from the queried find requests. Thats what manual says:
AdapterPopulatedRecordArray represents an ordered list of records whose order and membership is determined by the adapter. For example, a query sent to the adapter may trigger a search on the server, whose results would be loaded into an instance of the AdapterPopulatedRecordArray.
So the good reason for immutability is that this data is controlled by the server. But what if I dont need that? For example I have a Tasklist model with a number of Tasks and I find them in a TasklistController in a way like
this.get('store').find('task',{tasklist_id: this.get('model').get('id')})
And also I have a big-red-button "Add Task" which must create and save a new record but I dont want to make a new find request to server in order to redraw my template and show the new task. Good practice for me will be something like
var task = this.store.createRecord('task', { id: Utils.generateGUID(), name: 'Lorem ipsum' }); this.get('tasks').pushObject(task);
In that case I got announced error. But hey, I want to drink-and-drive!
Solution
DS.AdapterPopulatedRecordArray.reopen({ replace: DS.RecordArray.replace })
So that's it. A little "on my own" ember flexibility hack.