Emberjs templating and routing - javascript

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.

Related

Why does my routing not work in Ember Js?

I'm working on a todoApp with a rails API and frontend as Ember.
I've followed this tutorial Ember todo App
However, it a bit old and I kinda lost in my routing.
I've a todos.hbs which should be rendered localhost:4200/, but it is a clear page.
Here's what my router.jslooks like :
import EmberRouter from '#ember/routing/router';
import config from './config/environment';
const Router = EmberRouter.extend({
location: config.locationType,
rootURL: config.rootURL
});
Router.map(function() {
this.route('todos', { path: '/' });
});
export default Router;
And my routes/todos.js:
import Route from '#ember/routing/route';
export default Route.extend({
model: function() {
return this.store.find('todo');
}
});
On my application.hbs, there's only an ```{{outlet}}
and my todos.hbs looks like this :
<section id="todoapp">
<header id="header">
<h1>todos</h1>
{{input type="text" id="new-todo" placeholder="What needs to be done?" value=newTitle action="createTodo"}}
</header>
<section id="main">
<ul id="todo-list">
{{#each itemController="todo"}}
<li {{bind-attr class="isCompleted:completed isEditing:editing"}}>
{{#if isEditing}}
{{input insert-newline=(action "acceptChanges")}}
{{else}}
{{input type="checkbox" checked=isCompleted class="toggle"}}
<label {{action "editTodo" on="doubleClick"}}>{{title}}</label>
<button {{action "removeTodo"}} class="destroy"></button>
{{/if}}
</li>
{{/each}}
</ul>
<input type="checkbox" id="toggle-all">
</section>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
</footer>
{{outlet}}
So now, I dont know where the mistake is, it only rendered me a blank page. If anyone can explain what is wrong into my work, I would appreciate.
UPDATE
I've found that a troubles appear in my application.js, here's what it looks like now, if that can help :
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
host: 'http://localhost:3000/api'
});
As Joe has already mentioned, store.find('todo') has been removed and from what I can see it was removed in 2015 but I am not 100% sure about that 🤔 You are likely to find other large issues following such an old tutorial and using the latest Ember. Have you seen the official tutorial on the Ember Guides site? https://guides.emberjs.com/release/tutorial/ember-cli/ there is always a lot of people making sure that it is as up-to-date as possible!
I will continue to try to answer your question but I do strongly recommend that you try a slightly more modern tutorial in the meantime 😂
Joe is indeed correct that you need to update the call in your route to use the updated method this.store.findAll('todo') and you should be able to see that recommended in your console output if you bring it up (you can usually right-click anywhere on the page and select 'Inspect Element' and then click the console tab)
As you can see the error says:
Using store.find(type) has been removed. Use store.findAll(modelName) to retrieve all records for a given type. Error: Assertion Failed: Using store.find(type) has been removed. Use store.findAll(modelName) to retrieve all records for a given type.
Once I update that method so we're using findAll() it gives me a new error:
Error while processing route: todos No model was found for 'todo' Error: No model was found for 'todo'
Which means I need to create the file models/todo.js. If you do look at a more modern tutorial it will probably recommend that you use a generator to generate this file such as: ember generate model todo.
I then added your adapter and this is where I need to stop because I don't have the backend server running on my local machine so it will always fail for me 😄
You can see the code I was using to test this here: https://ember-twiddle.com/14bab300c47493c10a9de69efd591092
If you haven't used Ember Twiddle before I would highly recommend it if you are trying to ask a question here on Stack Overflow, on the Ember Discord server or on the Ember forums. If you are able to recreate your issue on an Ember Twiddle then you are very likely to be able to get a helpful response 🎉
Let me know if you have any questions and I hope this has been helpful 👍
I noticed that your routes/todos.js calls this.store.find('todo');. Using store.find(type) has been removed. I think you have to call this.store.findAll('todo');

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.

Override Ember application.hbs template

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}}

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