Undestanding Ember Route hooks to redirect - ember.js

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.

Related

PWA: how to refresh content every time the app is opened

I created a PWA app which sends API call to my domotic server and prints the response on the home page (e.g. outside temperature and vacuum robot status).
While all the data get refreshed at very first app opening, if I minimize the app whithout completely shutting it off I have no data refreshing at all.
I was wondering how to force a refresh every time the app gets re-opened without having to do it manually (no pull-down to refresh, no refresh-button).
Found myself the solution adding the following code in service worker:
self.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'visible') {
console.log('APP resumed');
window.location.reload();
}
});
Here is the solution that works.
You can place this code wherever you have access to the window object:
window.addEventListener("visibilitychange", function () {
console.log("Visibility changed");
if (document.visibilityState === "visible") {
console.log("APP resumed");
window.location.reload();
}
});
Consider this may affect user experience or data loss with a forced reload every time the user swipes between apps.

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.

Transition to previous route on error action 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.

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