EmberJS redirect when no subroute specified - ember.js

I have a set of nested routes and templates that I'd like to auto-select the first model if no sub-routes are specified. The route structure is:
App.Router.map(function() {
this.route('sales', function () {
this.route('orders', function () {
this.route('order', { path: ':order_id' });
});
});
});
If the user hits sales.orders then they should be redirected to the first order on the sales.orders model. Making this work is no problem. The issue comes when the user hits sales/orders/:order_id No matter what :order_id is the user is always redirected to the first order in the orders array.
I'm currently performing the redirect in the setupControllerhook of the SalesOrders route as I have some sorting on the controller that needs to be in place prior to redirecting.
App.SalesOrdersRoute = Ember.Route.extend({
model: function () {
return this.store.find('order');
},
setupController: function (controller, model) {
controller.set('model', model);
var firstObject = controller.get('sortedContent').get('firstObject');
this.transitionTo('sales.orders.order', firstObject);
}
});
App.SalesOrdersController = Ember.ArrayController.extend({
sortProperties: ['orderNumber:desc'],
sortedContent: Ember.computed.sort('model', 'sortProperties')
});
I have a jsbin that shows my issue.
Hitting any specific order will always redirect to the first order in the array (4 in this case).
I need it to keep the deep linked url and only redirect when no order is specified.
I feel like this question and this question are both similar to what I'm trying to do except neither addresses auto-selecting the first item if no sub-routes are specified.

You should do the redirect in your SalesOrdersIndex route. The additional index route of each route will only be created when it matches the complete URL mapping. So for any url that isn't exactly "sales/orders" it will not be created. Just what you want.
App.SalesOrdersIndexRoute = Ember.Route.extend({
setupController: function (controller, model) {
controller.set('model', model);
this.controllerFor('salesOrders').set('model',model);
var firstObject = this.controllerFor('salesOrders').get('sortedContent').get('firstObject');
this.transitionTo('sales.orders.order', firstObject);
}
});
jsbin: 4 3 2 1 redirect to 4

One option could be to check the transition on the afterModel hook and redirect if the user is trying to access the sales.orders.index route.
Something like this:
afterModel: function(model, transition){
if (transition.targetName === "sales.orders.index"){
var first = model.objectAt(0);
this.transitionTo('sales.orders.order', first);
}
}
Here's an example.
That won't work with setupController as the setupController hook does not have access to the transition. In your case, since you just want to sort, you could do something like:
var first = model.sortBy('orderNumber').reverse().objectAt(0);
As far as I know, setupController is called after both redirect and afterModel so I'm not sure it's possible to get the sorted content from the controller through the afterModel and redirect hooks.

Related

Emberjs - need to refresh model on transtionTo

I have an Emberjs app that has a search action that needs to be available from all routes. To accomplish this, I've defined the 'search' action in the application route like this:
App.ApplicationRoute = Ember.Route.extend({
actions: {
search: function (query) {
this.transitionTo('search', { queryParams: { q: query } });
}
}
});
The 'q' querystring parameter is defined in the SearchController:
App.SearchController = Ember.ArrayController.extend({
queryParams: ['q'],
q: ''
});
The search route calls a service that queries my database with the query parameter like this:
App.SearchRoute = Ember.Route.extend({
model: function (params) {
return this.store.find('activity', { query: params.q }),
}
});
I know that the model hook is not called on transtionTo, but when the user is already on the search page and wants to search again with a different query, I need to reload the search route with a new model.
Is using transitionTo in the application route the wrong approach in this case?
Any ideas are greatly appreciated!
I would add a named {{outlet}} in your Application template, wherever you want the search results to appear. Then, in your Application route, inside the renderTemplate hook, I would render the search results template into the new outlet, also specifying what controller it should use.
On the controller, you can create a computed property, which would detect changes in the query string (or however you want to supply the search results). This property (or properties) would then feed the data in your search results template.
More on rendering a template inside a route:
http://emberjs.com/guides/routing/rendering-a-template/
If you decide to go with putting the renderTemplate hook in Application route, you can set the Search controller's model (or whatever you want to call it) property from any route which needs to update the model on the search controller for it to display proper results:
this.controllerFor('search').set('model', model);
You can also create a Mixin, which would contain the renderTemplate hook, which you can include in any route you want to do your searches from. In the hook, you could send your route's model into the controller:
renderTemplate: function(controller, model) {
this.render('search', {
into: 'search',
outlet: 'application',
controller: 'search',
model: model
});
}
Play around with some of these techniques. I'm sure I'm missing some details, but I think you can get them to work.

Emberjs: Resetting controller based on dynamic segment

I have this route structure in my app:
Profile.Router.map(function() {
this.resource('profile', { path: '/:user' }, function(){
this.route('followers');
});
});
My /:user/followers page is supposed to show the list of followers of :user. The controller for profile.followers is setup for infinite scroll - so it has properties like curPage, resultsPerPage.
Profile.ProfileFollowersController = Ember.ArrayController.extend(InfiniteScroll.ControllerMixin,{
curPage: 1,
resultsPerPage: 20,
//other code for infinite scroll which increments curPage as user scrolls down
})
Now, since controllers are singleton in emberjs, when I move from /tom/followers to /jin/followers, the properties for infinite scroll that were set for /tom/followers get preserved and applied for /jin/followers as well.
For eg. say I am on /tom/followers and scroll down 4 pages, curPage property of 'profile.followers' controller gets set as 4. Now when I move to /jin/followers, though the model hook of the route would return list of followers for jin, but would pick curPage as 4 since ember's controllers are singleton and I had scrolled down to 4th page on /tom/followers.
How is this supposed to be handled?
Here is my profile.followers route as well:
Profile.ProfileFollowersRoute = Ember.Route.extend({
model: function(params) {
console.log("fetching followers model");
return Ember.$.getJSON('/'+this.modelFor('profile').user+'/followers?new=1');
},
});
You can use the route's setupController hook for this. This hook is fired every time the route is entered.
App.ProfileFollowersRoute = Ember.Route.extend({
setupController: function(controller, model) {
// Call _super for default behavior
this._super(controller, model);
// Reset the desired controller properties
controller.setProperties({
curPage: null,
someOtherProp: null
});
}
});
I've made a jsbin demonstrating it:
http://emberjs.jsbin.com/fiyetefeno/6/
You can read the API documentation here:
http://emberjs.com/api/classes/Ember.Route.html#method_setupController

How do you configure a root route in Ember.js

I would like to create a route for / that loads another route, say 'posts'. It seems that the only two solutions are to configure Ember's IndexRoute:
App.IndexRoute = Ember.Route.extend({
redirect: function() {
return this.transitionTo('posts');
}
});
OR
Map our 'posts' resource to the / path:
App.Router.map(function() {
return this.resource('posts', { path: '/' });
});
The first solution does not seem reasonable because it always sends visitors to /posts instead of having an actual base path of /. The second solution does not seem reasonable because it only allows posts to be viewed from / and not /posts. The second solution inherently creates strange nested URLs like /new for a new post instead of /posts/new.
What is the most idiomatic way to configure / to load another route instead of redirecting, while still making the target resource available from its normal URL? In other words, I would like the / path to access posts, and still have posts available via /posts.
Another way to go is to have your IndexController needs the PostsController, and then you can use render in your index template to render the posts.
App.IndexController = Ember.Controller.extend({
needs : ["posts"]
});
And then your index template might just be
{{render 'posts'}}
I think what you want to do is the following:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.get('store').findAll('post');
},
setupController: function(controller, model) {
this.controllerFor('posts').set('content', model);
}
});
That way the controller for this route will be an ArrayController filled with all your posts. And you can still use your /posts route whichever way you like. By default this would be App.IndexController (which you can override to implement custom functionality).
Alternatively, if you wanted to use a different controller (say App.PostsController), you could specify that in the routes renderTemplate hook. So if you wanted to use your posts template and your App.PostsController used in your App.IndexRoute, you would include:
renderTemplate: function() {
this.render('posts', { controller: 'posts' });
}
For more details have a look at the routing section of the Ember.js guides.

Find query concept for route and controller

my question is a little bit general. What is the best concept for route and controller with findQuery in ember.
I have api with data filtering. Data request is executed by
this.store.findQuery('dataModel', {"q": JSON.stringify({"filters": filters})});
after that I show them in table view. The filter is updated by form views in a template.
My current solution:
Form views set controller parameters and a button call action from controller. Controller action loads parameter, executes findQuery and set('content',data).
In most cases I saw concept with a defining model: function() .. in the Route and setupController: function(controller, model) with controller.set('content',model). I like this "set" because 'content' is RecordArray (not PromiseArray) and I can easily use that for datatables and another JavaScript plugins. I think my solution isn't good.
I think your concept is correct, I have been using the following flow:
In your router:
App.Router.map(function() {
this.resource('search', { path: '/query/:filters' });
});
App.SearchRoute = Ember.Route.extend({
model: function(params) {
return this.store.findQuery('dataModel', {"q": JSON.stringify({"filters": params.filters})});
});
In your html, just bind the action which will lead to the new Search Route,
something like below :
<button {{action "doSearch"}}>Search</button>
In your controller:
App.SearchController = Ember.ArrayController.extend({
...
actions: {
doSearch: function() {
var query = buildYourQueryObject();
this.transitionToRoute("search", query);
}
}
Upon clicking on the button, the app will transition into your search route, and "query" will be serialized and sent into the Route, and the Route.model() will attempt to be populated based on the serialized parameters provided.
Note: The code has been simplified, you might need to add more stuff in order to make it work

How to save path and return to it with Ember's V2 Router

So, I'm having some issues with Ember's new router. I'm trying to save and later return to the current path for a given dynamic segment, so my urls might look like
#/inventory/vehicle/1001
Which can then branch off into
#/inventory/vehicle/1001/details
#/inventory/vehicle/1001/photos
#/inventory/vehicle/1001/description
etc. I need a way to return to the most recent route. The Ember guides have a method for this here:
http://emberjs.com/guides/routing/redirection/
The problem with this method is that by creating the "/choose" route and assigning it to "/", this overwrites the standard "/inventory/vehicle/1001" route. For instance, if I were to try to create a linkTo a vehicle like so:
{{#linkTo "vehicle" vehicle}}
Then Ember will throw an error because the "vehicle" route no longer exists. Instead, it must be set to:
{{#linkTo "vehicle.choose" vehicle}}
Which works, activates the VehicleChooseRoute and everything. Except, since "vehicle.choose" is technically a child of "vehicle", the #linkTo ONLY has an active class applied when the current route is
#/inventory/vehicle/1001
Which instantaneously redirects to the latest filter, and so it's basically never on. So basically I'm trying to figure out a way around this. I tried changing the path of "vehicle.choose" to be the standard path (#/inventory/vehicle/1001/choose) so it doesn't overwrite the "vehicle" route, and then setting up VehicleRoute like so:
Case.Router.map(function() {
this.resource('inventory', function(){
this.route('review');
this.route('sheets');
this.resource('vehicle', { path: '/vehicle/:vehicle_id' }, function(){
this.route('choose');
this.route('details');
this.route('consignor');
this.route('additional');
this.route('price');
this.route('dmv');
this.route('expenses');
this.route('description');
this.route('tasks');
});
});
});
App.VehicleRoute = Ember.Route.extend({
model: function(params) {
return Case.Vehicle.find(params.vehicle_id);
},
setupController: function(controller, model) {
model.set('active', true);
},
redirect: function() {
this.transitionTo('vehicle.choose');
}
})
Case.VehicleChooseRoute = Ember.Route.extend({
redirect: function() {
var lastFilter = this.controllerFor('vehicle').get('lastFilter');
this.transitionTo('vehicle.' + (lastFilter || 'details'));
}
});
But the problem that arises from this (aside from feeling rather hacked together) is that redirect replaces the entire template that would normally be rendered by "vehicle" so I only get the subview. So that's not an option.