Retriving models for HasMany relationship - ember.js

I have two models
Time Entry
TimeTray.TimeEntry = DS.Model.extend({
startTime: DS.attr('date'),
endTime: DS.attr('date'),
status: DS.attr('string'),
offset: DS.attr('number'),
isDeleted: DS.attr('boolean'),
task: DS.belongsTo('task'),
duration: function () {
return TimeTray.timeController.duration(this.get('startTime'), this.get('endTime'));
}.property('startTime', 'endTime'),
title: function () {
if (this.get('task')) {
return this.get('task').get('title');
}
}.property('task')
});
Task
TimeTray.Task = DS.Model.extend({
title: DS.attr('string'),
totalTime: function () {
var timeEntries = this.get('timeEntries')
for (var entry in timeEntries) {
var duration = entry.get('duration')
}
}.property('timeEntries'),
isDeleted: DS.attr('boolean'),
isRecording: DS.attr('boolean', { defaultValue: false }),
timeEntries: DS.hasMany('TimeEntry')
});
how do i get an array of timeentry entities so that i can calculate the total time spent on a task? the above method doesnt work.
the Time Entry title property works.

You have some errors in your code:
1- In that foreach
...
for (var entry in timeEntries) {
var duration = entry.get('duration')
}
...
The for ... in not work like you expected for arrays, you need to use or for(var i; i < array.length; i++) or the array.forEach(func).
2 - In the computed property totalTime you will use the duration property of the TimeEntry, you need to specify that dependency using property('timeEntries.#each.duration').
3 - Probally your timeEntries property will be fetched from the server, so you will need to use the async: true option, in your definition:
timeEntries: DS.hasMany('TimeEntry', { async: true })
4 - If your timeEntries is always empty, even the data being saved in your database. Make sure that your returned json have the timeEntries ids. For example:
{id: 1, title: 'Task 1', timeEntries: [1,2,3] }
The changed code is the following:
TimeTray.Task = DS.Model.extend({
title: DS.attr('string'),
totalTime: function () {
var duration = 0;
this.get('timeEntries').forEach(function(entry) {
duration += entry.get('duration')
});
return duration;
}.property('timeEntries.#each.duration'),
isDeleted: DS.attr('boolean'),
isRecording: DS.attr('boolean', { defaultValue: false }),
timeEntries: DS.hasMany('TimeEntry', { async: true })
});
And this is the fiddle with this sample working http://jsfiddle.net/marciojunior/9DucM/
I hope it helps

Your totalTime method is neither summing timeEntry durations nor returning a value. Also your property is not set up correctly (use #each). The correct way of doing this is:
totalTime: function () {
var timeEntries = this.get('timeEntries')
var duration = 0;
for (var entry in timeEntries) {
duration = duration + entry.get('duration');
}
return duration;
}.property('timeEntries.#each.duration'),
Or, more elegantly using getEach() and reduce():
totalTime: function () {
return this.get('timeEntries').getEach('duration').reduce(function(accum, item) {
return accum + item;
}, 0);
}.property('timeEntries.#each.duration'),

Related

ember data array UI not updating on pushObject

I have a list of product-tag that I fetch for my model.
Route:
model: function() {
return {
product_tags: this.store.find('product-tag', {merchant: merchId})
}
}
I have a component that adds tags to the model, however when after I create the record and push it into the model (as suggested on other posts) my UI still isn't updating.
addTag: function(name) {
tag = this.store.createRecord('product-tag', {
name: name
});
this.model.product_tags.toArray().addObject(tag);
tag.save();
}
//model merchant.js
export default DS.Model.extend({
user_id: DS.attr('number'),
product_tags: DS.hasMany('product-tag', {async: true})
});
//model product-tag.js
export default DS.Model.extend({
merchant: DS.belongsTo('merchant'),
name: DS.attr('string'),
});
What am I doing wrong? Thanks in advance.
You should make it array in the route, so u can use it always afterwards like u want. Your calling toArray() which makes a new Array instance, then your model is not hooked to the array u just made.
model: function() {
return {
product_tags: this.store.query('product-tag', {merchant: merchId}).then(function(pt) {
return pt.toArray();
});
}
}
var x = this.get('model.product_tags') === model's p_t // true
var y = this.get('model.product_tags').toArray() === model's p_t // false
Later on just do
addTag: function(name) {
this.get('store').createRecord('product-tag', {
name: name
}).save().then(function(saved){ 
this.get('model.product_tags').pushObject(saved);
}.bind(this);
}

Ember.js nested property calculations returning undefined

I'm having an issue with two levels of calculated properties. I'm a bit new to ember so would appreciate some pointers.
The basic problem is that there are two levels of calculated properties - one at the order level and one at the item level. The order level is dependent on the calculation on the item.
After binding to the form - the item level calculation works great and the form is updated as I change the quantity. The order total however does not seem to calculate at all. Am I missing something in the property dependencies?
App.Order = DS.Model.extend({
items: DS.hasMany('item', { async: true } ),
payment_cash: DS.attr('number'),
payment_card: DS.attr('number'),
payment_credit: DS.attr('number'),
balance: DS.attr('number'),
total: function() {
return this.get('items').reduce(function(value,lineItem) {
value += lineItem.get('total');
});
}.property("items.#each.total"),
itemCount: function() {
return this.get('items').reduce(function(value,lineItem) {
value += lineItem.get('quantity');
});
}.property("items.#each.quantity"),
});
App.Item = DS.Model.extend({
order: DS.belongsTo('item'),
product: DS.belongsTo('product'),
quantity: DS.attr('number'),
adjustment: DS.attr('number'),
total: function() {
return this.get('product.price') * this.get('quantity')
}.property('product.price', 'quantity' )
});
App.Product = DS.Model.extend( {
name: DS.attr('string'),
description: DS.attr('string'),
price: DS.attr('number'),
imagePath: DS.attr('string')
});
The problem is that your reduce function is not returning anything. Try this:
total: function() {
return this.get('items').reduce( function(value, lineItem) {
return value += lineItem.get('total');
}, 0 );
}.property("items.#each.total"),
itemCount: function() {
return this.get('items').reduce( function(value, lineItem) {
return value += lineItem.get('quantity');
} , 0);
}.property("items.#each.quantity"),

How to return computed property that was created on Ajax request (hasMany relation)

I'm trying to create a computed property in my model, but because of the Ajax request that gets called when using get, I can't return the value (I want to calculate the percentage of the tasks that are done and assigned to this project)
Model
Docket.Project = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
number: DS.attr('string'),
archived: DS.attr('boolean'),
customer: DS.belongsTo('customer'),
tasks: DS.hasMany('task',{ async: true }),
// custom attributes
progress: function() {
var done = 0,
progress = 0;
this.get('tasks').then(function(tasks) {
tasks.forEach(function(task) {
if (task.get('status') == 5) done++
});
progress = (done / tasks.get('length')) * 100;
});
console.log(progress);
return progress;
}.property('tasks')
});
Template
<span class="progress"><span class="bar"></span>{{progress}}%</span>
How can I achieve that?
You do need to add #each to your observer. Try this:
progress: function() {
var done = 0,
progress = 0;
this.get('tasks').forEach(function(task) {
if (task.get('status') == 5) done++;
});
progress = (done / tasks.get('length')) * 100;
console.log(progress);
return progress;
}.property('tasks.#each.status')

EmberJS: Accessing child controller functionality in parent

This may be more of a structure question but the heading is my current issue.
I have the following basic app:
var App = Ember.Application.create();
App.ApplicationAdapter = DS.FixtureAdapter.extend();
App.Router.map(function () {
this.resource('numbers', {
path: '/'
});
this.resource('users');
});
App.UsersRoute = Ember.Route.extend({
model: function (params) {
return this.store.findAll('user');
}
});
App.NumbersRoute = Ember.Route.extend({
model: function (params) {
return this.store.findAll('number');
}
});
App.User = DS.Model.extend({
name: DS.attr('string'),
numbers: DS.hasMany('number', {
async: true
})
});
App.Number = DS.Model.extend({
value: DS.attr('number'),
user: DS.belongsTo('user')
});
App.NumbersController = Ember.ArrayController.extend({
total: function () {
var total = 0;
this.forEach(function (number) {
total += parseFloat(number.get('value'));
});
return total;
}.property()
});
App.User.FIXTURES = [{
id: 1,
name: 'Bob',
numbers: [100, 101]
}, {
id: 2,
name: 'Fred',
numbers: [102]
}];
App.Number.FIXTURES = [{
id: 100,
value: 25,
user: 1
}, {
id: 101,
value: 15,
user: 1
}, {
id: 102,
value: 60,
user: 2
}];
Working example with templates is here: http://jsfiddle.net/sweetrollAU/9DuR3/
The example shows a relationship between users and numbers. The first page is a list of all numbers in the app, their related user and a total of all numbers. The Users link shows the same content but each user should show its own subtotal for the numbers it has.
My question is basically, how can I access the NumbersController method 'total' in my UsersController? Should I be accessing this method or do I have the structure incorrect?
Thanks
In your case they are similar logic, but they ultimately come from different data sources. You can still create a Mixin that can help you share the code amongst different Ember objects.
App.AddNumberMixin = Em.Mixin.create({
sumNumbers: function(arr){
var total = 0;
arr.forEach(function (number) {
total += parseFloat(number.get('value'));
});
return total;
}
});
App.UserController = Ember.ObjectController.extend(App.AddNumberMixin, {
total: function () {
return this.sumNumbers(this.get('numbers'));
}.property('numbers.#each.value')
});
App.NumbersController = Ember.ArrayController.extend(App.AddNumberMixin, {
total: function () {
return this.sumNumbers(this);
}.property('#each.value')
});
http://jsfiddle.net/9DuR3/2/
#kingpin2k's answer seems sufficient, but here's another way to do it:
http://jsfiddle.net/9DuR3/3/
What happens here is that the numbers are rendered again, for each users, but this time with a different template. Namely the one between the {{render}} tags. The NumbersController is provided with the user.numbers collection, instead of the complete numbers collection.
Also it's important to specify on which properties the total function dependent is (.property('#each.value')).

Execute observer once model loaded

i am trying to have an observer execute when a model is loaded
sortAttachments:function(){
console.log('sorting')
var attachments = this.get('model').get('attachments');
for(var i = 0;i<attachments.length;i++){
var a = attachments[i];
if(a.type=="Link"){
links.push(a)
}
}
}.observes('models.attachments.#each.type'),
the method is currently being called twice, if i change the observes statement to
observes('blablabla'),
it also gets called twice.
the method must only execute when the attachments property of the model updates
the model code :
App.Card = DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
left: DS.attr('number'),
top: DS.attr('number'),
user: DS.belongsTo('user', { async: true }),
attachments: DS.hasMany('attachment',{async:true}),
tags:DS.hasMany('tag',{async:true})
});
Additionally you can observe the state of the model using the current state
App.ApplicationController = Em.ObjectController.extend({
timesFired: 0,
watcher: function(){
if(this.get('model.currentState.stateName') == 'root.loaded.saved'){
this.incrementProperty('timesFired');
}
}.observes('model.currentState.stateName')
});
http://jsbin.com/aYIkAcUk/9/edit
I think that your observer is called multiple times because for each item loaded in attachments relationship, the model.attachments.#each.type is triggered. You can use Ember.run.once to collapse all these calls in a single one:
sortAttachments:function(){
console.log('sorting')
var attachments = this.get('model').get('attachments');
for(var i = 0;i<attachments.length;i++){
var a = attachments[i];
if(a.type=="Link"){
links.push(a)
}
}
},
sortAttachmentsOnce: function() {
Ember.run.once(this, this.sortAttachments);
}.observes('model.attachments.#each.type'),
I hope it helps