问题
I've been learning ember by recreating the TodoMVC in EmberCli. Ive recreated all the functionality, but I ran into an issue and I was hoping someone could shed some light on the situation.
It seems that my Todos ArrayController will observe and fire functions when properties in my model change but not when values in my Todo ObjectController change.
I moved isEditing into the Model so that when I call editTodo
canToggle
fires. But I would prefer to store that value in my controller and not the model.
I set up a test with with a propTest boolean. on a button click I fire propToggle
but todoPropToggle
doesn't respond to the change. The only time that it does ever fire is on initialization.
Any insight would be super helpful.
TODOS CONTROLLER
import Ember from 'ember';
export default Ember.ArrayController.extend({
actions: {
createTodo: function() {
var title = this.get('newTitle');
if (!title.trim()) {
return;
}
var todo = this.store.createRecord('todo', {
title: title,
isCompleted: false,
isEditing:false
});
this.set('newTitle', '');
todo.save();
}
},
canToggle: function() {
var isEditing = this.isAny('isEditing');
return this.get('length') && !isEditing;
}.property('length','@each.isEditing'),
todoPropToggle: function() {
var hasPropTest = this.isAny('propTest');
return hasPropTest;
}.property('@each.propTest')
});
TODO CONTROLLER
import Ember from 'ember';
export default Ember.ObjectController.extend({
actions: {
editTodo: function() {
var todo = this.get('model');
todo.set('isEditing', true);
},
removeTodo: function() {
var todo = this.get('model');
todo.deleteRecord();
todo.save();
},
acceptChanges: function() {
var todo = this.get('model');
todo.set('isEditing', false);
if (Ember.isEmpty(this.get('model.title'))) {
this.send('removeTodo');
}
else {
this.get('model').save();
}
},
propToggle:function(){
this.set('propTest',!this.get('propTest'));
}
},
propTest:true,
isCompleted: function(key, value) {
var model = this.get('model');
if (value === undefined) {
return model.get('isCompleted');
}
else {
model.set('isCompleted', value);
model.save();
return value;
}
}.property('model.isCompleted')
});
回答1:
How about an alternative approach? We could toggle 'canToggle' in arrayController directly from the object controller by either using parentController or specifying needs. Avoids having to observer all the itemControllers which should be more efficient too.
TODOS CONTROLLER:
import Ember from 'ember';
export default
Ember.ArrayController.extend({
/**
* references the todo model that is currently being edited
*/
editingTodo: null,
canToggle: Ember.computed.notEmpty('editingTodo'),
actions: {
createTodo: function () {
var title = this.get('newTitle');
if (!title.trim()) {
return;
}
var todo = this.store.createRecord('todo', {
title: title,
isCompleted: false,
isEditing: false
});
this.set('newTitle', '');
todo.save();
}
}
});
TODO CONTROLLER
import Ember from 'ember';
export default
Ember.ObjectController.extend({
needs:['todos']
todos : Ember.computed.alias('controllers.todos'),
actions: {
editTodo: function () {
this.set('todos.editingTodo', this.get('model'));
},
removeTodo: function () {
var todo = this.get('model');
if (this.get('todos.editingTodo') === todo) {
this.set('todos.editingTodo', null);
}
todo.deleteRecord();
todo.save();
},
acceptChanges: function () {
this.set('todos.editingTodo', null);
if (Ember.isEmpty(this.get('model.title'))) {
this.send('removeTodo');
}
else {
this.get('model').save();
}
}
},
isCompleted: function (key, value) {
var model = this.get('model');
if (value === undefined) {
return model.get('isCompleted');
}
else {
model.set('isCompleted', value);
model.save();
return value;
}
}.property('model.isCompleted')
});
I have not tested it, but I hope you get the jist of the solution I am proposing.
回答2:
propTest
in this scenario here looks like it's a computed property, and not an observable. (edit: not that computed properties don't have underlying observables powering them, but they're different in usage enough that I like to keep them separate) It'll fire when the underlying propTest fires, but if there's no change, ember will no-op that out in the run loop. If you'd rather this be a raw observable, use the observes()
syntax. @each
will work here, but I like being explicit and have an observable update what it needs to rather than using the computed property, unless I need direct access to that property to bind with in a template.
The "only firing on execution" stems from the initial bindings from the computed property getting created. If it never fires after that, the underlying bindings you're using for the computed property @each.propTest
must be incorrect, otherwise that would indeed fire.
I think you may also be confused to the purpose of an ObjectController. An Ember.Object
can do all of the observable business that a controller can, short of having a 'model' or 'content' property backing it. It looks like you may want to go with a straight up object rather than a controller here for the todo, since ember doesn't really have a 'model' type. I'd then put the objects themselves as part of the content of the ArrayController, at which point @each would be able to iterate on them as you'd expect.
The ObjectController sits at the same level of usage as an ArrayController. You can certainly nest them as you've done here, but my spidey-sense is tingling that it's the wrong thing to do given the application. You probably don't need to have a backing controller for each todo object, you just need the todo object itself.
来源:https://stackoverflow.com/questions/25774035/observing-changes-to-an-itemcontroller-in-array-controller