I need to render one template to another layout


#1

Hi,

I have the application layout (with a navbar) that outlet all the templates. Now, I need to create one template, and it will be rendered in another layout, because this template shouldn’t have the navbar.

Any suggestions, or just point me to the related page in the doc?


#2

I’m afraid I’ll need something more concrete. Can you make an ember twiddle?


#3

I have simple app, users can login and logout.

There is a page called show, will display many information (of course related to the app). It doesn’t need for login or logout, it is just for visitors, and I don’t need navbar, logo or even footer just some information will be displayed.

Anyway, I did “if statement” in the application layout for “currentPath” to display the show template without the navbar.


#4

This is a simple but important requirement for most of apps, Ember once have View layer to let you implement logic to determine which layout (just some handlebar templates) should be used, nowadays View is not recommended to use any longer, hopefully Routable Components will be the next rescue but just for now we need something else.

Let me show you what I did with most recent version of Ember (works on ^2.0.0)

  1. app/pods/application/template.hbs

    {{#if showLayout}}
    <header>...</header>
    {{/if}}
    
    {{outlet}}
    

    For optional layout, only thing needed in template is shown above, we need put it in application/template since no matter where you are (in the routing tree), ApplicationRoute is the only one that always existed (which means being activated), so it can provide us site-wide control ability.

  2. app/app.js

    Ember.Route.reopen({
      hasLayout: true,            // true by default since most views need layout
      setupController() {
        this._super(...arguments)
        this.controllerFor('application').set('showLayout', this.get('hasLayout'))
      }
    })
    

    I think these code can explain themselves very well, maybe someone can rewrite this in more Ember way, but it’s okey for me.

So, how to use this?

  • For those routes that don’t need layout,

    export default Ember.Route.extend({
      hasLayout: false
    })
    
  • and for those routes that do need layout: Nothing, it already shows there!


#5

It sounds helpful, thank you @nightire. I’m just a new beginner but It gives me another view. :wink:

And thank you @lightblade for providing help. :grinning:


#6

Why not just leverage the router for this and instead of toggling the application layout, just hang your non-app things off one route tree and your app off another.

Router.map(function() {
  this.route('app', { path: '/app' }, function() {
    this.route('account');
    this.route('logout');
    this.route('inventory', function() {
      this.route('show');
    });
  });

  this.route('home', { path: '/' }, function() {
    this.route('login');
  });
});

Now your application template is just an outlet and your app template can have the navigation bar and everything you described. Your home route will be your other layout you’re trying to toggle.


#7

@jasonmit

I don’t understand, I learned that all templates will be rendered within application.hbs layout.

I have only those routes

this.route('login');
this.route('home', { path: '/' });

// This template doesn't need the header or footer of application.hbs layout.
this.route('show'); 

And excuse me I’m still a beginner, if you can explain more…


#8

They are. Imagine application.hbs is just an {{outlet}}.

Using the following structure:

Router.map(function() {
  this.route('app', { path: '/' }, function() {
    this.route('login');
  });

  this.route('show', { path: '/show' });
});

Your app template contains the header/footer and likely an outlet in the middle. The show template does not have the header/footer. Your application is just an outlet, does not hold anything other than an outlet in this case.


#9

Oh… very nice, got it, thanks @jasonmit :+1:


#10

I don’t like this way because of two reasons:

First, you have to reset namespace for every child routes to get a clean route name, Ember Router should give us a better way to solve this.

In fact, nested routes is the representation of nested resource, not inclusive layout

So the second reason is, there is always a extra div in the DOM, which is the outlet of application template. This is not a big deal, but something tell me that’s not perfect. I don’t like a useless application route with template (except for a outlet), if we have to do like this, why not just use body instead?


#11

you can set the tagName: '' on your application view so you dont get the extra div.


#12

Thank you for this, but still, View is involved which is not recommended to use any more. As I said above, Routable Components might be the rescue, until then, you can’t get prefect without my way.


#13

Views are still exactly as recommanded as Controllers, as long you always use it as a pair, like its in a Component.

As long we dont have routable Components this is absolutly fine. Later merge the View and the Controller to get your routable Component and have the same behaviour.


#14

Until there are routable components, this is the world we live in. There is a view backing that route, even today… it’s a generated view. As for resetting route namespace, that’s a choice.

What you have are two entirely different layouts but in your case you’re wrapping what should show in if/else blocks. That to me is less direct than just having two entirely different layouts instead of pretending they are one.

In the end, I’m just providing a second option and it works great for me.


#15

Yes, both way are not perfect for now, even in your situation (two entirely different layouts), application template still be a useless one, so the real problem is the route definition, repeat resetNamespace is so exhausting.

As we both said, until routable components coming…hope it good!


#16

I have done it using the router as well… Usually I do something as shown below. All the stuff for a logged in user is under the “master” resource which has a path of / this way i can still have /todos instead of /master/todo any logged-in-user layout stuff e.g. navbar goes in master.hbs. For example the code below the todos template would render through application > master > todos but the login template would render through application > login so the navbar wouldn’t show up since it is in the master.hbs template

  this.resource('master', {path: '/'}, function() {
      this.resource('todos', function() {
          this.resource('todo', {
              path: ':todo_id'
          }, function() {
              this.route('edit');
          });
      });
      this.resource('admin', function() {
          this.route('home');
      });
  });

  this.route('login');
  this.route('signup');
  this.route('resetpassword');
  this.route('logout');
  this.route('home');

#17

You don’t need to call reopen() method, just override Route property:

Ember.Route = Ember.Route.extend({
      isHasLayout: true,
      setupController: function(controller, model) {
        this._super(controller, model);
        this.controllerFor('application').set('isHasLayout', this.get('isHasLayout'));
      }
    });

#18

use reopen because this thing could happen in every routes, of course a mixin would be more explicit for this job.


#19

Affirmative, that worked for me too. Spectacular explanation. I have an index page header/footer, different than app header/footer, which is also different from signup page where there is no header/footer. Worked like a charm:

Router.map(function() {
  this.route('home', {path: '/home'}, function() {
    this.route('index');
  });
  this.route('app', {path: '/app'}, function() {
    this.route('dashboard');
    this.route('orders', function() {
      this.route('new');
    });
  });
  this.route('signup');
});