I'm trying to get my head around Ember and going through the todos tutorial. I get stuck on the displaying-model-data step here
http://emberjs.com/guides/getting-started/displaying-model-data/
here's the javascript i copied and pasted from the tutorial:
window.Todos = Ember.Application.create();
Todos.Router.map(function () {
this.resource('todos', { path: '/' });
});
Todos.TodosRoute = Ember.Route.extend({
model: function () {
return Todos.Todo.find();
}
});
Todos.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.FixtureAdapter'
});
Todos.Todo = DS.Model.extend({
title: DS.attr('string'),
isCompleted: DS.attr('boolean')
});
Todos.Todo.FIXTURES = [
{
id: 1,
title: 'Learn Ember.js',
isCompleted: true
},
{
id: 2,
title: '...',
isCompleted: false
},
{
id: 3,
title: 'Profit!',
isCompleted: false
}
];
Then here's my handlebars template:
...
{{#each controller}}
<li>
<input type="checkbox" class="toggle">
<label>{{title}}</label><button class="destroy"></button>
</li>
{{/each}}
And yet I get this error
Uncaught Error: assertion failed: an Ember.CollectionView's content must implement Ember.Array. You passed <(generated todos controller):ember257>
It looks to me like whatever default controller object Ember generates should be of type Ember.Array but it is not happening for some reason. I am wondering if it is a problem with ember-data?
I am using all the files from the starter kit which are
ember 1.0.0 rc5
handlebars 1.0.0 rc4
jquery 1.9.1
and ember-data, the only versioning indication i can tell is from a comment
// Last commit: 3981a7c (2013-05-28 05:00:14 -0700)
Is there a dependency problem someone knows about or did I do something wrong?
I wouldn't say its a problem with ember data, since that module is responsible only for talking to the api and giving you clever model objects.
You were right in saying ember is generating the wrong type of controller. By default Ember will probably generate a Controller, when what you need is an ArrayController. To get around the issue, simply create an empty controller like this
Todo.TodosController = Em.ArrayController.extend({});
The guide does say that ember creates an ArrayController, but perhaps it doesn't anymore!? let me know if it works by explicitly creating an arraycontroller. If it does we can let the ember team know.
I had this exact same issue today walking through the Getting Started Guide but it appeared to be due to a typo.
According to the documentation, the generated controller is supposed to be of type ArrayController. I dug into the Ember source and found the Ember.generateController method that generates the controller depending on the context. I set a break point and found that when Ember was trying to create a controller for the "Todos" route, the context was undefined, so the basic controller was generated.
Working backward from there, I set a breakpoint on the model function of my router to see what it was returning but found it was not being called at all. At this point, I began to get suspicious that I had done something wrong. And that is when I noticed that I had named the TodosRoute as TodosRouter (as you have in your original question). Changing the name to TodosRoute correctly called my model function and everything worked as expected. It was not necessary to include the line that explicitly created the TodosController as an ArrayController.
While it appears you had it correct in your question, I wanted to post this here in case someone else has the same issue.
Adding the line Gevious suggested corrected this issue for me. For clarification my router.js file now looks like this:
Todos.Router.map(function(){
this.resource('todos', {path: '/'});
});
Todos.TodosRoute = Ember.Route.extend({
model: function () {
return Todos.Todo.find();
}
});
Todos.TodosController = Em.ArrayController.extend({});
Related
I use Ember Data 1.0.0-beta.14.1 with Ember 1.9.1 at the moment (with Ember-cli). Somehow one of my collections doesn't work anymore since i update from an older ember data version.
I got a DirectoryModel (for a filesystem). Directories can have subdirectories and files.
import DS from 'ember-data';
var DirectoryModel = DS.Model.extend({
...
parent: DS.belongsTo('directory', { async: true, inverse: 'children' }),
children: DS.hasMany('directory', { async: true, readOnly: true, inverse: 'parent' }),
files: DS.hasMany('file', { async: true, readOnly: true })
});
A got a serializer to load the hashMany releationships:
export default ApplicationSerializer.extend({
normalizePayload: function(payload) {
payload.directories.forEach(function(directory) {
directory.links = {
children: '/link-to-server'),
files: 'link-to-server')
};
});
return this._super.apply(this,arguments);
}
});
My view:
//WORKS GREAT
{{#each directory in children itemController="file-directory"}}
...
{{/each}}
CREATES ERRORS
{{#each file in files }}
...
{{/each}}
Somehow that files loop ends up to an error. It looks like question "Cannot call method 'destroy' of undefined" in arrayWillChange, only in my case I just load the data from the server. I don't understand what i did wrong, as the children-relation does work well. In older versions this just works, but with Ember Data 1.0.0-beta.14.1 it doesn't...
I looked at the ember code at the arrayWillChange function, and saw that this._childViews was just an empty array. But if I set a breakpoint and executed this.get('content.content').toArray() in my console, I saw an array with one element. Somehow/somewhere it seems the data is out of sync...
In the end it was a bug in Ember Data 1.0.0-beta.14.1. It's solved in the next version, Ember Data 1.0.0-beta.15: https://github.com/emberjs/data/issues/2662
I'm was getting the same error and I also use links to load data. What I accidentally found out is that wrapping arrays in something like
files: function() {
return this.get('directory.files').map(function(file) { return file; });
}.property('directory.files.#each')
solves the issue.
Have no idea why it works :)
I'm using ember-cli and trying to make some sense of the structure of the app and how it is all wired together. There are some differences in the main Ember guide docs and what I'm seeing in the ember-cli generated project. I understand the API's are moving fast so I just need to be pointed in the right direction.
In router.js I have the following:
Router.map(function() {
this.route('domains', {path: "/domains" });
});
Then I have models/domain.js
import DS from 'ember-data';
var Domain = DS.Model.extend({
name: DS.attr('string')
});
Domain.reopenClass({
FIXTURES: [
{ id: 1, name: 'User'},
{ id: 2, name: 'Address'}
]
});
export default Domain;
And I have routes/domains.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.all('domain');
}
});
And finally ( I think ), I have templates/domains.hbs
<h1>Domains</h1>
{{#each}}
<p>{{name}}</p>
{{/each}}
Only the header is being rendered when I visit the http://localhost:4200/domains url. I'm using the ember chrome extension and I don't see any data coming back in the request. I'm not sure if it is a naming convention issue or what I'm doing wrong so any help is appreciated.
all just returns records that have already been found in the store. find will issue a request (in this case hitting the fixtures) and populate the store, and also return all of the records in the store.
this.store.find('domain');
The problem ended up being 2-fold. Kingpin2K was right in that I needed to use find instead of all. I also had to change the adapter to the following in adapters/application.js:
export default DS.FixtureAdapter.extend();
Lets say I have two models, Book and Chapter.
App.Book = DS.Model.extend({
name: DS.attr(),
chapters: DS.hasMany('chapter', { async: true })
});
App.Chapter = DS.Model.extend({
name: DS.attr(),
book: DS.belongsTo('book')
});
I'm using the RESTAdapter.
App.ApplicationAdapter = DS.RESTAdapter;
In the IndexRoute let's say I want to fetch the first book (id = 1) and it's associated chapters. Since the hasMany relationship is marked as async: true, I want to fetch this association before the template is rendered.
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
book: this.store.find('book', 1)
}).then(function(hash) {
hash.chapters = hash.book.get('chapters');
return hash;
})
},
setupController: function(controller, model) {
controller.set('model', model.book);
}
});
My index template simply displays the book title, and iterates over its chapters.
<script type="text/x-handlebars" id="index">
<h3>Book: {{name}}</h3>
<ul>
{{#each chapters}}
<li>{{name}}</li>
{{/each}}
</ul>
</script>
When using mockjax, I setup the mock responses.
$.mockjax({
url: '/books/1',
responseText: {
book: {
id: 1,
name: 'Book 1',
chapters: [1, 2]
}
}
});
$.mockjax({
url: '/chapters?ids[]=1&ids[]=2',
responseText: {
chapters: [{
id: 1,
name: 'Chapter 1',
book_id: 1
}, {
id: 2,
name: 'Chapter 2',
book_id: 1
}]
}
});
Problem 1: According to the RESTAdapter docs, accessing book.get('chapters') should issue a GET request to /chapters?ids[]=1&ids[]=2. However, my console is showing two separate requests to /chapters/1 and /chapters/2.
Problem 2: Furthermore, I believe the template is rendered before the chapters requests are happening because the template is rendered a second or two before I see the two requests to /chapters/1 and /chapters/2. If I remove the call to hash.book.get('chapters') from the route, the same problem happens. In other words, I don't think the route is sending the request, I think the template is.
Here is a jsbin. You'll notice it doesn't show any chapters because I haven't setup the two routes it's requesting (/chapters/1 and /chapters/2).
Cause for the first issue is simple: you used Dev(Canary) build of Ember Data which behaves differently to a beta build you can find on EmberJS site. It is bleeding edge so it's hard to tell if it's broken or if those are upcoming changes. If you want behavior consistent with the guide I would suggest using the latest beta build (Ember Data isn't marked as stable yet).
As for the second one: this is desired behavior since you're using async request along with Promises. This means, that you're immediately returning the result (there is no need for waiting for anything), however those results are empty until asynchronous requests are resolved.
You could wrap this behavior and (for example) inject a template only when your async routine is finished, however I would suggest rethinking it since based on personal experience it's not common pattern and most likely your app don't need it.
If you're are interested in how exactly those mechanisms work (you don't need this, it's only to satisfy potential curiosity on subject):
Article on JavaScript threading and pseudo multi-threading
Wikipedia - Definition of AJAX
Ember.RSVP.Promise - Promise used in Ember.js
Kriskowal's Q - Non-related library but I think it greatly introduces the concept of Promises
Ember.js Guide - Approach to asynchrony
Ember.js Guide - Handling of templates with async relationships
I have model:
App.Item = DS.Model.extend({
itemId: DS.attr('string'),
itemName: DS.attr('string'),
itemType: DS.attr('string'),
});
I successfully create some items from JSON. I can put them to page by {{#each items}}{{ itemName}}{{/each}}. But I don't know, how to get itemName in javascript.
I tried this:
var item = App.Item.find(1);
console.log(item.itemName);
--> undefined
I can't find anything useful from emberjs and ember-data docs. Can anyone help me?
Thanks
I tried this:
var item = App.Item.find(1);
console.log(item.itemName);
--> undefined
This is normal because the call to .find(1); is asyncronous and returns a promise and not the item you are expecting.
Therefore you should try:
App.Item.find(1).then(function(result) {
console.log(record.get('itemName'));
});
It also depends from where you are doing App.Item.find() if it's from inside a route you should wait until the afterModel hook is called to access your items:
App.FooRoute = Ember.Route.extend({
model: function() {
return App.Item.find(1);
},
afterModel: function(record) {
console.log(record.get('itemName'));
}
});
Also be aware that if you where calling find() without parameter then you will receive a RecordArray which you need to loop over to get access to your items. Also worth mentioning is that in ember you should always use .get() and .set() instead of the vanilla dot-notation otherwise you hijack the binding mecanism resulting in no updates in your view etc.
Note, if you are using the latest ember.js release (1.0.0) then the call to .find() should be made somewhat different. But that's not clear from your question.
Hope it helps.
I have been trying to set up an Ember.js application together with a RESTful API i have created in Laravel.
I have encountered a problem trying to get the data trough the store, and depending on my implementation, I get different errors, but never any working implementations.
The ember.js guide have one example, other places have other examples, and most information I find is outdated.
Here's my current code:
App = Ember.Application.create();
App.Router.map(function() {
this.resource("world", function() {
this.resource("planets");
});
});
App.PlanetsRoute = Ember.Route.extend({
model: function() {
return this.store.find('planet');
}
});
App.Planet = DS.Model.extend({
id: DS.attr('number'),
name: DS.attr('string'),
subjectId: DS.attr('number')
});
And when I try to click the link for planets, thats when the error occurs, and I get the following error right now:
Error while loading route: TypeError {} ember-1.0.0.js:394
Uncaught TypeError: Cannot set property 'store' of undefined emberdata.js:15
No request is sent for /planets at all. I had it working with a $.getJSON, but I wanted to try to implement the default ember-data RESTAdapter.
For reference, these are some of the implementations i've tried:
var store = this.get('store'); // or just this.get('store').find('planet')
return store.find('planet', 1) // (or findAl()) of store.findAll('planet');
App.store = DS.Store.create();
I also tried DS.Store.all('planet') as I found it in the ember.js api, but seemed like I ended up even further away from a solution.
Most other implementations give me an error telling me there is no such method find or findAll.
EDIT (Solution)
After alot of back and forward, I managed to make it work.
I'm not sure exactly which step fixed it, but I included the newest versions available from the web (Instead of locally), and the sourcecode now looks like this:
window.App = Ember.Application.create();
App.Router.map(function() {
this.resource("world", function() {
this.resource("planets");
});
});
App.PlanetsRoute = Ember.Route.extend({
model: function() {
return this.store.findAll('planet');
}
});
App.Planet = DS.Model.extend({
name: DS.attr(),
subjectId: DS.attr()
});
The error you had is probably due to the fact that you added a "s" plural of your objects.
i.e. if you use
App.Planets = DS.Model.extend({
})
you would get that error.