I currently have my routes defined like this:
App.Router.map(function() {
this.resource('players', { path: ':page_id' }, function() {
this.resource('player', { path: ':player_id' });
});
});
The idea is that I have a list of player names on the left. The player names displayed depend on the page_id. On the right, I display a single player and all its information based on the player_id. The thing is, both are independent, meaning that I could be on the third player page, while displaying the first player in the list, or no player at all.
What I keep trying to do is something like this, which is a method in the PlayersController that gets called when I click to go to the next player page:
doTransition: function() {
var players = App.Player.findAllForPage(this.playersPerPage, this.currentOffset);
players.reopen({
id: this.currentOffset
});
var playerController = this.get('controllers.player');
var currentPlayer = playerController.getWithDefault('content');
if (currentPlayer) {
this.transitionToRoute('player', players, currentPlayer);
} else {
this.transitionToRoute('players', players);
}
}
What I'm trying to do: When I click to go to the next player page, transition to the PlayersRoute if there is no player currently being displayed, otherwise transition to the PlayerRoute so that the player is still displayed when the transitioning is done.
The problem: sometimes the currentPlayer variable is not always null, even if no player is currently being displayed. Is there a way to get around this, perhaps by getting the current route from somewhere?
Given that you say the two sections (list of players based on page_id, and player information based on player_id) are completely independent, it seems to me like you wouldn't nest the routes, and instead, have two named outlets (call them left and right, or page and player, etc) that you selectively render into.
Router:
App.Router.map(function() {
this.resource('page', { path: ':page_id' });
this.resource('player', { path: ':player_id' });
});
application.hbs:
{{outlet page}}
{{outlet player}}
And then you can override your renderTemplate for your page and player routes to render into the appropriate template. To clarify, page route would display what you currently have as the players route (it's dependant on the page_id, but the page has many players, so the route displays the players based on the page), and player route would display the player information based on the player_id.
(As a side note, I don't think you can nest resources the way you do right now with putting resource player under resource players -- I think only routes can be nested.)
EDIT: Using single route with multiple dynamic segments
I think your suggestion could work. From the linked example it seems like you need to create the "parent" resources (not nested routes, but having more general paths, like /page/ and /page/:page_id/player/:player_id) anyway. You can then set up your models individually via the model in the appropriate route, and just provide a serialize hook for the double dynamic segment route:
serialize: function(model) {
return {
page_id : this.modelFor('page').get('id')
player_id : this.modelFor('player').get('id')
};
}
Note we're not relying on the model object passed in because you've said that the page and player panels can be completely independent, so we use modelFor instead.
I think you can also handle your logic about default page to render / default player to render if none are suggested here via the redirect hook.
Finally, you would override renderTemplate in your PagePlayer route to actually do the rendering:
renderTemplate: function(model, controller) {
this.render("page", { into: "page" });
this.render("player", { into: "player"});
}
I think you have to be careful to NOT render the templates in the more general routes because if you if you move from /page/1/player/2 to /page/1/player/3, the page route is NOT re-entered.
While Sherwin's answer gave me a good idea of where I was going, I just wanted to put a complete example and give a general idea of what I ended up implementing. This could be of help for future reference.
I'm going to make it simple by having the models be a simple int, that way we have a direct translation from url to model and vice versa.
Templates:
<script type="text/x-handlebars">
{{outlet a}}
{{outlet b}}
</script>
<script type="text/x-handlebars" id="a">
{{model}}
</script>
<script type="text/x-handlebars" id="b">
{{model}}
</script>
Application:
App = Ember.Application.create();
App.Router.map(function() {
// This route has 2 dynamic segments
this.resource("ab", { path: "/:a/:b" });
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
// At the entry point, encapsulate the 2 models in the context object,
// and transition to the route with dual dynamic segments
this.transitionTo('ab', {a: 3, b:4});
}
});
App.AbRoute = Ember.Route.extend({
model: function(params) {
// The model is {a, b} directly
return params;
},
renderTemplate: function(){
// Render in the named outlet using the right controller
this.render('a', {into: 'application', outlet: 'a', controller: 'a'});
this.render('b', {into: 'application', outlet: 'b', controller: 'b'});
},
serialize: function(model) {
return {
a: model.a,
b: model.b
};
},
setupController: function(controller, model) {
// Setup each controller with its own model
this.controllerFor('a').set('model', model.a);
this.controllerFor('b').set('model', model.b);
}
});
Additional note:
It would've been possible to have a single 'ab' template rendering {{model.a}} and {{model.b}} from the AbController, but I thought having separate controllers and templates was cleaner, and that it enabled reusability. Additionally, one of the controllers could've been an ArrayController and it would've work perfectly fine.
JS Bin of the example
Related
Here's an image that illustrates the design I was given and need to develop in ember. I'm a bit at a loss on how to handle the routing and implementation in ember.
So to explain this simplified example, say I have a search results page (results are being returned from the back end), where as you would expect the search results aren't always going to be the same. However, when I click on one of the search results, I need to be able to open the product's page as a nested route.
This raises a few problems given that someone returning at a later time will likely not get the same list of products. How would I handle the routing, something like this?
Router.map(function() {
this.resource('search', function(){
this.route('product', {route: ':productID'}, function())
});
});
I'm also not sure how to setup it up the heirarchy in terms of containerview, views, components, etc.
Help?
I think you need to create routes like that
this.resource('products', function() {
this.resource('product',{path: '/:id'});
});
After that you need to create 2 routes ProductsRoute and ProductRoute.
App.ProductsRoute = Ember.Route.extend({
model: function() {
return this.get('store').find('product');
}
});
App.ProductRoute = Ember.Route.extend({
model: function(product) {
return this.get('store').find('product', product.id);
}
});
When you will try to open page /products you will get all products. If you will use RestAdapter it sends request to your REST API /products
I think you need to create action in ProductsController to search products:
App.ProductsController = Ember.ArrayController.extend({
actions: {
searchProducts: function() {
var queryText = this.get('queryText');
if (queryText) {
return this.get('store').filter('product', {query: queryText}, function(item) {
return true;
});
}
}
}
});
This action sends request to your api with /products?query=queryText
In your products template create search form and use something like that {{action 'searchProduct'}} for Search button. Also in your template you need to show all products for that use {{#each item in model}} and to create link to each product use {{#link-to 'product' item}}. I think it will be best practice to create component for your search box http://guides.emberjs.com/v1.10.0/components/.
I have been looking to a solution to this for about a week now with no luck. We have an ember application which has a sidebar that is present on all routes which displays a list of user posts. It is important that the posts update in real-time as they are submitted as well as sort with the newest post at the top of the list, which from what I've read will require an array controller. The problem is, I cant find any way (or rather dont understand) to use an array controller and specific model that is not directly referenced to the current route. I have tried rendering the sidebar with the following code in the application route:
Destination.ApplicationRoute = Ember.Route.extend({
model: function(model) {
var self = this;
return new Em.RSVP.Promise(function (resolve, reject) {
new Em.RSVP.hash({
post : self.store.find('post')
}).then(function (results) {
resolve({
post: results.post
});
});
});
},
renderTemplate: function(controller, model) {
this.render();
this.render('sidebars/postBar', {
outlet: 'postbar',
into: 'application',
controller: 'posts',
model: 'post'
});
}
Then I have the following code for my array controller
Destination.PostsController = Ember.ArrayController.extend({
itemController: 'post',
sortProperties: ['id'],
sortAscending: false
});
However this doesnt work at all and I'm having trouble finding any examples of how to accomplish this.
The approach you can use is to load whatever models you need for the entire application in the ApplicationRoute. You don't have to create the RSVP.Promise as you have done, simply return an RSVP.all or RSVP.hash as follows:
Destination.ApplicationRoute = Ember.Route.extend({
model: function(model) {
return Em.RSVP.Hash({
post : self.store.find('post')
// fetch other models as required
});
}
});
Now there are two options for the controller setup and rendering.
Option 1: Outlets and route based controller setup.
The next thing is to setup the appropriate controller and render the view. Assuming you have defined an {{outlet 'sidebar'}} in your application template, the ApplicationRoute can render the sidebar as follows:
Destination.ApplicationRoute = Ember.Route.extend({
setupController: function(controller, model, transition) {
// perform default application controller setup
this._super(controller, model, transition);
// setup sidebar controller model
this.controllerFor('side-bar').set('model', model.posts);
// setup other controllers as required...
},
renderTemplate: function(controller, model) {
// render `posts` template into `side-bar` outlet with `side-bar` controller.
var c = this.controllerFor('side-bar');
this.render('side-bar', { outlet: 'side-bar, controller: c });
// other top level outlet rendering as required...
}
});
Option 2: View helper based controller setup and rendering.
Instead of using additional outlets, we can avoid the need to override setupController or renderTemplate in the route entirely. We can use the handlebars render helper to specify both the model and controller to use directly from our template.
So given your application controller will be setup with the result of the RSVP hash by default, it will contain a 'posts' property on its model/content. Just add the following to your application template:
{{render 'side-bar', posts}}
The above will render the sidebar template and setup the singleton SideBar controller using the posts model for you. I think this is cleaner than messing about with outlets given it doesn't sound like you going to be rendering different views into the sidebar based on your question.
API documentation on the render helper is here, with an overview of the rendering helpers here.
Note I have used Ember-cli resolver naming conventions which use a dasherized naming convention. If you're not using Ember CLI (which I highly recommend) then you may have to use the PascalCased string names ie 'SideBar' instead of 'side-bar'.
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'm trying to write an e-commerce style Ember App. I'm modelling it (this is an assignment and I figured it would be a great excuse to learn Ember.) https://www.humblebundle.com/store is sort of the model I'm going after.
I'd like to display two types of content on the index route - Promos, and Games, since they display differently.
Store.Router.map(function() {
this.resource('promos', function(){
});
this.resource('games', function(){
});
});
I presently redirect index to promos - But in reality, I'd like to have both promos and game render into their own named outlets. Are there any guides on doing this? So far, it seems like everything deals with making apps that have one concern at a time.
You should use renderTemplate() on your route like this:
Store.IndexRoute = Em.Route.extend({
model: function(){
return {
games: ['checkmates', 'magic the gathering', 'flappy bird'],
promos: ['1','42']
};
},
renderTemplate:function(){
this.render('games', { outlet: 'games' });
this.render('promos', { outlet: 'promos' });
}
});
JSBin here -- http://jsbin.com/jupet/2/edit
This is how I'm currently doing things. The code works, though I'm not sure whether or not it's the 'right way'.
Store.IndexRoute = Em.Route.extend({
// Hook that runs when the controllers are being set up.
setupController: function(controller, model) {
this.controllerFor('promos').set('model', this.store.find('promo'));
this.controllerFor('games').set('model', this.store.find('game'));
controller.set('content', model);
}
Then, in the index template:
{{render "promos"}}
{{render "games"}}
Things are working, and the GamesController and PromosController are hooking up to the right models.
Working hard on my Ember app here, and it's going along fine. However, I've run into an issue of unexpected behaviour and I'm not sure regarding the best approach to this problem.
The problem is that in a specific route, I want to render another route into another outlet. However, the other route that I render into the other outlet doesn't retain it's own model.
If I do this:
App.TestRoute = Ember.Route.extend({
model: function() {
return {
heading: "Test",
testContent: "This is test."
}
}
});
App.IndexRoute = Ember.Route.extend({
renderTemplate: function() {
this.render("test", {
outlet: "left"
});
this.render({
outlet: "right"
});
},
model: function() {
return {
heading: "Index",
indexContent: "This is index."
}
}
});
... and access the IndexRoute, I would expect the TestRoute's model to be rendered into the TestRoute's template, but only the IndexRoute's model is relayed to both templates.
Fiddle here:
http://jsfiddle.net/3TtGD/1/
How do I allow Ember to use the default model for a route without having to expressively merge them? It seems tedious.
Also, having the same name of some model properties, like {{heading}} is desirable, but not necessary.
What's the best approach for solving this issue?
Thank you for your time.
Best regards,
dimhoLt
In the renderTemplate method you're telling Ember to render a template inside an outlet but it will just default the controller to the one managing the route. Given it's the controller handling the route it makes sense that it manages all the templates within that route.
Of course you can specify a different controller using:
this.render("test", {
outlet: "left",
controller: 'test'
});
it can in turn be a controller you already instantiated (and maybe set its content):
var testController = this.controllerFor('test');
testController.set(....)
this.render("test", {
outlet: "left",
controller: testController
});
About using the model: You can call this.modelFor('test') inside the route and it will return the model of the test route (it even knows if it has already been resolved). I usually do this when I need to access the model of one of the parent routes.
I believe it makes sense to access the model of a parent route, but not so much if you're accessing the model of an unrelated route. Why don't you want to merge both models?