Validating a transition using the new Async Ember router - ember.js

Using the non-async router, we could expect redirect on a route to be called only after resolving promises on from the model function. That's no longer the case.
How can something like this be implemented today?
App.ClientRoute = Ember.Route.extend
model: (params) ->
App.Client.findById params.client_id
redirect: ->
unless #modelFor 'client'
#transitionTo 'clients'

As of RC6, you would implement this like so:
App.ClientRoute = Ember.Route.extend
model: (params) ->
App.Client.findById params.client_id
afterModel: (resolvedModel)->
unless resolvedModel
#transitionTo 'clients'

those are two Gists from the developer of the new async router of Ember.js which will explain the new behaviour and show you some examples:
https://gist.github.com/machty/5723945
https://gist.github.com/machty/5647589
Hope they'll help you - I'm reading and following through at the moment and I think everything is really well explained ;)

Related

Calling an Ember _super method from Promise handler

I'm trying to make use of _super in the handler of a Promise inside of a Controller action, but it doesn't work because it seems to lose the correct chain of functions.
ApplicationRoute = Ember.Route.extend SimpleAuth.ApplicationRouteMixin,
actions:
sessionAuthenticationSucceeded: ->
#get("session.user").then (user) =>
if #get("session.isTemporaryPassword") or not user.get "lastLogin"
#transitionTo "temp-password"
else
#_super()
I want to revert to the Mixin's default behavior on the else but I need to resolve user asynchronously before I can do a conditional statement. I tried:
ApplicationRoute = Ember.Route.extend SimpleAuth.ApplicationRouteMixin,
actions:
sessionAuthenticationSucceeded: ->
_super = #_super
#get("session.user").then (user) =>
if #get("session.isTemporaryPassword") or not user.get "lastLogin"
#transitionTo "temp-password"
else
_super()
and
ApplicationRoute = Ember.Route.extend SimpleAuth.ApplicationRouteMixin,
actions:
sessionAuthenticationSucceeded: ->
#get("session.user").then (user) =>
if #get("session.isTemporaryPassword") or not user.get "lastLogin"
#transitionTo "temp-password"
else
#_super.bind(#)()
Neither works.
This answer claimed this should work as of 1.5.0, but I'm using 1.7.0-beta.5 and it's no go. Is there a way to get this to work, even in terms of approaching this differently?
Ember currently doesn't support calling _super asynchronously. In that example I'm not actually calling _super asynchronously, it's synchronous still.
http://emberjs.com/blog/2014/03/30/ember-1-5-0-and-ember-1-6-beta-released.html#toc_ever-present-_super-breaking-bugfix
In order to continue the bubbling you need to call this.target.send() with the name of the action.
see: How can I bubble up an Ember action inside a callback function?
Something like this should work:
ApplicationRoute = Ember.Route.extend SimpleAuth.ApplicationRouteMixin,
actions:
sessionAuthenticationSucceeded: ->
#get("session.user").then (user) =>
if #get("session.isTemporaryPassword") or not user.get "lastLogin"
#transitionTo "temp-password"
else
#target.send('sessionAuthenticationSucceeded')

Ember sort data in router/ model

When the route /facades get visited I want to redirect to the first facade. This generally works, but as the list from the server is not sorted, my code doesn't redirect to the "first" facade. I know how to sort in the controller, but how can I sort in the router/ model by any property?
App.FacadesRoute = Ember.Route.extend
model: ->
#get("store").find("facade")
redirect: ->
facade = #modelFor("facades").get("firstObject")
#transitionTo("facades.show", facade)
redirect is deprecated, the recommendation is afterModel, and findProperty is super easy for finding a model in a collection based on some property in the model or if you don't know exactly you can use sortBy.
App.FacadesRoute = Ember.Route.extend
model: ->
#get("store").find("facade")
afterModel: (model, transition) ->
facade = model.findProperty("someproperty", "value on some property");
// or
facade = model.sortBy("someproperty").get('firstObject');
#transitionTo("facades.show", facade)
Ok, I just found a way which works. Not sure if it's the best, but I think this could be useful to others too:
App.FacadesRoute = Ember.Route.extend
model: ->
#get("store").find("facade")
redirect: ->
sortedFacades = Ember.ArrayProxy.createWithMixins Ember.SortableMixin,
sortProperties: ["id"]
content: #modelFor("facades")
facade = sortedFacades.get("firstObject")
#transitionTo("facades.show", facade)

Detect route/view transition in Ember.js application

The router of my application looks like this (it's CoffeeScript):
App.Router.map () ->
#resource 'conversations', { path: '/' } ->
#resource 'conversation', { path: ':conversation_id' }
#route 'new'
So, in my app, I have paths like /new, /1, /2, etc.
I would like to detect a transition from /1 to /2 to make some initializations in my view (basically, put the focus on a textarea field). Unfortunately, as /1 and /2 use the same route, it seems nearly impossible to detect that transition.
I tried by using didInsertElement in the view (as described here) or by observing currentPath in the controller (as described here). It works fine if I go from /new to /1 (different routes) but not if I go from /1 to /2.
I found this gist suggesting to use the StateManager but it seems outdated (and I'm not sure it's really what I need).
What do you suggest me to do?
EDIT
It seems that setupController is called every time so I decided to overload it like this:
App.ConversationRoute = Ember.Route.extend {
setupController: (controller, model) ->
controller.set 'model', model
# do something here?
}
And I want the init method in my view to be called:
App.ConversationView = Ember.View.extend {
init: ->
#$('form textarea').focus()
}
But I still can't figure out how to make these two things work together (it's a problem because I read that the controller is not supposed to know about the view).
Thank you for your help!
Use the didInsertElement view hook and an observer.
App.ConversationView = Ember.View.extend
didInsertElement: ->
#focusOnTextarea()
modelChanged: (->
#focusOnTextarea()
).observes('controller.model')
focusOnTextarea: ->
#$('form textarea').focus()
In the case of going from /1 to /2, the route and view are not changing. Ember does the least amount of work possible. There's no need to re-render the view, so it doesn't. But this tripped me up too, and I think it's a big gotcha.
Also, if you override init in your view, make sure to call #_super().
Note: the model hook is only called when landing on a page to deserialize the URL, not when transitioning from another page and changing the model instance.
Route#model is your friend here. It will receive a params hash containing information from the URL on every route change (even when changing just which instance of a class is being viewed) In your case,
App.ConversationRoute = Ember.Route.extend {
model: (params) ->
App.Conversation.find params.conversation_id
setupController: (controller, conversation) ->
// you have the correct Conversation object
The guides have more examples.
The didInsertElement method of the view is the best method if you need to instantiate something on your view. If you need to have the controller do something when the template loads, you can put the call in the setupController method of your route:
App.FirstRoute = Ember.Route.extend({
setupController: function(controller){
controller.onload();
}
});
Here's a jsfiddle with a full example:
http://jsfiddle.net/jgillick/Q5Kbz/
This will be called each time that route is loaded. Try the jsfidle. Click along to the second template and then use your browser's back button. The onload should fire again.
Also, fun fact, you can use the deactivate method as an unload, to do anything you need to that controller when the user navigates away from that route:
App.FirstRoute = Ember.Route.extend({
deactivate: function(){
this.controller.unload();
}
});
Unrelated
One thing to note, (not directly related to your question) if you set the model on the controller in the setupController method, it will overwrite your controllers content property each time that route is loaded. To prevent this, put a conditional around the assignment:
setupController: function(controller, model) {
controller.onload();
if (model && (!controller.content || Ember.keys(controller.content).length === 0)) {
controller.set('content', model);
}
}

Hooking into "root" route in new Ember rc1 router

I'm looking for a way to hook into the router as it transitions to any given leaf resource during app init. In other words, I want to run a little init routine every single time an app is loaded, no matter what the "incoming" url state is.
Given a router such as this:
App.Router.map ->
#resource "donkeys"
#resource "camels"
When the app is loaded (for the first time) with a dangling url such as this:
localhost:9000/#/donkeys
-- or --
localhost:9000/#/camels
I want the same router-level code to be called.
My first guess was to try something like this (this doesn't work):
App.IndexRoute = Ember.Route.extend
activate: ->
App.callTheBeastsOfBurden()
... but it turns out that IndexRoute is not being traversed. I only see the log message Transitioned into 'donkeys'
In ye olde days, there was this "root route" concept that you could hook in to. If I recall, it looked something like this (this is old and "wrong"):
App.Router = Em.Router.extend
root: Em.Route.extend
connectOutlets: ->
App.callTheBeastsOfBurden()
So what's the Router v2 approved method of accomplishing the same?
App.ApplicationRoute = Ember.Route.extend
activate: ->
App.callTheBeastsOfBurden()

Ember Router - Load content from different controller on same level

Having two routes (comments, trackbacks) nested within post I try to access content of commentsController from trackbacksController after entering the App directly through /posts/1/trackbacks. Unfortunately it seems like the controller is not fully initialized and the content doesn't get loaded.
This is how the Router looks like:
Router = Ember.Router.create
root: Ember.Route.extend
index: Ember.Route.extend
route: '/'
post: Ember.Route.extend
route: '/posts/:post_id'
index: Ember.Route.extend
route: '/'
redirectsTo: 'comments'
comments: Ember.Route.extend
route: '/comments'
connectOutlets: (router) ->
controller = router.get('postController')
controller.connectOutlet 'comments', controller.get('comments')
trackbacks: Ember.Route.extend
route: '/trackbacks'
connectOutlets: (router) ->
controller = router.get('postController')
controller.connectOutlet 'trackbacks', controller.get('trackbacks')
And here is the TrackbacksController:
App.TrackbacksController = App.ArrayController.extend
init: ->
console.log App.router.get('attributesController.content') # : []
#_super()
Is there a best practice to initialize router.commentsController manually to get it's content from trackbacksController? Is there anything wrong concerning my approach?
You can access all controllers from a controller, using its controllers property.
For example, from trackbacksController, you can use this.get('controllers.commentsController')
EDIT: I realize I did'nt get your problem. I think there is some dependencies between the two controllers, and from my point of view, you can't rely on the application initialization order. So in the init method, you will not be able to access other controllers.
But I think you can put an observer in the trackbacksController, which observes controllers.commentsController.content or controller.commentsController.#each, so when the commentsController is populated, you will be notified.