EmberJS show router model in application.hbs - ember.js

I am working with Ember.js, and I am trying to make my pages display the title of the page just below the navbar. To make this work I have tried to use model hook, and show it in the application.hbs file.
So far I have tried variations of this:
routes/contact.js
import Route from '#ember/routing/route';
export default class ContactRoute extends Route {
model() {
return 'Title of page';
}
}
templates/application.hbs
<div>
<NavBar />
<div class="pageTitle">
<h2>
<p>{{#model}}</p>
</h2>
</div>
<div class="body">
{{outlet}}
</div>
</div>
I've mostly tried to mess around with #model in application.hbs things like outlet.#model. But all of these attempts have resulted in empty titles or Template Compiler Errors.
Does anyone have a solution for this? Preferably one that does not involve jquery?

If I understand correctly what you are trying to accomplish, it is a good use case for services.
You need a couple parts. A service to keep track of the page title, and then you need to inject that service in the application controller so the template has access to the service, and also to inject the page title service in the routes so you can update the page title in the respective hooks.
Page service
import Service from '#ember/service';
import { tracked } from '#glimmer/tracking';
export default class extends Service {
#tracked title = "Your App"
}
Application controller and template
import Controller from '#ember/controller';
import { inject as service } from '#ember/service';
export default class ApplicationController extends Controller {
#service pageTitle;
}
<h1>Welcome to {{this.pageTitle.title}}</h1>
<br>
<br>
{{outlet}}
<LinkTo #route="my-route">my route</LinkTo>
<br>
<br>
MyRoute route updating the page title value in a model hook
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default class extends Route {
#service pageTitle;
model() {
this.pageTitle.title = "My Route"
}
}
I have put it all together in an interactive Ember Twiddle demo.
Hope this helps!

Since you have created a new page (route) named contact, the UI part of the page has to be in the corresponding template file, i.e., templates/contact.hbs and not templates/application.hbs as the templates/contact.hbs file can only access the #model of routes/contact.js
ie., the below markup has to in templates/contact.hbs file and will be displayed when accessing the page at http://localhost:4200/contact
<div class="pageTitle">
<h2>
<p>{{#model}}</p>
</h2>
</div>
Also, note that the markup present in the templates/contact.hbs file will be rendered in the place of application template's {{outlet}} (see here)
For a detailed reference, check this twiddle

Related

Add Extra Actions to LinkTo in Octane

I have a dropdown menu with links, when the links are clicked I'd like the menu to close.
Something like (a11y/i18n truncated):
<ul class="menu">
<li>
<LinkTo #route="myprofile">
Profile
</LinkTo>
</li>
<li>
<LinkTo #route="logout">
Logout
</LinkTo>
</li>
</ul>
I'd like to add an additional click handler to the link to, something like:
<ul class="menu">
<li>
<LinkTo #route="myprofile" {{on "click" this.closeMenu}}>
Profile
</LinkTo>
</li>
<li>
<LinkTo #route="logout" {{on "click" this.closeMenu}}>
Logout
</LinkTo>
</li>
</ul>
However this makes the LinkTo useless as it reloads the page as if following a link instead of transitioning to a new route. We're currently doing this using hember-link-action, but I'd love to find a more idiomatic way to approach this problem.
If you need to perform additional logic, you may implement redirect in an action instead of using the LinkTo helper. To do so, you need to inject RouterService into your component and then call its transitionTo method. Something like:
export default class ExampleComponent extends Component {
#service router;
#action
navigate(route) {
this.menuExpanded = false;
this.router.transitionTo(route);
}
}
Note that there also exist the transitionTo() method from Route and transitionToRoute() from Controller that behave like the LinkTo helper. But those methods are deprecated now, and using RouterService is a recommended idiomatic way of doing transitions in js code.
I've written a component to mostly handle this, but I'm quite certain there are more edge cases in LinkTo then I've covered (for example it doesn't cover a passed model or list of models). I called this <LinkToWithAction /> and it looks like:
<a href={{this.href}} class={{if this.isActive "active"}} {{on "click" this.navigate}} >
{{yield}}
</a>
import Component from '#glimmer/component';
import { inject as service } from '#ember/service';
import { action } from '#ember/object';
export default class LinkToWithActionComponent extends Component {
#service router;
get href() {
return this.router.urlFor(this.args.route, {
queryParams: this.queryParams,
});
}
get isActive() {
return this.router.isActive(this.args.route);
}
get queryParams() {
return this.args.queryParams ?? {};
}
#action
navigate(evt) {
evt.preventDefault();
this.args.action();
this.router.transitionTo(this.args.route, {
queryParams: this.queryParams,
});
}
}
and it is called as:
<LinkToWithAction
#route="mymaterials"
#action={{set this.isOpen false}}
#queryParams={{hash course=null}}
>
{{t "general.myprofile"}}
</LinkToWithAction>
This is made more annoying by this issue with transitionTo that adds unset queryParams to the URL when called which effects the public router service. The built in component uses the private internal router where this behavior doesn't exist, and it may be worth using that private service, but for now we're going to live with passing the query params.

Index Route vs Custom Route causes application.hbs model issues

I am working with Ember 2.9, and I'm facing a weird routing + model issue.
I have two routes that are identical (copied and pasted code inside the route) except for the pathing. I have one route which is '/', and another route which is 'my-route'.
import Ember from 'ember';
export default Ember.Route.extend({
model() {
let dict = {
myLog1Model: this.get('store').findAll('my-log1'),
myLog2Model: this.get('store').findAll('my-log2'),
myLog3Model: this.get('store').findAll('my-log3'),
myLog4Model: this.get('store').findAll('my-log4')
};
return dict;
}
});
When I access my ember application from localhost/ all model data is displayed properly, but when I access it from localhost/my-route none of the model data is being passed around in application.hbs
application.hbs
<section class='container-fluid'>
<div class='row'>
<div class='col-md-6'>
{{control-panel model=model}}
</div>
<div class='col-md-6'>
{{log-panel model=model}}
</div>
</div>
</section>
log-panel.hbs
{{myLog1 model=model.myLog1Model}}
{{myLog2 model=model.myLog2Model}}
{{myLog3 model=model.myLog3Model}}
{{myLog4 model=model.myLog4Model}}
Ember tables takes it from here...
myLog1.hbs
{{models-table
data=model
columns=columns
useNumericPagination=true}}
As I stated before this works perfectly fine with 'localhost/', but not with 'localhost/my-route'. Any idea why this is?
I was able to resolve my issue, by loading any application model data in the Application Route. I had no idea that there was a difference between '/', and the application route.

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)