Transition to previous route on error action ember.js - ember.js

I have the following situation to handle in ember :-
I have to transition to a route which has a model hook. The model hook returns a promise. (Say route A -> route B, Route B has a model hook returning a promise)
While the model hook in B is running, the loading route is entered(for showing "please wait" type msg to user).
In case the model hook in B fails, I need to transition back to route A. I handle the error action in route B.
The problem is, while handling error action in route B, the previous route is not always route A. It can be from other another route too.
I tried the possible workarounds, but they didn't work out for me:-
1) Using window.history.back() - This fails because Route B isn't entered yet, because the model hook promise failed. So I get the previous route of Route A.
2) Using this.controllerFor('application').get('currentRouteName') - This gives the loading route (While transitioning from Route A to Route B, intermediate route 'loading' is rendered).
I can use a conditional check with query Params, but I feel this is not efficient as I have to check for many conditions.
I simply wish to return to the route that invoked Route B.
Pardon me if I missed out on anything.

Sample project : Twiddle
I reopened Ember.Route to catch all transitions and save the current route name in a application controller property. When error action is called, i transition to the route saved in that property. Routes with dynamic segments will need extra handling.
Another Method :
In your router.js,
var Router = Ember.Router.extend({
location: config.locationType,
didTransition : function(infos){
this._super(infos);
var appController = this.container.lookup('controller:application');
var crn = infos[infos.length - 1].name;
if(appController && crn!=='loading' && crn!=='error'){
Ember.set(appController,'myRouteName',crn);
}
}
});
I am not sure if there is any in built solution.

Related

ember-cli-mirage in legacy app

We have application that used Pretender to provide fixtures for tests. Now we're trying to migrate to ember-cli-mirage. We cannot migrate all the fixtures at once. So what is basically happening there is that we are starting our own Pretender server and ember-cli-mirage is starting it's own. Whic renders following warning:
You created a second Pretender instance while there was already one running. Running two Pretender servers at once will lead to unexpected results and will be removed entirely in a future major version.Please call .shutdown() on your instances when you no longer need them to respond.
Since it is just a warning, it should not be an issue for the transient period. Problem is that once Mirage is loaded into our application, old Pretender routes stops responding. I guess that's what "... will lead to unexpected results" is referring to.
Any chance to run ember-cli-mirage alongside manually created Pretender routes? Or just to use Mirage server and inject those routes there?
I would use Mirage's server and then load up your Pretender routes inside of it. (Mirage's server is really just an object that news up a Pretender instance). If folks see mirage folder they'd probably expect the routes to be defined there. Also, Mirage cleans up its Pretender instance during testing.
In mirage/config.js you could import your existing Pretender routes and call them there. Mirage has sugar on top of Pretender but you can always access the underlying pretender instance via this.pretender within the config function:
// mirage/config.js
import setupYourOldRoutes from 'somewhere';
export default function() {
this.get('users'); // new Mirage shorthand
setupYourOldRoutes(this.pretender);
}
So setupYourOldRoutes could be a function that takes a pretender instance and then defines all your existing route handlers using it.
Based on #samselikoff answer I found a solution for my case. We already have one central point, that is handling creation of pretender instance. So the fix was just to pass Mirage's Pretender instead of creating new one:
// somewhere.js
export default function () {
// initPretender: function () {
// this.pretender = new Pretender();
// }
initPretender: function (pretender) {
this.pretender = pretender;
},
getPretender: function () {
return this.pretender;
}
}
// mirage/config.js
import pretenderWrapper from 'somewhere';
export default function() {
this.get('users'); // new Mirage shorthand
pretenderWrapper.initPretender(this.pretender);
}
Tricky part was to make sure that initPretender() is called before any of our legacy code tries to call getPretender(). I think usually that is not a trouble. In our case we patched tests/helpers/start-app.js so that some fixtures were injected in every test. And that caused calling getPretender() too early.

Undestanding Ember Route hooks to redirect

I have this router setup
this.route('dashboard', function () {
this.route('numbers');
this.route('workspaces', function () {
this.route('workspace', {path: '/:wid'});
});
dashboard.workspaces is a link in navigation bar. When you click it loads dashboard.workspaces.workspace and immediately redirects to first subnav. I do it like this:
export default Ember.Route.extend({
model: function() {
return this.modelFor('dashboard');
},
afterModel: function(model) {
this.transitionTo('dashboard.workspaces.workspace', model.get('workspaces.firstObject.id'));
}
}
It's all good except when I'm already on dashboard.workspaces.workspace route and click dashboard.workspaces then "afterModel" hook is not called and I end up on dashboard.workspaces, which is unwanted empty page. Are there other hook that would be always called? I have already tried "activate" and few others with no luck.
Would be also nice to be able to save last subnav transition and redirect to in when click on main nav.
So it's important to think of the router as a stack. As you visit routes you push a route on/off the stack. An item that is being pushed on or off the stack will have events fired correlating to the transition at hand.
In your particular case, the current implementation has two issues. The one issue you've already mentioned, when you pop off children routes the parent route doesn't fire any events. The second issue would occur under these circumstances.
Navigate to workspaces /workspaces (it redirects to the first workspace /workspaces/1)
Navigate to the second workspace /workspaces/2
Refresh the page while on /workspaces/2 (while it's in the afterModel hook of workspaces it redirects to the first workspace /workspaces/1)
So the easiest way to handle this is to create a child route under the workspaces route whose sole purpose is to redirect to the first workspace.
this.route('dashboard', function () {
this.route('numbers');
this.route('workspaces', function () {
this.route('first');
this.route('workspace', {path: '/:wid'});
});
Remove the afterModel logic from the workspaces route and put it in the workspaces.first route.
export default Ember.Route.extend({
redirect: function() {
var first = this.modelFor('workspaces').get('firstObject');
this.transitionTo('dashboard.workspaces.workspace', first);
}
}
Now in your template, instead of ever pointing to workspaces you would point to workspaces.first.
{{link-to 'Workspaces' 'dashboard.workspaces.first'}}
Now when you are in the dashboard and click it, it will push on the workspaces route into the stack execute its hooks. It will then push the first route onto the stack and execute its hooks. The hooks of the first route will cause it to pop the first route off the stack and push the workspace route onto the stack. When you try to click the workspaces route again it will pop workspace off the stack and then push first back onto the stack which will then again execute its hooks.
Example: http://emberjs.jsbin.com/cipawapali/edit?html,js,output
May be a bit late, but for anyone looking for a solution to this empty white page as I was.
Ember provides an implicit/hidden route, that can be used instead of creating a new one, which is pretty good for this.
In this question's case it would be DashboardWorkspacesIndexRoute, no need to declare it in the router, just add the route file with the redirection you want
export default Ember.Route.extend({
redirect() {
let first = this.modelFor('workspaces').get('firstObject');
this.replaceWith('dashboard.workspaces.workspace', first);
}
}
You can then use {{link-to 'Workspaces' 'dashboard.workspaces'}} and it will retain the active class while still redirecting every time.
For more information, here's the source.

From child to parent route, Ember transitions to parent.index subroute

I'm linking from a child route to a parent route.
In this example, I'm linking from a 'category' child page to the parent 'availability' page.
I understand that the Ember Router skips the model hook on links/transitions to a parent route.
I'm just looking for the beforeModel hook to be called on the parent route.
It does this all well and good, but it goes to the availability.index subroute, which is not where my beforeModel hook is written (see console output). My beforeModel hook is written in the availability route.
I could duplicate or move code to the index subroute, but I really don't understand why Ember is doing this. Putting my hooks in the availability route works all the time except for this case.
Here's my router pattern:
this.resource('availability', function() {
this.resource('category', {path: '/category/:category'});
})
Category Template
{{#link-to 'availability'}}
<span>Go Back</span>
{{/link-to}}
Console transition output:
Attempting transition to availability
ember.js?body=1:15374 Transition #5: availability.index: calling beforeModel hook
ember.js?body=1:15374 Transition #5: availability.index: calling deserialize hook
ember.js?body=1:15374 Transition #5: availability.index: calling afterModel hook
ember.js?body=1:15374 Transition #5: Resolved all models on destination route; finalizing transition.
ember.js?body=1:15374 Transitioned into 'availability.index'
ember.js?body=1:15374 Transition #5: TRANSITION COMPLETE.

How to trigger action on route from controller in ember

Considering this example: http://emberjs.jsbin.com/hecewi/1/edit?html,js,output
The docs state that an action will be searched for on the controller first (which works in the example e.g. by pasting the actions hash into the IndexController), then on the current route and then along its parent routes until it hits ApplicationRoute. I'd expect the testCamel action in the example to be triggered then, but instead there is an error about the route did not get handled. How to do it right?
The code to trigger an action is indeed correct. It's just an unfortunate chosen example. Since your route will initialize the controller, the route itself is probably not completely initialized by the time the action is sent. If you, for example, schedule the action to be triggered in the following run loop, it works perfect:
http://emberjs.jsbin.com/yaseva/1/edit

Updating client model without a full refresh

Is there a way to add records to a client model and indicate to Ember that these records are already on the server side?
So, for instance if my Person model has records for "Peter" and "Paul" but then later the server adds "Mary" and my client becomes aware of that in a non ember-data sort of way. I'd like to be able to have the client add "Mary" in a way that will not cause state problems with the subsequent interactions.
For those that must know the "use case" ... I'm trying to:
have the first request to findAll() for a given model to pull the full set of data back from the server
have subsequent requests in a session call a custom AJAX request that only returns differences to the resultset since that last request
I want to be able to push these differences into the client model without screwing up it's "state"
Sometimes we create and update EmberData records on the client with data from websockets. In this case, the changes are already on the server, so we just want to make the changes on the client without changing state, etc, exactly as you describe.
When we create new records on the client, we push them into the store, e.g.:
this.store.push('post', { id: 1, body: 'Ember!' });
And, when we update existing records on the client, we update them in the store, e.g.:
this.store.update('post', { id: 1, hidden: true });