I've been recently playing with Ember.js utilizing Ember CLI and I am running into an issue with a very simple application I cannot seem to get around.
I have set up an Ember Data fixture adapter with a simple model "Post" which contains two string properties--title & content. I've then proceeded to map a route like so:
Router.map(function() {
this.resource('posts', {path: '/'});
});
Then create a router for posts which simply returns the model data:
export default Ember.Route.extend({
model: function() {
this.store.find('post');
}
});
My template simply iterates through all the posts in the model like so:
{{#each post in model}}
<h3>{{post.title}}</h3>
<p>{{post.content}}</h3>
{{/each}}
The Ember browser extension properly shows my two test posts in the Data tab like so:
But for some reason the template simply will not render the model data. My application.hbs file does contain the appropriate {{outlet}} which I've tested by adding some dummy text into the post.hbs file. This text shows up properly.
Any idea why my model will not display properly would be greatly appreciated. Thanks!
Unless you made an error while typing your code into StackOverflow, your issue is that you're not actually returning anything in your model hook. Change this:
this.store.find('post');
To this:
return this.store.find('post');
Related
just started to play with emberjs.dont know if this is stupid question or good one, but I am stuck in this for some days and cant figure out why.
in my router.js
Router.map(function() {
this.resource('posts', {path: '/'});
..........
});
.....
this.resource('post', {path: 'posts/:post_id'});
});
and in the route folder i have posts.js setup as following.it has simple js varible used to hold id , title, and body of articles.
export default Ember.Route.extend({
model: function(){
var posts = [
{
id:'1',
title:'lipsome vesicle',
body:"A liposome is a spherical vesicle"},
{
id:'2',
title:'another lipsome vesicle',
body:"A liposome is a another vesicle"}
]
//console.log(posts)
return posts;
}
});
In posts.hbs, the title of each post is shown as link to the to post. of course by looping through each model as |post| and print link-to post.title.
my post.js file simply get the model for posts and returns it.
export default Ember.Route.extend({
model: function(params) {
return this.modelFor('posts').findBy('id', params.post_id);
}
});
In my template for post.hbs I wanted to simply show the title and body for the post .It would have been more redable if it was like post.title or something like that. but i saw some tutorials that does following.
<h1>{{title}}</h1>
<h1>{{body}}</h1>
the url goes to the localhost:4200/post/1
but I cannot see the title and body
when I checked the value in console for
this.modelFor('posts').findBy('id', params.post_id).title , it prints the title
but view is blank.
I have read somewhere, that the controller is the one that is responsible to get the value from model. in that case too, I dont know how to access the returned model in that controller.
I have watched many tutorials, including the raffler example by railscast, since I have background in rails. but those tuts including lot other seems preety outdated. So for all new learners this is frustating and confusing too. Is there good fresh resources except the emberjs guide?
since the title was being printed in console, after many trials when i tried this trick, i finally managed to get my title in individual post.using Ember inspector plug in when i clicked $E console gave me all the post.so i put that object in array and returned that array.
model: function(params) {
// console.log("hell")
console.log(this.modelFor('posts').findBy('id', params.post_id).title);
var post = [this.modelFor('posts').findBy('id', params.post_id)];
return post;
}
then in my view I looped through the array as:
<ul>
{{#each model as |post|}}
<li>{{post.title}}</li>
{{/each}}
</ul>
but I would like to know better way for this.
I'm pretty new to Ember so hopefully I'm just doing something stupid, but I've been running into a lot of random issues with data not displaying properly and I now see in the Ember Debugger that my data does not exist until I hit a specific model data endpoint. For instance, I have a template to display all products, here's the route:
App.ProductsRoute = Ember.Route.extend({
model: function() {
return this.store.find('product');
}
});
the controller:
App.ProductsController = Ember.ArrayController.extend({
itemController: 'product'
});
the template:
<script type="text/x-handlebars" data-template-name="products">
<div class="row">
{{#each}}
{{name}}
{{/each}}
</div>
</script>
Hitting the endpoint '/products' displays nothing initially, but if I go to '/products/1' I can see the product data in the view (and in the Ember debugger), and then if I navigate back to '/products' that particular product's data (but no other data) displays properly. So I'm super confused as to what I'm doing wrong. As the title suggests, I'm using the DjangoRESTAdapter if that helps narrow things down and here's my app.js as well
window.App = Ember.Application.create({});
window.api_location = 'http://localhost:8000/api';
App.ApplicationAdapter = DS.DjangoRESTAdapter.extend({
host: api_location,
pathForType: function(type) {
return Ember.String.underscore(type);
}
});
App.ApplicationSerializer = DS.DjangoRESTSerializer.extend({
});
App.Store = DS.Store.extend();
Thanks in advance for any help, and let me know if other code snippets would help.
Okay, I finally figured this out: it's an issue with pagination. I was not aware that we had pagination set up for our Django REST Api, so instead of returning a list of objects, it was returning a result object, with the list of products buried in a 'results' attribute. So until I can convince the other devs to turn off pagination I can modify all my queries:
this.store.find('product', {page_size:0});
to override our default page size.
Edit: I'm also trying out modifying the json response on the server side rather than using the djangorestadapter with this library: https://github.com/ngenworks/rest_framework_ember . Hopefully this saves some people some digging...
I've successfully setup Ember CLI and Firebase and I'm attempting to bring some basic data into my templates. My 'title' and 'subtitle' data are apparent in the Ember Inspector, as well as my Firebase project dashboard. However, {{foo.title}} and {{foo.subtitle}} are coming back empty and undefined in the browser. Why is that? Here's my code:
application.js (adapter)
import DS from 'ember-data';
export default DS.FirebaseAdapter.extend({
firebase: new window.Firebase('https://<firebase-database-name>.firebaseio.com/')
});
foo.js (model)
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
subtitle: DS.attr('string')
});
index.js (controller)
import Ember from 'ember';
export default Ember.Controller.extend({
model: function() {
var titles = this.store.createRecord('foo', {
title: 'Title',
subtitle: 'Subtitle'
});
titles.save();
}
});
index.js (route)
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('foo');
}
});
application.hbs (template)
<h2 id='title'>{{foo.title}}</h2>
{{outlet}}
index.hbs (template)
<h1>{{foo.title}}</h1>
<h3>{{foo.subtitle}}</h3>
The title and subtitle fail to display in the templates.
The Ember Inspector View Tree tab shows 'index' with 'DS.RecordArray:ember368' for the model.
The Ember Inspector Data tab shows Model Type of 'foo' with # Records of 1. When I click on that record, it displays the Firebase ID, title, and subtitle values. When I inspect my Firebase data url, I see the following structure:
firebase-database-name
|— foos
|— JU1Ay8emCNNZBeqYoda
|— subtitle: "Subtitle"
|— title: "Title"
Seems like everything is correct, but the templates do not display the data values. Thanks for any help.
The answer to this question centers on properly retrieving and exposing Ember Data, and not so much to do with Firebase or Ember CLI. There are multiple issues with the code above…
The foo.js code represents a simple model, and is written correctly.
The index.js route is implemented correctly. It is retrieving and returning the ‘foo’ model from the Ember Data store as an array, which, via EmberFire and the Firebase adapter, is ultimately being pulled from the Firebase database. However, this is part 1 of 3 problems. If you want this data displayed once across the application, dispense with the index.js route, and just define an application.js route, like this:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('foo');
}
}
The index.js controller has a number of issues, and is part 2 of 3 problems. Firstly, controllers do not have a ‘model’ method, they only have a ‘model’ property (Ember Routes are the ones that employ a ‘model’ method, and can also set the ‘model’ property of a controller via a Route’s ‘setupController’ method). Secondly, instead of Ember.Controller, it needs to extend Ember.ObjectController for a singular data instance, or, Ember.ArrayController for an array of data, which is the controller needed here, since ‘this.store.findAll(“foo”)’ in the index.js route is going to return an array of objects. Controllers are not used to save or retrieve data from a server, but they can be used to decorate a model. Given that the route is returning the model, the controller, in this simple data exercise, is not even necessary.
The application.hbs handlebars template is part 3 of 3 problems. It is not setup to properly display the model that is being provided to it via the route. It’s necessary to employ the {{#each}} helper, to loop over the data array that is being returned via the route’s model method. Try this:
{{!-- looping over the 'foo' model returned via the route --}}
{{#each foo in model}}
<h2>Application Title = <span style="color: blue;">{{foo.title}}</span></h2>
<h4>Application Tagline = <span style="color: blue;">{{foo.tagline}}</span></h4>
{{/each}}
{{outlet}}
The index.hbs handlebars template is not necessary. The application.hbs template is sufficient to display the data of interest.
This is a very basic exercise, but illustrates fundamental aspects of using Ember Data properly.
So my understanding from the Ember docs is that the pattern for views/controllers/models is as follows:
[view] <- [controller] <- [model]
(with views consuming controllers consuming models)
In my previous experience using Ember, I'd set up a view to consume a model, like so:
{{#with blogpost}}
{{#view MyApp.BlogPostView contentBinding="this"}}
<h1>{{title}}</h1>
<p>{{content}}</p>
{{/view}}
{{/with}}
Now say I create a controller:
MyApp.BlogPostController = Ember.BlogPostController.extend()
Where do I initialize this controller?
Looking at the Ember docs, it seems like this happens automatically if the controller is associated with a route, but what if I just want an ad-hoc controller which ties together a view and a model? This could be for an arbitrary component on my page.
Am I responsible for instanciating the controller? Should I use some kind of controllerBinding attribute? Will it be instantiated automatically with my model, or with my view?
Any advice appreciated; I'm comfortable with the model/view pattern in Ember, but I'm having some difficulty working out where controllers fit in.
Looking at the Ember docs, it seems like this happens automatically if the controller is associated with a route
This is correct, a controller associated with a route will be automatically instantiated by ember when needed.
but what if I just want an ad-hoc controller which ties together a view and a model? This could be for an arbitrary component on my page. Am I responsible for instanciating the controller? Should I use some kind of controllerBinding attribute? Will it be instantiated automatically with my model, or with my view?
There are different way's to get your arbitrary controller instantiated automatically by ember without the needs of doing it yourself.
For the examples, let's assume you have a controller which is not associated with any routes called LonelyController,
App.LonelyController = Ember.ArrayController.extend({
content: ['foo', 'bar', 'baz']
});
Approach 1
Let's assume you have a route and you hook into setupController, if you try here to request you LonelyController with this.controllerFor('lonely'); this will make ember instantiate it for you:
App.IndexRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controllerFor('lonely').get('content');
// the above line will retrive successfully
// your `LonelyController`'s `content` property
}
});
Approach 2
Another possible way to get your LonelyController automatically instantiated by ember would be by defining a dependence with the needs API in another controller:
App.IndexController = Ember.ObjectController.extend({
needs: 'lonely',
someAction: function() {
this.get('controllers.lonely').get('content');
// the above line will retrive successfully
// your `LonelyController`'s `content` property
}
});
Using the needs API you could also doing something like this:
App.IndexController = Ember.ObjectController.extend({
needs: 'lonely',
lonelysContentBinding: 'controllers.lonely.content',
someAction: function() {
this.get('lonelysContent');
// the above line will retrive successfully
// your `LonelyController`'s `content` property
}
});
There are also some other combinations of the mentioned methods to get your LonelyController automatically instantiated, but I guess this should be more clear by now.
One last tip: to get a clue of what ember creates automatically under the hood you could also enable the generation logging to observe this in your console, which is very helpful, by doing:
var App = Ember.Application.create({
LOG_ACTIVE_GENERATION: true
});
Hope it helps.
The task:
Open a form in a lightbox to create a new "event"; the opened form should be bookmarkable.
The road blocks:
There are examples of opening a lightbox using {{action}} tags, but could not find one that opened in its own route.
There are many examples using older versions of ember.js.
There is not a lot of documentation related to ember-data and REST (I know, I know...it isn't "production ready").
The problem:
The fields in the form were not being tied to a backing model so "null" was being posted to my servlet (a Spring controller).
My very first iteration was not too far off from the final outcome (jsfiddle). The thing that finally made it works swapping this:
EP.EventsNewRoute = Ember.Route.extend({
...
setupController : function(controller, model) {
controller.set("model", model);
},
...
});
...for this:
EP.EventsNewRoute = Ember.Route.extend({
...
setupController : function(controller, model) {
this.controllerFor("events-new").set("model", model);
},
...
});
The question:
Why does the setupController function need to call controllerFor in order to properly set up the model?
And finally, since I struggled to find a fully-functional example, I wanted to make this accessible (and hopefully discover improvements).
Here's the fiddle: http://jsfiddle.net/6thJ4/1/
Here are a few snippets.
HTML:
<script type="text/x-handlebars">
<div>
<ul>
{{#linkTo "events.new" tagName="li"}}
Add Event
{{/linkTo}}
</ul>
</div>
{{outlet events-new}}
</script>
<script type="text/x-handlebars" data-template-name="events-new">
<form>
<div>
<label>Event Name:</label>
{{view Ember.TextField valueBinding="name"}}
</div>
<div>
<label>Host Name:</label>
{{view Ember.TextField valueBinding="hostName"}}
</div>
</form>
</script>
JavaScript:
...
EP.Router.map(function() {
this.resource("events", function() {
this.route("new");
});
});
EP.EventsNewRoute = Ember.Route.extend({
model : function() {
return EP.Event.createRecord();
},
setupController : function(controller, model) {
//controller.set("model", model); // Doesn't work? Why not?
this.controllerFor("events-new").set("model", model); // What does this do differently?
},
...
});
EP.EventsNewController = Ember.ObjectController.extend({
save : function() {
this.get("content.transaction").commit(); // "content.store" would commit _everything modified_, we only have one element changed, so only "content.transaction" is necessary.
}
});
EP.EventsNewView = Ember.View.extend({
...
});
EP.Event = DS.Model.extend({
name : DS.attr("string"),
hostName : DS.attr("string")
});
Resources:
http://emberjs.com/guides/routing/setting-up-a-controller/
http://emberjs.com/guides/getting-started/toggle-all-todos/ (trying to mimic what I learned, but morph the add-new to a new route)
Writing a LightboxView causes problems / Integrating DOM Manipulating jQuery-Plugins makes actions unusable (lightbox "example")
Dependable views in Ember (another lightbox "example" but doesn't have routes for the lightbox opening)
Why does the setupController function need to call controllerFor in order to properly set up the model?
Ember makes URLs a very integral part of its conventions. This means that the state of your application is represented by the route it is on. You've grokked most of this correctly. But there are couple of subtle nuances, that I will clarify below.
First consider an app with the following URLs,
/posts - shows a list of blog posts.
/posts/1 - shows a single blog post.
And say clicking on a post in the list at /posts takes you to /posts/1.
Given this scenario, there 2 ways a user will get to see the post at /posts/1.
By going to /posts and clicking on the 1st post.
By typing in /posts/1, via bookmarks etc.
In both these cases, the PostRoute for /posts/1 will need the model corresponding to Post id 1.
Consider the direct typing scenario first. To provide a way to lookup the id=1 post model, you would use,
model: function(params) {
return App.Post.find(params.post_id);
}
Your template for post will get the model and it can render using it's properties.
Now consider the second scenario. Clicking on post with id=1 takes you to /posts/1. To do this your template would use linkTo like this.
{{#linkTo 'post' post}} {{post.title}} {{/linkTo}}
Here you are passing in the post model to the linkTo helper. It then serializes the data for the post into a URL, ie:- '/posts/1'. When you click on this link Ember realizes that it needs to render the PostRoute but it already has the post model. So it skips the model hook and directly calls setupController.
The default setupController is setup to simply assign the model on the controller. It's implemented to do something like,
setupController: function(controller, model) {
controller.set('model', model);
}
If you do not need to set custom properties on your controller, you don't need to override it. Note: if you are augmenting it with additional properties you still need to call _super to ensure that the default setupController behaviour executes.
setupController: function(controller, model) {
this._super.apply(this, arguments);
controller.set('customProp', 'foo');
}
One final caveat, If you are using linkTo and the route does not have dynamic segments, then the model hook is still called. This exception makes sense if you consider that you were linking to the /posts route. Then the model hook has to fire else Ember has no data to display the route.
Which brings us to the crux of your question. Nearly there, I promise!
In your example you are using linkTo to get to the EventsNewRoute. Further your EventsNewRoute does not have dynamic segments so Ember does call the model hook. And controller.set("model", model); does work in so much as setting the model on the controller.
The issue is to do with your use of renderTemplate. When you use render or {{render}} helper inside a template, you are effectively getting a different controller to the one you are using. This controller is different from the one you set the model on, hence the bug.
A workaround is to pass the controller in the options, which is why renderTemplate gets this controller as an argument.
renderTemplate: function(controller) {
this.render("events-new", {
outlet : "events-new", controller: controller
});
}
Here's an updated jsfiddle.
Final Note: Unrelated to this question, you are getting the warning,
WARNING: The immediate parent route ('application') did not render into the main outlet and the default 'into' option ('events') may not be expected
For that you need to read this answer. Warning, it's another wall of text! :)