Is there a route hook in Ember.js that is called on every transition, even if the new route is the same as the old route (for example, clicking a top-level navigation link to the same route).
I tried activate, but it's only being called once, and is not being called again when I use the top-level navigation to go to the same route I'm already in.
Example jsFiddle: When I click "Test" the first time, the activate hook is called, but when I click it a second time, it does not.
You can setup a didTransition in the router, exactly how Ember does it for Google Analytics.
App.Router.reopen({
doSomething: function() {
// do something here
return;
}.on('didTransition')
});
See example here: http://emberjs.com/guides/cookbook/helpers_and_components/adding_google_analytics_tracking/
UPDATE: (new link since old is broken)
https://guides.emberjs.com/v1.10.0/cookbook/helpers_and_components/adding_google_analytics_tracking/
Activate is not being called a second time because This hook is executed when the router enters the route... And when you click on that link-to a second time, the router isn't doing anything... As in, no transition is being made (although it is "attempted").
http://emberjs.com/api/classes/Ember.Route.html#method_activate
The method that I have found to work best is observing the currentPath from within a controller. I use this for animations between routes.
In your application controller you can do something like the following:
currentPathChange: function () {
switch(this.get('currentPath')){
case 'test.index':
this.doSomething();
break;
case 'test.new':
this.doSomethingElse();
break;
}
}.observes('currentPath')
You should be able to access almost any part of your app from the application controller, so it's a nice "root hook," I suppose.
Example: http://jsfiddle.net/mattblancarte/jxWjh/2/
Did you already consider the hook willTransition?
http://emberjs.com/guides/routing/preventing-and-retrying-transitions/
App.SomeRoute = Ember.Route.extend({
actions: {
willTransition: function(transition) {
// do your stuff
}
}
});
Alter/Hack EmberJS code and add a jQuery event trigger inside the doTransition() Function. This is Best but kind of defeating the point.
As of today, 1 year later and Ember 2.0 kind of out, there is NO OTHER WAY :(
Ember does not provide a way to track route-change attempts! This includes URLattemts(history), link-to attempts, hash change attempts etc..
Related
I have an Ember application with several routes and I want to scroll the page to top every time the user makes a transition. The problem is that I don't want to duplicate the same code on every route's didTransitionmethod.
So, Is there a way that I can observe all routes transitions and execute (declare) an action in just one place ?
Another way to put put this: If I have a parent route, can I fire the same action to all of its children ?
My current solution is to fire the action on every route's didTransition method like this:
//router.js
Router.map(function() {
this.route('login');
this.route('portal',{ path:'/'}, function() {
this.route('clientes');
this.route('convite');
this.route('usuario', function() {
this.route('view', { path: '/:id' });
});
});
//everyChildren.route.js
didTransition(){
this.super(...arguments);
Ember.$("html, body").animate({ scrollTop: 0 }, "slow");
}
Is there a way to do this ?
PS:. I am using Ember version 2.14.0
You can try to define didTransition action on application route. If child route does not define handler for event, it should bubble up to parent route and application is a most top route.
Alternatively, you can create a mixin with didTransition action, and add that mixin to certain routes.
Also, looking at your code I think you might be interested in using liquid-fire addon (if your goal is animated transitions)
Use ember-router-scroll.
This addon does what you want
I have a property in an Ember controller which has an observer. This is called when I first visit the page / route and also when I change the a dropdown that the value is bound to (as expected).
What isn't happening, is the observer being fired when I re-visit the page and the value is re-set (to the same value it was when I left the page).
The reason I need this, is that the value is used as a filter for another function and that function is called inside the observer.
Is there a way to make an observer fire even if the value is the same as it was before?
Or alternatively, a sensible way to fire a function (perhaps via the run loop) from a controller, each time the controller is loaded / route visited?
In setupController route hook, you will get controller reference so you can use that to call observer function.
setupController(controller,model){
this._super(...arguments);
controller.get('testObserver')();
}
I prefer below style to observe a property.
setupController(controller, model){
if (this.get('somelogic')) {
controller.addObserver('yourProperty', controller, this.get('callback'));
}
}
resetController(controller, isExisting, transition) {
if (isExisting) {
controller.removeObserver('yourProperty', controller, this.get('callback'));
}
}
I'm using Discourse (http://www.discourse.org/), which is built on EmberJS, and trying to observe any time the URL changes, e.g. when opening a new topic. I've seen the answer for observing the currentPath, for example here:
Detect route transitions in EmberJS 1.0.0-pre.4
App.ApplicationController = Ember.Controller.extend({
routeChanged: function(){
// the currentPath has changed;
}.observes('currentPath');
});
But I'm trying to detect any URL change, not just a path change. As mentioned in the comments for that answer:
This observer doesn't fire when transitioning from for example
/pages/1 to /pages/2 because the path is staying the same:
pages.page.index
What I'd like to do is actually detect those aforementioned transitions which don't get triggered by observes('currentPath'). Along those lines, if I do this.get('currentPath'); inside of my function, I get something like topic.fromParams but I actually am interested in the URL path e.g. /t/this-is-my-url-slug.
To put it simply, I'd like to detect when the app goes from:
/t/this-is-my-url-slug
to
/t/another-url-slug
and be able to capture the path: /t/another-url-slug
Sorry but I'm a bit of an Ember n00b and my only experience with it is through Discourse. Any ideas?
You don't need anything Ember-specific to do this. Depending on whether you are using hash or pushstate, you can use...
$(window).on('hashchange', function(){
console.log("Hash URL is " + location.hash.substr(1));
// Do stuff
});
or
$(window).on('popstate', function(e) {
console.log("Hash URL is " + window.location.pathname);
// Do stuff
});
The solution is pretty specific to Discourse (and not as general to EmberJS), but Discourse has a URL namespace which is called for URL related functions (/components/url.js). There is a routeTo(path) function in there which gets called every time a new route is loaded. So I was able to add my own function inside of there, which ensures that:
my function will be called every time a Discourse route changes
I can capture the path itself (i.e. the URL)
With Luke Melia's answer you are not doing any teardown to prevent memory leaks without causing issues when using the browsers back button.
If this is needed globally for your app, and you only want to use this event to call one function, then ok. But if you want to call off() when you leave the route (which you should tear it down when you don't need it) you will cause bugs with ember. Specifically when trying to use the browsers back button.
A better approach would be to leverage the event bus and proxy the event to one that will not cause issues with the back button.
$(window).on('hashchange', function(){
//Light weight, just a trigger
$(window).trigger('yourCustomEventName');
});
Then When you want to listen to hash changes you listen to your custom event, then tear it down when it is not needed.
Enter Route A:
$(window).on('yourCustomEventName', function(){
// Do the heavy lifting
functionforA();
});
Leave Route A:
$(window).off('yourCustomEventName');
Enter Route B:
$(window).on('yourCustomEventName', function(){
// Do the heavy lifting maybe it's different?
functionforB();
});
Leave Route B:
$(window).off('yourCustomEventName');
I have a question about Ember routing and controllers. I've just written a small App to get familiar with the new router. Therefore I've built a button that transitions to another state by clicking on it.
App.PostsView = Em.View.extend({
click: function() {
var router;
this.get('controller').transitionTo('about');
}
});
My question now is: what does the get method return?. Obviously an instance of the PostController but on the one hand the controller doesn't have a transitionTo() method and on the other hand that would not make any sense.
this.get('foo') returns a property of your Ember object. Since Views can have a "controller" property, this.get('controller') returns the controller bound to your view's controller property (by default the postsController).
this.get('controller').transitionTo() works because as sly7_7 mentioned, transitionTo() is also defined on the controller and delegates to the router. Note, it's probably going to be deprecated and one should use
this.get('controller').transitionToRoute('about');
instead.
You should not do this at the view level, this is what the router is designed for so instead of capturing a click event in the view, rather implement an action on the button and let the router handle it. I suggest you learn the best practices from the get-go, chances are your application will evolve, requiring more elaborate concepts such as handling transactions commits/rollbacks, creating/updating records. So here's my suggestion to you
In your view
<button type="button" {{action onSaveClick}} />Save</button>
In your router
App.FooRoute = App.Route.extend({
events: {
onSaveClick: function() {
this.transitionTo('bar');
}
}
})
If for any other reasons, say styling or animation, you find yourself forced to capture the click event in the view, then i suggest to, capture the event, perform your styling and finally send an event to the controller. The event can then be handled the same way in the router
App.FooView = Ember.View.extend({
click: function() {
// Do some styling
this.get('controller').send('onSaveClick')
}
})
Finally speaking of best practices, try to learn when working with ember to think of your application as a series of states interacting with each other. You'll find yourself bound to implement the concept right. Have a look at this
the controller has a transitionTo: https://github.com/emberjs/ember.js/blob/master/packages/ember-routing/lib/ext/controller.js#L36, it basically delegate to it's target (which is the router)
I am building an Ember.js app and I need to do some additional setup after everything is rendered/loaded.
Is there a way to get such a callback? Thanks!
There are also several functions defined on Views that can be overloaded and which will be called automatically. These include willInsertElement(), didInsertElement(), afterRender(), etc.
In particular I find didInsertElement() a useful time to run code that in a regular object-oriented system would be run in the constructor.
You can use the ready property of Ember.Application.
example from http://awardwinningfjords.com/2011/12/27/emberjs-collections.html:
// Setup a global namespace for our code.
Twitter = Em.Application.create({
// When everything is loaded.
ready: function() {
// Start polling Twitter
setInterval(function() {
Twitter.searchResults.refresh();
}, 2000);
// The default search is empty, let's find some cats.
Twitter.searchResults.set("query", "cats");
// Call the superclass's `ready` method.
this._super();
}
});
LazyBoy's answer is what you want to do, but it will work differently than you think. The phrasing of your question highlights an interesting point about Ember.
In your question you specified that you wanted a callback after the views were rendered. However, for good 'Ember' style, you should use the 'ready' callback which fires after the application is initialized, but before the views are rendered.
The important conceptual point is that after the callback updates the data-model you should then let Ember update the views.
Letting ember update the view is mostly straightforward. There are some edge cases where it's necessary to use 'didFoo' callbacks to avoid state-transition flickers in the view. (E.g., avoid showing "no items found" for 0.2 seconds.)
If that doesn't work for you, you might also investigate the 'onLoad' callback.
You can use jQuery ajax callbacks for this:
$(document).ajaxStart(function(){ console.log("ajax started")})
$(document).ajaxStop(function(){ console.log("ajax stopped")})
This will work for all ajax requests.
I simply put this into the Application Route
actions: {
loading: function(transition, route) {
this.controllerFor('application').set('isLoading', true);
this.router.on('didTransition', this, function(){
this.controllerFor('application').set('isLoading', false);
});
}
}
And then anywhere in my template I can enable and disable loading stuff using {{#if isLoading}} or I can add special jQuery events inside the actual loading action.
Very simple but effective.