I have a Property model and a Pricing Summary model, which relate to each other and are shown below:
App.Property = DS.Model.extend({
totalRoomCount: DS.attr(),
name: DS.attr(),
address: DS.attr(),
city: DS.attr(),
state: DS.attr(),
zip: DS.attr(),
pricingSummaries: DS.hasMany('pricingSummary', {async: true})
});
App.PricingSummary = DS.Model.extend({
startDate: DS.attr(),
endDate: DS.attr(),
days: DS.hasMany('day', {async: true}),
property: DS.belongsTo('property', {async: true})
});
Inside of my Property route I set the model to a Property, and then in the template, I want to output a list of the PricingSummary's that are related to that Property, as follows:
{{#each pricingSummary in pricingSummaries}}
{{render 'summaryRow' pricingSummary}}
{{/each}}
This works, and I'm able to output the attributes of each particular PricingSummary inside of the summaryRow template, like its startDate and endDate, for example. But what I REALLY want to do here is modify/format the startDate and output this formatted version. Basically I think I want a controller at this point, but I don't know how to tie a controller to the specific Pricing Summary model being output.
How do I do this? And furthermore, you can see that a PricingSummary also has a relationship to my Day model, so I'm going to want to do this again, another level deep.
Please help!
There are several ways to accomplish this, and all of them are relatively simple.
In relation to actually decorating a model, the easiest method would be to create a computed property on the model itself. Some people don't like this because they believe the models should be skinny and decorators should be in controllers/components, but it's all up to your preference. You could accomplish it this way:
App.YourModel = DS.Model.extend({
date: attr('date'),
formattedDate: function() {
var date = this.get('date');
return date ? this.get('date').toDateString() : null ; // Use your own format :-)
}.property('date')
});
Alternatively, I like to use a getter/setter pattern so you can use two-way bindings and it will marshal the value to a date on set, or to a string on get. In the following example, I'm using moment.js to parse/format:
App.YourModel = DS.Model.extend({
date: attr('date'),
dateMarshal: function(key, value) {
if (arguments.length > 1) {
var parsed = moment(value);
this.set('date', parsed.isValid() ? parsed.toDate() : null);
}
return this.get('date') ? moment(this.get('date')).format('MM/DD/YYYY') : null ;
}.property('date'),
});
Another option would be to provide an itemController property to the {{#each}} helper, but that's effectively the same as using render without having to use a custom view.
If you're using more properties and perhaps some actions on the pricing summary row (to delete it, for instance), my preference would be to use a component:
{{#each pricingSummary in pricingSummaries}}
{{pricing-summary-item content=pricingSummary}}
{{/each}}
And your component:
App.PricingSummaryItem = Ember.Component.extend({
content: null,
dateFormatted: function() {
var formattedDate = this.get('content.date');
// Format your date
return formattedDate;
}.property('content.date')
actions: {
'delete': function() {
this.get('content').deleteRecord();
},
markRead: function() {
this.set('content.isRead', true);
this.get('content').save();
}
}
});
Finally, to address JUST the date issue and not decoration, I would make a bound helper. Again, this example uses moment.js (and I'm using ember-cli as well, so pardon the ES6 syntax):
import Ember from 'ember';
export function formatDate(input, options) {
input = moment(input);
if (options.hashContexts.fromNow) {
return input.fromNow();
} else {
return input.format(options.hash.format || 'LL');
}
}
export default Ember.Handlebars.makeBoundHelper(formatDate);
Then you just use {{format-date yourDateProperty}} in your template.
Related
i have two models:
User:
export default DS.Model.extend({
books: hasMany('book', { async: true }),
});
book:
export default DS.Model.extend({
title: DS.attr('string'),
state: DS.attr('number'),
});
in my of my controller i am getting one user as my model and i want to create a computed property like this
activeBooks: Ember.computed('model.books', function() {
var books = this.get('model.books').filter(function (book, index, array) {
// debugger;
return (this.get('book.state') === 1);
}.bind(this));
return books;
}),
but the filter is not working and basically return a empty array.
P.S 1. i am side loading books alone with user. and at the debugger line i query for this.get('user.books.length') i get the correct number of books for each user. could someone point out what i am doing wrong here?
thanks a lot!
You can use filterBy and return the result.
activeBooks:Ember.computed('model.books', function() {
return this.get('model.books').filterBy('state',1);
})
You don't need to use bind(this) inside filter function.
My ember app is not sending my foreign key to the back-end.
I have a table called issues which is has a related table called categories
My model is:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
category_id: DS.belongsTo('category'),
description: DS.attr('string')
});
My route is:
import Ember from 'ember';
export default Ember.Route.extend({
model: function(){
return this.store.findAll('issue');
},
actions: {
create: function(){
var issue = this.store.createRecord('issue');
issue.name = this.get('controller').get('newName');
issue.description = this.get('controller').get('newDescription');
issue.category_id = parseInt(this.get('controller').get('newCategory'));
//debugger;
console.log(issue);
issue.save();
},
...
other actions
...
}
}
});
the console.log from above looks like the category_id is getting set correctly:
category_id: 3
description: "foobar"
name: "test"
However my JSON payload that gets sent to the backend looks like:
{"issue":{"name":"test","description":"foobar","category_id":null}}
I tried stepping through by adding a custom serialiser in app/serializers/application.js
export default DS.RESTSerializer.extend({
...
serialize: function(snapshot,options){
console.debug('options='+options);
debugger;
var json = this._super(snapshot, options);;
return json;
}
...
});
But I got lost in all the super calling super indirection.
The snapshot.record has category_id: 3, but the json coming back from the this._super() call has category_id: null
options has includeID:true
Any clues will be much appreciated ...
Ember : 2.0.2
Ember Data : 2.0.0
Your model definition is wrong, when dealing with relationships you define them just as you would define any other attribute, there is no need to use _id.
export default DS.Model.extend({
name: DS.attr('string'),
category: DS.belongsTo('category'),
description: DS.attr('string')
});
As for the creation you should always use setters/getters when dealing with ember objects:
create: function() {
var issue = this.store.createRecord('issue', {
name: this.get('controller').get('newName'),
description: this.get('controller').get('newDescription'),
category: this.get('controller').get('newCategory') // assuming new category is a DS.Model instance of category
});
issue.save();
}
If you wish to stick to the syntax you have you would use issue.set('name', this.get('controller').get('newName')), from the looks of your code it seems you are going about this in the wrong way.
You should have a this.route('new') nested under your issues route, that way you wouldn't have to use the controller to store information.
You would simply set the model of the new route to:
model: function() {
return this.store.createRecord('issue');
}
Your template would make use of the input helpers like so:
{{input value=model.name}} and your action would just get the currentModel and call .save().
I am using Ember 1.13.2 and Ember Data 1.13.4. The API conforms to JSON API format (http://jsonapi.org/format).
A user has many items. Doing {{model.items}} in the template will return ALL items of the user.
What if I also need to display ONLY blue items from the user. How should I go about this?
// Route
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
// Executes: http://localhost:3099/api/v1/users/5
return this.store.findRecord('user', params.user_id);
}
})
// Template
firstName: {{model.firstName}} - works
<br>items: {{model.items}} - works
<br>blue items: {{model.items}} - what do we do about this?
// app/models/user.js
import DS from 'ember-data';
export default DS.Model.extend({
items: DS.hasMany('item', { async: true }),
firstName: DS.attr('string')
});
// app/models/item.js
import DS from 'ember-data';
export default DS.Model.extend({
user: DS.belongsTo('user', { async: true }),
name: DS.attr('string')
});
I misunderstood the original question. It seems as if you want to fetch only the items where the color is blue (and avoid fetching the rest). For this, you'll need to query the server, which requires server-side code. But, once you have the server-side code done, you can do something like this:
blueItems: Ember.computed('items.#each.color', {
get() {
const query = {
user: this.get('id'),
color: 'blue'
};
return this.get('store').find('item', query);
}
})
But again, you'll need your server to support querying for that data. (The JSON API states how you need to return the data, but you'll need to implement the query yourself.)
Old answer that filters the items after fetching for display (just for reference):
I would use a computed property:
blueItems: Ember.computed('items.#each.color', {
get() {
return this.get('items').filter((item) => {
return item.get('color') === 'blue';
});
}
})
Or the shorthand ;)
blueItems: Ember.computed.filterBy('items', 'color', 'blue')
Not every operation has an Ember shorthand which is why I gave the full example first.
Using computed properties with promises is sometimes tricky, but this computed property should update whenever your items array updates.
I have a pretty basic setup where I'm trying to format a date in my Controller. The problem is I can't access it in the formattedStart function below, whereas I CAN access it in the summaryRowAction handler. This is baffling me, because console.logging this in both places gives the same result. But for some reason inside of formattedStart, this.get('model.startDate') is undefined.
App.SummaryRowController = Ember.ObjectController.extend({
formattedStart: function(){
console.log(this.get('model.startDate');)
return this.get('model.startDate');
}.property(),
actions: {
summaryRowAction: function(){
console.log(this.get('model.startDate'));
}
}
});
Here is my model and my template (in Jade) for reference:
App.PricingSummary = DS.Model.extend({
startDate: DS.attr(),
endDate: DS.attr(),
days: DS.hasMany('day', {async: true}),
property: DS.belongsTo('property', {async: true})
});
script(type="text/x-handlebars", data-template-name="summaryRow")
.summaries__summary("{{action 'summaryRowAction'}}")
.summaries__summary--item{{formattedStart}} — {{endDate}}
It's because the first (and only) time that the property is evaluated, model is actually null. You need to specify startDate as a dependency in the property so Ember knows to re-evaluate when the data changes. Also, you don't need model.* in an object controller, the properties are automatically delegated to content/model
So:
formattedStart: function(){
console.log(this.get('startDate');)
return this.get('startDate');
}.property('startDate'),
I have a model that has a one to many relationship with multiple translations:
App.Category = DS.Model.extend({
translation_ids: DS.hasMany('translation', { embedded: 'always' }),
});
App.Translation = DS.Model.extend({
name: DS.attr(),
locale: DS.attr()
});
I want to fetch the name of the category according to the selected language :
App.CategoryController = Ember.ObjectController.extend({
needs: ['settings'],
currentLocale: Ember.computed.alias('controllers.settings.currentLocale'),
name: function() {
var translations = this.get('translation_ids').filterBy('locale', this.get('currentLocale'));
Ember.assert("Only one translation is expected", translations.length === 1);
return translations[0].get('name');
}.property('translation_ids')
});
Everything works out great. But when I edit my category, the "name" property doesn't update :
I have tried a million different things, but nothing work so far. Could someone point out my mistake?
translation_ids is an array, so you want to observe the elements in the array, not just the array itself. Use .property('translation_ids.#each').