I have a template called new, which has some input helpers to submit a new Request (subject and body). After them I have a named outlet tags, which should display a list of possible services we can add to the request (tags: fix, purchase, etc).
The problem is that when I navigate to new, only static data is displayed from the tags template ("inside tags template" would be displayed), but the #each does not loop at all.
If I add tags as a new resource into new, and navigate to new/tags, then the tags template would be rendered into both outlets of the new template ({{outlet}} and {{outlet tags}}, so my code is not faulty when it comes to displaying data, its just faulty when it comes to displaying it where and when I want to (only inside the new route).
Also, both my routes' models have a console.log message saying which route is accessed, and when I go to new only the new route displays a message.
I believe new does not know that it is supposed to use the tags controller, but I am clueless when it comes to Ember... (I do not want to get the tags via the new route, I want to use the tag route)
export default Ember.Route.extend({
model: function(){
console.log("in new");
},
setupController : function(controller, model){
controller.set("model", model);
},
renderTemplate: function() {
this.render();
this.render('tags', {
outlet: 'tagO',
into: "new",
controller: 'tags'
});
}
});
It's easier to just type {{render 'tags' someModel}} from the template than to programmatically do it in the renderTemplate hook and named outlet. You need to make the someModel available on the controller that you are currently in.
You'll want to hook up multiple models on the controller, see: EmberJS: How to load multiple models on the same route?
Example: http://emberjs.jsbin.com/OxIDiVU/1051/edit
Related
In my EmberJS applications I have two separates routes as follows,
Route A - "main/books/add"
Route B - "main/authors/add"
I have an "Add Authors" button In Route A template and when a user presses that button I want to load and render Route B in a modal to add new authors.
I know its possible to achieve somewhat smiler to this by using the route's render method to render the Route B template and respective controller.
But in that case, the "model" hook of Route B in "main/authors/add.js" file does not get invoked.
It would be really nice if someone can suggest me a method to render a separate route into a modal.
EDIT - Although this is entirely valid (the premise of rendering into using named outlets, views are now deprecated in Ember 1.1. The same can be achieved by using a Component
Yup, you can do this:
What you'd want to do is create a modal in a template and assign a named outlet into it (or create a view that is a modal with an outlet):
in modal.hbs:
<div class='modal'>
{{outlet "modalContent"}}
</div>
Then I would override your base button like so:
App.BasicButton = Em.View.extend({
context: null,
template: Em.Handlebars.compile('<button>Click Me!</button>');
click: function(evt) {
this.get('controller').send('reroute', this.get('context'));
}
});
And in your template set up your button to trigger your modal:
in trigger.hbs
<!-- content and buttons for doing stuff -->
{{View App.BasicButton context='modalContent'}}
Finally, you want to create a method in your route which handles rendering specific content into your outlet:
App.TriggerRoute = Em.Route.extend({
actions: {
reroute: function(route) {
this.render(route, {into: 'modal', outlet: route});
}
}
});
So in essence, you're rendering the template (called "modalContent") into a specific outlet (called "modalContent"), housed within the template/view (called "modal")
You would also want to write some logic to trigger the modal to open on element insertion. To do that, I would use the didInsertElement action in the modal view:
App.ModalView = Em.View.extend({
didInsertElement: function() {
this.$.css("display", "block");
//whatever other properties you need to set to get the modal to pop up
}
});
This is probably a very common task, but I am not sure which is the right approach to tackle this.
I have a Route / Controller / Model setup that loads groups. In the header area before the main content I want to show notifications for the user. I have created a notificationcontroller that is an arraycontroller and as well as a route that loads all notifications.
I don't want this route to be used for anything else than within this area.
I have already tried in my groups.hbs to add an additional {{ outlet notification }} and try to render in it:
App.GroupsRoute = GambifyApp.BaseRoute.extend({
model: function() {
return this.get('store').find('group');
},
renderTemplate: function() {
this.render('notifications', { // the template to render
// into: 'notifications', // the route to render into
outlet: 'notification', // the name of the outlet in the route's template
controller: 'notifications', // the controller to use for the template
});
this.render('groups');
}
});
But somehow my notifications template is not used. For now there is not much in my NotificationsController (It's an ArrayController) , NotificationsRoute is only reading all Notifications as Model and the template is currently only text, which is not rendered in my groups.hbs
The template containing the outlet you want to render into must be rendered first in order to render into the outlet. Here is an example of rendering into a named outlet that is in the application template from the index route:
http://emberjs.jsbin.com/qoyi/1/edit?html,js,output
Leave a comment if this example doesn't clear it up for you.
I have a jsfiddle with nested route where I am nesting one dynamic routes timeSlot inside another dynamic route appointment. Inside the appointment template I have a #link-to 'timeSlot'. When I click that link, the timeSlot template is never rendered and in the console I see:
This link-to is in an inactive loading state because at least one of its parameters presently has a null/undefined value, or the provided route name is invalid.
This is the router. Note that if I changed the timeSlot route from a dynamic route to a normal route, that is to something link this, this.resource('timeSlot'), the error goes away and the template is displayed. The route needs to be dynamic, as it accepts dates passed in via users click from a calendar.
It is a small jsfiddle and 95% of the code in the jsfiddle is pasted in this question:
App.Router.map(function(){
this.resource('appointments', {path: "/"}, function(){
this.resource('appointment', {path: "/:appointment_id"}, function(){
this.resource('timeSlot', {path: '/:day'});
});
});
});
The appointment route
App.AppointmentRoute = Ember.Route.extend({
model: function(params){
},
setupController: function(controller, model){
this._super(controller, model);
controller.set('content', model);
}
});
The TimeSlot route
App.TimeSlotRoute = Ember.Route.extend({
model: function(params){
},
setupController: function(controller, model) {
this._super(controller, model);
controller.set('content', model);
},
serialize: function(dateText) {
/*
return {
day: //this.controllerFor('timeSlot').today.pushObject(dateText)};
*/
}
});
The appointment template
<script type="text/x-handlebars" data-template-name="appointment">
<br/>
{{#link-to 'timeSlot' [2013-07-18]}}click timeSlot{{/link-to}}
{{outlet}}
</script>
The time Slot template
<script type="text/x-handlebars" data-template-name="timeSlot">
<h3> from timeslot template</h3>
{{outlet}}
</script>
update
For this route: this.resource('timeSlot', {path: ':appointment_id/:day'});
Explicitly passing values for each of the dynamic segment to link-to, via {{#link-to 'timeSlot' id=this day=['today'] }}click timeSlot{{/link-to}} in this jsfiddle, has now made it possible to hover over the link and see '#/2/dateText', where each slash segment is meant to be the value for the dynamic segment. Before passing in the value for the dynamic segment, all i saw when I hovered over the link was /#' suggesting the dynamic segment were not being picked.
It is still not working though. Because It using hard-coded values passed in the serialize method and not those passed to link-to.
update 2
This version is working though #linkTo doesn't seem to be calling serialized method.
http://jsfiddle.net/GQdbD/5/
It's kind of hard to tell what you're trying to do since all of your model hooks are empty. (Are they actually empty in your app?) In general, when you call link-to, transitionTo, or transitionToRoute Ember expects you to pass in a live model. In these cases the model hook on your route is skipped and the model that you pass to link-to is used instead.
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! :)