Ember Data, Relationships, output of attribute - ember.js

I have two entities with fixtures data: User and Role. When I load a User, it contains one Role. I would like to show the name of the role. Here is relevant code:
App.User = DS.Model.extend({
name: DS.attr('string'),
role: DS.belongsTo('App.Role'),
});
App.Role = DS.Model.extend({
name: DS.attr('string'),
});
App.User.FIXTURES = [{
id:1,
name:'user',
role:1
}];
App.Role.FIXTURES = [{
id:1,
name:'reader',
}]
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
<script type="text/x-handlebars" data-template-name="users">
{{#each controller}}
{{name}} {{role.name}}
{{/each}}
</script>
the name of role is not displayed but if I change it to {{role.id}} the id of role is displayed

i think you may need to add
users: DS.hasMany('App.User')
to the definition of your Role model.

Related

Computed property on a model in a different controller

I'm building the mandatory TODO app to learn ember.
I have tasks and tags in a belongsTo/hasMany relationship (each tag hasMany tasks). When showing tasks, I want to show a computed property on each available tag.
Models:
App.Tag = DS.Model.extend({
tasks: DS.hasMany('task', {async: true}),
..
});
App.Task = DS.Model.extend({
tag: DS.belongsTo('tag', {async: true}),
..
});
Route:
App.TasksRoute = Ember.Route.extend({
model: function(params) {
return Ember.RSVP.hash({
tasks: this.store.find('task'),
tags: this.store.find('tag')
});
},
setupController: function(controller, model) {
this.controllerFor('tasks').set('content', model.tasks);
this.controllerFor('tags').set('content', model.tags);
}
});
Tags controller:
App.TagsController = Ember.ArrayController.extend({
needs: ["tag"]
})
Tag controller:
App.TagController = Ember.ObjectController.extend({
taskCount: function() {
// FOLLOWING DOES NOT WORK
return this.get('tasks.length')
}.property('tasks')
});
Tag partial:
<ul>
{{#each tag in model}}
<li>
{{tag.name}} ({{controllers.tag.taskCount}} tasks)
</li>
{{/each}}
</ul>
The computed property 'taskCount' does not work. There is something wrong with 'this'.
Is this canonical way of doing it? And if so, what is wrong? Thanks
EDIT: FIXED
I had missed out
App.ApplicationSerializer = DS.ActiveModelSerializer.extend();
And I've used render to get the controller decoration:
{{render 'tag' tag}}
which calls the controller before rendering

Best practice for instantiating Ember model on load (LocalStorage)

I'm creating a basic store using ember with products and a shopping bag. I'm using LocalStorage as the adapter for the shopping bag so the user can come back to a cart with products they've added previously. At any one time, there should only be one shopping bag. Right now, I've set up a checker in the application route on activate to see if there's already a bag saved. If not, create one.
I also want to set-up the model correctly for it to be used in the controller and in templates. Here's my application route"
var ApplicationRoute = Ember.Route.extend({
activate: function() {
var store = this.store;
store.find('bag').then(function(bags) {
var existing_bag = bags.get('firstObject');
// If there isn't already a bag instantiated, make one and save it
if(typeof existing_bag === 'undefined') {
var new_bag = store.createRecord('bag');
new_bag.save();
}
});
},
model: function() {
return this.store.find('bag');
},
setupController: function(controller,model) {
controller.set('content', model);
}
});
export default ApplicationRoute;
Here is my bag model:
import DS from 'ember-data';
import Ember from "ember";
export default DS.Model.extend({
products: DS.hasMany('product', {async: true}),
bagProducts: DS.hasMany('bagProduct', {async: true}),
productCount: Ember.computed.alias('products.length')
});
In my controller, I'd like to check if the bag has products in it so I can display a product count:
import Ember from "ember";
export default Ember.ArrayController.extend({
bag: Ember.computed.alias("content"),
cartHasProducts: function() {
var bag = this.get('bag').objectAt('0');
return bag.get('productCount') > 0;
}.property('content.#each.productCount')
});
And my template:
<div id="bag" class="js-btn" {{action "showModal" 'bag-modal' model}}>
<i class="icon ion-bag"></i>
<p class="label">Your Bag</p>
{{#if controller.cartHasProducts}}
<div class="count">
<span>{{productCount}}</span>
</div>
{{/if}}
</div>
This works, but only after I use objectAt('0') to get the first instance of the bag. Shouldn't content just return the one instance of the bag? Is there a way to set up the model to just return the one current instance? What am I doing wrong?
I really really appreciate your help!
This could be a solution:
http://codepen.io/szines/pen/Aufmp?editors=101
(Should work with LocalStorage as well, above example uses FixtureAdapter.)
You can download both models in the store with RSVP, and in afterModel, you can check, bag already exist or not. If not, you can create one.
You can map your main model and your secondary model in setupController, so they gonna be exist in Controller and in views.
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
bagModel: this.store.find('bag'),
productList: this.store.find('product')
})
},
afterModel: function(model, transition) {
if (model.bagModel.get('length') < 1) {
model.bagModel = this.store.createRecord('bag');
}
},
setupController: function(controller, model) {
var productList = model.productList,
bagModel = model.bagModel;
model = model.bagModel;
this._super(controller, model);
controller.set('productList', productList);
}
});
Because you have only one bag as main model, better if you use ObjectController.
Controllers and models:
App.IndexController = Ember.ObjectController.extend({
productCount: Ember.computed.alias('model.products.length'),
actions: {
addProduct: function(product) {
this.get('model').get('products').pushObject(product);
}
}
});
App.Bag = DS.Model.extend({
products: DS.hasMany('product', {async: true}),
});
App.Product = DS.Model.extend({
name: DS.attr('string'),
});
App.Bag.FIXTURES = [];
App.Product.FIXTURES = [
{id: 1, name: 'First Product'},
{id: 2, name: 'Second Product'},
{id: 3, name: 'Third Product'}
]
and the index template:
Number of products: {{productCount}}
{{#each productList}}
<button {{action 'addProduct' this}}>Add {{name}}</button>
{{/each}}

Attempting to create a profile page but App.User.find() errors

I have a simple app on fiddle http://jsfiddle.net/kitsunde/qzj2n/2/
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" id="profile">
Profile Page
{{ email }}
</script>
Where I'm trying to display a profile page.
window.App = Ember.Application.create();
App.Router.map(function () {
this.resource('profile', {path: '/'});
});
App.User = DS.Model.extend({
email: DS.attr('string')
});
App.User.FIXTURES = [
{
id: 1,
email: 'herpyderp#gmail.com'
}
];
App.ProfileRoute = Ember.Route.extend({
model: function(){
return App.User.find().get('firstObject');
}
});
But I'm getting an exception:
Error while loading route: TypeError: undefined is not a function
What am I missing?
There are a few things missing. You can find the fixed fiddle here:
http://jsfiddle.net/47cHy/
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.FixtureAdapter;
App.Router.map(function () {
this.resource('profile', { path: '/' });
});
App.User = DS.Model.extend({
email: DS.attr('string')
});
App.User.FIXTURES = [
{
id: 1,
email: 'herpyderp#gmail.com'
}
];
App.ProfileRoute = Ember.Route.extend({
model: function() {
return this.store.find('user').then(function(users) {
return users.get('firstObject');
});
}
});
Your template had the id index and not the name of the route profile
You have to tell Ember specifically to use the fixture adapter.
You accessed the model directly via the global object. You should let Ember do the work via the internal resolver and use this.store.find.
.find() returns a promise. You should get the first object in the then handler and return it there.

Ember.js - Cannot render nested models

Have an upcoming weekend project and using it to evaluate Ember.js and I cannot figure out why I cannot display nested objects in my template. This does not work:
{{#each emails}}
{{email_address}}
{{/each}}
When I try just {{emails}} I get a hint that something is right:
Models:
App.Contact = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
company: DS.attr('string'),
emails: DS.hasMany('App.Email')
});
App.Email = DS.Model.extend({
contact: DS.belongsTo('App.Contact'),
emailAddress: DS.attr('string'),
});
Route:
App.Router.map(function() {
this.resource('contacts', function() {
this.resource('contact', {path: ':contact_id'});
});
});
App.ContactsRoute = Ember.Route.extend({
init: function() {},
model: function() {
return App.Contact.find();
}
});
App.ContactRoute = Ember.Route.extend({
model: function(params) {
return App.Contact.find(params.contact_id);
}
});
I have no idea what to try next. I'm using active_model_serializer in Rails. I've tried embedding, side-loading to no avail. I'm sure it's something simple that I'm missing.Thanks in advance!
When using the each helper it's recomendable to be more specific about the items you are looping over to avoid such problems.
Try the following:
{{#each email in model.emails}}
{{email.emailAddress}}
{{/each}}
This should also work:
{{#each emails}}
{{this.emailAddress}}
{{/each}}
And also, your model property is called emailAddress and not email_address.
Hope it helps.

Link route to single object, without id

I have prepared this jsfiddle. This is the code:
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 13,
adapter: 'DS.FixtureAdapter'
});
App.Router.map(function() {
// put your routes here
});
App.User = DS.Model.extend({
name : DS.attr('string'),
email : DS.attr('string'),
});
App.User.FIXTURES = [{ id: 'me', name: 'Max Smith', email: 'max.smith#email.com' }];
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
And this is the template:
<script type="text/x-handlebars" data-template-name="index">
{{#each model}}
<div class="form_labels_wrapper">
<dl class="dl-horizontal">
<dt>Id:</dt> <dd>{{id}}</dd>
<dt>Name:</dt> <dd>{{name}}</dd>
<dt>Email:</dt> <dd>{{email}}</dd>
</dl>
{{/each}}
</div>
</script>
What I want to do is to display the data of a single user. The opbject has no id (it represents the logged in user, which is session related, and has no id visible for ember). Instead, I am forced to create a list of users (with one user), and give it a fake id (me). This makes no sense in my application.
I would like to use this template, but I have no idea how to configure ember for this:
<script type="text/x-handlebars" data-template-name="index">
<div class="form_labels_wrapper">
<dl class="dl-horizontal">
<dt>Name:</dt> <dd>{{name}}</dd>
<dt>Email:</dt> <dd>{{email}}</dd>
</dl>
</div>
</script>
With this fixture:
App.User.FIXTURES = { name: 'Max Smith', email: 'max.smith#email.com' };
Note that this is a single element, and that I am not looping through the models with #each, because this should not be a list: there is only a single element.
Ember refuses to accept this. What can I do to get this to work?
You can returning an array of records from the model hook, as a result ember is generating an ArrayController for it, which expects it's content to be an array.
Change the model hook to return the single record. For instance using me as the id.
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.User.find('me');
}
});
Then your template works. See the updated jsfiddle.