I'm learning Ember.js using a Ruby on Rails API server. I've got the routes, template, and model all setup and working - but the template is never re-rendered once the data has been loaded from the API server. I'm not getting any error messages, and I know the customer is being loaded from looking at the Ember inspector.
Customer list is supposed to be displayed after start
Customer list is being loaded correctly from the API server:
Router
// javacripts/router.js
App.Router.map(function() {
this.resource('customers', { path: "/" });
});
Customers Route
// javascripts/routes/customer_routes.js
App.CustomersRoute = Ember.Route.extend({
model: function() {
return this.store.find('customer');
},
renderTemplate: function() {
this.render('customers/index');
}
});
Customer Model
// javascripts/models/customer.js
App.Customer = DS.Model.extend({
name: DS.attr('string')
});
Customer Index Template
// javacripts/templates/customers/index.js.handlebars
<ul>
<li>start</li>
{{#each customers}}
<li>{{name}}</li>
{{/each}}
</ul>
Store
// javacripts/store.js
App.ApplicationAdapter = DS.ActiveModelAdapter.extend({
namespace: 'api/v1'
});
Instead of
{{#each customers}}
It should read either
{{#each controller}}
{{name}}
{{/each}}
or
{{#each customer in controller}}
{{customer.name}}
{{/each}}
I have recently posted two screencasts. One showing how to get started with a new application, and one showing how to setup Grunt:
https://www.youtube.com/watch?v=T-s34EVSE_0
https://www.youtube.com/watch?v=kPaHt_F3VcU
You might also get some use out of a talk I gave earlier this year as well, which goes through developing a simple application during the talk, including Ember Data.
https://www.youtube.com/watch?v=KH5RreHtaaQ
Your customers/index template is referencing a "customers" collection that doesn't exist.
Your route's model hook is returning an array of records, which makes Ember generate an Ember.ArrayController with its model set to your array of customers. It doesn't have a property called "customers", so the {{#each customers}} doesn't have anything to iterate over. If you change it to just {{#each}} (because this in this scope references the controller, which is array-like) or {{#each model}} (to explicitly access the model array of the ArrayController), it should work correctly.
Also, your renderTemplate hook in the Route is the default behavior, so you can just delete it.
Incidentally, I'd recommend just using an Ember JSBin or something while you're starting out and learning the basics, so when you need to ask for help, you can just link to the bin, and people have live code they can work with to help you out, with a minimum of effort. That low barrier to entry makes a big difference to people who are doing free work for internet points.
Related
I'm new to ember js. I was trying to get a single record from my mongo db database but the query to server returns the full list view instead of a record.
templates/profile.hbs
{{#each item in model}}
{{item.userName}}
{{/each}}
routes/profile.hbs
export default Ember.Route.extend({
model: function() {
return this.store.find('user',{userName:'sanka'});
}
});
New syntax for printing arrays is:
{{#each model as |item|}}
{{item.userName}}
{{/each}}
Does your backend have a support for this kind of filter? Go check your network tab. Otherwise we won't be able to tell what your issue is. But it really looks as if you dont have support in your mongoDB. Because you're actually retrievering records, so it looks like the filter is "broken" or "non-existing"
To query for a single record that is not in an array you should be using
findRecord(type,id)
peekRecord(type, id)
queryRecord(type, {query})
http://emberjs.com/blog/2015/06/18/ember-data-1-13-released.html
URL to current version of my project
Here is the code for my project
Background:
I am making a blog using meteor and iron-router. I want to use a single controller for several different "category pages," which filter a list a blog articles in the yield region.
The Problem:
The article list does not get rerendered when the URL changes. I.e. the article list is not reactive. Interestingly, if I navigate back to the home page, the correct article list shows up.
The Question:
How do I make that article list change when I change between different routes on the category route controller?
Some example code:
Please note that the code for this whole project is available here.
Here is my Route Controller:
CategoryController = RouteController.extend({
action: function(){
this.render();
},
template: 'category',
data: function(){
return {category: this.params.category};
}
});
CategoryController.helpers({
articles: function(){
return Articles.find({category: this.params.category});
}
});
And here is the template it is rendering:
<template name='category'>
<div class="container">
<h2>{{category}}:</h2>
<ul>
{{#each articles}}
<li>
{{#linkTo route="article.show"}}
{{title}}
{{/linkTo}}
</li>
{{/each}}
</ul>
</div>
</template>
Resources/Updates:
Read this article on Meteor Reactivity and the Deps Package. Very interesting, but after trying some Deps.autoruns in different places, I don't think that this is the answer.
Currently trying to make different "category" routes inherit from the controller.
The article list does not change because the Template helper is not using a reactive data source. You may use the RouteController.getParams method to establish a reactive dependency on route parameters as shown below.
CategoryController.helpers({
articles: function(){
var controller = this;
var params = controller.getParams();
return Articles.find({category: params.category});
}
});
From Iron Router documentation:
Note: If you want to rerun a function when the hash changes you can do
this:
// get a handle for the controller.
// in a template helper this would be
// var controller = Iron.controller();
var controller = this;
// reactive getParams method which will invalidate the comp if any part of the params change
// including the hash.
var params = controller.getParams();
By default the router will follow normal browser behavior. If you
click a link with a hash frag it will scroll to an element with that
id. If you want to use controller.getParams() you can put that in
either your own autorun if you want to do something procedural, or in
a helper.
I have an ember data app and I'm using createRecord to instantiate a model on a new records page. The problem is that this instantly creates the record in the store. So if someone navigates away from the the new record page the object is already persisted. There used to be a createModel method but it seems to have been removed. How is this handled now?
You can check if Model.isNew so you can see has it been persisted. For example, you can do following in Handlebars to display list of records from database and hide new non-persisted models when you, for example, navigate backwards from model/add route:
{{#each item in model}}
{{#unless item.isNew}}
{{item.name}}
{{/unless}}
{{/each}}
According to Ember API docs, DS.Store.createRecord method:
Creates a new record in the current store.
If you don't want to check if record isNew. You can have some properties for user input and call createRecord only if you are sure it can, and will, be persisted.
Alternatively, instead of checking for Model.isNew you can remove the record from the store once the user leaves the route using Route.resetController and Store.unloadRecord.
Route.resetController is meant for all controller clean up you have to do once the model changes or the user transitions away. IMHO this includes removing unsaved models from the store.
PostsNewRoute = Ember.Route.extend
model: (params) ->
#store.createRecord 'post'
resetController: (controller, isExiting, transition) ->
#store.unloadRecord controller.get('model') if isExiting
See http://emberjs.com/api/classes/Ember.Route.html#method_resetController and http://emberjs.com/api/data/classes/DS.Store.html#method_unloadRecord
SCENARIO
I currently have an IndexRoute. I want to load 3 other controllers into it. Those 3 other controllers are called Sports, News, Business. I read the embersjs documentation and it states that you need to implement the renderTemplate hook into the IndexRoute for each of those controllers to be shown in the index. http://emberjs.com/guides/routing/rendering-a-template/
Once I did that I put in the index template
{{ outlet sports }}
{{ outlet news }}
{{ outlet business }}
They are showing up but as I look through the EmberInspector extension the individual model for Sports, News, Business are not loading.
QUESTION
Why are the models for these Sports, News, Business not loading in the index?
SEE JSBIN for my code sample
http://jsbin.com/gecarido/1/edit
ATTACHED IMAGE
route's are only hit when you define and hit a route via url.
For example if you'd defined your router like this:
Ember.Router.map(function(){
this.resource('foo', function(){
this.resource('bar');
});
});
And hit /foo/bar
It would hit
App.FooRoute = Em.Route.extend({
});
and
App.BarRoute = Em.Route.extend({
});
If you want to hit it all from just the root url you might as well return it all from the application model hook.
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return {
colors: ['red', 'yellow', 'blue'],
news: ['Europe', 'Asia', 'America'],
business: ['Markets', 'Finance', 'Stocks'],
sports: ['golf', 'hockey', 'football']
};
}
});
And then you can use render from the template and supply it a template name and a model.
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
<ul>
{{#each item in colors}}
<li>{{item}}</li>
{{/each}}
</ul>
<br>
{{render 'sports' sports}}
<br>
{{render 'news' news}}
<br>
{{render 'business' business}}
<br>
{{outlet}}
</script>
http://jsbin.com/gecarido/3/edit
I have another answer that solves this problem in a more modular approach
In my original solution
I was under the assumption that each controller has it’s own route and that route would deal with returning the data for that controller. So if you you included all 3 controllers each of them would deal with getting it’s own model. But I had the wrong assumption. I re-read the “note on coupling” in embersjs http://emberjs.com/guides/controllers/ .
So what I got from that documentation is the route is responsible for getting all models but you have to tell it to assign it to the additional controllers in that route. I also read up on models and fixtures http://emberjs.com/guides/models/the-fixture-adapter/
My new solution
Got rid of the extra routes (for now)
Added Ember Data with fixture data
I still kept {{ outlet }} I used the
“setupController” hook in the IndexRoute to connect the outside
controllers with the correct data. This is key
The reason for this approach is I might want to use those controllers “news”,”business”, “sports” somewhere else in the UI. I could potential even set up their own routes in the future and I think by using the ember data and models now will help.
See JSBIN solution
note this solution works on my desktop but the JSBIN is throwing some weird Script 0 error
http://jsbin.com/gecarido/5/edit
Say I have two objects in my application: 1) Topic, 2) Categories.
Topic has a route /topic/:topic_id
Categories doesn't have a URL route. It contains the list of all available categories that topics can be in.
On the page for Topic, I would like to show a list of all categories, while highlighting the category that the topic is a part of (the 'active' category)
Original Solution
I had originally solved this with a component as follows (a lot of code removed for brevity):
Component Template:
(Goes trough each category and lists them. If the current one is active, it says so.)
<script type="text/x-handlebars" id="components/category-list">
{{#each categories}}
{{#if this.active}} Active -> {{/if}}
{{this.name}}
<br />
{{/each}}
</script>
Component Object:
(Goes through the models via the CategoriesController, marks one category as the current active category for that topic via a "current" parameter passed in from the {{category-list}} component helper)
App.CategoryListComponent = Ember.Component.extend({
tagName: '',
didInsertElement: function () {
var categories = App.CategoriesController;
var current = this.get('current').get('id');
categories.get('content').forEach(function (c) {
if (c.id === current) {
c.active = true;
}
});
this.set('categories', categories);
}.observes('current')
});
Displaying the Category List:
In the Topic view ('category' is the property of Topic that says what category the topic is in):
{{category-list current=category}}
This all works, but I have a feeling that it is not the correct way to go about solving the problem. Components strike me as something that should be very reusable, and this really only is just an encapsulated part of one view.
Second Attempt
Now I am trying to use the category list as a view. So now instead of just a Component, I have a CategoriesRoute (not hooked up in the router though), a CategoriesController, and a CagetoriesView.
When the Topic resource loads up, it sets up the categories controller with the loaded model data:
App.TopicRoute = Ember.Route.extend({
model: function (params) {
return Em.RSVP.hash({
topic: this.store.find('topic', params.topic_id),
categories: this.store.find('category')
});
},
setupController: function (controller, context) {
this._super(controller, context.topic);
this.controllerFor('categories').set('model', context.categories);
}
});
The CategoriesController is just a standard array controller:
App.CategoriesController = Ember.ArrayController.extend({});
The CategoriesView is also very simple:
App.CategoriesView = Ember.View.extend({
templateName: 'categories',
tagName: ''
});
And finally the categories template:
<script type="text/x-handlebars" id="components/category-list">
{{#each categories}}
{{this.name}}
<br />
{{/each}}
</script>
I am rendering the list with the {{render}} helper in the Topic template:
{{render "categories" }}
The Problem:
The above seems to work fine in terms of showing the list of categories. However, I can't figure out how to tell the view which category is active (the category in which the topic is in).
I looked around on the internet, and it looks like the {{render}} helper used to allow for an optional options hash (which is how I solved the problem with a Component in my first attempt), but it seems to have been removed in some recent version of Ember.
I found a StackOverflow entry that mentioned making my own {{render}} helper, but it looks like that is to dynamically change models, or something of that nature. I'd like to keep the models backing for the categories view, I just need to be able to tell it which category is active via the Topic.category property.
Or, was my first attempt the better solution? I am very new to Ember, so I'm not sure what would be the best strategy here. My instinct tells me I should use my second attempt rather than the first, but I am not positive.
Any help is very appreciated.
You are right in saying that components must be re-usable everywhere and should not be tied to any particular controller. I would use a view for this. The way I would do is, I would have a
App.CategoriesController = Em.ArrayController.extend({
itemController: 'category',
//some other stuff here
});
for categories and then have an itemController called
App.CategoryController = Em.ObjectController.extend({
setActiveState: function() {
if (this.get('parentController.current.id') === this.get('content.id')) {
this.set('active', true);
}
}
});
and in the template, you could say
{{#each category in categories}}
{{category.name}}
{{/each}}