Override Ember application.hbs template - javascript

I have an Ember JS application with 99% of it's resources on the root level, which the user has to be logged in to access. All those resources, with the exception of /login, have the same template.
/login, however, has an entirely different template (no nav bar, etc). What's the best way to override the application.hbs template for the login page?
Most of the answers I see in other questions involve a sort of parent route for the authenticated routes, like so:
this.route('login');
this.route('main', { path: '/' }, function() {
this.route('posts');
});
That solution yields the right URL's, but it means link-to helpers and such have to use 'main.posts', etc routes, which rubs me the wrong way, and I'd like not to have that extraneous route level all over the place.
In Rails, I'd just say something like layout: nil, and and that would prevent the main template from being applied. Is there any equivalent in Ember, or another way to achieve the same goal cleanly?

To get rid of {{link-to 'main.posts'}}. You can add {resetNamespace:true} to a given route.
this.route('main', {path:'/'}, () => {
this.route('posts', {resetNamespace:true});
});
{{link-to 'posts'}} will navigate to your posts route

I got another solution
{{!-- application.hbs --}}
{{#if session.isAuthenticated}}
{{outlet}}
{{else}}
Then the welcome/login page goes here
{{/if}}
Inspired by samselikoff in this post
======EDIT======
Maybe we can even do this
//in app/routes/login.js
export default Ember.Route.extend({
renderTemplate: function() {
this.render({ outlet: 'un-auth' });
}
});
and
{{!-- application.hbs --}}
{{#if session.isAuthenticated}}
{{outlet}}
{{else}}
{{outlet "un-auth"}}
{{/if}}

Related

toggle element visibility in ember js based on a boolean

I have created template and controller both called navbar. The code that i have in controller is simply
import Ember from 'ember';
export default Ember.Controller.extend({
isLogged: true,
});
and that in template is
{{#if isLogged}}
{{#link-to 'login' class="uk-button uk-button-danger"}}Login{{/link-to}}
{{#link-to 'signup' class="uk-button uk-button-danger"}}Join now{{/link-to}}
{{else}}
{{#link-to 'dashboard' class="uk-button uk-button-danger"}}Dashboard{{/link-to}}
{{/if}}
<button class="uk-button uk-button-large uk-button-primary uk-width-1-1" disabled={{isLogged}}>Test button</button>
The same does not seem to be working. Am i going wrong somewhere ?
The template and controller were generated using ember generator itself and the code above is the only modifications that i made.
EDIT:
Exploring the documentation, i noticed that the name of controller should be same as defined in route. Now navbar is only a template which i import using partial is there any workaround i might use for the same ?
You should use a component for this.
Use {{partial}} only if you want to render a template in the current context. Use it only if your .hbs files became to big, but use it rarely. Often a component is the better choice.
Also there you should almost never use {{render}}. You can almost always refactor it to a component.
Also using {{render}} with a model param is deprecated.
The problem was that 'partial' will only provide the template not the controllers. The correct way for this would be to use render which will import views and controllers also.

Using if to have different elements on different pages in Meteor.js

I have a certain div class (landing), that I only want to have appear on certain routes. I am using a "main" template that has my header and footer (with a >yield included between for each pages unique code). However, there is an element in the header that I only want to appear on the landing page ('/' route).
I am using Iron Router.
Is there a way to do this easily with an #if in meteor? Thanks!
Here's a generic route name equality testing helper:
Template.registerHelper('routeEquals',function(name){
return Router.current().route.getName() === name;
});
Then in any template you can do (for example):
{{#if routeEquals '/'}}
in the / route
{{else}}
Not in the / route
{{/if}}
You want to create a helper on your header template that reactively checks the current route. I haven't used Iron Router in a while, but believe Router.current().route.getName() is reactive. Try the following:
Template.header.helpers({
onLanding() {
return Router.current().route.getName() === '<landingRouteName>';
},
});
<template name="header">
{{#if onLanding}}
<div>I should only show on the landing page!</div>
{{/if}}
</template>
Or, alternatively (and maybe optimally - it would keep the template loaded, rather than injecting it into the DOM when you hit landing), you can use the helper to apply a class like
<div class="{{#if onLanding}}isVisible{{/if}}>...</div>
You can put a session flag in the onBeforeAction of the landing route and then, on the header, check if that session is valid and show the div
Something like this
this.route("landing", {
path: "/landing",
onBeforeAction: function () {
Session.set('showDiv', true);
}
});
<template name='main'>
<header>{{#if helper_getShowDivFlag}}<div></div>{{/if}}</header>
</template>
helper_getShowDivFlag: function() {
return Session.get('showDiv');
}
Alternatively, checking the router name in the main template and show/hide the div
<template name='main'>
<header>{{#if helper_showDivFlag}}<div></div>{{/if}}</header>
</template>
helper_showDivFlag: function() {
return Router.current().route.getName() == 'landing'
}
Or if you don't want to write code, use this package and check the router :D
<template name='main'>
<header>{{#if isActiveRoute 'landing' }}<div></div>{{/if}}</header>
</template>
You can accomplish this using the data property of your route.
Router.route('/', {
name: 'home',
data: function() {
return {
showMySpecialHeaderWidget: true
};
}
});
Then you can access this data property in your template.
<template name="main">
<header>
{{#if showMySpecialHeaderWidget}}
<div>My special header widget</div>
{{/if}}
</header>
{{> yield}}
</template>
Using this method, you don't need to worry about updating your template if you ever change the names of your routes, and it's easy to show your widget on multiple routes by including this data property.

Emberjs templating and routing

So I have a regular website that I built in Emberjs. Now I want to add an admin to the app. The problem I'm facing is that the header for the app is in application.hbs but for the admin I want the header to be different. the header needs to stay in the application.hbs because of the urls need to be /about not user/about or what ever arent route I could create. I tried making the header a component and add it to the route that worked but it loses the webapp feel since the header leaves from route to route. Is there a way for me not to extend the application template in admin?
As far as I have understood your question, you are looking for having different headers for admin and non-admin routes without converting the header as a component. If so, then you can achieve it by using a flag in the application handlebar say, "isAdmin". For example:
In the application.hbs
{{#if isAdmin}}
<h2>Header for Admin Route</h2>
<button {{action "goToApp"}}> Go To Application </button>
{{outlet "admin"}}
{{else}}
<h2>Header for Application Routes</h2>
<button {{action "goToAdmin"}}> Go To Admin Panel </button>
{{outlet "app"}}
{{/if}}
Here "isAdmin" will be property in "ApplicationController". And, you can set this flag inside the "activate" and "deactivate" handlers of the route.
In the route/admin.js
activate: function () {
this.controllerFor("application").set("isAdmin", true);
},
deactivate: function () {
this.controllerFor("application").set("isAdmin", false);
},
renderTemplate: function () {
this.render({
outlet: "admin"
});
}
Working JSBIN: http://jsbin.com/yilipa/edit?html,js,output
I hope this satisfies your requirement. If I have misunderstood your question, please do correct me.

How to have nested routes without nested templates

I have ember routes set up like so:
App.Router.map(function() {
this.resource("subreddit", { path: "/r/:subreddit_id" }, function() {
this.resource('link', { path: '/:link_id'} );
});
});
But I want the to view each link in a completely separate template. In other words, I want to render a different block of html, rather than render the link html into subreddit's {{outlet}}.
When you hit /r/xxx in the browser Ember will look for two templates related to subreddit. First it looks for 'subreddit' and then it looks for 'subreddit/index'. If 'subreddit' if found, then it is rendered and then if 'subreddit/index' is also found, it is rendered into the {{outlet}} of 'subreddit'. If 'subreddit' is not found Ember will just move on to 'subreddit/index'. The 'subreddit' template is also rendered for any sub-paths that are nested under /subreddit, such as the 'link' template for /r/xxx/yyy. 'subreddit' is kind of like a "per model layout" template that allows you to wrap (decorate) all of the sub-path templates.
Something like this should let you keep your nested routes, and allow you to use Ember default behavior.
<script type="text/x-handlebars" data-template-name="subreddit">
<p>A static header bar for this subreddit could go here, or not.</p>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="subreddit/index">
<p>
All of the details of the subreddit go here, this will be rendered
into the {{outlet}} of "subreddit".
</p>
</script>
<script type="text/x-handlebars" data-template-name="link">
<p>
Link view goes here. This template will replace "subreddit/index"
in the {{outlet}} of "subreddit".
</p>
</script>
If you want to render the links in separate templates you should not nest your routes:
App.Router.map(function() {
this.resource("subreddit", { path: "/r/:subreddit_id" });
this.resource('link', { path: '/:link_id'});
});
A different approach you can take could be to use named outlet's and override renderTemplate of the corresponding route and render the template in the place of the named outlet's, have a look here for more info on that: http://emberjs.com/guides/routing/rendering-a-template/
Hope it helps.

Combine linkTo and action helpers in Ember.js

I need to combine linkTo and action helpers in Ember.js. My code is:
{{#link-to 'index'}}<span {{action 'clear'}}>Clear</span>{{/link-to}}
But I would like to make this something like this:
{{#link-to 'index' {{action 'clear'}} }}Clear{{/link-to}}
And also:
<li>
{{#link-to 'support'}}
<span {{action 'myAction' 'support'}}>Support</span>
{{/link-to}}
</li>
To:
<li>
{{#link-to 'support' {{action 'myAction' 'support'}} }}Support{{/link-to}}
</li>
How can I achieve this?
Solution
Check my answer for Ember 2.0 compatible, OK for SEO solution.
Ember Link Action addon
This is OK for SEO solution!
Install addon
ember install ember-link-action
Usage
You can pass closure action as invokeAction param to {{link-to}} component:
{{#link-to 'other-route' invokeAction=(action 'testAction')}}
Link to another route
{{/link-to}}
To pass parameters to action you can use:
{{#link-to 'other-route' invokeAction=(action 'testAction' param1 param2)}}
Link to another route
{{/link-to}}
Compatibility
Automated test suite confirms that addon works with 1.13 up to latest Ember 3 releases.
It works with a release, beta and canary versions of Ember.
Addon GitHub repository. Contributions are welcome.
Update: See Michael Lang's comment below for Ember 1.8.1+
The problem with Myslik's answer (not using link-to at all but instead using an action and then transitionToRoute) is that it's useless for SEO, search engine bots will see nothing.
If you want what your link is pointing to to be indexed, it's easiest to have a good old <a href=x> in there. It's best to use link-to so that your link URLs are kept in sync with your route URLs. The solution I use gives both an action to do the work and a handy link-to to index the pages.
I override some functionality of Ember.LinkView:
Ember.LinkView.reopen({
action: null,
_invoke: function(event){
var action = this.get('action');
if(action) {
// There was an action specified (in handlebars) so take custom action
event.preventDefault(); // prevent the browser from following the link as normal
if (this.bubbles === false) { event.stopPropagation(); }
// trigger the action on the controller
this.get('controller').send(action, this.get('actionParam'));
return false;
}
// no action to take, handle the link-to normally
return this._super(event);
}
});
Then I can specify which action to take and what to pass the action in Handlebars:
<span {{action 'view' this}}>
{{#link-to 'post' action='view' actionParam=this}}
Post Title: {{title}}
{{/link-to}}
</span>
In the controller:
App.PostsIndexController = Ember.ArrayController.extend({
actions: {
view: function(post){
this.transitionToRoute('post', post);
}
}
}
This way, when I cache a rendered copy of the page and serve that to an indexing bot, the bot will see a real link with an URL and follow it.
(note also that transitionTo is now deprecated in favour of transitionToRoute)
None of these combinations will work in Ember.js, but you do not need to combine these two helpers. Why don't you just use action helper and let it bubble to controller or route? There you can use transitionToRoute in controller or transitionTo in route.
For example in controller you could have code like this:
App.PostsController = Ember.ArrayController.extend({
clear: function () {
// implement your action here
this.transitionToRoute('index');
}
});
This works fine in 1.6.0-beta.5:
<span {{action "someAction"}}>
{{#link-to "some.route"}}
Click Me
{{/link-to}}
</span>
The link will happen and then the click will bubble up to the action handler. It's documented (albeit indirectly) here.
Edit: corrected syntax in opening link tag
I like Cereal Killer's approach for its simplicity, but unfortunately it exhibits a problem for me. When the browser navigates to another route, it restarts the Ember application.
As of Ember 2.6, the following simple approach does the trick:
<span {{action 'closeNavigationMenu'}}>
{{#link-to 'home' preventDefault=false}}
Go Home
{{/link-to}}
</span>
This achieves the following:
navigates to route 'home'
action 'closeNavigationMenu' is invoked
on mouseover, browser displays link that will be followed (for SEO and better UX)
browser navigation does not result in reboot of Ember app
Having the same problem, i found this simple solution:
{{#linkTo eng.rent class="external-button"}}<div class="internal-button" {{action "updateLangPath"}} >X</div>{{/linkTo}}
then, managing the css classes external-button and internal-button in the stylesheet, i made sure that the "internal-button" was covering the whole "external-button" area; in this way it is not possible to click on the external-button without clicking on the internal-button.
It works well for me; hope it can help...
This is how I solved this in our demo application for the O'Reilly Ember.js book: https://github.com/emberjsbook.
You can see the complete source here: https://github.com/emberjsbook/rocknrollcall
In the view:
{{#if artistsIsChecked}}
{{#if artists.length}}
<h3>Artists</h3>
<ul class="search-results artists">
{{#each artists}}
<li><a {{action 'viewedArtist' this.enid }}>{{name}}</a></li>
{{/each}}
</ul>
{{/if}}
{{/if}}
And the controller:
App.SearchResultsController = Em.ObjectController.extend({
actions: {
viewedArtist: function(enid) {
this.transitionToRoute('artist', enid);
},
viewedSong: function(sid) {
this.transitionToRoute('song', sid);
}
},
needs: ['artists', 'songs'],
artistsIsChecked: true,
songsIsChecked: true,
artists: [],
songs: []
});
This is what it would look like after Ember 3.11.0 with the on modifier.
<LinkTo
{{on "click" this.recordMetrics}}
#route="post.see-all"
#model={{#model}}
#bubbles={{false}}>
Link Me
</LinkTo>
bubbles shown just to illustrate the LinkTo API.
https://blog.emberjs.com/ember-3-11-released/
Ember's link-to tags use routes to open new views, so you can perform whatever functionality you wanted to put in the link's 'action' attribute in the setupController method of the target route instead of in a controller action.
See here in the Ember guide:
http://emberjs.com/guides/routing/setting-up-a-controller/
This only works if you want to perform the action every time the route is accessed, however, as opposed to with specific links.
Make sure to include the controller.set('model', model); line along with whatever else you put in there.

Categories