Simply, the web app needs to use the application.hbs template 99% of the time but for a single route, I would like to disable it and render only the route template.
Like 'Layout=null;' in asp.net
Thanks in advance.
The shortest answer is that you need to nest all the routes that share a layout under a common parent route, since you can't remove the application template. This can be done in a couple different ways, depending on the goal.
If they all share a URL segment, you can put then under a common parent:
Router.map(function() {
this.route('pretty-layout', function() {
this.route('page-1'); // http://localhost:4200/pretty-layout/page-1
this.route('page-2'); // http://localhost:4200/pretty-layout/page-2
});
});
You can override the top-level index route. You'll likely need to move the current application.index to application.index.index:
Router.map(function() {
this.route('index', { path: '/' }, function() {
this.route('page-1'); // http://localhost:4200/page-1
this.route('page-2'); // http://localhost:4200/page-2
});
});
Related
Working for a few years with ember.js now, it's still not quite clear to me, what should be considered as best practice for structuring list, view, create and update routes.
The projects I've worked with so far mostly used to routing trees per entity. The pluralized entity name for listing with a subroute for create and the singular entity name for detail view with a subroute for editing. As an example a post model would have these for routes: /posts for listing posts, /posts/new for the create functionality, /post/:post_id for showing a single post and /post/:post_id/edit for editing that one. The corresponding router would look like this one:
Router.map(function() {
this.route('post', { path: '/post/:post_id' }, function() {
this.route('edit');
});
this.route('posts', function() {
this.route('new');
});
});
This approach is working quite nicely for detail and edit view cause they are sharing the same model. So the model hook of the edit route could just reuse the model of the detail view route. In ember code this looks like the following:
// app/routes/post.js
import Route from '#ember/routing/route';
export default Route.extend({
model({ post_id }) {
return this.get('store').findRecord('post', post_id);
}
});
// app/routes/post/edit.js
import Route from '#ember/routing/route';
export default Route.extend({
model() {
return this.modelFor('post');
}
});
Normally we would return a collection of posts from posts route model hook and not implementing the model hook of posts.new route (or returning a POJO / Changeset there depending on architecture but that's not the question here). Assuming we are not implementing the model hook of posts.new the routes would look like:
// app/routes/posts.js
import Route from '#ember/routing/route';
export default Route.extend({
model({ post_id }) {
return this.get('store').findAll('post');
}
});
// app/routes/posts/new.js
import Route from '#ember/routing/route';
export default Route.extend({
});
But now this approach is not working well anymore cause a transition to posts.new route is blocked until the collection of posts are loaded. Since we don't need this collection to create a list of posts (at least if we only show them in posts.index route and not on all subroutes) this doesn't feel right.
Side note for those ones not that familiar with ember: Nested routes model hooks are executed in order. So in our case first the model hook of application route, afterwards posts route and then posts.new route waiting for any promise executed by one of them.
So what should then be considered as best practice?
Should the fetching of posts live in posts.index route if we are not showing them on nested routes?
Shouldn't the create route be a nested under the list route? So should we have posts, post-new, post and post.edit routes? Feels confusing since the post related code is splited over three route trees. Also it would go against the goal of the improved file layout being developed currently since the code would be splitted over three directories.
Should we just take the tradeoff of unnecessarily fetching the collection of posts since mostly the user flow comes from this route before the creation route and therefore the model hook is in most cases already loaded anyway?
Would appreciate any thoughts on that one. Decided to not ask that question in the community slack to better document the answer.
The main point of having a nested route in ember is to nest the output of your child route within the parent route. While your current structure works, it doesn't really match up with how ember has structured route functionality.
You should use a singular nested route with an explicitly defined index route.
At every level of nesting (including the top level), Ember
automatically provides a route for the / path named index. To see when
a new level of nesting occurs, check the router, whenever you see a
function, that's a new level.
Router.map(function() {
this.route('posts', function() {
this.route('favorites');
});
});
is equivalent to
Router.map(function() {
this.route('index', { path: '/' });
this.route('posts', function() {
this.route('index', { path: '/' });
this.route('favorites');
});
});
If you create an explicit posts/index.js file, this can be used as your list route. Doing this will help your avoid the issue where all posts are fetched before transitioning into the create route.
While different from the structure you currently have, I'd suggest the following.
Router.map(function() {
this.route('posts', function() {
this.route('index'); // /posts - posts/index.js
this.route('new'); // /posts/new - posts/new.js
this.route('view', { path: '/:post_id' } // /posts/1234 - posts/view.js
this.route('edit', { path: '/:post_id/edit' } // /posts/1234/edit - posts/edit.js
});
});
Depending on the complexity of logic in the new and edit, you can consider combining the two routes into one, or simply transitioning the new to edit after generating the empty model.
The benefits of this include:
Simplicity
You don't have to re-define your paths for all of the routes. Everything falls under posts/ and the route specifies the next piece.
Consistency
the JSONapi schema uses plural routes for both fetching a collection as well as a singular object.
Logic wrapping
If, you use and explicit index.js file, you can use the old posts.js file as a common wrapper for all items within the post namespace. Posts.js will have an outlet that the index, new, edit, and view routes are placed into
If you need your view and edit route to share the same model generation, you can nest your view/edit into a common group so they share a parent model.
this.route('posts', function() {
this.route('post', { path: '/:post_id' }, function() {
this.route('view', { path: '/' }) ;
this.route('edit', { path: '/edit' });
})
})
To link to record you can use something like this in your route:
this.route('clients', function() {
this.route('all',function(){
this.route('view',{
path:'view/:client_id'
});
});
});
So if the user were to go to:
/clients/all/view/-KdFmDwwWAHDFjjaG6aA
They could view that client record.
Is it possible to link at a deeper level? For example:
/clients/all/view/-KdFmDwwWAHDFjjaG6aA/property/-KdFeTqqUIKLFqbaP9aB
?
That way you could be looking at a specific client record and then launch an overlay for example to show the specifics on a single property that client has for sale?
I'm not sure how to structure the router or the link-to to accomplish this?
I hope I understand your question correctly. Here is my answer,
Yes, it's possible to have a deeper level, I will change your route config a bit to :
this.route('clients', { path: '/clients' }, function(){
this.route('view', { path: '/:clients_id' }, function(){
this.route('property', { path: '/property/:property_id' });
});
});
So in this case, your link-to code in the HBS would be
{{#link-to 'clients.view.property' clientId}}
whatever
{{/link-to}}
and now file structure is :
clients/
|___index.hbs
|___view.hbs
|___view/
|___propery.hbs
Please remember that you need to also modify your route.js for each properly. I assumed you don't have any problems for that.
if you need more help please let me know.
Hi guys i have bunch of images that i want to sort by 'Recent' or 'Popular' or 'Hot'.
For now i have a route which is defined like this:
App.Router.map(function () {
this.route("browse");
});
I wanted to do something like browse/recent to show the images by recent and browse/popular for the popular but I cant nest routes.
Shall I change my code so instead of the browse route ill have images resource?
And nest into it my filters? so ill have something like images/recent images/popular...
It seems like too many routes, maybe ill have in the future 10 filters does it mean ill have to create 10 different routes & controllers? cant i just use 1 controller and set a logic to filter(with ember-data)?
You should probably use a noun (images) as a resource name. You can then create multiple routes, each applying different filter on your data (different model hook), but each using the same controller / template. A simplified example:
First, create an images resource, with individual routes for your filters:
App.Router.map(function() {
this.resource('images', function () {
this.route('hot');
this.route('new');
});
});
Then, create a shared route, which will use hardcoded template and controller. The part with setupController is needed because the default controller will be (probably auto-generated) controller for ImagesNew or ImagesHot. You must take the given model and use it to set up shared ImagesController.
App.ImagesRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('images', {
controller: 'images'
});
},
setupController: function (_, model) {
this.controllerFor('images').set('content', model);
}
});
App.ImagesController = Ember.Controller.extend({
// your shared logic here
});
Finally, you can create filtering routes. Each should inherit the base ImagesRoute and provide its own filtered data in the model hook.
App.ImagesHotRoute = App.ImagesRoute.extend({
model: function () {
return this.store.getHotImages();
}
});
App.ImagesNewRoute = App.ImagesRoute.extend({
model: function () {
return this.store.getNewImages();
}
});
Working jsbin example here.
It's a best practice to start with a resource and then nest routes within it.
App.Router.map(function() {
this.resource('images', { path: '/' }, function() {
this.route('browse');
this.route('hottest');
this.route('popular');
});
});
As far as creating ten different controllers, that is not necessary. I'd imagine that the route logic will be different (HottestRoute will load the hottest photos, PopularRoute will load the most popular), but the controller logic should be the same. It is probably best to have named controllers, but they can just extend an already defined controlled.
App.ImagesPopularController = ImagesController.extend();
I have a need for deep nesting some routes in ember, I have something like this.
this.resource('wizards', {
path: '/wizards'
}, function() {
this.resource('wizards.google', {
path: '/google'
}, function() {
this.resource('wizards.google.register', {
path: '/register'
}, function() {
this.route('step1');
this.route('step2');
this.route('step3');
this.route('summary');
});
});
});
What I was expecting was as structure like this:
url /wizards/google/register/step1
route name wizards.google.register.step1
route Wizards.Google.Register.Step1Route
Controller Wizards.Google.Register.Step1Controller
template wizards/google/register/step1
but I got this:
url /wizards/google/register/step1 //as expected
route name wizards.google.register.step1 //as expected
route WizardsGoogle.Register.Step1Route
Controller WizardsGoogle.Register.Step1Controller
template wizards/google.register.step1
What I don't get is when does ember stop using capitalization (WizardsGoogle) and start using namespaces (WizardsGoogle.Register). The seemingly inconsistency confuses me. I would have expected either of them.
I met the same things with deep nested resources. Although I didn't know how this happens, what I can tell is that you can always use CapitalizedNestedRoute without namespace, and Ember can recognize it. Although in Ember Inspector it displays "WizardsGoogle.Register.Step1Route".
In your example I defined such route:
App = Em.Application.create();
App.Router.map(function() {
this.resource('wizards', function() {
this.resource('wizards.google', function() {
this.resource('wizards.google.register', function() {
this.route('step1');
this.route('step2');
this.route('step3');
});
});
});
});
App.IndexRoute = Em.Route.extend({
beforeModel: function() {
// Transition to step1 route
this.transitionTo('wizards.google.register.step1');
}
});
App.WizardsGoogleRegisterStep1Route = Em.Route.extend({
model: function() {
// You can see this alert when you enter index page.
alert('a');
}
});
In this example the app will transition to WizardsGoogleRegisterStep1Route with no problem. And if you use container to find route like this:
App.__container__.lookup('route:wizards.google.register.step1').constructor
It will also display App.WizardsGoogleRegisterStep1Route. It's the same as Ember Guide describes. http://emberjs.com/guides/routing/defining-your-routes/#toc_nested-resources And Ember Guide doesn't introduce namespace route.
So I think it's better to according to what Ember Guide suggests (always use CapitalizedNestedRoute). And in my opinion it's easier to define CapitalizedNestedRoute than nested.namespace.route.
Finally, if you really want to use namespace route/controller/template, you can have a look at Ember.DefaultResolver. Check the API to learn how to extend it so container can lookup modules by your own rules.
Routes are "namespaced" inside resources. And resources uses what you call capitalization, where they sort of define a namespace (for routes to use).
So this set of routes:
App.Router.map(function() {
this.resource('posts', function() {
this.route('new');
this.route('old');
this.route('edit');
this.route('whatever');
});
});
Would result in routes with the following name:
PostsRoute
PostsNewRoute
PostsOldRoute
PostsEditRoute
PostsWhateverRoute
Whereas, the following set of routes:
App.Router.map(function() {
this.resource('posts', function() {
this.resource('photos');
this.resource('comments');
this.resource('likes');
this.resource('teets');
});
});
Would result in route with the following names:
PostsRoute
PhotosRoute
CommentsRoute
LikesRoute
TeetsRoute
Also note, that resources within resources don't get "namespaced" to the "parent" resource, so you'll always ever have the form:
{CapitalizedResourceName}Route // for resources
{CapitalizedParentResourceName}{RouteName}Route // for routes
I hope this helps you!
Can some one explain why nested resources require to list the path hierarchy in the route name instead of just the route?
Eg. resource1 > resource1.resource2
Emberjs seems to be all about reducing the amount of code. Is there some usecase for resources I don't see which explains why resources should be defined this way.
I couldn't get my example to work in jsfiddle or jsbin so I hosted it here: http://emberjs.mattmazzola.net/
I was basing my solution from the technique described in this similar StackOverflow question is here: Ember.js pre4 multiple nested routing
Basically, you notice I have a resource 'animals' with sub resources 'cats' and 'dogs'. However, if I just name them 'cats' and 'dogs' respectively the router says "route animals.cats' is not found. Then if I add the 'animals.' prefix to make the nested route 'animals.cats' the url becomes index#/animals/animals.cats which doesn't make sense. Of course we fix this by overriding the path attribute, but I don't understand why Emberjs doesn't do this by default. Am I defining my resources/routes incorrectly and this is a side affect?
In other words, I'm currently doing this:
App.Router.map(function() {
this.resource('products', function() {
this.route('desktops');
this.route('laptops');
});
this.resource('animals', function() {
// the url for this route is bad, but default behavior?
this.resource('animals.cats', function() {
this.route('cat', {path: ':cat_id'});
});
// Why does this require stating the parent route 'animals' again?
this.resource('animals.dogs', {path: 'dogs/'}, function() {
this.route('dog', {path: ':dog_id'});
});
});
});
How can I write routes like this:
App.Router.map(function() {
this.resource('products', function() {
this.route('desktops');
this.route('laptops');
});
this.resource('animals', function() {
this.resource('cats', function() {
this.route('cat', {path: ':cat_id'});
});
this.resource('dogs', function() {
this.route('dog', {path: ':dog_id'});
});
});
});
hmm, i think the second version should work if you have App.AnimalsIndexRoute, App.CatsIndexRoute and App.DogsIndexRoute (and possibly a few other Ember.Routes) defined correctly. could you maybe post the rest of your code here or in a jsfiddle if you still have that problem?