I'm getting familiar with ember and can't seem to debug an issue I'm having with a generated resource. After generating a new app with ember-cli, I generated a resource:
ember generate resource contacts
This generated a few files, all seemed well. The problems began when attempting to list the contacts from a http-mock. I can see from the network tab in dev tools that the records are retrieved, but the view won't display them:
Model:
#app/models/contact.js
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string')
});
Route:
#app/routes/contacts.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.get('store').find('contact');
}
});
The view
#app/templates/contacts.hbs
<h2 id="title">Contacts</h2>
{{#each contact as |c|}}
{{c.title}}
{{/each}}
{{outlet}}
JSON from http mock:
{
"contacts": [
{
"id": 1,
"title": "Test"
}
]
}
I'm not sure what I'm doing wrong, any help is appreciated.
Found the answer, the problem was in my view contacts.hbs. Changed to:
<h2 id="title">Contacts</h2>
{{#each model as |c|}}
{{c.title}}
{{/each}}
{{outlet}}
Thanks to #jacefarm on this question for putting me on the right track
Related
I have a two panel display where I show a list of items on the left, then detail about a selected item on the right (using nested route).
My route looks like this:
Router.map(function() {
this.route('authenticated', {path: '/'}, function() {
this.route('bakery', function() {
this.route('cakes', function() {
this.route('detail', { path: '/:id' });
});
});
});
});
My URL looks like
http://localhost:3333/bakery/cakes/e34b3ce3
When an item is selected, it is set to "active" (temporary property on the model - default is false) and highlighted via an action on the bakery/cakes route. The detail is then shown on the right.
If I refresh the page, the item is no longer highlighted - but the detail is still shown.
Ideally I'd like to use the afterModel() hook in the bakery/cakes route to set that item back to active again, but I've been unable to get the Id to be able to do this.
I've tried the following:
Accepted answer from here
This question doesn't help me as the model will have reloaded and my "active" property will be false so I can't just select where active = true.
I'm using ember 2.5.0. Thanks.
I wonder if it'd be better to architect your structure a bit differently (from what I assume you're doing).
First, load all of the cakes on the authenticated.bakery.cakes route;
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('cakes');
}
});
Secondly, show your "full width" cakes list on the authenticated.bakery.cakes.index template (the cake models will be inherited);
<div class="full width cake list">
{{#each model as |cake|}}
{{#link-to "authenticated.bakery.cakes.detail" cake.id}}
{{!-- cake photo --}}
{{cake.name}}
{{!-- other cake details... --}}
{{/link-to}}
{{/each}}
</div>
Next, on your authenticated.bakery.cakes.detail route, load the specific cake along with the list of cakes;
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
let cakes= this.modelFor('authenticated.bakery.cakes');
return Ember.RSVP.hash({
cakes: cakes,
cake: cakes.findBy('id', params.id)
});
}
});
Finally on the authenticated.bakery.cakes.detail template, show the condensed/narrow list of cakes along with the specific cake details. And using {{link-to}}, the 'active' class will automatically be applied;
<div class="narrow width cake list">
{{#each model.cakes as |cake|}}
{{#link-to "authenticated.bakery.cakes.detail" cake.id}}
{{cake.name}}
{{/link-to}}
{{/each}}
</div>
<div class="cake details">
{{model.cake.name}}
</div>
As another option, change your model active flag on the proper route hooks should work. (I think anyway, haven't done this myself.) On your authenticated.bakery.cakes.detail route;
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('cakes', params.id);
},
afterModel(cake) {
cake.set('active', true);
},
actions: {
willTransition() {
this.get('controller.model').set('active', false);
}
}
});
I have set up a simple api and a new ember app using ember cli
However, while I can get a full list of 'items' to appear, I can't get a single item to work using params.
I have my files set up like the following:
/models
item.js
/routes
/items
index.js
show.js
/templates
/items
index.hbs
show.hbs
application.hbs
index.hbs
items.hbs
router.js
For reference, here is what I have in the files
I set up my models/item.js a follows:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string')
});
I added my code to my router.js
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
this.route('items', function() {
this.route('show', { path: '/:item_id' });
});
});
export default Router;
Then I created my templates/index.hbs
{{outlet}}
And finally, added my templates/items/index.hbs
<ul>
{{#each model as |item|}}
<li>
{{#link-to 'items.show' item}}
{{item.name}}
{{/link-to}}
</li>
{{/each}}
</ul>
This works as intended.
However, when I click a link (/items/1), it no longer performs as expected. It will show the items/show.hbs correctly assuming I do no use any handlebars variables ({{item.name}}). For example, If I just include text, this is my items/show.hbs it will display the text. When I add in, {{name}} it displays a blank page
Here is my templates/items/show.hbs
{{name}}
and my routes/items/show.js
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.store.queryRecord('item', params.item_id);
}
});
What am I doing wrong?
Thanks in advance!
It looks like you're not using reference to model when using it in template
Here is my templates/items/show.hbs
{{name}} < This is a model's property- name
Correct usage: {{model.name}}
and my routes/items/show.js
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.store.queryRecord('item', params.item_id);
}
});
Way of using hash
return Ember.RSVP.hash({
something: store.find('y', { study: params.study_id }),
somethingElse: store.find('x', 1),
});
I'm building a simple application using Ember v1.8.1, Ember CLI 0.1.15 and Ember Data 1.0.0-beta.15 in an attempt to learn the basics of Ember.js. Data is coming from a simple REST API, so I'm using the DS.RESTAdapter and DS.RESTSerializer. The payload is normalized in the serializer, because the API doesn't properly wrap its data in the required hashes.
I'm trying to implement an edit route, but am unable to populate the form fields of that template or even display a simple value. My index route, displaying a list of posts, works as expected, but edit doesn't for some reason.
Using {{title}} in my template doesn't render the value, but outputs a string of unexpected data, something along the lines of: <my-app#model:post::ember413:21>. I'm probably passing something improperly from the route or elsewhere, but I don't know what and where exactly.
Below is some of my relevant code:
app/router.js
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
this.route('login');
this.resource('posts', function() {
this.route('add');
this.route('view', { path: ':post_id'});
this.route('edit', { path: ':post_id/edit'});
});
});
export default Router;
app/routes/posts/edit.js
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function(params){
return this.store.find('post', params.post_id);
}
});
app/routes/posts/index.js
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function(){
return this.store.find('post');
}
});
app/controllers/posts/edit.js
import Ember from 'ember';
export default Ember.ObjectController.extend();
app/serializers/application.js
import Ember from "ember";
import DS from "ember-data";
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
primaryKey: 'ID',
attrs: {
author: { embedded: 'always' }
},
extractSingle: function(store, type, payload, id) {
payload = { post : payload };
return this._super(store, type, payload, id);
},
extractArray: function(store, type, payload) {
payload = { posts : payload };
return this._super(store, type, payload);
},
serializeIntoHash: function(hash, type, record, options) {
Ember.merge(hash, this.serialize(record, options));
}
});
app/templates/edit.hbs
<div class="page">
<div class="page-content">
<h2>{{title}}</h2>
{{{content}}}
</div>
</div>
app/templates/index.hbs
<div class="page">
<div class="page-content">
<ul>
{{#each}}
<li>
<h2>{{title}}</h2>
{{{content}}}
</li>
{{/each}}
</ul>
</div>
</div>
Having looked at some examples the above should work, so probably there's an error in the serializer. In the route I can access values from the store however. The following logs the expected data when used in the model method of my edit.js route:
this.store.find('post', params.post_id).then(function(post) {
console.log(post.get('title'));
});
UPDATE: Here is a JS Bin recreating most of the application without Ember CLI. The behaviour is exactly the same as on my local machine; the edit route doesn't properly show the content value (title works, for some reason). When I swap to fixtures instead of the API output the problem remains the same. Also the model in the bin doesn't map to all the values returned by the API, but that doesn't seem to make a difference.
UPDATE 2: Seems the content entry of the returned payload is causing the problems, when I rename the key it's displayed correctly in the edit template. Didn't realise this before, because I was so focussed on getting the content value to display and forgot to test other values. Not sure why it happens only happens on a nested route though and with that particular keyword.
UPDATE 3: If I understand correctly, this might be related to an outstanding issue for Ember Data, there's a proposal for reporting such collisions as well.
I'm having an issue where I'm unable to get nested outlets to appear properly in my Ember CLI app. The view tree I want is as follows:
application (list of all resources, of which client_availability is one)
- client_availabilities.index (list of client_availabilities)
-- client_availability (individual client_availability)
This is very similar to the "application > posts.index > post" hierarchy in the Ember Starter Kit. My desired behavior is for a list of client_availabilities to appear in "mainoutlet" when I navigate to client_availabilities.index, then persist when I bring up an individual client_availability in "suboutlet".
Easy, right? This is the default behavior & why we all love Ember. However, I can't seem to get it working. When I explicitly target my named suboutlet in client_availabilities.index and click on an individual client_availability, nothing shows up in either outlet:
Scenario 1: Render suboutlet inside client_availabilities
/app/template/application.hbs:
{{link-to 'Client Availabilities' 'client_availabilities'}}
{{outlet 'mainoutlet'}}
/app/template/client-availabilities/index.hbs:
{{outlet 'suboutlet'}}
/app/routes/client-availabilities/index.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render({
into: "application",
outlet: "mainoutlet"
});
},
model: function() {
return this.store.find('client_availability');
}
});
/app/routes/client-availability.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render('client_availability', {
into: "client_availabilities",
outlet: "suboutlet"
});
},
model: function(params) {
return this.store.find('client_availability', params.client_availability_id);
}
});
Alternately, when I target my mainoutlet in application, client_availability appears in "suboutlet" client_availabilities.index disappears from "mainoutlet":
Scenario 2: Render suboutlet inside application
/app/template/application.hbs:
{{link-to 'Client Availabilities' 'client_availabilities'}}
{{outlet 'mainoutlet'}}
{{outlet 'suboutlet'}}
/app/template/client-availabilities/index.hbs: (empty)
/app/routes/client-availabilities/index.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render({
into: "application",
outlet: "mainoutlet"
});
},
model: function() {
return this.store.find('client_availability');
}
});
/app/routes/client-availability.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render('client_availability', {
into: "application",
outlet: "suboutlet"
});
},
model: function(params) {
return this.store.find('client_availability', params.client_availability_id);
}
});
And here's my router, the same in both cases:
/app/router.js:
import Ember from 'ember';
var Router = Ember.Router.extend({
location: 'auto'
});
Router.map(function() {
this.resource('client_availabilities', function() {
this.resource('client_availability', { path: ':client_availability_id' });
});
});
export default Router;
I'm happy to share more code, but the application is split into several files and unfortunately not something I can post in its entirety. Can anyone see what I'm doing wrong? The rest of the app is working fine, I just can't seem to get this basic behavior to work.
Do you have an /app/templates/client-availibilities.hbs template with only {{outlet}} inside of it? Without this, the app is going to lose its place in the outlet tree. Ember-CLI and the Ember Starter Kit are very, very different from each other in structure, so I can see where the confusion comes from.
How I like to think of Ember's rendering style is that each handlebars file inside the templates folder (i.e. /templates/users.hbs) represents a change the overall state of the application from one subject to another (example: from newsfeed to users).
The corresponding subfolders inside the templates folder change the state of the subject itself.
For example:
Required Templates
Users container OR the only users page you need app-wide is at /templates/users.hbs
Optional Templates
Users Index would be at /templates/users/index.hbs
Users Show would be at /templates/users/show.hbs
Users New would be at /templates/users/new.hbs
You can have [ /templates/users.hbs ] without having [ /templates/users/*.hbs ] and still keep track of your data; however, you cannot have [ templates/users/index.hbs ] without [ /templates/users.hbs ] and still keep track of your data. Why? Imagine if you navigate to somesite.com/users. There is currently no top-level template with an outlet into which Ember can render the [ users/index.hbs ] template. The [ /templates/users.hbs ] template bridges that gap and also serves as a container for all other pages inside the /templates/users folder as well.
For example, in the terms of your app, in order to render [ /app/templates/client-availibilities/index.hbs ] when a user visits http://www.yourwebsite.com/client-availibilities, your app will need these templates defined so that ember can drill down into them.
application.hbs // and in its outlet, it will render...
--client-availibilities.hbs // and in its outlet, it will render by default...
----client-availibilities/index.hbs // then, for the client-availability (singular), you can have ember render it in
----client-availibilities/show.hbs // will render also in the client-availabilites as it is a separate state of the subject. Can also be nested inside the index route within the router so that it renders inside the index template.
As it is, I would structure your app as such...
/app/router.js
... // previous code
Router.map(function() {
this.resource('client_availabilities', function() {
this.route('show', { path: '/:client_availability_id' });
// this.route('new'); ! if needed !
// this.route('edit', { path: '/:client_availability_id/edit' ); ! if needed !
});
});
... // code
/app/templates/application.hbs
{{link-to 'Client Availabilities' 'client_availabilities'}}
{{outlet}}
/app/templates/client-availabilities.hbs
{{outlet}}
/app/templates/client-availabilities/index.hbs
<ul>
{{#each}}
{{#if available}}
<li>
{{#link-to #link-to 'client-availabilities.show' this}}
{{firstName}} {{lastName}}
{{/link-to}}
</li>
{{/if}}
{{else}} <!-- we want this to only render if the each loop returns nothing, which is why it's outside the if statement -->
<li>Nobody is available</li>
{{/each}}
</ul>
<!-- Note: you don't need to put an outlet here because you're at the end of the tree -->
/app/templates/client-availabilities/show.hbs
<!-- Everything you want to show about each availability -->>
<!-- Note: you don't need to put an outlet here because you're at the end of the tree -->
/app/routes/client-availabilities/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('client_availability');
}
});
/app/routes/client-availabilities/show.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return this.store.find('client-availability', params.client_availability_id);
}
});
/app/models/client-availability.js
import DS from 'ember-data';
var client-availability = DS.Model.extend({
firstName: DS.attr('string'),
lastname: DS.attr('string'),
available: DS.attr('boolean'),
available_on: DS.attr('date')
});
export default client-availability;
However, are you sure you want to structure your app by the availability of each client? Wouldn't it make more sense to structure it by each client and then just filter each client to show if they were available or not? Resources are supposed to be nouns, and routes are supposed to be adjectives. Therefore, it would be best to use a client as your model instead of their availability and have a either an isAvailable property on the model (as used in the example above) or a one-to-many association with an additional availability model if you want to show clients who have several availabilities (as shown below).
For example,
/app/models/client.js
import DS from 'ember-data';
var Client = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
availabilities: DS.hasMany('availability')
});
export default Client;
/app/models/availability.js
import DS from 'ember-data';
var Availability = DS.Model.extend({
date: DS.attr('date'),
client: DS.belongsTo('client')
});
export default Availability;
In the long run, this latter approach would set up your app to show all availabilities at once and allow the user to filter by the client, plus it would allow the user to view a client and see all their availabilities. With the original approach (the isAvailable property on the client model), the user can only get the availabilities from the client model itself, but what if the user wants to see all clients who are available on, say, March 3rd at noon? Well, without an availability model associated with the client model, you are going to have to put a lot of code into your client controller that ember would give you by default if you go down the one-to-many path.
If you need more advice on where to go from here, let me know. I'm more than happy to add more examples of the templates, controllers, and routes that you'll need in order to pull this off.
I'm using the latest ember-cli and currently testing it, creating very simple app using ember-data and http-mock for RESTAdapter - ember generate http-mock api-server
Single post get from the API:
http://localhost:4200/api/api-server/posts/1
Result
{"post":
{
"id":1,
"title":"How to write a JavaScript Framework",
"author":"Tomhuda Katzdale",
"body":"Lorem ipsum dolor sit amet"
}
}
Here are all the relevant codes:
Adapters
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
namespace: 'api/api-server'
});
Model
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
author: DS.attr('string'),
body: DS.attr('string')
});
router.js
import Ember from 'ember';
var Router = Ember.Router.extend({
location: EmbercliDataENV.locationType
});
Router.map(function() {
this.route('application');
this.resource('posts', function() {
this.resource('post', { path: ':post_id' });
});
});
export default Router;
Routes (posts and post) - view and child view
posts route
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('post');
}
});
post route
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return this.store.find('post', params.post_id);
}
});
template
posts.hbs
<h2>Posts List</h2>
<ul>
{{#each}}
<li>
{{#link-to 'post' this}}{{title}}{{/link-to}} | {{author}} | {{body}}
</li>
{{/each}}
</ul>
{{outlet}}
post hbs
<h2>Single Post</h2>
<p>Post title: {{title}}</p>
<p>Post author: {{author}}</p>
<p>Post body: {{body}}</p>
THE PROBLEM: if you look at the screen-cap below, in the chrome ember debugger, the single post model is hooked to the post route but is not printed in the post.hbs
Using just the ember starter kit, I got no problem at all with the exact same app.
Thank you for any help, cheers
UPDATE
The following is the exact same Ember app coded using the starter kit, host in xampp and using PHP Slim framework for REST Api. Working FINE!
try to fix your data, because post is nested in posts, try this:
{"post":[
{
"id":1,
"title":"How to write a JavaScript Framework",
"author":"Tomhuda Katzdale",
"body":"Lorem ipsum dolor sit amet"
}]
}
check within your posts hbs if the data is visible, then post should work fine
After browsing the Ember.js forum I got the answer there - http://discuss.emberjs.com/t/strange-behavior-w-retrieving-model-from-controller/6155
It seems when using the Ember generate controller command, you end up with Ember.Controller, not Ember.ObjectController or Ember.ArrayController lol.
I edit my post controller from: (generate by the Ember generate controller command)
export default Ember.Controller.extend({
});
to
export default Ember.ObjectController.extend({
});
and now the model is binding :D
<h2>Posts List</h2>
<ul>
{{#each}}
<li>
{{#link-to 'post' post}}{{title}}{{/link-to}} | {{author}} | {{body}}
</li>
{{/each}}
</ul>
{{outlet}}
1.instead of 'this' use post, hope this will fix the issue
http://emberjs.com/guides/templates/links/#toc_adding-additional-attributes-on-a-link