I have just written extremly simple Ember app, built on top of the Rails app, working with Ember Data and displaying, creating and persisting just one entity type to the server. Everything with the latest tools (Ember v1.0.0-pre.4-134-gaafb5eb).
However, there is very strange problem I have encountered. My app has two views: entity list (index) and form for creating new entities. When I enter the index directly, everything displays OK. But when I go to the other view and then back to the list, the view is not rendered again. Where could be the problem?
I guess it might be caused by my (maybe incorrect) using new Ember router. So I'm pasting important (from my point of view) parts of the app here:
Router:
App.Router.map(function() {
this.resource('bands', function() {
this.route('new');
});
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('bands');
}
});
App.BandsRoute = Ember.Route.extend({
model: function() {
return App.Band.find();
}
});
App.BandsNewRoute = Ember.Route.extend({
renderTemplate : function(){
this.render('bands_new',{
into:'application'
});
}
});
Link back to list - which does not work:
App.BandsNewController = Em.ArrayController.extend({
cancel: function() {
this.transitionTo('bands');
}
});
Have a look at the whole app here: https://github.com/pavelsmolka/roommating
(It's hugely inspired by great https://github.com/dgeb/ember_data_example)
I don't believe it, but could it be bug in Ember itself?
I think your "render" call in your BandsNewRoute is messing things up.Try making things go more with Ember defaults. So I would refactor your app to do this:
(working fiddle: http://jsfiddle.net/andremalan/DVbUY/)
Instead of making your own render, all you need to do is create a "bands" template (it can be completely empty except for {{outlet}} if you want) and a "bands.index" template.
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="bands/index">
<h2>Bands Index</h2>
{{#linkTo bands.new}}New Band{{/linkTo}}
</script>
<script type="text/x-handlebars" data-template-name="bands">
<h1>Bands</h1>
<p>
{{#linkTo index}}Start Again{{/linkTo}}
{{#linkTo bands.new}}New Band{{/linkTo}}
</p>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="bands/new">
I'm in new band!
<a {{action "cancel"}}>Cancel</a>
</script>
Your routes also clean up really nicely this way:
App.Router.map(function() {
this.resource('bands', function() {
this.route('new');
});
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('bands');
}
});
App.BandsNewController = Ember.Controller.extend({
cancel: function() {
this.transitionTo('bands');
}
});
I hope that helps!
Related
How do I render default content into a nested outlet?
For example, if I have an index template such as this:
<script type="text/x-handlebars" id="index">
<div>{{outlet}}</div>
</script>
<script type="text/x-handlebars" id="photo">
Photo!
</script>
<script type="text/x-handlebars" id="default">
Default photo
</script>
And a nested routes:
App.Router.map(function() {
this.resource('index', { path: '/'}, function() {
this.resource('default');
this.resource('photo', { path: ':id' });
});
});
That works fine when I use to link-to helper to load the page into the outlet. However, I cannot work out how to render default content into the outlet when the page first loads.
If I do something like this:
App.IndexRoute = Ember.Route.extend({
renderTemplate: function(controller, model) {
this._super(controller, model);
this.render('default');
},
});
It renders the default content into the main outlet. If I try to specify a named outlet instead:
this.render('default', { outlet: 'centre' });
I get the following error message:
Error while processing route: index.index Assertion Failed: An outlet (centre) was specified but was not found. Error: Assertion Failed: An outlet (centre) was specified but was not found.
Even when using a named outlet:
{{outlet "centre"}}
Any help appreciated.
Remove the index resource, it's already created for you and will make things confusing. Also, if you're needing to hook renderTemplate this early in the game, you're probably not following Ember's conventions.
I would also suggest removing the default resource, as Ember provides that by way of index. The top template is application.hbs, which essentially just has an {{outlet}} in it. So in summary:
Delete the index template
Change id="default" to id="index"
Remove the index resource from your router map
Thanks everyone, I used oshikryu's solution.
Templates:
<script type="text/x-handlebars" id="index">
<div>{{outlet}}</div>
</script>
<script type="text/x-handlebars" id="photo">
Photo!
</script>
<script type="text/x-handlebars" id="default">
Default photo
</script>
JavaScript:
App.Router.map(function() {
this.resource('index', { path: '/'}, function() {
this.resource('photo', { path: ':id' });
});
});
App.IndexIndexRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('default');
}
});
I am working on a small app that animates different iframes in and out of view. Right now I am just trying to start simple with two iframes for my data.
App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
{current: true, url:'http://www.flickr.com'},
{url:'http://bing.com'}
];
}
});
App.IndexController = Ember.ArrayController.extend({
itemController: 'iframe',
now: function() {
return this.filterBy('isCurrent').get('firstObject');
}.property('#each.isCurrent')
});
App.IframeController = Ember.ObjectController.extend({
isCurrent: Ember.computed.alias('current')
});
App.IframeView = Ember.View.extend({
classNameBindings: [':slide', 'isCurrent'],
templateName: 'iframe'
});
And my templates:
<script type="text/x-handlebars" data-template-name="index">
<button {{action "next"}}>Next</button>
{{#each}}
{{view "iframe"}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="iframe">
<iframe {{bind-attr src=url}}></iframe>
</script>
Why can't my IframeView access my isCurrent property of my itemController? I am also unsure if this is the right way to do this, or if there is an easier way to have my each use my IframeView
Here is a jsbin: http://emberjs.jsbin.com/vagewavu/4/edit
isCurrent lives on the controller. The controller property will be in scope in the view, but the properties under the controller aren't in scope of the view. You just need to reference controller first.
App.IframeView = Ember.View.extend({
classNameBindings: [':slide', 'controller.isCurrent'],
templateName: 'iframe'
});
Additionally your next action isn't doing anything, just creating some local variables, maybe you weren't finished implementing it. Either way I tossed together an implementation.
next: function() {
var now = this.get('now'),
nowIdx = this.indexOf(now),
nextIdx = (nowIdx + 1) % this.get('length'),
next = this.objectAt(nextIdx);
now.toggleProperty('current');
next.toggleProperty('current');
}
http://emberjs.jsbin.com/vagewavu/10/edit
I'm trying to render a modal. For that I've created a custom outlet using {{outlet modalOutlet}} My application template has two outlets, the default outlet and the modalOutlet. However when the modal template is rendered into {{outlet modalOutlet}}, my default {{outlet}} becomes empty.
How do I change it, so that the default {{outlet}} doesn't change, so I can actually render {{outlet modalOutlet}} as modal window, or as a sidebar as a part of a layout
I'm not sure if this is due to my code, or something about the renderTemplate() method that I'm missing. The jsFiddle with my code is here.
// Router
App.Router.map(function(){
this.route('contributors');
this.route('contributor', {path: '/contributors/:contributor_id'});
});
App.ContributorsRoute = Ember.Route.extend({
model: function() {
return App.Contributor.all();
},
});
App.ContributorRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('contributor', {
outlet: 'modalOutlet'
});
}
});
<script type="text/x-handlebars" data-template-name="application">
<nav>
{{#linkTo "index"}}Home{{/linkTo}}
{{#linkTo "contributors"}}Contributors{{/linkTo}}
</nav>
<div style='padding:5px;margin:5px;border:1px dotted red;'>
Default Outlet
{{outlet}}
</div>
<div style='padding:5px;margin:5px;border:1px dotted blue;'>
modalOutlet
{{outlet modalOutlet}}
</div>
</script>
You must render the contributors template as well, since the default outlet gets cleared when you transition to a sibling route.
App.ContributorRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('contributors');
this.render('contributor', {
outlet: 'modalOutlet'
});
}
});
You can avoid this, however, if you nest your routes like this:
App.Router.map(function(){
this.resource('contributors', function() {
this.route('show', {path: ':contributor_id'});
});
});
...and adjust the rest of your app to match the new structure. In this case, you need to specify the place the modalOutlet lies with the into option (in this case: 'application')
The issue is your routing structure is not nested, and once you nest your routes you will need to specify the route which contains the modal outlet.
What is happening is you render
Application -> Contributors
to show your list, but when you click a link you are now rendering
Application -> Contributor
and the Contributor template is removed.
If you nest your routes, like this:
Application -> Contributors -> Contributor
Then you will still have the Contributors template showing the list.
updated JSFiddle
//Router
App.Router.map(function(){
this.resource('contributors', function() {
this.resource('contributor', {path: '/:contributor_id'});
});
});
//Route
App.ContributorRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('contributor', {
into: 'application',
outlet: 'modalOutlet'
});
}
});
Can someone explain why this works:
Code in App.js:
App.ApplicationRoute = Ember.Route.extend({
setupController : function (params) {
this.controllerFor('food').set('model', App.Food.find(params.food_id));
}
});
But the following won't, unless I explicitly declare App.FoodController = Ember.ObjectController.extend();
App.FoodRoute = Ember.Route.extend({
model : function(params) {
return App.Food.find(params.food_id);
}
});
This is the code I'm using in index.html and does not change between blocks of code
<script type="text/x-handlebars" data-template-name="application">
{{ outlet }}
</script>
<script type="text/x-handlebars" data-template-name="food">
{{name}}
</script>
Router:
App.Router.map(function() {
this.resource( 'foods' );
this.resource( 'food', { path : '/food/:food_id' } );
});
The code that you have shown seems OK. Here is a working fiddle that proves it:
http://jsfiddle.net/ebXeS/2/
The only thing wrong about the code is this part (which is excluded from the fiddle):
App.ApplicationRoute = Ember.Route.extend({
setupController : function (params) {
this.controllerFor('food').set('model', App.Food.find(params.food_id));
}
});
According to your Router definition, you should not have food_id in the parameters of your application route. More than that, you should access the controller for the food route in the uhm... FoodRoute. Read more about Ember and the way it does routing (http://emberjs.com/guides/).
I'm confused how to connect outlets with the new router approach.
index.html:
...
<script type="text/x-handlebars" data-template-name="application">
<h4>The application handelbar</h4>
{{! outlet 1}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h4>The index handelbar</h4>
{{! outlet 2 and 3}}
{{outlet nav}}
{{outlet main}}
</script>
<script type="text/x-handlebars" data-template-name="main">
<h4>The main handelbar</h4>
</script>
<script type="text/x-handlebars" data-template-name="nav">
<h4>The nav handelbar</h4>
</script>
...
app.js:
...
App.Router.map(function(match) {
this.resource("index", { path: "/" });
this.route("test");
});
App.IndexController = Ember.Controller.extend({
});
App.IndexView = Ember.View.extend({
templateName: 'index'
});
...
This code renders outlet-1.
Questions:
Why is outlet-1 rendered? How are outlet-1 and "index" connected?
How can I connect outlet 2 and 3 to the same "index" site?
Thanks
miw
You need to specify this stuff in a route handler, using the renderTemplate method (or renderTemplates method, depending on your build).
What you're not seeing is that Ember is setting quite a few defaults for you already. In fact, the defaults set by Ember have allowed you to omit the entire route handler.
App.Router.map(function(match) {
this.resource("index", { path: "/" });
this.route("test");
});
App.IndexRoute = Ember.Route.extend({
renderTemplate: function() {
this.render();
/* this is the default, it will basically render the
default template, in this case 'index', into the
application template, into the main outlet (i.e. your
outlet 1), and set the controller to be IndexController.
*/
}
});
What you want is to render additional templates in the renderTemplate function, likeso:
renderTemplate: function() {
this.render("index");
// this renders the index template into the primary unnamed outlet.
this.render("navtemplate", {outlet: "nav"});
// this renders the navtemplate into the outlet named 'nav'.
this.render("main", {outlet: "main"});
// this renders the main template into the outlet named 'main'.
}
Hope this helps.
Ember automatically assumes / matches with IndexRoute, IndexController and IndexView. This is in the ember routing guide
To connect nested routes you can do it like this:
App.OtherRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('otherTemplate', {
into: 'index',
outlet: 'nav'
});
}
});
Here is a more in depth answer from another question.