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"),
Related
Below is a basic ember model:
App.Person = DS.Model.extend({
firstName: DS.attr('string'),
birthday: DS.attr('date'),
day1: DS.attr('string'),
day2: DS.attr('string'),
day3: DS.attr('string')
});
For the purpose of this example, what if I had days go up to 50? Rather than going line by line... day4, day5, day6... is there a way to loop through dynamically? My first instinct is to use a MIXIN, and push these onto the object, but I don't think it would work if I had computed property:
isHoliday: function(){
if(this.get('day1') == 'off'){
return true;
}
}.property('day1'),
Given that 'this' is in there and we have a return, I don't believe you can simply 'push' this onto the model to generate something like this:
App.Person = DS.Model.extend({
firstName: DS.attr('string'),
birthday: DS.attr('date'),
day1: DS.attr('string'),
day2: DS.attr('string'),
day3: DS.attr('string'),
isHoliday: function(){
if(this.get('day1') == 'off'){
return true;
}
}.property('day1')
});
This is strange approach but still possible:
var defaults = {
firstName: DS.attr('string'),
birthday: DS.attr('date'),
isHoliday: function(){
if(this.get('day1') == 'off'){
return true;
}
}.property('day1')
}
var dayProps = {};
var count = 20;
while(count--){
dayProps['day' + (count + 1)] = DS.attr('string');
}
App.Person = DS.Model.extend(Ember.merge(defaults, dayProps));
instead defining with dynamic props, it is better to define Day model and have one-to-many relations with Person:
App.Person = DS.Model.extend({
days: DS.hasMany('day')
});
App.Day = DS.Model.extend({
person: DS.belongsTo('person')
});
I have an ember model Category:
export default DS.Model.extend({
name: DS.attr('string'),
img: DS.attr('string'),
url: DS.attr('string'),
cnt: DS.attr('number'),
// parent_id: DS.belongsTo('category', {
// inverse: 'children',
// async: true
// }),
parent_id: DS.attr('string'),
// children: DS.hasMany('category', {
// inverse: 'parent_id',
// async: true
// }),
children: DS.attr(),
isSelected: false,
isExpanded: false,
hasChildren: function() {
return this.get('children').get('length') > 0;
}.property('children').cacheable(),
isLeaf: function() {
return this.get('children').get('length') == 0;
}.property('children').cacheable()
});
In my index route I have:
export default Ember.Route.extend({
model: function() {
var store = this.store;
return Ember.ArrayProxy.create({
categories: store.find('category'),
menuTopCategories: store.find('category', { parent_id: 1 })
});
}
});
I'm using a RESTAdapter so the store.find will send two requests to the server: categories and categories?parent_id=1.
I would like to have only the first request and then filter through the categories. I tried store.all - since I saw it reuses the already fetch data, but I can't manage to apply the filter.
I've rewritten the menuTopCategories and I don't see a new request:
menuTopCategories: store.filter('category', function(category) {
return category.get('parent_id') === "1";
})
My problem right now is to get the root category (first one) without hardcoding the parent_id.
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')).
I create a shop cart. I using fixture adapter.
My models
App.Clothing = DS.Model.extend({
name: DS.attr('string')
, category: DS.attr('string')
, img: DS.attr('string')
, price: DS.attr('number')
, num: DS.attr('number')
, fullPrice: function(){
return this.get('price') + " $";
}.property('price')
})
App.CartRecord = App.Clothing.extend({
numInCart:DS.attr('number',{defaultValue:1})
, fullPrice: function(){
return this.get('price')*this.get('numInCart');
}.property('numInCart','price')
})
App.CartRecord.FIXTURES = [];
Route
App.CartRoute = Em.Route.extend({
model: function(){
return this.store.find('cartRecord');
}
})
And my controller
App.CartController = Em.ArrayController.extend({
totalPrice: 0
});
How i can calculate a total price?
You can put together a reduceComputed property for sum. Here are a few links for inspiration: one, two, and three. Basically, you can do something like this:
Ember.computed.sum = function (dependentKey) {
return Ember.reduceComputed.call(null, dependentKey, {
initialValue: 0,
addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
return accumulatedValue + item;
},
removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
return accumulatedValue - item;
}
});
};
Then, in your controller do something like this:
App.CartController = Em.ArrayController.extend({
prices: Ember.computed.mapBy('content', 'fullPrice'),
totalPrice: Ember.computed.sum('prices')
});
I have ember models called survey, question, and response. surveys have multiple questions, which have multiple responses. Each response has an attribute count.
How do I set a total_response_count computed value in the survey model? In emberjs 1.0.0, the questions are in a DS.PromiseArray (due to the async: true), so when I return the computed value, it shows up in my template as an Object rather than a value.
I can easily access responses from the question model because responses are embedded in question. However, Ember automatically makes promises for the questions referenced by survey because {async: true}.
Survey Model:
App.Survey = DS.Model.extend({
title: DS.attr('string'),
owner_id: DS.belongsTo('user'),
questions: DS.hasMany('question', {async:true}),
total_responses: function() {
var question_cb = function(prevValue, item) {
return prevValue + item.get('total_responses');
};
return this.get('questions').then(function(questions){
return questions.reduce(question_cb, 0);
});
}.property('questions')
});
Question Model:
App.Question = DS.Model.extend({
survey: DS.belongsTo('survey'),
question: DS.attr('string'),
responses: DS.hasMany('response'),
total_responses: function() {
var response_cb = function(prevValue, item) {
return prevValue + item.get('count');
};
return this.get('responses').reduce(response_cb, 0);
}.property('responses')
});
Response Model:
App.Response = DS.Model.extend({
response: DS.attr('string'),
count: DS.attr('number'),
question: DS.belongsTo('question')
});
I'm using ember-1.0.0 and ember-data 1.0 beta-2.
I also asked this question on Github, and got this response from Yehuda Katz:
You can try something like this:
App.Survey = DS.Model.extend({
title: DS.attr(),
owner: DS.belongsTo('user'),
questions: DS.hasMany({ async:true }),
totalResponses: Ember.arrayComputed('questions', {
initialValue: 0,
addedItem: function(accum, item) {
accum += item.get('totalResponses');
},
removedItem: function(accum, item) {
accum -= item.get('totalResponses');
}
})
});
When questions resolves, the addedItem callback in totalResponses will be called once for every item in the resolved array.