I'm in the process of grokking Ember after the 1.0 release, and thought was going well at it until I tried following this tutorial on integration testing. It's well written and pretty didactic, but I've been stuck debugging the setup test for a few days already :/ Noob pains...
This gist shows the test and the error on qunit. I'm following the setup from the tute, and which I have seen elsewhere.
On IRC somebody pointed out this tute uses R5, not the latest 1.0 release. He didn't know whether ember-testing had changed since then, but this is a possible culprit.
Any ideas on what I could be doing wrong? It's gotta be something stupid, I'm aware :)
(using Ember with Rails 4)
Update
Márcio's fiddle let me play around adding and removing stuff until I replicated the error. Turns out I didn't have any templates setup, and the test didn't like that, though the application loaded with no errors, and the ember inspector saw routes etc.
I got this working following the tutorial:
Javascript:
App = Ember.Application.create();
App.Store = DS.Store.extend({
adapter: DS.FixtureAdapter
});
App.Router.map(function() {
this.route('edit', { path: ':person_id' });
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('person');
}
});
App.Person = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
App.Person.FIXTURES = [
{id: 1, firstName: 'Kris', lastName: 'Selden'},
{id: 2, firstName: 'Luke', lastName: 'Melia'},
{id: 3, firstName: 'Formerly Alex', lastName: 'Matchneer'}
];
App.rootElement = '#ember-testing';
App.setupForTesting();
App.injectTestHelpers();
function exists(selector) {
return !!find(selector).length;
}
module("Ember.js Library", {
setup: function() {
Ember.run(App, App.advanceReadiness);
},
teardown: function() {
App.reset();
}
});
test("Check HTML is returned", function() {
visit("/").then(function() {
ok(exists("*"), "Found HTML!");
});
});
Templates:
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<div id="ember-testing-container"><div id="ember-testing"></div></div>
<script type="text/x-handlebars" data-template-name="application">
<h1>ember-latest jsfiddle</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Index Content:</h2>
<ul>
{{#each}}
<li>{{#link-to 'edit' this}} {{firstName}} {{lastName}} {{/link-to}}</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="edit">
<h2>Edit:</h2>
<p id='name'>
{{firstName}}
</p>
</script>
Here is fiddle with this working http://jsfiddle.net/marciojunior/GveWH/
Related
I'm stuck in this situation where I cant set up my data correctly. I want to be able to load all my posts, and have the author data load with each post as well. These are my models.
App.Post = DS.Model.extend({
authorId: DS.attr('number'),
author: DS.belongsTo('author'),
title: DS.attr('string'),
body: DS.attr('string'),
snippet: DS.attr('string'),
postDate: DS.attr('date'),
imageName: DS.attr('string'),
postSnippit: Ember.computed();
imageUrl: Ember.computed()})
});
App.Author = DS.Model.extend({
name: DS.attr('string'),
imageName: DS.attr('string'),
posts: DS.hasMany('post')
});
and heres the relevant part of my router...
App.Router.map(function() {
this.route('blog', {path: '/blog'}, function() {
this.resource('posts', { path: '/' });
this.resource('post', {path: '/:id'});
});
});
I have it setup correctly for a single post like so ...
App.PostRoute = Ember.Route.extend({
setupController: function(controller, model){
this._super(controller, model);
controller.set('post', model);
controller.set('author', this.get('author'));
},
afterModel: function(){
Ember.run.scheduleOnce('afterRender', this, scrollToNav);
var self = this;
var id = this.get('model.authorId');
return this.store.find('author', id).then(function(result){
self.set('author', result);
});
},
model: function(params){
return this.store.find('post', params.id);
}
});
So what happens now, is I am loading a list of all posts as the model for 'blog'. The route has a sidebar on the left with a list of all the blog posts, and an outlet on the right side that defaults to loading the 'posts' route. I need the same info for both routes, a list of all posts, as well as the author information. The outlet loads the current post and replaces the 'posts' template, but retains the list of posts on the left sidebar. Currently I don't have the JSON data sideloaded because I couldn't figure it out either, so right now the data comes like this...
{
"posts": [
{
"id": "1",
"authorId": "1",
"title": "Title 1",
"body": "This is the body 1",
"uploadDate": "2015-06-03 19:26:15",
"imageName": "image1.jpg"
},
{
"id": "2",
"authorId": "2",
"title": "Title 2",
"body": "This is the body 2",
"uploadDate": "2015-06-03 19:26:15",
"imageName": "image2.jpg"
}
]
}
and then there is a separate call for authors...
{
"authors": [
{
"id": "1",
"name": "John Smith",
"email": "jsmith#gmail.com",
"imageName": "image1.jpg",
"gender": "M",
"bio": "John Smith is an awesome dude who went to awesome school!",
"facebookUrl": "null",
"twitterUrl": null,
"linkedinUrl": null
}
]
}
And now here are my current route objects (after going back and forth through 1million things).
App.BlogRoute = Ember.Route.extend({
model: function() {
var store = this.store;
var posts = store.findAll('post');
return posts;
/* *********************************
**Tried this, keep getting an error saying 'content.slice' is not a function**
*
var authors = store.findAll('author');
return Ember.RSVP.hash({
posts: posts,
authors: authors
}).then(function(hash){
return hash;
}, function(reason){
console.log(reason);
});
********************************* */
},
setupController: function(controller, model) {
this._super(controller, model);
controller.set('model', model);
controller.set('authors', this.get('authors')); //This doesnt do much, and I dont think its necessary.
/* *********************************
**Also tried this with the RSVP.hash function, would not render because of 'content.slice' error**
controller.set('model', model.posts);
controller.set('authors', model.authors)
********************************* */
},
afterModel: function(model) {
Ember.run.scheduleOnce('afterRender', this, scrollToNav);
var self = this;
return this.store.findAll('author').then(function(result){
self.set('authors', result);
});
}
});
App.PostsRoute = Ember.Route.extend({
afterModel: function() {
Ember.run.scheduleOnce('afterRender', this, scrollToNav);
},
model: function() {
return this.modelFor('blog');
}
});
The current behavior that happens, is that everything loads in the template, except for the author information. It only loads AFTER the actual post has been visited (ex: 'posts/1'), and then from then on, if I visit the posts or blog route, the author name will stay there.
I can't figure out how to make the information display all the time. From what I can see in the ember inspector, the data for both is being loaded! Ik there is probably something simple I'm missing. Please let me know if you guys have a solution.
There is also a secondary problem that i'm sure is somehow linked. No matter what method I choose to set data to the controller, (ex: controller.set('posts', posts) I cannot reference that data from the template in an each loop by calling it the given name. (ex: {{#each posts as |post|}} should be the correct format}}
Please correct me if that's not the correct way, or even what is the intended way to do it.
And just for reference, here is the relevant parts of the templates for blogs, posts, and post.
<script type="text/x-handlebars" id="blog">
<div class="container-fluid">
<h3>Posts</h3>
<ul>
{{#each model as |post|}}
<li>
{{#link-to 'post' post}}
<h3>{{post.title}}</h3>
<h4>{{post.author.name}}</h4>
<h5>{{format-date post.date}}</h5>
{{/link-to}}
</li>
{{else}}
No Models Loaded!
{{/each}}
</ul>
</div>
<div class="container-fluid">
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" id="posts">
<h1>Recent Blog Posts</h1>
{{#each model as |post|}}
<h1>{{post.title}}</h1>
<h4>By: {{post.author.name}} </h4>
<h4>Posted: {{format-date post.date}} </h4>
<img src={{post.imageUrl}} class="img-responsive img-thumbnail">
<p class="indent">
{{post.body}}
</p>
{{#link-to 'post' post}}Read More{{/link-to}}
{{/each}}
</script>
<script type="text/x-handlebars" id="post">
{{#link-to 'posts' class="pull-right"}}<span class="glyphicon glyphicon-chevron-left"></span>Back{{/link-to}}
<h1>{{post.title}}</h1>
<h4>Written By: {{author.name}} </h4>
<h4>Posted: {{format-date post.date}} </h4>
<hr/>
<div class="img-container center">
<img src={{post.imageUrl}} class="img-responsive img-thumbnail">
</div>
<br/>
<p class="indent">
{{post.body}}
</p>
</script>
Note: I tried a few different versions of Ember, and no matter what version I use I get the "content.slice is not a function" error, so I'm sure something I' m doing is wrong, but here is some extra info just incase :D!
VERSION INFO:
Ember Inspector: 1.8.1
Ember: 1.13.0-beta.2+76473dd3
Ember Data: 1.0.0-beta.18
jQuery: 2.1.4
1) I recommend you should not use store.findAll, since this is private api method. api link. The right way is to use store.find('modelName') instead. For example,
model: function() {
return this.store.find('post'); // promise to find all posts
}
2) If you want to load all posts and all authors in a route (for example in blog route)
App.BlogRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
posts: this.store.find('post'),
authors: this.store.find('author')
});
},
setupController: function(controller, modelHash) {
controller.setProperties(modelHash);
// or directly
// controller.set('posts', modelHash.posts);
// controller.set('authors', modelHash.authors);
// even
// this.controllerFor('posts').set('authors', modelHash.authors);
}
});
3) I'd simplify post route code:
App.PostRoute = Ember.Route.extend({
model: function(params){
return this.store.find('post', params.id);
},
afterModel: function(model){
var self = this;
return model.get('author').then(function(author) {
self.controllerFor('post').set('author', author);
});
}
// setupController not needed here
});
4) You may need more steps. Jsbin would be helpful to give accurate answer.
UPD 08/jun/15: Looks like findAll will become public soon https://github.com/emberjs/data/pull/3234.
I have a single template - editPerson.hbs
<form role="form">
FirstName: {{input type="text" value=model.firstName }} <br/>
LastName: {{input type="text" value=model.lastName }}
</form>
I want to render this template when the user wants to edit an existing person or create a new person. So, I set up routes:
App.Router.map(function(){
this.route("createPerson", { path: "/person/new" });
this.route("editPerson", { path: "/person/:id"});
// other routes not show for brevity
});
So, I define two routes - one for create and one for edit:
App.CreatePersonRoute = Ember.Route.extend({
renderTemplate: function(){
this.render("editPerson", { controller: "editPerson" });
},
model: function(){
return {firstName: "John", lastName: "Smith" };
}
});
App.EditPersonRoute = Ember.Route.extend({
model: function(id){
return {firstName: "John Existing", lastName: "Smith Existing" };
}
});
So, I hard-code the models. I'm concerned about the createPerson route. I'm telling it to render the editPersonTemplate and to use the editPerson controller (which I don't show because I don't think it matters - but I made one, though.)
When I use renderTemplate, I lose the model John Smith, which in turn, won't display on the editTemplate on the web page. Why?
I "fixed" this by creating a separate and identical (to editPerson.hbs) createPerson.hbs, and removing the renderTemplate hook in the CreatePerson. It works as expected, but I find it somewhat troubling to have a separate and identical template for the edit and create cases.
I looked everywhere for how to properly do this, and I found no answers.
I'm writing a small test app using Ember, in the form of a budget manager. I have a Budget object, which contains general properties like the monthly limit and a description. I also have a set of Expense objects, which contain a name, the amount spent, etc. Both are retrieved from a server using Ember Data's REST adapter.
HTML:
<body>
<script type="text/x-handlebars" data-template-name="budget">
<h2>{{name}} (€ {{amount}})</h2>
</script>
<script type="text/x-handlebars" data-template-name="expenses">
<ul id="expense-list">
{{#each model}}
{{render "expense" this}}
{{/each}}
</ul>
</script>
<!-- expense template -->
<script type="text/x-handlebars" id="expense">
<li>
<label>{{description}}</label>
<label class="subtle">{{formatDate time}}</label>
<label class="amount">{{amount}}</label>
</li>
</script>
</body>
</html>
JavaScript:
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'http://localhost:5000',
namespace: 'outthepocket/api'
});
// Model
App.Expense = DS.Model.extend({
amount: DS.attr('number'),
description: DS.attr('string'),
time: DS.attr('date')
});
App.Budget = DS.Model.extend({
name: DS.attr('string'),
amount: DS.attr('number')
});
// Routes
App.Router.map( function() {
this.resource('budget');
this.resource('expenses');
});
App.ExpensesRoute = Ember.Route.extend({
model: function()
{
return this.store.find('expense');
}
});
App.BudgetRoute = Ember.Route.extend({
model: function()
{
return this.store.find('budget', 1);
}
});
Following the architecture I see in all the Ember tutorials, there is an ExpensesRoute with the list of expenses as its model, and a BudgetRoute with the selected budget as its model. This works great as long as I go through the proper URL to see each resource:
myapp.html#budget renders the budget template with data from the server.
myapp.html#expenses renders the expenses template with data from the server.
The problem I'm having now is that I want to display both templates, with their data, on one page (the index page). I've tried two solutions for this:
Solution 1: Have separate routes and templates and call {{render budget}} and {{render expenses}} in the main application template. This renders both templates, but without any data.
Solution 2: Have just an IndexRoute and return both budget and expenses from its model property, rendering them into the index template. This more or less works, but seems counter to Ember's otherwise nice separation of different resources, routes and controllers.
Any thoughts? I've been through five or six tutorials and Ember's official guide, but none of those have made clear how to assemble a one-page web app with multiple templates backed by multiple resources without having to link to different pages/routes.
You can use Ember.RSVP.hash to load more than one model, in a single object:
App.IndexRoute = Ember.Route.extend({
model: function()
{
return Ember.RSVP.hash({
expenses: this.store.find('expense'),
budget: this.store.find('budget', 1)
})
}
});
And in the template you can access each resolved promise by the key:
{{expenses}} will return the result from this.store.find('expense') promise and {{budget}} the result from this.store.find('budget', 1) promise.
So in your index template you will able to do:
<script type="text/x-handlebars" id="index">
{{render "expenses" expenses}}
{{render "budget" budget}}
</script>
Here is the issue I'm having.
Say you have an app with two models, Project and Post. All posts belong in a specific project. So, the paths to the posts contain the project ID as well (example.com/:project_id/:post_id).
How can I transition from post X on project A to post Y in project B? Simply calling transitionToRoute('post', postA) from post B's route will retain post B's project ID in the url.
Here's a fiddle describing my predicament. As you can see, when using the project links at the top of the page, the correct posts appear in the correct projects. However, click the link after "other post" and you'll see how Ember is happy to display the post in the context of the incorrect project.
How can I transition between these "cousin" routes in Ember?
The JS:
window.App = Ember.Application.create({
LOG_TRANSITIONS: true
});
App.Store = DS.Store.extend({
adapter: DS.FixtureAdapter
});
App.store = App.Store.create();
App.Router.map(function(match) {
this.resource('projects');
this.resource('project', {path: ':project_id'}, function(){
this.resource('post', {path: ':post_id'});
});
});
App.Project = DS.Model.extend({
title: DS.attr('string'),
posts: DS.hasMany('App.Post')
});
App.Post = DS.Model.extend({
title: DS.attr('string'),
body: DS.attr('string'),
project: DS.belongsTo('App.Project')
});
App.Project.FIXTURES = [
{
id: 1,
title: 'project one title',
posts: [1]
},
{
id: 2,
title: 'project two title',
posts: [2]
}
];
App.Post.FIXTURES = [
{
id: 1,
title: 'title',
body: 'body'
},
{
id: 2,
title: 'title two',
body: 'body two'
}
];
App.ApplicationController = Ember.ObjectController.extend({
projects: function() {
return App.Project.find();
}.property()
});
App.PostController = Ember.ObjectController.extend({
otherPost: function(){
id = this.get('id');
if (id == 1) {
return App.Post.find(2);
} else {
return App.Post.find(1);
}
}.property('id')
});
And the templates:
<script type="text/x-handlebars" data-template-name="application">
{{#each project in projects}}
<p>{{#linkTo project project}}{{project.title}}{{/linkTo}}</p>
{{/each}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="project">
<h2>{{title}}</h2>
{{#each post in posts}}
{{#linkTo post post}}{{post.title}}{{/linkTo}}
{{/each}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="post">
<h3>{{title}}</h3>
<p>{{body}}</p>
other post: {{#linkTo post otherPost}}{{otherPost.title}}{{/linkTo}}
</script>
I found 3 issues.
1. Your belongsTo fixture data is missing the id's they belong to.
App.Post.FIXTURES = [
{
id: 1,
title: 'title',
body: 'body',
project:1
},
{
id: 2,
title: 'title two',
body: 'body two',
project:2
}
];
2. When you transition to a resource, if you only send in a single model, it will only change that resource's model, if you want to update multiple models in the path, send in all the models necessary
{{#linkTo 'post' otherPost.project otherPost}}{{otherPost.title}
3. linkTo routes should be in quotes. (in the future it won't work properly without them), see example above
http://jsfiddle.net/3V6cy/1
BTW, thanks for setting up the jsfiddle, it makes me like a million times more likely to answer a question. Good luck working with ember, we love it!
Cheers! I've got routes:
TravelClient.Router.map(function() {
this.resource('tours', function() {
this.resource('tour', { path: ':tour_id' }, function(){
this.route('seats');
});
});
});
And a template:
<script type="text/x-handlebars" data-template-name="tour/seats">
{{...}}
</script>
Seats is an attribute of Tour object:
TravelClient.Tour.find(1).get('seats');
12
And I extend my TourSeats route like this:
TravelClient.TourSeatsRoute = Ember.Route.extend({
model: function(params) {
return TravelClient.Tour.find(params.tour_id).get('seats');
}
});
Question: how to render tour's seats in template?
UPDATE:
My fixtures looks like that:
TravelClient.Store = DS.Store.extend({
revision: 11,
adapter: 'DS.FixtureAdapter'
});
TravelClient.Tour = DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
seats: DS.attr('number')
});
TravelClient.Tour.FIXTURES = [{
id: 1,
title: "Brighton, England",
description: "Lorem ipsum dolor ... .",
seats: 12
},...
And I've changed my route extend to this:
TravelClient.TourSeatsRoute = Ember.Route.extend({
model: function(params) {
return TravelClient.Tour.find(params.tour_id);
}
});
And in template:
<script type="text/x-handlebars" data-template-name="tour/seats">
{{tour.seats}}
</script>
UPDATE 2:
<script type="text/x-handlebars" data-template-name="tour/seats">
{{controller.model.seats}}
</script>
and it gives undefind back.
After some debugging I founded out, that there is no any id in params and params is empty, thats why I can't get the right model in TourSeatsRoute function.
If you're using ember-1.0-pre.4+, the params are only returned for the specific route you're on, not the whole URL. There's some discussion about this here.
I believe the desired approach at this time is to use this.modelFor passing the name of the parent resource you've set up in the parent route. So in your case, it would be:
TravelClient.TourSeatsRoute = Ember.Route.extend({
model: function() {
return this.modelFor("tour");
}
});
You just need to return the model from the model method:
TravelClient.TourSeatsRoute = Ember.Route.extend({
model: function(params) {
return TravelClient.Tour.find(params.tour_id);
}
});
And then in your template you can do the following where controller is the context:
{{model.seats}}
I'm still new to EmberJS but I would've written my router and routes like this.
I'm not sure that you need to wrap the post resource inside the posts resource. Note the double plurals in ToursSeatsRoute
TravelClient.Router.map(function() {
this.resource('tours', function() {
this.route('/:tour_id/seats');
});
});
This would give you the following urls:
/tours - you could map this to an ArrayController
/tours/:tour_id/seats - you could map this to an ObjectController
TravelClient.ToursSeatsRoute = Ember.Route.extend({
model: function(params) {
console.log(params);
return TravelClient.Tour.find(params.tour_id);
}
});
Give it a go? Or maybe put your code a in a JSFiddle?