Listing fetched records in an Ember template - javascript

I'm trying to learn ember by following along with Vic Ramons Ember Rails tutorial.
I'm stuck trying to list the users I have fetched from Rails.
<!-- /templates/users.handlebars -->
<article id="users">
<h2>Users</h2>
<ul>
{{#each user in controller }}
<li>{{ user.email }}</li>
{{/each}}
</ul>
</article>
The /templates/users.handlebars is rendered but no users are listed.
I have checked the JSON output which is ok. When I check the ember inspector tab in chrome I can see 20 'user' models in the Data section.
What is the correct way to list the user records?
/routes/users.js:
Tagged.UsersRoute = Ember.Route.extend({
model: function(){ return this.store.findAll('user') }
});
/models/users.js:
Tagged.User = DS.Model.extend({
email: DS.attr('string')
});
/controllers/users.js:
Tagged.UsersController = Ember.Controller.extend({
});
/router.js:
Tagged.Router.map(function() {
this.resource('users', { path: '/users' })
});
adapters/application_adapter.js:
Tagged.ApplicationAdapter = DS.ActiveModelAdapter.extend({});
The controller JSON response:
{ "users":[
{"id":"5599bbc44d6178d3ae000000","email":"sasha#example.com"},
{"id":"5599bbc54d6178d3ae000001","email":"roberta#example.com"},
{"id":"5599bbc54d6178d3ae000002","email":"reyna.mayert#example.com"}
# ...
] }

<article id="users">
<h2>Users</h2>
<ul>
{{#each user in model }}
<li>{{ user.email }}</li>
{{/each}}
</ul>
</article>

In templates model property of controller is not usually used. If you change you /controllers/users.js to:
Tagged.UsersController = Ember.ArrayController.extend({
});
It should work. Because UsersController becomes a ArrayController which works with #each while normal controller does not.

Related

Emberjs itemController / controller property binding

I've made a jsbin to illustrate my issue.
the binding seems KO with lastname property defined inside the itemController and the fullname value is not updated in my items loop.
What am I doing wrong ?
Controller for item in list is different than one you edit property lastname for, so it will never get updated. Propery lastname has to be specified as Model's property (if using Ember Data you simply don't use DS.attr for it and it won't be persisted). If you use custom library for data persistence you have to manually remove lastname property. You can use Ember Inspector extension to see that there are 5 controllers when you click on item. 4 for each item in list and one is being generated when you click. You edit property lastname for this fifth controller. To solve this you can use:
JavaScript:
App = Ember.Application.create();
App.Router.map(function() {
this.resource('items', function() {
this.resource('item', {path: '/:item_id'});
});
});
App.Model = Ember.Object.extend({
firstname: 'foo',
lastname: 'bar',
fullname: function() {
return this.get('firstname') + ' ' + this.get('lastname');
}.property('firstname', 'lastname')
});
App.ItemsRoute = Ember.Route.extend({
model: function() {
return [App.Model.create({id: 1}), App.Model.create({id: 2}), App.Model.create({id: 3}), App.Model.create({id: 4})];
}
});
App.ItemRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor('items').findBy('id', +params.item_id);
}
});
Templates:
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{link-to "items" "items"}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="items">
<ul>
{{#each item in model}}
<li>
{{#link-to 'item' item.id}}
{{item.fullname}} {{item.id}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="item">
{{input value=model.firstname}}
{{input value=model.lastname}}
{{model.fullname}}
</script>
Please keep in mind that ArrayController and ObjectController aren't recommended to use, because they will be deprecated in future. Demo.

How to pass model in Nested routes - emberjs

I have some nested routes.
App.Router.map(function() {
this.route("dashboard", { path: "/dashboard" });
this.resource("customers", { path: "/customers" },function(){
this.resource("customer",{ path: "/:customer_id" },function(){
this.resource("customer.contact",{path:'/contact'});
});
});
});
TEMPLATES
customers/index
<script type="text/x-handlebars" data-template-name="customers/index">
<h3>Customers</h3>
<table>
{{#each item in model}}
<tr>
<td>{{item.name}}</td>
{{#link-to "customer" item tagName="td"}}Info{{/link-to}}
</tr>
{{/each}}
</table>
</script>
customer
<script type="text/x-handlebars" data-template-name="customer">
<h3>Customer {{name}}</h3>
{{#link-to}}Gallery{{/link-to}}
{{#link-to "customer.contact" this}}Contact{{/link-to}}
{{outlet}}
</script>
contact
<script type="text/x-handlebars" data-template-name="customer/contact">
<h3>Contact info of customer {{name}}</h3>
{{contact}}
</script>
Go Customers->Info
Everything works fine, the link from "customers/index" template passes the item to the customer template where {{name}} will be used. but if i want to pass the context to "contact" template, it doesnt work.
here is the JsBin
http://emberjs.jsbin.com/EveQOke/107
You need to specify a route for customer contact (as well for customer). The reason it works initially is because the link-to is passing the model to the route, so it can skip the non-existent model hook. But when you refresh the page, or hit the contact route, which has no dynamic segment, you need to tell ember that you want to use a model. There is a beta feature that allows all the routes under a resource to use the resource if they don't have another resource defined, but that's still a feature, and isn't yet gold.
App.CustomerRoute = Ember.Route.extend({
model: function(param){
return this.store.find('customer', param.customer_id);
}
});
App.CustomerContactRoute = Ember.Route.extend({
model: function(){
return this.modelFor('customer');
}
});
http://jsbin.com/EveQOke/110/edit

How to build comment system in ember.js

I am fairly a newbie to ember.js. I currently working on a school project which is essentially a message board (forum) application that allows users to add posts with comments.
My application contains three models: courses, messages, comments
course->hasMany->messages->hasMany->comments
So far I have been able to view all the courses and related messages using filter query to my server. Adding new messages works fine also except that it is not updating the new message added to the screen.
Problem: It is only when I refresh the page the new message I added is displayed.
App.Router.map(function() {
this.resource('home', { path : '/'}, function() {
this.resource('mycourse', { path : ':course_id' } );
});
});
App.MycourseRoute = Ember.Route.extend({
model: function(params) {
// the model for this route is a new empty Ember.Object
var string = '{"filters":[{"name":"courseid","op":"eq","val":'+params.course_id+'}]}'
return this.store.find('message', { q: string });
}
});
App.HomeRoute = Ember.Route.extend(
{
model: function() {
return this.store.find('course');
}
});
Here is my message controller:
App.MycourseController = Ember.ArrayController.extend({
actions: {
addMessage: function(messageText) {
var message = messageText;
var messageAdd =this.store.createRecord('message', {
message: message,
posttime: "12:00pm",
courseid: 4,
userid: 1
});
messageAdd.save();
}
}
});
My html part:
<script type="text/x-handlebars" id="home">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
{{#each}}
<li>{{#link-to 'mycourse' this.id}}{{name}}{{/link-to}}</li>
{{/each}}
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">Subscribed Courses:</h1>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" id="mycourse">
<button class="btn btn-default" type="button" id="create-message" {{action "addMessage" messageTxt}}>
Post!
</button>
{{input type="text" value=messageTxt}}
<br><br>
{{#each}}
<b>{{message}}</b>
<li>{{posttime}}</li>
<li>User name: {{user.username}}</li>
<li>Course name: {{course.alt_name}}</li>
<h4>Comments:</h4>
{{#each comments}}
<li>{{comment}}</li>
{{/each}}
<br>
{{/each}}
</script>
Turns out when you use findQuery (which I believe is the same as using find with query parameters), Ember does not return a live updating array, whereas it does for a straight up find/findAll. See this question on that exact issue that I asked a while back.
The solution here (adapted from kingpin2k's answer to said question) is to use filter to trick Ember into auto-updating:
App.MycourseRoute = Ember.Route.extend({
model: function(params) {
// the model for this route is a new empty Ember.Object
var string = '{"filters":[{"name":"courseid","op":"eq","val":'+params.course_id+'}]}'
return this.store.find('message', { q: string });
},
setupController:function(controller, model){
var filter = this.store.filter('color', function(color){
return model.contains(color);
});
this._super(controller, filter);
}
});

Ember multiple Json request Error while loading route Object has no method 'addArrayObserver'

im having a problem with my ember app. Im new to it, and trying to do something fun. So the idea of this app is to go and fetch a list of artists from a server via an ajax call, and then if you click the artist it will go to the server again and fetch the albums via another ajax call.
So the first part is working, it is actually fetching the artists through the ajax call when i click on "music library", but then when clicking on the artist it throws the following error:
Assertion failed: Error while loading route: TypeError: Object [object Object] has no method 'addArrayObserver'
I've read so many different options, and i think im on the right track because by printing on the console i can see that it is actually going to the server and fetching the right artist's albums, but the error is throw at the last moment, so it is not displaying the albums. I was also able to show the albums when reloading or typing the url (not now, since i changed the code to implement the afterModel)
So, here is my code:
App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true
});
App.Library = Ember.Object.extend({
name: null,
artist: []
});
App.Library.reopenClass({
loadArtist: function() {
var artistList = Em.A();
$.getJSON('url').then(function(data){
//json parsing, creating a library object and putting it into the array
});
return artistList;
}
});
App.Artist = Ember.Object.extend({
id: null,
name: null,
coverArt: null,
albumCount: null
});
App.Albums = Ember.Object.extend({
albums: []
});
App.Artist.reopenClass({
loadAlbums: function(params) {
var albumsJson = 'url' + params.artist_id +'';
var albumList = Em.A();
$.getJSON(albumsJson).then(function(data){
//parsing json, creating artist objects and pushing them into the array
});
return albumList;
//});
}
});
/*****************************ROUTER**************************************************************************************/
App.Router.map(function() {
// put your routes here
this.resource('library', function() {
this.resource('artist', { path: '/:artist_id'});
});
});
App.IndexRoute = Ember.Route.extend({
model: function() {
var hi = ['Welcome'];
return hi;
}
});
App.LibraryRoute = Ember.Route.extend({
model: function() {
return App.Library.loadArtist();
}
});
App.ArtistRoute = Ember.Route.extend({
model: function(params) {
this.transitionTo('artist', params);
},
afterModel: function(params, transition){
var artist = Em.A();
if(params.artist_id==null){
artist.push(App.Artist.create({artist_id: params.id}));
} else {
artist.push(App.Artist.create({artist_id: params.artist_id}));
}
return App.Artist.loadAlbums(artist[0]);
}
});
/**************************************CONTROLLERS***********************************************************************************/
App.ArtistController = Ember.ArrayController.extend({
needs: "library"
});
App.LibraryController = Ember.ArrayController.extend({});
I would really appreciate some help!
Also, the HTML is as follows:
<script type="text/x-handlebars">
<div class="navbar navbar-default">
<div class="navbar-inner">
<a class="navbar-brand" href="#">My Library</a>
<ul class="nav navbar-nav">
<li>{{#linkTo 'index'}}Home{{/linkTo}}</li>
<li>{{#linkTo 'library'}}Music Library{{/linkTo}}</li>
</ul>
</div>
</div>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<div class="container">
{{#each item in model}}
<h1>{{item}}</h1>
{{/each}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="library">
<div class="container">
<div class="row">
<div class="col-md-4">
<table class="table">
{{#each model}}
<tr><td>
{{name}}
</td></tr>
{{#each artist}}
<tr><td>
{{#linkTo 'artist' this}}
{{name}}
{{/linkTo}}
<!--<a {{action 'selectArtist' this}}> {{name}} </a>-->
</td></tr>
{{/each}}
{{/each}}
</table>
</div>
<div class="col-md-8">
<p>Albumes</p>
{{outlet}}
</div>
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="artist">
<div class="container">
<div class="col-md-4">
<table class="table">
<tr><td><p>{{controllers.library.artist.name}}</p></td></tr>
{{#each itemController='album'}}
<tr><td>
{{{name}}}
</td></tr>
{{/each}}
</table>
</div>
</div>
</script>
Thanks a lot!!
To get rid of the error you need to modify the model function of the App.ArtistRoute, to return an array as App.ArtistController is an Ember.ArrayController.
For example,
App.ArtistRoute = Ember.Route.extend({
model: function(params) {
//this.transitionTo('artist', params);
return [];
},
....
Or even place the code of afterModel function in model function to retrieve the albums of this artist.
Although i'm not certain if you really want the model of your artist context to be the albums, it does not look correct to me. I would suggest to make the App.ArtistController aν Ember.ObjectController, assign the model to an App.Artist object and store the albums related to this artist in a property of the App.Artist class. In that case you will need to add a property in App.Artist and create a class of App.Album.
With this in mind have a look at the following example which is a very rough modification of your code (caution the App.ArtistController has not been switched instead its model is an array of albums),
http://emberjs.jsbin.com/AdOfiyiN/2#/library/2
OK, i solved it using this question:
Why isn't my ember.js route model being called?
Instead of putting the logic in the model or afterModel, i just needed to set the controller.
Hope it helps to someone.
Best!

Ember Data: preload relationships

What I'm trying to do is very basic but I'm having very little luck...
Simply enough, I don't want to display a chunk of HTML until a certain Ember Data model property is fully loaded.
As you can see from the jsfiddle, the parent model: App.Person gets loaded into the DOM and it also loads the 3 placeholders for its hasMany property belts.
It then executes the request to populate App.Belt and fills in the placeholders.
While this is usually ok, it makes a big mess of things when trying to build an SVG, for example. Since the surrounding <svg> tags will get appended to the DOM immediately and then some time down the track (once the asynchronous request returns data), the inner svg components will be added between the tags. This usually creates browser rendering errors.
TL;DR
In the example, how do I defer the <h3>...</h3> section of the template from being added to the DOM until the model data and its relationships (belts) are fully loaded? This way everything gets visually and physically added to the DOM at once.
The JS:
// Create Ember App
App = Ember.Application.create();
// Create Ember Data Store
App.store = DS.Store.create({
revision: 11,
//Exagerate latency to demonstrate problem with relationships being loaded sequentially.
adapter: DS.FixtureAdapter.create({latency: 5000})
});
// Create parent model with hasMany relationship
App.Person = DS.Model.extend({
name: DS.attr( 'string' ),
belts: DS.hasMany( 'App.Belt' )
});
// Create child model with belongsTo relationship
App.Belt = DS.Model.extend({
type: DS.attr( 'string' ),
parent: DS.belongsTo( 'App.Person' )
});
// Add Parent fixtures
App.Person.FIXTURES = [{
"id" : 1,
"name" : "Trevor",
"belts" : [1, 2, 3]
}];
// Add Child fixtures
App.Belt.FIXTURES = [{
"id" : 1,
"type" : "leather"
}, {
"id" : 2,
"type" : "rock"
}, {
"id" : 3,
"type" : "party-time"
}];
// Set route behaviour
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Person.find();
},
renderTemplate: function() {
this.render('people');
}
});
The HTML/HBS:
<script type="text/x-handlebars">
<h1>Application</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" id="people">
<h3>Don't load this header until every belt defined in App.Person.belts is loaded</h3>
<ul>
{{#each controller}}
{{debugger}}
<li>Id: {{id}}</li>
<li>Name: {{name}}</li>
<li>Belt types:
<ul>
{{#each belts}}
<li>{{type}}</li>
{{/each}}
</ul>
</li>
{{/each}}
</ul>
</script>
The fiddle: http://jsfiddle.net/zfkNp/4/
Check for the controller.content.length and belts.isLoaded, See the jsfiddle for a solution.
<script type="text/x-handlebars" id="people">
{{#if controller.ready}}
<h3>Don't load this header until every belt defined in App.Person.belts is loaded</h3>
{{/if}}
<ul>
{{#each controller}}
{{debugger}}
{{#if belts.isLoaded}}
<li>Id: {{id}}</li>
<li>Name: {{name}}</li>
<li>Belt types:
<ul>
{{#each belts}}
<li>{{type}}</li>
{{/each}}
</ul>
</li>
{{/if}}
{{/each}}
</ul>
</script>
App.IndexController = Ember.ArrayController.extend({
content: null,
ready:function() {
return this.get('content.length')>0
}.property('content.length')
});

Categories