Is there a way to have a conditional redirect in the Ember.js Router, without breaking internal consistency of the router?
What you could do (as of today), is something like that:
root: Ember.Route.extend({
index: Ember.Route.extend({
enter: function(router) {
var logged = /* get from appropriated source... */;
Ember.run.next(function() {
if (logged) {
router.transitionTo('loggedIn');
} else {
router.transitionTo('loggedOut');
}
});
}
}),
loggedIn: Ember.Route.extend({
// ...
}),
loggedOut: Ember.Route.extend({
// ...
})
})
Do not miss the Ember.run.next as while you are in enter, the state transition is always pending, so you have to transition after that.
We use it as shown for authent, but you could imagine using it for whatever condition you have to...
The new router now includes a
beforeModel
hook which you could over-ride to include conditional logic while transitioning to a route.
The beforeModel hook will be called before the
model
hook is called and it gets passed a
transition
object. You can decide if you want to redirect to another route using
transitionToRoute()
or you could abort the transition if you don't want to redirect by calling
transition.abort()
Depending on what you're trying to do, you may be looking for conditional transitions. This is covered in another stackoverflow question, the TLDR of which is to check this fiddle.
Related
I am just playing with angularjs and the ui-router module and I am not satisfied with it.
I am missing a function/a process that allows me to execute logic before a state is activated.
The logic is to calculate the first day of the week of the current logged in user. Thus before the weekly view of a calendar is activated I have to execute this date logic so the user gets an url like:
/dateplanner/week/'firstdayOfWeek'
Its not enough for me to show a /dateplanner/week url.
Yes. In overriding a route's beforeModel function you can call this.transitionTo() and provide a different route and model as parameters. This will automatically abort the current route transition.
For example:
App.Router.map(function() {
this.resource('dateplanner', function() {
this.resource('week', { path: "/week/:firstDay" });
});
});
App.WeekRoute = Ember.Route.extend({
beforeModel: function(transition, queryParams) {
return someFunctionToGetFirstDayReturningPromise().then(function(firstDay) {
return this.transitionTo('dateplanner.week', firstDay);
});
}
});
You can find another example in the guide here (one that doesn't use promises or asynchronous code):
http://emberjs.com/guides/routing/redirection/#toc_based-on-other-application-state
API References:
http://emberjs.com/api/classes/Ember.Route.html#method_beforeModel
http://emberjs.com/api/classes/Ember.Route.html#method_transitionTo
I'm trying to observe the route change to apply some common action once rendered. The idea is to have a feature similar to the onload but as we use a single-page app this needs to be triggered on each route changes. (could be scoped to the new view)
I found how to observe the currentPath changes:
App.ApplicationController = Ember.Controller.extend({
currentPathDidChange: function() {
prettyPrint()
}.observes('currentPath');
});
While this works good in some cases, it gets triggered when the route changes, but still to early to apply content changes as it seem to append before the content gets rendered.
Any idea on the best practice to achieve such goal?
Have you tried deferring the code with Ember.run.schedule? For instance,
App.ApplicationController = Ember.Controller.extend({
currentPathDidChange: function() {
Ember.run.schedule('afterRender', this, function() {
prettyPrint();
});
}.observes('currentPath')
});
Due to the deprecation of Controllers in Ember 1.x finding the url in the router would be a good way to future proof your apps. You can do this in ember-cli like so:
// similar to onLoad event behavior
export default Ember.Route.extend({
afterModel: function (model){
Ember.run.next(() => {
console.log(this.get('router.url'));
});
}
});
// hacky way to get current url on each transition
export default Ember.Route.extend({
actions: {
didTransition: function() {
Ember.run.next(() => {
console.log(this.get('router.url'));
});
}
}
});
This will log: /posts and /posts/3/comments ect.
What is the best way to ensure that user is logged in into the Ember application?
I would like to intercept any URL before the routing is excited, show a modal popup with user_id/password, and go to the original URL.
Perhaps something similar to Rail's application_controller#before_filter which gets executed before any other controller methods.
I have the following routing:
App.Router.map(function(){
this.resource('folder', {path: '/'}, function(){
this.resource('files', {path: '/:folder_id'}, function(){
this.route('index');
});
});
});
I am trying to setup ApplicationRoute:
App.ApplicationRoute = Ember.Route.extend({
activate: function(){
console.log("activating app route");
}
});
But the problem is, if I hit index.html#/folder01, folder route is executed before the application route.
Summarizing the workflow ideally should look like:
User hits index.html#/folder01 URL
App checks whether user logged in. If not modal popup appears
After login, the app should render index.html#/folder01 URL
Thanks a lot!
Looks like I found a solution that works. Here it is:
App.Router.reopen({
startRouting: function() {
if (!App.userProfile.get('loggedIn')) {
$("#login_dialog").modal('show').one('click', "#login_btn", function(){
$("#login_dialog").modal('hide');
App.userProfile.set('loggedIn', true);
App.startRouting();
});
} else {
this._super();
}
}
});
If the App detects a session timeout, it executes window.location.reload(). Would be nice to have App.reset() working but afaik it has not been implemented correctly yet.
I am not 100% sure this solution does not have problems like memory leaks or things like that. I would love to hear anything from the Ember experts about this approach.
Thanks
You can use your route to accomplish this:
App.DatasetEditRoute = Ember.Route.extend({
redirect: function() {
if (this.controllerFor('currentUser').get('isSignedIn') === false) {
this.transitionTo('user.login');
}
}
});
I've created userapp-ember which is an Ember.js module for user authentication. It's meant for UserApp, but it's open-source, so you could modify it to use your own service instead if you'd like.
To demonstrate briefly how I solved the problem:
App.ProtectedRouteMixin = Ember.Mixin.create({
beforeModel: function(transition) {
// check if the user if authenticated
if (!isAuthenticated) {
transition.abort();
this.transitionTo('login'); // transition to the login route
}
}
});
App.IndexRoute = Ember.Route.extend(App.ProtectedRouteMixin);
I used a mixin to extend all routes that I wanted to protect. The mixin listens on the beforeModel event and aborts the transition if the user is not logged in (isAuthenticated). Then, instead of redirecting to the login route, you could show a login popup instead. After a successful login, reload the page or transition to the same route.
Inspired by Emberjs: Conditional redirect in router I thought I could use transient states in Ember router, as that is what the 'index' route in that question is - it is entered and immediately transitions to another state without a new event triggering it.
I can get the desired effect using the following code (action triggering transitionTwice), however the URL is updated in reverse order leaving it at the first state even though the App traverses through the states in the correct order ending in the expected last state.
App.Router = Ember.Router.extend({
enableLogging: true,
root: Ember.Route.extend({
transitionTwice: Ember.Route.transitionTo('twiceStep1'),
index: Ember.Route.extend({
route: '/',
}),
twiceStep1: Ember.Route.extend({
route: '/twiceStep1',
connectOutlets: function (router) {
router.transitionTo('twiceStep2');
}
}),
twiceStep2: Ember.Route.extend({
route: '/twiceStep2',
})
})
}),
App.initialize();
The console output shows the correct state changes:
STATEMANAGER: Sending event 'transitionTwice' to state root.
STATEMANAGER: Entering root.twiceStep1
STATEMANAGER: Entering root.twiceStep2
However the URL ends up in:
...test2.html#/twiceStep1
Can anyone spot anything I am doing wrong?
Thanks,
Jon.
I have been able to get this to work successfully by wrapping the second transitionTo() call in Ember.run.next().
Ember.run.next(function () {
router.transitionTo('twiceStep2');
});
The problem is that for any transition the URL is updated after the connectOutlets() call completes. This means that chained calls to transitionTo from connectOutlets nest and the URL is updated in reverse order as the calls complete.
While this fixes my immediate problem I'm not sure this really constitutes a final answer to the question as this implies that the final state needs to have the knowledge that it will be entered from a previous, transient state - which isn't good separation. But I thought I should record it in case anyone else struggles with this anomaly.
Sample code for my question is here.
It's a simple Ember app that displays the SearchView containing a TextField by default.
When the user enters some text and hits Enter, I want to transition to another state (displayUserProfile) passing the value entered in the textbox.
At first, in the Textbox's insertNewline callback, I called the transitionTo method of the application's router, passing the value as part of the parameter object:
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
App.router.transitionTo('displayUserProfile', {
username: this.get('value')
});
}
});
That works fine, but then I noticed that pangratz's answer on a question about infinite scrolling, uses a different approach. Instead he invokes a method on the view's controller, which in turn calls a method on the controller's target (which is the router).
This changes my code to:
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
Em.tryInvoke(this.get('controller'), 'displayUserProfile', this.get('value').w());
}
});
App.SearchController = Em.Object.extend({
displayUserProfile: function(username) {
this.get('target').transitionTo('displayUserProfile', {
username: username
});
}
});
My question is: which approach is better?
Calling transitionTo directly from the view or delegating it to the view's controller?
I would recommend a different approach. insertNewLine should trigger an action that is handled by the router, which will then transition its state.
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
this.get('controller.target').send('showUser', {username: this.get('value')});
}
});
App.Router = Ember.Router.extend({
...
foo: Ember.Router.extend({
showUser: function(router, evt) {
router.transitionTo('displayUserProfile', evt);
});
}
});
You should put the showUser handler at the top-most route where it is valid in your app.
This approach follows the general pattern of events in Ember apps that views handle DOM-level events and where appropriate, turn them into semantic actions that are handled by the router.
Personally I think the second approach is better.
The first thing is that it's a bad idea to access the router statically. Then for me, you have to keep the views logic-less, so delegating to controller seems a good choice.
In your case this is only a call to the router, but you can imagine processing some algorithms on the textfield value. If you do this proccessing in you view, this will lead to a view, mixing UI code, and logic code. View should handle only UI code.