我正在研究habit tracking app。它是用Ember编写的,由CouchDB支持。
在主屏幕上,我想显示一个习惯列表以及每次记录事件的最后时间。以下是我的模型:
App.Habit = DS.Model.extend( {
type: DS.attr('string', { defaultValue: 'habit' } ),
name: DS.attr( 'string' ),
color: DS.attr( 'string' ),
events: DS.hasMany( 'event', { async: true, defaultValue: [] } ),
style: function() {
return 'background-color: %@'.fmt( this.get( 'color' ) )
}.property( 'color' ),
lastTime: function() {
return this
.get( 'events' )
.then( function( events ) {
var times = events.map( function( e ) {
return e.get( 'time' )
} )
return times.length == 0 ? undefined : times.sort()[0]
} )
}.property( 'events' ),
} )
App.Event = DS.Model.extend( {
type: DS.attr('string', { defaultValue: 'event' } ),
time: DS.attr( 'date' ),
habit: DS.belongsTo( 'habit' )
} )
请注意lastTime
的{{1}}属性会返回一个承诺。当我没有承诺时,数组的长度为0.我的把手模板看起来像:
Habit
<script type="text/x-handlebars" id="habits">
<div id="habits">
{{#each}}
<div class="row" {{action 'createEvent' id}}>
<div class="col-xs-1 color" {{bind-attr style=style}}></div>
<div class="col-xs-8">{{name}}</div>
<div class="col-xs-3" data-role="timer">{{format-time-numeric lastTime}}</div>
</div>
{{/each}}
</div>
</script>
助手只是:
format-time-numeric
传递给片刻的Ember.Handlebars.registerBoundHelper( 'format-time-numeric', function( time ) {
return moment( time ).format( 'YYYY/M/D @ H:mm' )
} )
是一个承诺,并呈现为一堆time
。如果我从NaN
传回承诺,则会将其呈现为format-time
。
答案 0 :(得分:2)
在这种情况下你可以使用reduceComputed
属性:
maxTime: Ember.reduceComputed('events.@each.time', {
initialValue: -Infinity,
addedItem: function(accValue, event) {
return Math.max(accValue, event.get('time'));
},
removedItem: function(accValue, event) {
if (event.get('time') < accValue ) {
return accValue;
}
}
})
答案 1 :(得分:0)
我没有Ember Data专家,但尝试在事件中使用async:false。 http://discuss.emberjs.com/t/what-is-an-async-relationship-async-true-vs-async-false/4107/4
您也可以输入调试器:http://emberjs.com/api/classes/Ember.Handlebars.helpers.html#method_debugger
答案 2 :(得分:0)
不要从
返回承诺lastTime
相反,请将其写为:
lastTime: function() {
var times = this.get('events').map(function(event) {
return event.get('time');
});
return times.length == 0 ? undefined : times.sort()[0]
} )
}.observes('events').property(),
答案 3 :(得分:0)
你的模型的逻辑是有缺陷的。它正在观察“事件”的变化,但也通过承诺获得“事件”。如果“事件”已经解决,那么您不需要承诺。如果“事件”没有,那么你就无法观察它。
模型通常不是Promises的地方 - 您的视图不了解如何显示Promise。 Promise通常在Routes中解析,结果存储在Models中。我会重新建议将它移动到Route或者如果不可能有一些动作或观察者在模型中设置属性。
选项1:如果事件已经在内存中:
见Hrishi回答
选项2:如果事件不在内存中且需要Promise:
这样的事情可能有用:
lastTime: null,
然后在你的控制器中有一个类似于此的动作(或使用其他一些触发类似代码的方式):
lastTimeAction: function() {
var self = this;
this.get( 'events' ).then( function( events ) {
var times = events.map( function( e ) {
return e.get( 'time' );
});
self.set('lastTime', times.length == 0 ? undefined : times.sort()[0]);
});
}
答案 4 :(得分:0)
引用您的评论:
我认为Ember应该等待承诺完成并将结果发送到模板。
那是......一个棘手的问题。简单地说,不,它不会等待。我能想到id 等待的唯一一次是在模型钩子中。如果您从beforeModel
,model
或afterModel
返回承诺,路由器将在继续之前暂停。但是,这不适用于控制器或模板,并且在您获得模型的异步关系时不适用于您的情况。
因此,一个可能的解决方案是暂停路由器,直到加载events
关系。你的路由器里有这样的东西:
model: function(params) {
return this.store.find('habit', params.habit_id).then(function(habit) {
// Return another promise, to continue pausing the router
return habit.get('lastTime');
});
}
这将暂停路由器(而不是渲染模板),直到您的lastTime
属性完全计算。
另一个选项(我建议)是将lastTime
属性异步插入模板中。如果更改了属性的计算方式,则可以在获取events
时更新模板:
lastTime: function() {
var events = this.get('events');
if (events.length <= 0) {
return 0; // Or some other default
}
return events.mapBy('time').sort()[0];
}.property('events.@each.time')
因此,您的模板最初会显示0,但会在events
准备好时以实时更新。
正如Michael Benin所提到的,第三个选项是将模型上的events
关系设置为同步关系。您可以立即计算您的属性,但每当您获取events
模型时,您必须在JSON中包含所有habit
模型。 (不推荐。拥抱异步。)