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

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

Related

Getting length of hasMany association

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

Weird binding with Ember.Select value

I'm having a weird issue with the Ember.Select view when I try to bind its value to a model.
Here is an abstract of what I'm doing, the complete jsbin can be found here:
Using JavaScript: http://jsbin.com/jayutuzazi/1/edit?html,js,output
Using CoffeeScript: http://jsbin.com/nutoxiravi/2/edit?html,js,output
Basically what I'm trying to do is use an attribute of a model to set an attribute of another model.
I have the Age model like this
App.Age = DS.Model.extend({
label: DS.attr('string'),
base: DS.attr('number')
});
And an other model named Person like this
App.Person = DS.Model.extend({
name: DS.attr('string'),
ageBase: DS.attr('number')
});
The template looks like this:
<!-- person/edit.hbs -->
<form>
<p>Name {{input value=model.name}}</p>
<p>
Age
{{view "select" value=model.ageBase
content=ages
optionValuePath="content.base"
optionLabelPath="content.label"}}
</p>
</form>
What I am trying to do is have a select in the Person edit form that lists the ages using base as value and label as label.
I expect the correct value to be selected when loading and to change when the selected option changes.
Has can be seen in the jsbin output, the selected is correctly populated but it sets the ageBase value of the edited person to undefined and does not select any option. The model value is correctly set when an option is selected though.
Am I doing something wrong ? Is it a bug ? What am I supposed to do to make this work ?
Thank you :)
You can conditionally render based on fulfilment of the ages as follows, since select doesn't handle promises (more on that below):
{{#if ages.isFulfilled}}
{{view "select" value=ageBase
content=ages
optionValuePath="content.base"
optionLabelPath="content.label"}}
{{/if}}
I updated your JsBin demonstrating it working.
I also illustrate in the JsBin how you don't have to qualify with model. in your templates since object controllers are proxies to the models they decorate. This means your view doesn't have to be concerned with if a property comes from the model or some computed property on the controller.
There is currently a PR #9468 for select views which I made a case for getting merged into Ember which addresses some issues with selection and option paths. There is also meta issue #5259 to deal with a number of select view issues including working with promises.
From issue #5259 you will find that Ember core developer, Robert Jackson, has some candidate select replacements. I cloned one into this JsBin running against latest production release version of Ember.
There is nothing at all preventing you using Roberts code as a select view replacement in your app. Asynchronous collections/promises will just work (and it is MUCH faster rendering from the benchmarks I have seen).
The template for that component is just:
{{#if prompt}}
<option disabled>{{prompt}}</option>
{{/if}}
{{#each option in resolvedOptions}}
<option {{bind-attr value=option.id}}>{{option.name}}</option>
{{/each}}
The js of the component is:
App.AsyncSelectComponent = Ember.Component.extend({
tagName: 'select',
prompt: null,
options: null,
initialValue: null,
resolvedOptions: null,
resolvedInitialValue: null,
init: function() {
var component = this;
this._super.apply(this, arguments);
Ember.RSVP.hash({
resolvedOptions: this.options,
resolvedInitialValue: this.initialValue
})
.then(function(resolvedHash){
Ember.setProperties(component, resolvedHash);
//Run after render to ensure the <option>s have rendered
Ember.run.schedule('afterRender', function() {
component.updateSelection();
});
});
},
updateSelection: function() {
var initialValue = Ember.get(this, 'resolvedInitialValue');
var options = Ember.get(this, 'resolvedOptions') || [];
var initialValueIndex = options.indexOf(initialValue);
var prompt = Ember.get(this, 'prompt');
this.$('option').prop('selected', false);
if (initialValueIndex > -1) {
this.$('option:eq(' + initialValueIndex + ')').prop('selected', true);
} else if (prompt) {
this.$('option:eq(0)').prop('selected', true);
}
},
change: function() {
this._changeSelection();
},
_changeSelection: function() {
var value = this._selectedValue();
this.sendAction('on-change', value);
},
_selectedValue: function() {
var offset = 0;
var selectedIndex = this.$()[0].selectedIndex;
if (this.prompt) { offset = 1; }
return Ember.get(this, 'resolvedOptions')[selectedIndex - offset];
}
});
The problem is that in:
{{view "select" value=model.ageBase
When the app starts, value is undefined and model.ageBase gets synchronized to that before value is synchronized to model.ageBase. So, the workaround is to skip that initial undefined value.
See: http://jsbin.com/rimuku/1/edit?html,js,console,output
The relevant parts are:
template
{{view "select" value=selectValue }}
controller
App.IndexController = Ember.Controller.extend({
updateModel: function() {
var value = this.get('selectValue');
var person = this.get('model');
if ( value ) { // skip initial undefined value
person.set('ageBase', value);
}
}.observes('selectValue'),
selectValue: function() {
// randomly used this one
return this.store.find('age', 3);
}.property()
});
givanse's answer should work.
I don't think it's because value is undefined, but because value is just an integer (42) and not equal to any of the selects content, which are Person objects ({ id: 2, label: 'middle', base: 42 }).
You could do something similar to what givens suggests or use relationships.
Models
//
// Models
//
App.Person = DS.Model.extend({
name: DS.attr('string'),
ageBase: DS.belongsTo('age', { async: true })
});
App.Age = DS.Model.extend({
label: DS.attr('string'),
base: DS.attr('number')
});
//
// Fixtures
//
App.Person.reopenClass({
FIXTURES: [
{ id: 1, name: 'John Doe', ageBase: 2 }
]
});
App.Age.reopenClass({
FIXTURES: [
{ id: 1, label: 'young', base: 2 },
{ id: 2, label: 'middle', base: 42 },
{ id: 3, label: 'old', base: 82 }
]
});
Template:
<h1>Edit</h1>
<pre>
name: {{model.name}}
age: {{model.ageBase.base}}
</pre>
<form>
<p>Name {{input value=model.name}}</p>
<p>
Age
{{view "select" value=model.ageBase
content=ages
optionValuePath="content"
optionLabelPath="content.label"}}
</p>
</form>
Ok, I found a solution that I think is more satisfying. As I thought the issue was coming from ages being a promise. The solution was to ensure that the ages list was loaded before the page was rendered.
Here is how I did it:
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
person: this.store.find('person', 1),
ages: this.store.findAll('age')
});
}
});
That's it! All I need from there is to change the view according the new model:
{{#with model}}
<form>
<p>Name {{input value=person.name}}</p>
<p>
Age
{{view "select" value=person.ageBase
content=ages
optionValuePath="content.base"
optionLabelPath="content.label"}}
</p>
</form>
{{/with}}
The complete working solution can be found here: http://jsbin.com/qeriyohacu/1/edit?html,js,output
Thanks again to #givanse and #Larsaronen for your answers :)

Unexpected text being rendered by Ember

I have models
// models/group
export default DS.Model.extend({
parent: DS.belongsTo('parent'),
items: DS.hasMany('item', {async: true}),
quantity: Ember.computed.sum('items.#each.quantity'),
});
// models/item
export default DS.Model.extend({
...
quantity: DS.attr('number')
});
And in my template (with controller.model set to parent) I try to render
{{#each group}}
{{quantity}}
{{/each}}
and expect a list of numbers, but instead what's rendered is a list of text like <spa#model:item::ember1036:165>
I'm guessing that the async promise is only resolved after rendering, but then why does it not update?
I don't believe sum will pull properties from each item in a collection. I believe it has to be a collection of numbers.
quantities: function(){
return this.get('items').getEach('quantity');
}.property('items.#each.quantity'),
quantity: Ember.computed.sum('quantities'),

Ember.js What is the difference between the setupController and declaring a <Name>Controller

I see many confusing examples in Ember.js official tutorials.
One example which I really don't like is:
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('title', "Hello world!");
}
});
App.ApplicationController = Ember.Controller.extend({
appName: 'My First Example'
});
Now as I understand it I could have written it like that instead:
App.ApplicationController = Ember.Controller.extend({
appName: 'My First Example',
title: 'Hello world!'
});
And removing the setupController from route.
What is the purpose/benefit of using setupController?
setupController is primarily for setting up some controller context dynamically. In your example, if the title is always gonna be "Hello world!" it's fine to set it in class declaration.
By default, setupController will set the model property of controller to the value returned from model hook of the route.
You could also you it to, for example, set the model of another controller, or set some initial controller state that depends on the model.
For example, suppose you have the following:
// Model
App.Post = DS.Model.extend({
title: DS.attr('string'),
text: DS.attr('string'),
autoEdit: DS.attr('string')
});
// Controller
App.PostController = Ember.ObjectController.extend({
isEditing: null,
toggleEdit: function() { this.toggleProperty('isEditing'); }
});
Template:
<a href="#" {{action 'toggleEdit'}}>Toggle edit mode</a>
{{#if isEditing}}
{{input type="text" value=title placeholder="Title"}}
{{textarea type="text" value=text placeholder="Text"}}
{{else}}
<h1>{{title}}<h1>
<article>{{text}}</article>
{{/if}}
And then, you decide that it would be nice to turn editing mode on by default for posts with autoEdit equal to true. You'll probably want to do that in the route (since the controller, when instantiated, knows nothing about the model):
App.PostRoute = Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
if (model.get('autoEdit')) {
controller.set('isEditing', true);
}
}
});
So basically, it's for "initializing" the controller (setting model and default state).

Clear Ember.TextField after submit

I'm learning ember.js on a small example app and there is a final piece I can't get to work.
I have a list of "quips" (tweets) and there is a text input field that allows to create a new one. After I submit a new tweet, I want to clear the text input, to no avail. I basically copied the todomvc example verbatim at this point and it works there (I even use the same ember.js and ember-data.js versions just to rule out this possibility).
Here is the template:
<script type="text/x-handlebars" data-template-name="index">
<h2>Latest Quips</h2>
<div>
{{view Ember.TextField id="new-quip" placeholder="Enter your Quip"
valueBinding="newQuip" action="createQuip"}}
</div>
The action in the appropriate controller:
App.IndexController = Ember.ArrayController.extend({
createQuip: function() {
App.Quip.createRecord({
text: this.get('newQuip'),
user: 'ree'
});
this.set('newQuip', ''); // this row should clear the view
this.get('store').commit();
}
});
And the model for the sake of completeness:
App.Quip = DS.Model.extend({
text: DS.attr('string'),
user: DS.attr('string')
});
App.Store = DS.Store.extend({
revision: 11,
adapter: 'App.QuipsAdapter'
});
App.Quip.FIXTURES = [
{ id: 1, user: 'ree', text: 'Which is the best JS MVC?' },
{ id: 2, user: 'baaz', text: '#ree Definitely Ember.js!' }
];
App.QuipsAdapter = DS.FixtureAdapter.extend({});
The app runs here.
I would be really glad if someone could point at what I'm doing wrong.
Thank you,
Balint
It's a bug related to jQuery 1.9.0 - try v1.8.x
Also, as I can recall, it's been fixed on master, so grabbing the latest Ember release may also solve your problem.