Ember rendering into outlet within named outlet - ember.js

Given these templates
<script type="text/x-handlebars">
<h3>Application outlet:</h3>
<div class="outlet">
{{outlet}}
<div>
<h3><code>'base'</code> outlet:</h3>
<div class="outlet base-outlet">
{{outlet 'base'}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="index">
<h1>I am the application index, I should be in the application outlet.</h1>
</script>
<script type="text/x-handlebars" data-template-name="foo">
<p>I am foo, I have my own outlet here:</p>
<h3>Foo Outlet:</h3>
<div class="outlet foo-outlet">
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="foo/index">
<p>I am foo index, I should be inside <code>#foo-outlet</code> (green border)</p>
</script>
And these routes:
App.Router.map(function() {
this.resource('foo', function() {
this.resource('bar');
});
});
App.IndexRoute = Ember.Route.extend({
afterModel: function() {
this.transitionTo('foo.index');
}
});
App.FooRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({into: 'application', outlet: 'base'});
}
});
I would expect the foo/index template to be rendered inside the {{outlet}} contained in the foo template, however it is rendered into the applications outlet, there is a live example here: http://jsbin.com/yecatu/6/edit?html,js,output
Note if I put a call to this._super() in renderTemplate I get the foo/index sort of in the right place, but the foo template is rendered twice.
Update: What I'm trying to achieve is the following:
Application has a named outlet, let's say 'sidebar'
A route can render a wrapper into this sidebar outlet which contains its own (unnamed) outlet
Then sub routes of that route should render in the outlet within the wrapper.
So in the above example the foo route should be rendering its wrapper into the 'base' outlet, then the content of its sub routes (foo/index in the example) should be rendered within the outlet inside that wrapper (the one with the class foo-outlet in the example).

Related

Emberjs template not rendering

I'm trying to understand Ember routing and template rendering with a simple example. I have a page like the following:
<html>
<head>.....</head>
<body>
<script type="text/x-handlebars" data-template-name="cars">
cars<br/>
</script>
<script type="text/x-handlebars" data-template-name="cars/new">
cars new
</script>
<script type="text/x-handlebars">
<header id="header">
{{outlet header}}
</header>
<section id="main">
{{outlet main}}
</section>
<footer id="footer">
Some footer
</footer>
</script>
<!--lib files-->
<!--script will go here-->
</body>
</html>
Throughout the application, all contents need to be entered in the main outlet. I have a javascript file with the following contents:
window.App = Ember.Application.create();
App.Router.map(function() {
this.resource("cars",function (){
this.route("new");
});
});
App.ApplicationAdapter = DS.FixtureAdapter.extend();
App.CarsRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({ outlet: 'main' });
}
});
App.CarsNewRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({ outlet: 'main' });
}
});
The problem is that cars template is properly being rendered while cars/new isn't. How can I correct this issue ?
Not sure all the outlets are necessary--but I think the simplest change that can get you running is to change
<script type="text/x-handlebars" data-template-name="cars">
to
<script type="text/x-handlebars" data-template-name="cars/index">
and add
<script type="text/x-handlebars" data-template-name="cars">
{{outlet}}
</script>
The issue is that it's using cars template as the parent template for cars/index and cars/new. Ember will try to render cars/new into an outlet, but you haven't specified one in cars. Ember will figure out how to make it happen if you omit the cars template, but not without a notice in your console.
tWhen you explicitly tell a route to render in a specific outlet, I believe you need to disconnect from that outlet before something new can be rendered in it. Especially if the new route is a child of the existing route in that outlet.
Typically you will want to render cars into a generic {{outlet}} and cars/new should render into another {{outlet}} within the cars template.
If you want to continue doing this the way you want, you will need to call disconnectOutlet() in an action like willTransition() before the new template can be rendered. But I can imagine that would get confusing fast.
for what it's worth, this is how i would attempt it.
renderTemplate: function() {
this.render({
into: 'application',
outlet: 'main'
});
},
actions: {
willTransition: function() {
this.disconnectOutlet({
outlet: 'main',
parentView: 'application'
});
}
}
some other things to consider:
if an error were to occur, ember would not automatically switch to a substate. you would also need to explicitly define the errors route to do this... same thing for loading substates... and literally every route in your app would need to connect and disconnnect from the main outlet

Template doesn't render into named outlet

I'm trying to create several pages that will use the same menu so that I can save the state of the menu easily and avoid code duplication. My problem is that the menu template doesn't render in the menu outlet of the pages.
Here is the code :
App.Router.map(function() {
// put your routes here
this.resource('profil', function() {
this.route('news');
this.route('menu');
});
});
App.MenuRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({
into: 'profil/news',
outlet: 'menu',
});
}
});
And in index.html I have :
<script type="text/x-handlebars" id="profil/news">
...
{{outlet}}
{{outlet menu}}
</script>
<script type="text/x-handlebars" id="menu">
...
</script>
The template 'profil/news' is rendered but the template 'menu' is not rendered at all : it should be rendered inside 'profil/news' when I visit profil/news . Any idea ?
Thank you
The problem was that I needed to use :
<script type="text/x-handlebars" id="profil/news">
...
{{outlet}}
{{render 'profil/menu'}}
</script>
And to change the name of the handlebar to match the routing:
<script type="text/x-handlebars" id="profil/menu">
Moreover, the statement beginning with App.Menuroute is unnecessary and be removed.
Now everything is working as expected.

How to render template other than application.hbs in EmberJS?

In EmberJS, the main template file is the application.hbs. Any template rendered from the routes goes the the {{outlet}} of this main template file.
Now, I have another main template file, say print.hbs wherein the template design is very different from the application.hbs. How do I do this?
In the router file, I have:
App.Router.map(function() {
this.resource('print', function() {
this.route('display1');
this.route('display2');
});
this.route('dashboard', {path: '/'});
this.route('anything');
});
The routes dashboard and anything uses the application.hbs.
What should I do to use print.hbs on the print route? Please help.
You can't easily change application template. Ember doesn't listen on templateName property changes and responds poorly when you try to re-render the template yourself.
A nice way to do this would be to use different partials within your application template, based on whether you are in the 'screen' or 'print' mode.
<script type="text/x-handlebars">
{{#if isPrint}}
{{partial "application-print"}}
{{else}}
{{partial "application-normal"}}
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="application-normal">
<div id="app-normal">
<h2>Normal template</h2>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="application-print">
<div id="app-print">
<h2>Print template</h2>
{{outlet}}
</div>
</script>
App.ApplicationController = Ember.Controller.extend({
isPrint: false,
currentPathChange: function () {
var currentPath = this.get("currentPath");
var isPrint = currentPath ? currentPath.indexOf("print") === 0 : false;
this.set("isPrint", isPrint);
}.observes('currentPath').on("init")
});
This JSBin will demonstrate why this, unfortunately, doesn't work either. According to this bug report, Ember's handlebars gets confused when there are multiple outlet directives in the same page, even if they are in different #if scopes.
Until this gets fixed, I propose the following, slightly modified, solution.
Application template is empty. One template each for normal and print section.
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="normal">
<div id="app-normal">
<h2>Normal template</h2>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="print">
<div id="app-print">
<h2>Print template</h2>
{{outlet}}
</div>
</script>
In router, everything goes into normal and print resources. Normal resources are placed at /, so that all the links remain the same. No need for special coding in ApplicationController.
App.Router.map(function() {
this.resource("print", function () {
this.route("a");
this.route("b");
});
this.resource("normal", {path: "/"}, function () {
this.route("a");
this.route("b");
});
});
Working jsbin here.

How to use application model inside partial of route?

I think this should be a really easy fix, i just cant figure it out.
I have this model in my ApplicationRoute:
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return {
sideNav: data.side,
breadcrumbs: Util.breadcrumbs()
}
}
});
application template:
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
overview template:
<script type="text/x-handlebars" data-template-name="overview">
{{partial side}}
</script>
_side partial:
<script type="text/x-handlebars" id="_side">
{{#each model.side}}
<i {{bindAttr class="iconClass"}}></i><p>{{label}}</p>
{{/each}}
</script>
When I load my overview route, the stuff in there loads, but the application model is not applied to the side partial like I would like it to be... there may be a better way to do this. Thanks!
It looks like you have two problems:
You are binding to side instead of sideNav inside your partial.
You're trying to access the model of ApplicationRoute inside of the overview template (which presumably has its own route and controller)
Partials don't change context, so they don't add any complexity on their own. For instance, if we restrict ourselves to the application template, the following will work:
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return {
foo: 'bar'
};
}
});
<script type="text/x-handlebars" data-template-name="application">
{{partial partial}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="_partial">
{{foo}}
</script>
However, if you want to get at the application model inside of a nested route, you'll need to ask for it. For example, inside of the (default) index route, you could add the following:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.modelFor('application');
}
});
<script type="text/x-handlebars" data-template-name="index">
{{partial partial}}
</script>
You can see both examples in this jsfiddle.

Break up my application.handlebars into separate templates using Ember.js and Ember.Router

I'm building a front-end (on top of Ruby on Rails) using ember.js and the ember-rails gem.
My (ember) application consists of Models, Views, Controllers and an application.handlebars template which describes my UI.
Whats the best practice to break up this application.handlebars file so that I can manage the UI? For example, I'd like to have Navigation at the top of the page.
I've tried using the Ember.Router, a separate navigation.handlebars (with NavigationView and NavigationController) the {{outlet}} helper to no avail. Here's what the Router looks like:
App.Router = Ember.Router.extend(
enableLogging: true
root: Ember.Route.extend(
index:
route: '/'
connectOutlets: (router, context) =>
router.get('applicationController').connectOutlet('navigation')
)
and the application.handlebars
<h1>Lots of HTML that I want to break up</h1>
{{outlet}}
Let me know if you need more info...thanks.
As per my Understanding, Let's suppose you want 3 sections(can be any number) Header, Content & Footer, You can do something as follows
<script type="text/x-handlebars" data-template-name="application">
{{view MyApp.HeaderView}}
{{#view MyApp.ContentView}}
{{outlet}}
{{/view}}
{{view MyApp.FooterView}}
</script>
<script type="text/x-handlebars" data-template-name="app-header">
All your Header related HTML
</script>
<script type="text/x-handlebars" data-template-name="app-content">
HTML related to content
{{yield}} //this will be explained at the end
More HTML if you want
</script>
<script type="text/x-handlebars" data-template-name="app-footer">
HTML related to footer
</script>
MyApp.HeaderView = Ember.View.extend({
templateName: 'app-header'
})
MyApp.ContentView = Ember.View.extend({
templateName: 'app-content'
})
MyApp.FooterView = Ember.View.extend({
templateName: 'app-footer'
})
MyApp.ApplicationView = Ember.View.extend({
templateName: 'application'
})
explaining {{yield}} In a nutshell, whatever is between in the block helper of a given view goes in there, In the above example for the MyApp.ContentView, the {{outlet}} defined in the {{#view MyApp.ContentView}} handlebars gets inserted at the {{yield}}
On the similar lines let me show the difference between layoutName property & templateName property,
App.someView = Ember.View.extend({
tagName: 'a',
templateName: 'a-template',
layoutName: 'a-container'
})
<script type="text/x-handlebars" data-template-name="a-template">
Hi There
</script>
<script type="text/x-handlebars" data-template-name="a-container">
<span class="container">
{{yield}}
</span>
</script>
Will result in following HTML
<a class="ember-view" id="ember235">
<span class="container ember-view" id="ember234">
Hi There
</span>
</a>
Use these concepts to split the application handlebars in your case it would be something like
{{view App.NavigationView}}
{{outlet}}
Update as per latest ember
The new ember supports partials similar to rails, now we can modify the above to use {{partial}} as follows:
{{partial "header"}}
{{outlet}}
{{partial "footer"}}
Ember when encountered this template will look for the template whose name is _header(similar to rails) and inserts the template(same goes for footer)
And If want to associate a controller we can use {{render}} helper
{{render "sidebar"}}
inserts the template whose name is sidebar at specified location in handlebars besides it also associates App.SidebarController to it,
Note: we cannot use {{render 'sidebar'}} more than once in same handlebars file.
But again if you want to use a widget like view multiple places in a given page then use {{view}} helper
For this problem, what you need to do is think about what views change and where that changes happen. If for example you have a navigation section and a main section, then think about how each of these sections change with the state of your application. Be sure to only create an {{outlet}} for dynamic content, otherwise things will get messy and the application will be slower. Then setup your templates and your router similar to the example below.
Templates:
<script type="text/x-handlebars" data-template-name="application">
<!--Your application template goes here-->
{{outlet navigation}}
{{outlet body}}
</script>
<script type="text/x-handlebars" data-template-name="navigation">
<!--Your navigation template goes here-->
</script>
<script type="text/x-handlebars" data-template-name="main-one">
<!--Your mainOne template goes here-->
</script>
<script type="text/x-handlebars" data-template-name="main-two">
<!--Your mainTwo template goes here-->
</script>
Note: You can have {{outlet}} in any of your view templates to change in more sub-states
Javascript:
window.App = Em.Application.create({
ApplicationView: Em.View.extend({
templateName: "application"
}),
ApplicationController: Em.Controller.extend({
}),
NavView: Em.View.extend({
templateName: "navigation"
}),
NavController: Em.Controller.extend({
}),
MainOneView: Em.View.extend({
templateName: "main-one"
}),
MainOneController: Em.Controller.extend({
}),
MainTwoView: Em.View.extend({
templateName: "main-two"
}),
MainTwoController: Em.Controller.extend({
})
Router: Em.Router.extend({
root: Em.Route.extend({
index: Em.Route.extend({
route: '/',
connectOutlets: function(router,context) {
router.get("applicationController").connectOutlet("navigation","nav");
router.get("applicationController").connectOutlet("body","mainOne");
}
}),
otherState: Em.Route.extend({
route: '/other-state',
connectOutlets: function(router,context) {
router.get("applicationController").connectOutlet("navigation","nav");
router.get("applicationController").connectOutlet("body","mainTwo");
}
}),
})
})
});
App.initialize();
Note: The applicationController must extend Controller and not ObjectController or ArrayController