How to render the route name in an Ember.js application template? - ember.js

Given a list of routes:
Router.map ->
#route 'home', path: '/'
#route 'sign-in'
#route 'about'
How can I dynamically render this information into my application template (main layout)?
<div id="container" class="{{routeNameGoesHere}}">
{{outlet}}
</div>
For example, fully rendered:
<div id="container" class="sign-in">
<h1>Sign In Page</h1>
</div>

Application Controllers automatically receive the properties currentPath and currentRouteName from the router. So you can use those directly in your application template.

Based on #Grapho's answer, I found that I needed to create a computed property on my application controller in order to convert the dot-separated route names with dashes.
import Ember from 'ember';
export default Ember.Controller.extend({
hyphenatedCurrentRouteName: Ember.computed('currentRouteName', function(){
return this.get('currentRouteName').split('.').join('-')
}
});
Now in my template, I can use {{hyphenatedCurrentRouteName}}.

Related

How to create action for my own component

I am creating one ember app.
Flow is like " page1 displays list of feeds item and clicking on any of the feed will take user to page2 showing details about that feed"
What i am doing:
i have one component named app-feed. Template is as below
<div onclick={{action 'click' feed}}>
{{#paper-card class="card-small" as |card|}}
<!-- --> {{card.image src=feed.imagePath class="small-feed-img" alt=feed.title}}<!---->
{{#card.header class="flex-box short-padding" as |header|}}
{{#header.avatar}}
<img class="profile-small" src="http://app.com/users/{{feed.userName}}.jpg" alt="{{feed.name}}" />
{{/header.avatar}}
<span class="tag-sm like-box">
{{feed.likes}} {{paper-icon "thumb_up" size="18"}}
{{feed.commentCount}}{{paper-icon "chat_bubble" size="18"}}
</span>
{{/card.header}}
{{#card.actions class="action-block"}}
{{#paper-button iconButton=true}}{{paper-icon "favorite" size="18"}}{{/paper-button}}
{{#paper-button iconButton=true}}{{paper-icon "share" size="18"}}{{/paper-button}}
{{#paper-button iconButton=true}}{{paper-icon "shopping_basket" size="18"}}{{/paper-button}}
{{/card.actions}}
{{/paper-card}}
</div>
component.js is as below
import Ember from 'ember';
export default Ember.Component.extend({
actions:{
click(feed){
console.log("Click event fired:"+feed.id); //Output is correct in console
this.sendAction("onClick", feed); //sending onClick Action
}
}
});
I'm populating list of this component in one of my route.
Template is as below
{{#app-sidenav user=model}}{{/app-sidenav}}
<div class="content">
<div class="row">
{{#each model as |item|}}
{{#app-feed-small onClick=(action "getDetail" item) class="col-xs-5" feed=item}} {{/app-feed-small}}
{{/each}}
</div>
</div>
route.js is as below
import Ember from 'ember';
export default Ember.Route.extend({
store: Ember.inject.service(),
model(){
//Populating module. Works just fine
} ,
actions:{
getDetails(feed){
console.log("Getting details of "+feed.id);
}
}
});
I have defined getDetails action as mentioned in my template.js of the route still i am getting below error
""Assertion Failed: An action named 'getDetail' was not found in (generated feed.index controller)""
feed.index is my route.
I used same method and modified paper-chip's source to get action corresponding to click on paper-chip's item which worked. But i am not able to do same in my own component.
Please let me know what is missing
Your problem is that in your second last code snippet, the one with your template. You refer to the action as getDetail but in route.js your last code snippet you declare the action as getDetails which is different to the code in your template. It's a common spelling error, one has an "s" st the end whereas the other doesn't.
The actions should be in controllers. And if controller bubbles up then the action in route be called.
For your case you don't need controller.
You can use ember-transition-helper
I assume you have in router.js :
this.route('feeds', function(){
this.route('edit', {path: '/:id'});
});
Now your template is going to be :
{#app-sidenav user=model}}{{/app-sidenav}}
<div class="content">
<div class="row">
{{#each model as |item|}}
{{#app-feed-small onClick=(transition-to "feeds.edit" item) class="col-xs-5" feed=item}} {{/app-feed-small}}
{{/each}}
</div>
</div>
sendAction is an ancient way to calling action inside controller/route.
The new style is to use closure action, which passes action as a value by creating a closure at the time of value passing.
Yes, you are correct. The action has been sendAction is able to bubble up from,
correspond controller -> correspond route -> upper route -> ... -> application route
However, closure action does NOT bubble.
Please refer to Ember component send action to route where #kumkanillam detailed explained how to call action inside route using different method and the differences between sendAction and closure action.
I have also made a sample project and write a simple explanation for it at,
https://github.com/li-xinyang/FE_Ember_Closure_Action

Controlling component data across multiple routes

I have a mapping app that has a full-screen map with a sidebar for information. This app has two routes:
one route that should display a list of places with markers on the map, for example /places/
one route that should display a single place with that particular place's marker centered on the map, for example places/1/
My map is currently a Component that is in application.hbs, so it is "outside" of the route templates and persists across route changes. It looks something like:
<div class="page">
<aside class="sidebar">
{{outlet}}
</aside>
<div class="content">
{{places-map ... }}
</div>
</div>
and my routes looks something like:
Router.map(function() {
this.route('index', { path: '/' });
this.route('place', { path: "/place/:place_id" });
this.route('places');
});
So while I have all this set up and working (I can see a list of places and move a single particular place, in both cases with the map in the "background"), I can't understand how my routes can feed information to my component or simply how my routes can communicate with the component that is sitting "outside" of their context?
Is this a possible pattern with Ember and is there a way to achieve it?
Ditto on what #GerDner said about data-down-actions-up.
Starting from the top:
application/controller.js
import Ember from 'ember';
export default Ember.Controller.extend({
somethingDownFromController: null
});
application/route.js
import Ember from 'ember';
const {
set
} = Ember;
export default Ember.Route.extend({
actions: {
sendSomethingUp(something) {
set(this.controllerFor('application'), 'somethingDownFromController', something);
}
}
});
application/template.hbs
<div class="page">
<aside class="sidebar">
{{outlet}}
</aside>
<div class="content">
{{places-map
something=somethingDownFromController
}}
</div>
</div>
place/route.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return {
somethingFromNestedRoute: 'boooooyaaaaah'
}
}
});
place/template.hbs
<button {{action 'sendSomethingUp' model.somethingFromNestedRoute}}>
Send model up
</button>
You might not need to send anything up with the action you're bubbling here. If that's the case then you can just grab what you need from the application controller or route and pass it down into places-map.
places-map/template.hbs
Insert something from the outer context:
<div>{{something}}</div>
Here's an ember-twiddle. I made a few notes in the router.js file that might be useful depending on the exact needs of your application.
The data-down/actions-up Pattern is the answer.
http://www.samselikoff.com/blog/data-down-actions-up/
You hold the data on a toplevel component/controller and pass the data down to child components. Data changes are triggered via actions on the child component and handled by the toplevel component/controller via action bubbling. So you need only one component/controller which knows how to change the data and how to get data.

Ember - Set a Dynamic Page Title per route

I have set up my Ember app to use the pod structure. I've created a topbar-nav component which I display on the main page. In the topbar-nav component I have set up the page title, which I would like to set automatically per route so for the dashboard route the title should be Dashboard, for the dashboard/categories the title should be categories and so on.
Here's my app structure:
app
application
components
topbar-nav
dashboard
categories
The app router - router.js
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
this.route('dashboard', function() {
this.route('categories');
});
});
export default Router;
And the templates:
application/template.hbs
<aside>
{{sidebar-nav}}
</aside>
<main>
<header>
{{topbar-nav}}
</header>
{{outlet}}
</main>
components/topbar-nav/template.hbs
<nav class="topbar-nav">
<div class="text-center">
<h5 class="page-title">{{title}}</h5>
</div>
</nav>
{{yield}}
components/topbar-nav/component.js
import Ember from 'ember';
export default Ember.Component.extend({
title: 'Component title'
});
As you can tell already the title I get rendered is 'Component title'
The most accurate answer I got by now is this one:
Ember dynamic titles
though after trying a lot of different approaches I couldn't find a good solution.
Is there a clean and easy way of doing this? Any ideas and solutions are much appreciated.
I wanted a similar dynamic title and found this page. So here is my solution based on #AcidBurn's note that the application controller has a currentRouteName property.
If you want the last child route:
screen_title: Ember.computed('currentRouteName', function () {
let route = this.get('currentRouteName').split('.');
// Return last route name (e.g. categories from dashboard.categories)
return route[route.length - 1];
}),
If you want the parent route:
screen_title: Ember.computed('currentRouteName', function () {
let route = this.get('currentRouteName').split('.');
// Return parent route name (e.g. reports from reports.index)
return route[0];
}),
ember-page-title seems better than ember-cli-document-title now (better docs+api)

How to populate nested views/controllers?

I'm attempting to create an Ember application with nested views. I (basically) want three vertical columns. The left-most containing a list of Environments. Clicking an environment will populate the second (centre) column with the Job objects associated with the environment, and then when clicking on a Job in second column it populates the third column with various details based on the Job.
The data is coming from a Rails api via ActiveModel serializers, embedding IDs as required.
So far, I have the following routes defined:
Skills.Router.map ()->
this.resource 'environments', ->
this.resource 'environment', {path: ":environment_id"}, ->
this.resource 'jobs'
Skills.IndexRoute = Ember.Route.extend
redirect: ->
this.transitionTo 'environments'
Skills.EnvironmentsRoute = Ember.Route.extend
setupController: (controller) ->
controller.set('model', this.store.find('environment'))
My handlebars application template:
<div class="container">
<div class="row">
{{outlet }}
</div>
</div>
The Environments template
<div id="environments" class="col-sm-4">
{{#each }}
{{view Skills.EnvironmentView }}
{{/each}}
</div>
<div class="col-sm-8">
{{outlet}}
</div>
Environments View:
Skills.EnvironmentView = Ember.View.extend
templateName: 'environment'
click: ->
# should I be trying to update the Jobs controller here?
Environments controller:
Skills.EnvironmentsController = Ember.ArrayController
Models:
Skills.Environment = DS.Model.extend
title: DS.attr('string')
description: DS.attr('string')
jobs: DS.hasMany('job')
Skills.Job = DS.Model.extend
title: DS.attr('string')
environment: DS.belongsTo('environment')
This will lookup and display the Environments in the first column. How do I get the Jobs controller populated with the selected Environments Jobs from the association?
Edit: Binding seems like what I'm after, but I'm unsure as to how to bind the model/content property of one controller to a property from another?
What you should do is populate the list of environments in the environments template
(not the environments.index template). When an environment is selected from this list it should transition to the environment.show route. In the environment resource route it should show the list of jobs for that environment. This environment template should be rendered into the environments template. Then choosing a job will transition to the job.show route underneath the jobs resource.
The key is that the top level resource route will be retained and the subsequent child routes will be rendered into it, preserving the resource lists while rendering the child details.
Skills.Router.map ()->
this.resource 'environments', ->
this.route 'index'
this.resource 'environment', {path: ":environment_id"}, ->
this.route 'index'
this.route 'show'
this.resource 'jobs'
this.route 'index'
this.resource 'job'
this.route 'show'

Understanding Ember routes

Assume I have routemap like this:
App.Router.map ->
#resource 'posts', ->
#resource 'post', {path: '/:post_id'}
So I have list of posts on /posts route and a single post on posts/2. By default posts template will be rendered to {{outlet}} of a parent template (application) which is OK and post template to {{outlet}} of posts template which is not what I want. I want to replace list of posts with a single post.
So I came with that solution to set desired template to render to it's {{outlet}}:
App.PostRoute = Em.Route.extend
renderTemplate: ->
this.render(into: 'application')
But as a result I have list of posts and a single post rendered into one {{outlet}}. I can emtpy {{outlet}} each new route but then it breaks back button, cuz Ember wont render previous template again, assuming that is already rendered.
So questions is how to use single {{outlet}} for different routes and don't break ember's logic. Of course I can avoid nested routes but they a really usefull when you need semantic links like this post/2/comment/12/edit.
If your templates are not nested, you shouldn't nest your routes, or you'll end up fighting Ember. Acting as Ember's state manager, routes describe your app structure. Nesting should follow your UI, not how you want your URLs to look like.
If your concern is URLs, you can just modify the path to fit your needs:
App.Router.map ->
#resource 'posts'
#resource 'post', { path: 'posts/:post_id' }
The approach I have used for handling nested and non nested templates even when my routes are nested is to use named outlets. Ember makes it really easy.
I have a top level menu outlet that always holds a menu relevant to the route being visited.
Example Application Template:
<div id="nav">
{{partial "nav"}}
</div>
<div id="menu">
{{outlet "menu"}}
</div>
<div id="main">
{{outlet}}
</div>
{{outlet "modal"}}
I have two named outlets, "menu" and "modal", which are used to hold content which is not nested but used by nested routes or any route to be precise. Any route can render a modal in response to actions into the global "modal" outlet, and similarly any route can render a menu into the "menu" outlet.
My examples use coffeescript.
Router:
App.Router.map ->
#resource "posts", ->
#route "create"
#resource "post", {path: ':id'}, ->
#resource "comments", {path: ':id'}, ->
#route "create"
#resource "comment", {path: ':id'}, ->
Routes which render a menu into a named outlet that is not nested:
App.PostsIndexRoute = Em.Route.extend
renderTemplate: (controller, model)->
# default render
#_super arguments...
# render posts menu into the application menu outlet
#render "posts.menu",
outlet: "menu"
into: "application"
App.CommentsIndexRoute = Em.Route.extend
renderTemplate: (controller, model)->
# default render
#_super arguments...
# render comments menu into the application menu outlet
#render "comments.menu",
outlet: "menu"
into: "application"
You don't have to do the default nested rendering as above, you could just define a route type that always renders into a named outlet such as "content" (just don't call it "main" because Ember uses that).
App.ContentRoute = Em.Route.extend
renderTemplate: (controller, model)->
#render outlet: "content", into: "application"
And then subclass from it for any routes that should always render into the application 'content' outlet:
App.PostsIndexRoute = App.ContentRoute.extend()
App.CommentsIndexRoute = App.ContentRoute.extend()
But it would be better to do it with a mixin so you can easily include whatever concerns you wish into any route (eg authenticated route concerns).
App.RenderIntoContent = Em.Mixin.create
renderTemplate: (controller, model)->
#render outlet: "content", into: "application"
App.PostsIndexRoute = Em.Route.extend App.RenderIntoContent,
...
App.CommentsIndexRoute = Em.Route.extend App.RenderIntoContent,
...