Getting length of hasMany association - ember.js

I have the following Models defined in my Ember project (project created with ember-cli 0.2.3)
import DS from 'ember-data';
var Game = DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
geekRating: DS.attr('number')
});
Game.reopenClass({
FIXTURES: [
{ id: "1", title: "Catan", description: "Catan the game", geekRating: "5.65"}
]
});
export default Game;
import DS from 'ember-data';
var OwnedGame = DS.Model.extend({
rating: DS.attr('number'),
plays: DS.hasMany('gamePlay'),
game: DS.belongsTo('game', {async: true}),
playCount: function() {
return this.get('plays.length')
}.property('plays')
});
OwnedGame.reopenClass({
FIXTURES: [
{ id: "1", rating: "8.25", game: "1"}
]
});
export default OwnedGame;
import DS from 'ember-data';
var GamePlay = DS.Model.extend({
date: DS.attr('date'),
ownedGame: DS.belongsTo('ownedGame', {async: true})
});
GamePlay.reopenClass({
FIXTURES: [
{id: "1", date: "2015-01-01", ownedGame: "1"},
{id: "2", date: "2015-02-01", ownedGame: "1"}
]
});
export default GamePlay;
And then in my owned-games/index.hbs
<h1>My Games</h1>
<table class="table table-striped table-condensed table-hover">
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Your Rating</th>
<th>Geek Rating</th>
<th>Plays</th>
</tr>
</thead>
<tbody>
{{#each game in this}}
<tr>
<td>{{game.game.title}}</td>
<td>{{game.game.description}}</td>
<td>{{game.rating}}</td>
<td>{{game.game.geekRating}}</td>
<td>{{game.playCount}}</td>
</tr>
{{/each}}
</tbody>
</table>
I'm trying to display the length of the plays for an OwnedGame with a computed property. Note that as you can see I'm using Fixture Data so not sure if this might be a limitation. I'm not getting any errors, and the list of OwnedGames displays just fine (just 1 of them for now). I'm getting 0 for the size of plays.
I've read other similar threads about this issue, and I believe I have the code correct, but not sure why it isn't working.
UPDATE:
I also tried doing this on each iteration of game:
{{#each play in game.plays}}
{{play.date}}
{{/each}}
And I get no output.
Update #2:
I created all the necessary code to view a list of GamePlay's. The size is 0 until I visit the list of GamePlay's. Then if I go back to my list of OwnedGames, the length of GamePlay's is now 2, which is accurate. So I think I just need to figure out how to get the computed property to query for the data?

With a hasMany relationship, you have two options on how to go about fetching the side loaded data.
First option is to specify what plays records you want by specifying an array with game-play ids on the owned-game fixture. This will make another request for game-play automatically for you and populate your model asynchronously.
App.OwnedGame.reopenClass({
FIXTURES: [
{id: 1, rating: 8.25, game: 1, plays: [1,2]}
]
});
Second option is to manually make a second request to fetch game-play data, which will populate the plays property of owned-game asynchronously.
App.IndexRoute = Ember.Route.extend({
model: function () {
return new Ember.RSVP.hash({
gamePlay: this.store.find('game-play'),
ownedGame: this.store.find('owned-game')
});
},
setupController: function(controller, model) {
controller.set('model', model.ownedGame);
}
});
http://emberjs.jsbin.com/woruqugida/1/edit?html,js,output

Related

Ember can't get data from hasMany relationship

I try to build Rails+Ember app using Ember data.
Model:
export default DS.Model.extend({
name: DS.attr('string'),
content: DS.attr('string'),
image_before: DS.attr('string'),
created_at: DS.attr('date'),
updated_at: DS.attr('date'),
status: DS.attr('number'),
comments: DS.hasMany('comment', {async: true}),
user: DS.belongsTo('user'),
});
and get json like this (json is generated with active_model_serializers)
{"work":{
"id":3,
"user_id":1,
"content":"My first photo",
"image_before": "image_before/original/AnViWyuup5I.png",
"created_at":"2015-08-11T16:57:24.153Z",
"updated_at":"2015-11-13T11:39:44.076Z",
"status":1,
"comment_ids":[13]
},
"comments": [{
"id": 13,
"text": "good!",
"created_at": "2015-09-28T10:34:16.461Z",
"user_id": 2
}]
}
Template is:
<div class="work__img-wrapper">
<img class="work__img" src="{{model.image_before}}">
</div>
<div class="work__content">{{model.content}}</div>
<div class="work__comments-list">
<strong>Comments( {{model.comments.length}} ):</strong>
{{#each model.comments as |comment|}}
<div class="work__comment">
<div class="work__comment-text">{{comment.text}}</div>
</div>
{{/each}}
</div>
At page, I get all information about work and Comments( 0 ), but Ember Inspector at Chrome shows comment.
How i can print comments too?
Your model definition should read:
comments: DS.hasMany('comment', {async: false}),
because the related data is being side-loaded with the response, not being fetched asynchronously.
If this is an Ember 2.0 application, the default async setting is true. From the Ember Blog (http://emberjs.com/blog/2015/06/18/ember-data-1-13-released.html, emphasis mine):
In Ember Data 2.0 relationships will be asynchronous by default. Sync
relationships will still be supported but you will need to manually
opt into them by setting { async: false } on your relationships. Ember
Data 1.13 will log a deprecation warning you if you have any
relationships where the async property is not explicitly set.
Additionally you can use ember-watson to help identify cases in your
codebase where you have relationships without an explicit async
property.
Problem solved. By default, DS.hasMany associated IDs are not added to the objects that are serialized (http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html).
Should add
export default DS.ActiveModelSerializer
.extend(DS.EmbeddedRecordsMixin)
.extend({
attrs: {
comments: {serialize: 'ids', deserialize: 'ids'}
}
});

Drop-down menu which changes the content on an other drop-down menu

I'd like to create a drop-down menu from a hasMany relation which filters the values of an other drop-down menu. I have ships which belongsTo companies and which haveMany cruises. The user of the webpage should be able to select nothing (the table displays all ships) or a company (the table displays just the ships of that company) or a specific ship (the table just displays one ship). If the user selected a specific company only ships of that company should be displayed in the ship drop-down menu. The table of ships should be updated too.
How can I create a set of two drop-down menus (above or below the table) which behave like this example and filter the content of the table?
I know this question is a tall order. I tried to break it down as much as possible. Thank you for your time and effort.
The code of my example application
ember new travel_agency
cd travel_agency
ember install:addon ember-cli-scaffold
ember g scaffold ship name:string
ember g scaffold company name:string
ember g scaffold cruise starts_at:date
ember generate adapter application
ember g http-mock ships
ember g http-mock companies
ember g http-mock cruises
ember install:addon ember-moment
app/adapters/application.js
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
'namespace': 'api'
});
app/models/company.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
ships: DS.hasMany('ship', { async: true })
});
app/models/ship.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
company: DS.belongsTo('company', { async: true }),
cruises: DS.hasMany('cruise', { async: true })
});
app/models/cruise.js
import DS from 'ember-data';
export default DS.Model.extend({
startsAt: DS.attr('date'),
ship: DS.belongsTo('ship', { async: true })
});
server/mocks/ships.js
[...]
var shipList = [
{"id":1,"name":"Carnival Fantasy","company":1,"cruises":[1,2]},
{"id":2,"name":"Carnival Triumph","company":1,"cruises":[3,4]},
{"id":3,"name":"Queen Mary 2","company":2,"cruises":[5]},
{"id":4,"name":"Queen Elizabeth","company":2,"cruises":[6]},
{"id":5,"name":"Norwegian Jewel","company":3,"cruises":[7,8]}
]
[...]
server/mocks/companies.js
[...]
var companyList = [
{"id":1,"name":"Carnival"},
{"id":2,"name":"Cunard"},
{"id":3,"name":"Norwegian Cruise Line"}
]
[...]
server/mocks/cruises.js
[...]
var cruiseList = [
{"id":1,"startsAt":"2014-10-01","ship":1},
{"id":2,"startsAt":"2014-10-15","ship":1},
{"id":3,"startsAt":"2014-10-30","ship":2},
{"id":4,"startsAt":"2014-11-10","ship":2},
{"id":5,"startsAt":"2014-11-20","ship":3},
{"id":6,"startsAt":"2014-11-20","ship":4},
{"id":7,"startsAt":"2014-10-20","ship":5},
{"id":8,"startsAt":"2014-11-20","ship":5}
]
[...]
app/templates/ships/index.hbs
[...]
<tbody>
{{#each ship in model}}
<tr>
<td>
{{ship.name}}
</td>
<td>
{{ship.company.name}}
</td>
<td>
{{#each cruise in ship.cruises}}
{{moment date "L" cruise.startsAt}},
{{/each}}
</td>
<td>
{{link-to "Edit" "ships.edit" ship}}
</td>
<td>
{{link-to "Show" "ships.show" ship}}
</td>
<td>
<a href="#" {{action "remove" ship}}>Remove</a>
</td>
</tr>
{{/each}}
</tbody>
[...]
I'm not going to dive into your whole application. Because it doesn't have much to do with the functionality you want. If I understand correctly you want to filter the second option-list based on the value of the first.
You'd need a template containing two option-lists
{{view "select" content=model.colors value=selectedItem}}
color: {{selectedItem}}
{{view "select" content=filteredBoats optionLabelPath="content.boat"}}
You'd need a route providing a model:
App.IndexRoute = Ember.Route.extend({
model: function() {
return {
colors: [],
boats: []
};
}
});
And then you need a controller wiring things up:
App.IndexController = Ember.Controller.extend({
filteredBoats: function(){
var selI = this.get('selectedItem');
return this.get('model.boats').filter(
function(boat) {
return boat.color == selI;
});
}.property('selectedItem')
});
Lets recap and see what happens.
In the view we bind the result of the first option-list to selectedItem in the controller
In the controller we create a computed property and filter model.boats based on it's value
In the view we bind the second option-list to the computed property.
See this jsbin.

Add second model to a route

My application is for swimming lessons. I need to add swimmers to a class. The relationship is has many in both directions. A Lesson can have many Swimmers and Swimmers can have many Lessons.
From the lesson route, I would like to select a swimmer in a drop down, from a list of all swimmers and have an action add that swimmer's ID to the lesson's "swimmers" array.
I can't get the swimmers to show up in the drop down field because I don't think I am loading the second model correctly.
I would also be open to suggestions of how to add a specific swimmer to a specific class. It's important to see all available swimmers
I am new to both ember and programming so please keep this in mind when making suggestions. Thank you!
App.Router.map(function() {
this.resource('lessons', { path: '/lessons' }, function() {
this.resource('lesson', { path: '/:lesson_id' })
this.route('new', { path: '/new' })
});
this.resource('swimmers', { path: '/swimmers' }, function() {
this.resource('swimmer', { path: '/:swimmer_id' })
this.route('new', { path: '/new' })
});
});
App.Lesson = DS.Model.extend({
type: DS.attr(),
name: DS.attr(),
/*level: DS.attr(), sometimes there are hybrid levels, likely leave it up to */
instructor:DS.belongsTo('instructor', {async: true}),
startDate: DS.attr(),
endDate: DS.attr(),
capacity: DS.attr('number'),
swimmers: DS.hasMany('swimmer',{async: true}),
});
App.Swimmer = DS.Model.extend({
nameFirst: DS.attr(),
nameLast: DS.attr(),
level: DS.attr(),
birthdate: DS.attr(),
gender: DS.attr(),
note:DS.attr(),
lessons: DS.hasMany('lesson', {async: true}),
});
App.LessonRoute = Ember.Route.extend({
model: function(params) {
return Ember.RSVP.hash({
lesson: this.store.find('lesson', params.lesson_id),
swimmers: this.store.findAll('swimmer')
})
},
setupController: function(controller, model) {
controller.set('model', model.lesson);
controller.set('swimmer', model.swimmer);
},
});
Drop down I am trying to use
<div class="form-group">
<label for="lesson_swimmers" class="col-sm-2 control- label">Swimmers</label>
<div class="col-sm-9">
{{view Ember.Select content=swimmers optionLabelPath="content.fullName" class="form-control" id="lesson_swimmers"}}
</div>
</div>
There are two answers to this question. The right one for you will depend on how you are providing the Model.
If you are arriving via transferTo or linkTo you will need to use a different approach than if you are arriving directly via URL.
#Kingpin2k answers the question and provides examples here,
Kingpin2k's elegant solution.

Ember ArrayController this.get doesn't work

I'm having hard time to understand arrayController and ObjectController in Ember (at least I think this is the point.)
I'm working with an ArrayController and I need to get a model and modify it. (take today model and make in order to figured out how many days are in a month) but every time I do:
this.get("today")
nothing happen. Which from the documentation, that is how it should be call.
If I look at other example, most of the people use ObjectController, so i try it with that one too but I got an error complaining the #each loop i'm using need an ArrayController
Here is my code so far:
//Router
WebCalendar.Router.map(function() {
this.resource('index', {path: '/'}, function() {
this.resource("cal", {path: '/'});
this.resource("location", {path: '/location'});
this.resource("organization", {path: '/organization'});
});
});
WebCalendar.CalRoute = Ember.Route.extend({
model: function(){
return this.store.find('dates');
}
});
//Model
WebCalendar.Dates = DS.Model.extend({
today: DS.attr('date'),
month: DS.attr('date'),
year: DS.attr('date'),
daysName: DS.attr('array'),
daysInMonth: DS.attr('array')
});
WebCalendar.Dates.FIXTURES = [
{
id: 1,
today: moment().format('MMMM Do YYYY, h:mm:ss a'),
month: moment().format('MMM'),
year: moment().format('YYYY'),
daysName: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
daysInMonth: [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
}
];
//CalController
WebCalendar.CalController = Ember.ArrayController.extend({
getMonthDays: function(){
return this.get("today");
}.property('today')
});
//Cal Handlebars
<table>
{{#each date in controller}}
<tbody id="table">
<tr id="year">
<td>{{date.year}}</td>
</tr>
<tr>
<td id="prev-month"> Prev </td>
<td id="month">{{date.month}}</td>
<td id="next-month"> Next </td>
</tr>
<tr id="days-of-week">
{{#each date.daysName}}
<td>{{this}}</td>
{{/each}}
</tr class="days">
<tr>{{getMonthDays}}</tr>
</tbody>
{{/each}}
</table>
My questions are:
Why this.get method doesn't work? Documentation here: http://emberjs.com/api/classes/Ember.ArrayController.html#method_get
Is it correct that i'm using ArrayController in this specific situation?
Why seems i cannot use #each loop with ObjectController?
{{getMonthDays}} is being invoked within the {{each}}, which means it is being called in the context of individual Dates objects, but you are defining it on the ArrayController--where Ember won't even look. You are confused between the ArrayController managing the collection of model instances, and individual model instances (or controllers therefor, which you haven't defined).
You need an itemController. I refer you to the documentation rather than summarizing it here. getMonthDays would be a method on the item controller.
By the way,
getMonthDays: function(){
return this.get("today");
}.property('today')
is often better written as
getMonthDays: Ember.computed.alias('today')
or
getMonthDaysBinding: 'today'

Assertion failed: an Ember.CollectionView's content must implement Ember.Array

Very new to ember and trying to mess around with my first test script. I keep getting the error in the title and it occurs when I try to use handlebars to loop through a data fixture. Any help would be appreciated, thank you!
Link to fiddle
Loop statement that is giving error
{{#each player in controller}}
<tr> <!-- foreach statement here order by wins>loss>ties -->
<td>{{ name }}</td>
</tr>
{{/each}}
App.JS
var App = Ember.Application.create({
LOG_TRANSITIONS: true,
});
// Router
App.Router.map(function(){});
App.PlayersRoute = Ember.Route.extend({
model: function(){
return App.Player.find();
}
});
// Controllers
App.PlayersController = Ember.ArrayController.extend();
// View Helpers
// Models
App.Store = DS.Store.extend({
revision: 11,
adapter: 'DS.FixtureAdapter'
});
App.Player = DS.Model.extend({
name: DS.attr('string'),
wins: DS.attr('number'),
losses: DS.attr('number'),
ties: DS.attr('number'),
drop: DS.attr('boolean')
});
App.Player.FIXTURES = [{
id: 1,
name: 'Joe Jame',
wins: 2,
losses: 0,
ties: 0,
drop: 'False'
}, {
id: 2,
name: 'Froggy Bottom',
wins: 2,
losses: 0,
ties: 0,
drop: 'False'
}];
One more answer, first I've ported your example to jsbin IMO it's more reliable than jsfiddle, for the kind of setup you have I've changed the render statement to partial and renamed the playerlist template to _playerlist. additionally I've defined in the IndexRoute a model hook to get your fixture data, and this are the results: http://jsbin.com/iceraz/2/edit. It seams to be working now as expected. Since we are using the partial helper there is no need for an extra Playerlist controller nor route, it will be all handled in the IndexRoute.
Hope it helps.
The render helper creates a new template/view/controller so in your case you will need to pass in the array of Player objects to allow the playerlist template to have the correct context for rendering.
{{ render "playerlist" content }}
You also need to change PlayersRoute to IndexRoute otherwise the model hook won't get called and your data won't be loaded.
App.IndexRoute = Ember.Route.extend({
model: function(){
return App.Player.find();
}
});
JSFiddle example