Dynamic model in LinkTo component in Ember - ember.js

I am using Ember 3.18, I am facing the below issue. Consider the following routes:
Router.map(function() {
this.route('author');
this.route('author' , {path:"/author/:author_id"});
});
Now, in my hbs file, I am trying to transition to the above routes using a single LinkTo. As you can see, only the second route requires model attribute. In simple terms, I want to combine the below 2 into a single line.
<LinkTo #route="author" />
<LinkTo #route="author" #model="2" />
As you can see, I require the model attribute to be gone in certain cases and availble in certain cases.
Please help.

I think the easiest way forward is to tweak your routing setup a bit. I know you want to combine the routes, but it's hard/confusing, imo, and would be "more standard" to do something more traditional like:
Router.map(function() {
this.route('author', function() {
this.route('view', {path:":author_id"});
});
});
and
<LinkTo #route="author.index" />
<LinkTo #route="author.view" #model="2" />
author.index would match /author
and author.view (with a #model) would match /author/2.
Note that index is an implicit convention of the web, and not needed in the router.js file

Related

Multiple layouts in Ember 2

I need implement next application structure with 3 routes:
localhost/users
localhost/posts
localhost/settings
'users' and 'posts' routes should have basic layout1 with main navbar.
'settings' route should have another layout2 with second navbar.
How can I implement multiple layouts approach with Ember > 2.12?
Can I set a layout name/path for each route or group of routes?
I can think of two possible recommended approaches to this problem. I've used both in different scenarios. You can either:
Use a component to encapsulate each navbar and then present them accordingly in each template
Set the templateName attribute of each route to use the correct template.
The component approach seems to be the easiest/most used in my experience. It also allows you to have differences within your base route template. e.g.:
users.hbs:
{{layout1}}
<h1>Users<h1>
...
posts.hbs:
{{layout1}}
<h1>Posts</h1>
...
While if you use the templateName approach, you are locked into using the same exact template. So, if you need any customization between any page that uses the same layout, you must use a subroute. e.g.:
routes/users.js:
import Ember from 'ember';
export default Ember.Route.extend({
templateName: 'layout1'
});
routes/posts.js:
import Ember from 'ember';
export default Ember.Route.extend({
templateName: 'layout1'
});
templates/layout1.hbs:
<nav>...</nav>
{{outlet}}
A third possible approach, though I don't necessarily recommend it, is to dynamically replace the navbars in the application.hbs template. The application controller/template have access to a special attribute called currentPath, which you can used to create two computed properties (isLayout1 and isLayout2).
With that, this becomes a viable, though like I said, not necessarily recommended solution:
application.hbs:
{{#if isLayout1}}
<nav>layout 1</nav>
{{else}}
<nav>layout 2</nav>
{{/if}}
{{outlet}}

How show component only in a specific route?

I have a componente called hero (in application.hbs) and I wish display this componente only in home page.
I researched about how do this but without any success. Thanks!
After a few minutes and some searches on GitHub...
Just install ember install ember-truth-helpers and check the route name:
{{#if (eq currentRouteName 'index')}}
{{hero}}
{{/if}}
Glad to help!
I need more specifics, however, I am going to make the assumption that your home route is the '/' route.
The '/' route is actually your index route, so if you create an index.hbs file it will act as the template for your index route. And then your should just move the hero component to your index.hbs file.
I can't be sure your reasons, but I suspect that this could be a solution.
There is an invisible 'application' route... there is also an implicit 'index' route, but you can skip the confusion of that and just create a 'home' route and give it a path to the root. The application template will house the outlet - and then you can place your component just in the 'home' template;
(don't write an application route like this, but just for visualization)
Router.map(function() {
// overarching 'application' route
this.route('application', function() {
this.route('home', { path: '/' });
this.route('other');
});
});
Here is a twiddle with the full example in place. If this doesn't do what you want, then refer to the conditional suggestions. : )
Router.map(function() {
// here's an example of skipping of skipping the mysterious 'index' in another situation
this.route('books', function() {
this.route('books-list', { path: '/' });
this.route('book');
});
});
You can also render a component dynamically using component helper which save you a conditional statement inside your template.
The first parameter of the helper is the name of a component to render, as a string. So {{component 'blog-post'}} is just the same as using {{blog-post}}.
When the parameter passed to {{component}} evaluates to null or undefined, the helper renders nothing. When the parameter changes, the currently rendered component is destroyed and the new component is created and brought in.
So you can safely pass in anything to the component helper, in your case you can make the component name dynamically without worry an error will raised.
https://guides.emberjs.com/v2.1.0/components/defining-a-component/#toc_dynamically-rendering-a-component

How to define two ember routes with same name with different number of query params?

I have defined two ember routes as follows.
export default Router.map(function () {
this.route('stock-overview', {path: '/:lan/stock-overview/:companyId'});
this.route('stock-overview', {path: '/:lan/stock-overview/:exchange/:symbol'});
same name but different number of params. but when i add link-to helper as follows it gives an error saying.
<li>{{#link-to 'stock-overview' 'en' '70'}}Stock Overview{{/link-to}}</li>
<li>{{#link-to 'stock-overview' 'en' 'exchange' 'someSymbol'}}Stock Overview{{/link-to}}</li>
Console Error:
Uncaught Error: More context objects were passed than there are dynamic segments for the route: stock-overview
How can I handle this situation.? My ember version is:1.11.1
Appreciate any help.
I'm not %100 sure, but if i'm not wrong, you can only have one model for a route.
IMO, it would be better for you to do this with nested route.
Update:
I found some Q-A's from SO. Maybe those can be helpful for you:
Is resource nesting the only way to enable multiple dynamic segments?
Multiple Dynamic Segments in a Single Resource in Ember.js (see also Kingpin2k's answer)

When to use controllers when using components with Ember

In the latest (1.10+) versions of Ember there seems to be a trend to make use of components instead of controllers and views.
The wisdom seems to be:
"Only use controllers at the top-level for receiving data from the route, and use Ember.Controller instead of Ember.ArrayController or Ember.ObjectController"
This makes some sense to me as it's cool to create reusable components, but there are some scenarios where it is unclear to me what the appropriate approach is.
Say we have a component which represents a row in a table, something like this:
# entry-row.js component
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'tr',
actions: {
toggleResolving: function() {
this.toggleProperty('resolving');
}
},
isChecked: function() {
return this.get('resolving');
}.property('resolving')
});
# entry-row.hbs template
<td {{action "toggleResolving"}}>{{entry.name}}</td>
<td>{{entry.currency.symbol}}{{entry.amount}}</td>
<td>{{entry.date}}</td>
<td class="bs-checkbox">{{input type="checkbox" checked=isChecked class="toggle"}}</td>
To form a table, many of the same component are used. That's great. If there is some non-persistent state which applies only to that row then that can belong to the component and be changed there. Something like:
actions: {
toggleResolving: function() {
this.toggleProperty('resolving');
}
}
But say that my resolving property is also useful outside the component. In this case, I wish to be able to mark several rows for resolution and then perform some action on them collectively. I believe that before the switch to components, I would have defined the resolving property on the Controller and made use of that controller wherever I needed the model + state.
Should I still be putting this on the controller?
Besides using an outer component, another option is to put properties on the model. You can also share common actions with mixins.

Ember.js how to design different representations of Data (with TodoMVC as an example)?

I would like to know what's the best way of designing the display of different representations of the same data model in Ember.js. To ask my question, I'll use the TodoMVC of Ember.JS, which has 3 representations of todo-data:
any todo, i.e. the entire todo list (TodosIndexRoute)
todos that are still active and incomplete (TodosActiveRoute)
todos that have been completed (TodosCompletedRoute)
Currently, you can see each of the 3 by clicking on the words at the bottom of the list, directing to a different URL each time. Since currently each representation has a route, it makes sense that each representation gets its unique URL. The main page displays the entire todo list (1.).
A. What is the best ember.js design to make the main page display all 3 representations (i.e. under just one URL)?
B. How about the best design that displays all 3 on the main page as well as on separate pages?
Currently I only figured out a clumsy way and made this modified TodoMVC app that shows incomplete and completed lists at the bottom of the page.
In index.html, I added new named lists:
{{#each todosactive itemController="todo"}}
{{ title }},
{{/each}}
In the js router, I copied TodosActiveRoute and TodosCompletedRoute into TodoIndexRoute, which is code duplication, very bad.
Todos.TodosIndexRoute = Ember.Route.extend({
setupController: function () {
var todos = Todos.Todo.find();
this.controllerFor('todos').set('filteredTodos', todos);
var todos_active = Todos.Todo.filter(function (todo) {
if (!todo.get('isCompleted')) {
return true;
}
});
this.controllerFor('todos').set('todosactive', todos_active);
...
});
I feel like I'm missing an elegant way of doing this, but my current ember.js knowledge is very limited. Should I use {{partial}}, {{render}}, render, or something else?
I tried {{ partial }} and {{ render }}, but I can't get them to display any data .
Thanks for helping out.
A) Ember tries to work really closely with urls. This is a good thing since if you want to share a url, the view should be consistent. The url is a powerful tool and each unique url should link to the same unique page. Having one url that links to multiple views isn't great, and certainly not shareable. If you have some time listen to some talks by Tom Dale and Yehuda Katz for an interesting overview of ember and what they're trying to do.
B) You can include different views on one page. Have a look at the guides, most notably on rendering templates and using helpers for more information on including different views under one url.
A) To display all 3 representations in one view, we actually just need the basic model in the single route. The key is for the controller to give you flavors of that model. The other important thing is to use data binding in the handlebars template. You can see the running version here.
In the IndexRoute, add a model that gets the plain todos list:
Todos.TodosIndexRoute = Ember.Route.extend({
model: function(params) {
return Todos.Todo.find();
},
...
Make a TodosListView that doesn't need to have anything:
Todos.TodoListView = Ember.View.extend();
In the controller, add 2 computed properties that returns the desired arrays:
Todos.TodosController = Ember.ArrayController.extend({
...
todosActive: function() {
return this.filterProperty('isCompleted', false);
}.property('#each.isCompleted'),
todosCompleted: function() {
return this.filterProperty('isCompleted', true);
}.property('#each.isCompleted'),
...
Finally, in the HTML template:
<script type="text/x-handlebars" data-template-name="todos">
Active:
{{#view Todos.TodoListView lalaBinding="todosActive"}}
{{#each view.lala}}
{{title}},
{{/each}}
{{/view}}
Completed:
{{#view Todos.TodoListView dataBinding="todosCompleted"}}
{{#each view.data}}
{{title}},
{{/each}}
{{/view}}
</script>
note the dataBinding.
Thanks to the folks on #emberjs irc for helping out.