Is there any event fired stating the transition/rendering has completed (and the dom is visible/ready).
setupcontroller/activate are before the dom is built/rendered
didInsertElement gets fired only the first time when I've already inserted an element and I'm just switching the model out underneath it.
What I'm really looking for is the transition is complete event
I guess I can do this, but I was kind of hoping it was already built in...
Ember.Router.reopen({
didTransition:function(infos) {
this._super(infos);
console.log('transition complete');
}
});
Even cooler would be a callback to the route that the transition completed for it, I may have to write this and submit a pull request.
There are a couple of different ways you can solve this
didInsertElement
This is fired when the view is inserted on the first time, but not fired if the model is switched out under the view (because Ember likes to reuse items, since it's cheaper than rebuilding the entire DOM). Example below.
Simple
If you only need to do it once, the first time the view is inserted, use didInsertElement
App.FooView = Em.View.extend({
setupSomething: function(){
console.log('the dom is in place, manipulate');
}.on('didInsertElement')
});
Example: http://emberjs.jsbin.com/wuxemo/1/edit
Complex
If you need to schedule something after the DOM has been rendered from the route itself, you can use schedule and insert it into the afterRender queue.
App.FooRoute = Em.Route.extend({
setupController: function(controller, model){
this._super('controller', model);
Ember.run.schedule('afterRender', this, function () {
//Do it here
});
}
});
Example: http://emberjs.jsbin.com/wuxemo/2/edit
Transition promise
The transition's promise will complete before it's finished rendering items. But it gives you a hook for when it's done with fetching all of the models and controllers and hooking them up.
If you want to hook up to the transition event you can do it like so:
var self = this;
transitionTo('foo').then(function(){
Ember.run.schedule('afterRender', self, function () {
//Do it here
});
})
The afterModel hook might work for you:
App.MyRoute = Ember.Route.extend({
afterModel: function(model, transition) {
transition.then(function() {
// Done transitioning
});
}
});
I tested this using RC-7 with routes that both do and don't have dynamic segments (i.e., a route with a model and a route without a model). It seems to work either way.
See this JSBin for an RC-6 example:
Output: http://jsbin.com/OteC/1/
Source: http://jsbin.com/OteC/1/edit?html,js
setupController is the last thing that the Router calls before finalizing the transition. And if it completes without errors, as far as Ember is concerned the transition is complete. You actually see this in action by enabling LOG_TRANSITIONS_INTERNAL.
At that point, It doesn't matter if the controller has thrown an error, view has thrown an error, etc. The router has completed transitioning into the target route.
So setupController is the last place in terms of the Router that corresponds to didTransition.
When the content/model backing the controller changes on an existing View, the bindings kick in. Most of the changes that happen to the view at that point are via Metamorphing.
The closest place I can think of to hook into would be View.render which pushes changes into the RenderBuffer. But you still need to account for Metamorphing via mixins that happens here.
didTransition does exist as you hoped -- but its an action and not a hook
XXRouter
actions: {
didTransition: function() {
this.controller.set("hasTransitioned", true); // or whatever is needed?!
return true; // Bubble the didTransition event
},
}
XXController
observeTransition: function() {
alert('complete Transition');
}.observes('hasTransitioned'),
Related
i have an ember view with a bunch of jquery code to manipulate the dom, the thing is when i use:
this.transitionTo("forecast.workpage", id);
it only loads the view once but when i try click it again the view doesn't execute again, i try several ways for instance adding this.rerender() in the end of my didInsertElement like that:
didInsertElement : function(){
//all my js code
this.rerender()
}
but it trows an error
Uncaught TypeError: Cannot read property '0' of undefined
, try adding this._super() in the begin, same result, is there a way to force the view to refresh from the view itself or the route? or controller ?
edit: try this approach with no results...
didInsertElement : function(){
this._super();
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
},
afterRenderEvent: function(){
//$(document).foundation();
$(".sortables li:even").find('div.row').addClass("gray");
}
Same thing, executes onces but where i click on the action doesn't execute the code.
To re-render the whole route (page), you can use the route's refresh method. Or, as a possible solution to this.rerender() failing, you could call that after it renders the first time.
didInsertElement: function() {
Ember.run.scheduleOnce('afterRender', this, function() {
this.rerender();
});
}
Personally, I wouldn't recommend either one. Your view should update automatically when the data it depends on changes. It if it's not updating, you probably have your observers set up wrong. (I don't really know your use case though, so use your best judgement.)
Is there any event fired stating the transition/rendering has completed (and the dom is visible/ready).
setupcontroller/activate are before the dom is built/rendered
didInsertElement gets fired only the first time when I've already inserted an element and I'm just switching the model out underneath it.
What I'm really looking for is the transition is complete event
I guess I can do this, but I was kind of hoping it was already built in...
Ember.Router.reopen({
didTransition:function(infos) {
this._super(infos);
console.log('transition complete');
}
});
Even cooler would be a callback to the route that the transition completed for it, I may have to write this and submit a pull request.
There are a couple of different ways you can solve this
didInsertElement
This is fired when the view is inserted on the first time, but not fired if the model is switched out under the view (because Ember likes to reuse items, since it's cheaper than rebuilding the entire DOM). Example below.
Simple
If you only need to do it once, the first time the view is inserted, use didInsertElement
App.FooView = Em.View.extend({
setupSomething: function(){
console.log('the dom is in place, manipulate');
}.on('didInsertElement')
});
Example: http://emberjs.jsbin.com/wuxemo/1/edit
Complex
If you need to schedule something after the DOM has been rendered from the route itself, you can use schedule and insert it into the afterRender queue.
App.FooRoute = Em.Route.extend({
setupController: function(controller, model){
this._super('controller', model);
Ember.run.schedule('afterRender', this, function () {
//Do it here
});
}
});
Example: http://emberjs.jsbin.com/wuxemo/2/edit
Transition promise
The transition's promise will complete before it's finished rendering items. But it gives you a hook for when it's done with fetching all of the models and controllers and hooking them up.
If you want to hook up to the transition event you can do it like so:
var self = this;
transitionTo('foo').then(function(){
Ember.run.schedule('afterRender', self, function () {
//Do it here
});
})
The afterModel hook might work for you:
App.MyRoute = Ember.Route.extend({
afterModel: function(model, transition) {
transition.then(function() {
// Done transitioning
});
}
});
I tested this using RC-7 with routes that both do and don't have dynamic segments (i.e., a route with a model and a route without a model). It seems to work either way.
See this JSBin for an RC-6 example:
Output: http://jsbin.com/OteC/1/
Source: http://jsbin.com/OteC/1/edit?html,js
setupController is the last thing that the Router calls before finalizing the transition. And if it completes without errors, as far as Ember is concerned the transition is complete. You actually see this in action by enabling LOG_TRANSITIONS_INTERNAL.
At that point, It doesn't matter if the controller has thrown an error, view has thrown an error, etc. The router has completed transitioning into the target route.
So setupController is the last place in terms of the Router that corresponds to didTransition.
When the content/model backing the controller changes on an existing View, the bindings kick in. Most of the changes that happen to the view at that point are via Metamorphing.
The closest place I can think of to hook into would be View.render which pushes changes into the RenderBuffer. But you still need to account for Metamorphing via mixins that happens here.
didTransition does exist as you hoped -- but its an action and not a hook
XXRouter
actions: {
didTransition: function() {
this.controller.set("hasTransitioned", true); // or whatever is needed?!
return true; // Bubble the didTransition event
},
}
XXController
observeTransition: function() {
alert('complete Transition');
}.observes('hasTransitioned'),
My view should be destroyed after the current route position is left.
So in this schematic example the login view should be destroyed after the user entered his credentials:
I tried to solve this by using Backbone.Router events:
var Router = Backbone.Router.extend({
initialize: function () {
Backbone.history.start();
},
routes: {
"sample" : "sample"
},
sample: function(){
// Build view
var demoView = $("<div/>")
.appendTo(document.body)
.text("I am lost!");
// Destroy view
this.once('route', function(){
demoView.remove();
});
},
});
Unfortunately this does not work as the route events are raised after the routes are executed:
http://jsfiddle.net/hcuX9/
Is there a solution to destroy views after leaving the route position?
Do I have to hack a new event into Backbone.js?
What I use to do is to have an App.current variable pointing to the current view being rendered.
At the top of each route (or the relevant ones in your case), I remove the current view from App.current and then assign it the new view:
someRoute: function() {
if(App.current && App.current.remove) App.current.remove();
// Asign a new current page
App.current = new SomeView();
...
}
That way I only let one view live per route, getting rid of problems like yours.
If you don't like to be checking for App.current and invoking the remove method at the top of every route, you can listen for Backbone.history route event and injecting that logic there:
Backbone.history.on('route', function() {
if(App.current && App.current.remove) App.current.remove();
});
I think you are stuck with your hack, unless you can adapt .listenTo to your needs - then you will need to fire a custom event with .trigger anywhere you have a route change, which might not be possible. Note that this functionality has been requested (and denied) before in backbone:
https://github.com/documentcloud/backbone/pull/494
See that pull request for other patches that try to do the same thing you are doing.
Here, we're using on and off to listen for route events coming in instead of once because we can't rely on a single event not being the current route. When we receive a route even that is not our current route, we can destroy the view and remove the listener:
// Destroy view
var self = this;
var onRoute = function(route, params){
if(route !== 'sample'){
demoView.remove();
self.off('route', onRoute);
}
};
this.on('route', onRoute);
I've modified your test fiddle here: http://jsfiddle.net/rgthree/hcuX9/3/
Another option, as your fiddle (not in your question) navigates directly to another view. This causes the other route's event to fire after the sample2 route. Because of this the above will remove the view. Now, it's much more complete. A hackier way you could handle it is to simply defer the once in a setTimeout so it doesn't listen until after the current route has been fired:
// Destroy view
var self = this;
setTimeout(function(){
self.once('route', function(){
demoView.remove();
});
}, 0);
You can see your fiddle with this method here: http://jsfiddle.net/rgthree/hcuX9/4/
Is there any reason why setupController would not get called when using {{linkTo}}? I have two instances in my app where linkTo is being used, and in the second case. It doesn't work. The only difference that I can see is that in the first case linkTo is being used in a loop, and in the second it's not. Below is relevant code for the non-working one:
App.Router.map(function() {
this.resource("search", { path: "/search/:args" });
});
App.SearchCriteria = Ember.Object.extend({ });
App.SearchRoute = Ember.Route.extend({
serialize: function(model, params) {
// .. some code that converts model to a string called args
return {'args': args}
},
model: function(params) {
// convert args, which is query string-formatted, to an object
// and then make a App.SearchCriteria object out of it.
return App.SearchCriteria.create($.deparam(params.args));
},
setupController: function(controller, model) {
controller.set("searchCriteria", model);
}
});
In the search template:
{{view Ember.Checkbox checkedBinding="searchCriteria.music"}} Music
{{#linkTo search searchCriteria}}Search{{/linkTo}}
The last thing I see in the logs is:
Transitioned into 'search'
Normally, I'd see the setupController being called at some point, but it's not happening or some reason. I even tried using the {{action}} method to call a handler and then use transtionTo, but that had the same results.
UPDATE 1: Adding more details
The only difference between the working and non-working cases is that in the working case, the {{linkTo}} is being called from the same template as that of the controller and router (i.e., the linkTo is in the search template and it's invoking the SearchRoute). In the working case, the linkTo is being called on the SearchRoute but from a different template belonging to a different router).
After some Chrome debugging of Ember code, I found out that the router isn't being called is because partitioned.entered is empty. In the working case, it is non-empty.
var aborted = false;
eachHandler(partition.entered, function(handler, context) {
if (aborted) { return; }
if (handler.enter) { handler.enter(); }
setContext(handler, context);
if (handler.setup) {
if (false === handler.setup(context)) {
aborted = true;
}
}
});
UPDATE 2: Root issue found - bug?
I think I understand the root cause of why the handler isn't being triggered, and I think it's because the partitionHandlers(oldHandlers, newHandlers) method doesn't think that the model has changed, thus doesn't fire the handler.
To be specific, this is the relevant part of the view:
{{view Ember.Checkbox checkedBinding="searchCriteria.music"}} Music
{{#linkTo search searchCriteria}}Search{{/linkTo}}
Although the user checks off the checkbox (thus changing the state of searchCriteria), Ember doesn't think that searchCriteria is any different, thus doesn't do anything.
Is this a bug?
I'm not sure what your problem is, but this may help.
setupController is called every time the route is entered. But model hook may not be called every time.
See Ember guide: http://emberjs.com/guides/routing/specifying-a-routes-model/
Note: A route with a dynamic segment will only have its model hook called when it is entered via the URL. If the route is entered through a transition (e.g. when using the link-to Handlebars helper), then a model context is already provided and the hook is not executed. Routes without dynamic segments will always execute the model hook.
Genrally speaking, if you click the link generated by link-to to enter the route, Ember will not call model hook for that route. Instead it passes the model (link-to parameter) to that route.
The philosophy here is since the client already has the model context, Ember think there is no need to get it again from server (that's model hook's job).
I am using Backbone's "all" event to catch all route events in my app in order to log the page views. This works well as long as I don't use navigate to manually trigger a route.
In the following example, I forward the user from the dashboard route to the login route. Backbone fires the event AFTER the route callback is executed, leading to the following output:
showDashboard
showLogin
route:showLogin
tracking:/login
route:showDashboard
tracking:/login
Obviously this is not what I want. I know I could call showLogin instead of using navigate to trigger the login route and this is what I am doing right now, but I would like to know why the order of the events is not the same than the order of the triggered callbacks.
Here is my router (shortened):
var AppRouter = Backbone.Router.extend({
routes: {
"/login": "showLogin",
"": "showDashboard",
},
initialize: function() {
return this.on('all', this.trackPageview);
},
trackPageview: function(eventName) {
console.log(eventName);
var url = Backbone.history.getFragment();
console.log('tracking: ' + url);
},
showDashboard: function() {
console.log('showDashboard');
// check if the user is logged in etc.
this.navigate('#/login', { trigger: true });
},
showLogin: function() {
console.log('showLogin');
}
});
Backbone's Router is actually very simple, and if you read the code you'll see the following in it's constructor:
this._bindRoutes();
this.initialize.apply(this, arguments);
_bindRoutes attaches all your routes as you expect, and it does this before your initialize function gets called. So your binding will always fire after Backbone's does.
You're probably going to be better off finding another way to do this.
You could call a before type function yourself in your routes to do stuff like track pageviews/etc. Or maybe you could just override route, track your pageview and then make sure to call Backbone's implementation with something like Backbone.Router.prototype.route.call(arguments);