If I want to do something on route init, I use
MyRoute = Ember.Route.extend({
init: function(){
// do stuff
}
})
What about if I want to run the same function for initialization of all routes. Is there a way to do it globally without going through each route individually ?
Indeed there is. Just use a mixin.
var InitializeMixin = Ember.Mixin.create({
__init: function() {
// do stuff
}.on('init')
});
App.MyRoute = Ember.Route.extend(InitializeMixin, {
});
Just mix it into any route you want to do the setup in. Also note that I used on('init') instead of overriding the init function. This is a little cleaner (I think) because you don't have to call this._super().
Extend your base route:
MyRoute = Ember.Route.extend({
init: function(){
this._super();
// do stuff
}
});
OtherRoute = MyRoute.extend({
init: function(){
this._super();
}
});
Both answers are good, but require changing application code. In my case I want to use it for switching stylesheets so every route has it's own stylesheet. I need it only for development, and in production I would compile all stylesheets into one and remove the code which switches stylesheets.
In this case I found it's best to put your code in Ember core ( search for var Route = EmberObject.extend )
I also realized that for switching stylesheets it's better to have individual stylesheets not for routes, but for templates.
When I find out, I will post how to do it here: https://stackoverflow.com/questions/24068433/ember-change-stylesheet-for-every-template
Related
Here is possibly an edge case for how ember adds the 'active' class on a link to helper.
I have my current router set up like so:
import Ember from 'ember';
var Router = Ember.Router.extend({
location: PortalDevENV.locationType
});
Router.map(function() {
this.resource('portal', function() {
this.route('admin');
this.resource('placements', function() {
this.route('import-debtors');
this.resource('add-debtor', function() {
this.route('debtor-form');
});
this.route('view-debtors');
});
this.resource('debtor', {path: 'placements/view-debtors/debtor/:debtor_id'}, function() {
this.route('agent-notes');
this.route('transactions');
});
});
});
export default Router;
notice how I have a resource called "debtor" that- while it is being rendering into the portal template- i still need it to appear (in terms of the URL) to be a child of the "view-debtors" route... which, in reality, is nested deeper within a separate set of templates.
This structure seems to be working fine, but it is breaking my breadcrumb-style navigation.
When moving into the "debtor" page.. i still want "view-debtors" {{link-to}} helper to get the 'active' class from ember... along with the {{link-to}}'s that lead up to the "view-debtors".
Is this possible to do by calling some functions in my routes... or some other way?
It doesn't seem to be a common ember convention... but then again perhaps Ember actually does work in this way and I did something else that broke it? Take a look and see if my set up is correct.
You should be able to bind the active class to a computed property. Assuming the {{link-to}} you are referring to is in your application.hbs template, you could do something like this:
// templates/applictaion.hbs
{{#link-to "view-debtors" class="isDebtorsRoute:active"}}View Debtors{{/link-to}}
// controllers/application.js
export default Ember.Controller.extend({
isDebtorsRoute: function() {
// use this.get('currentRouteName') or this.get('currentPath')
}.property('currentPath')
})
EDIT: Here is a jsbin example http://emberjs.jsbin.com/wuhor/1/edit?html,css,js,output
I have a set of exchange rates that are being used to calculate prices on order objects, and I'm trying to figure out the best way to make these available whereever they're needed (Models, basically just flat Ember.Objects, and Controllers).
I've searched a lot for a good solution, and there's a lot of old code examples that no longer seem to work, as well as a lot of talk about dependency injection, but no good examples on how to use it.
So I've resorted to this, which feels kinda dirty, but this way I can call window.App.exchangeRates whereever I need it:
var ApplicationController = Ember.Controller.extend({
init: function() {
this._super();
var exchangeRates = new ExchangeRates();
exchangeRates.refresh();
this.set('exchangeRates', exchangeRates);
// Expose the exchangeRates to the global scope.
window.App.exchangeRates = exchangeRates;
},
});
More specificly, I need it injected into my Model, which is created by the Router like this:
var BuyBankTransferIndexRoute = Ember.Route.extend({
setupController: function (controller) {
controller.set('model', BuyOrder.create({
type: 'bank_transfer',
}));
}
});
And then bound inside the model, like this:
var BuyOrder = Ember.Object.extend({
init: function () {
// Whenever the currency data reloads.
window.App.exchangeRates.addObserver('dataLoaded', function () {
// Trigger a re-calculation of the btcPrice field.
this.propertyWillChange('currency');
this.propertyDidChange('currency');
}.bind(this));
},
});
Is there a better way? I'm using Ember App Kit with Ember 1.2.0 here.
I would say your best bet is to use the 'needs' functionality of controllers. You have a exchangeRates property on your application controller. If you wanted to, say, have that variable in a posts controller, you could do this:
App.PostsController = Ember.Controller.extend({
needs: ['application'],
exchangeRates: Ember.computed.alias('controllers.application.exchangeRates')
});
I would like to create a route for / that loads another route, say 'posts'. It seems that the only two solutions are to configure Ember's IndexRoute:
App.IndexRoute = Ember.Route.extend({
redirect: function() {
return this.transitionTo('posts');
}
});
OR
Map our 'posts' resource to the / path:
App.Router.map(function() {
return this.resource('posts', { path: '/' });
});
The first solution does not seem reasonable because it always sends visitors to /posts instead of having an actual base path of /. The second solution does not seem reasonable because it only allows posts to be viewed from / and not /posts. The second solution inherently creates strange nested URLs like /new for a new post instead of /posts/new.
What is the most idiomatic way to configure / to load another route instead of redirecting, while still making the target resource available from its normal URL? In other words, I would like the / path to access posts, and still have posts available via /posts.
Another way to go is to have your IndexController needs the PostsController, and then you can use render in your index template to render the posts.
App.IndexController = Ember.Controller.extend({
needs : ["posts"]
});
And then your index template might just be
{{render 'posts'}}
I think what you want to do is the following:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.get('store').findAll('post');
},
setupController: function(controller, model) {
this.controllerFor('posts').set('content', model);
}
});
That way the controller for this route will be an ArrayController filled with all your posts. And you can still use your /posts route whichever way you like. By default this would be App.IndexController (which you can override to implement custom functionality).
Alternatively, if you wanted to use a different controller (say App.PostsController), you could specify that in the routes renderTemplate hook. So if you wanted to use your posts template and your App.PostsController used in your App.IndexRoute, you would include:
renderTemplate: function() {
this.render('posts', { controller: 'posts' });
}
For more details have a look at the routing section of the Ember.js guides.
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.
From this [EDIT] [ToDo's sample]1, [/EDIT] I can connect a View via the connectOutlet. Is there an updated example for this using RC1?
index: Ember.Route.extend({
route: '/',
connectOutlets: function( router ) {
var controller = router.get( 'applicationController' );
var context = controller.namespace.entriesController;
context.set( 'filterBy', '' );
// This require was left here exclusively for design purposes
// Loads decoupled controller/view based on current route
require([ 'app/controllers/todos', 'app/views/items' ],
function( TodosController, ItemsView ) {
controller.connectOutlet({
viewClass: ItemsView,
controller: TodosController.create(),
context: context
});
}
);
}
}),
Actually the example you are linking should work. As you might know the Router API has changed and the code based on pre4 should still work. I am not aware of the requirements for the Todos App, so i cannot 100% tell, if it still works:
Todos.Router.map(function() {
this.resource('todos', { path: '/' }, function() {
this.route('active');
this.route('completed');
});
});
Todos.TodosRoute = Ember.Route.extend({
model: function() {
return Todos.Todo.find();
}
});
Todos.TodosIndexRoute = Ember.Route.extend({
setupController: function() {
var todos = Todos.Todo.find();
this.controllerFor('todos').set('filteredTodos', todos);
}
});
Here a little summary of the changes to the old router API:
You don't extend the Ember.Router Class anymore.
The URL Mappings don't reside in the Routes anymore. This is done via Todos.Router.map.
There is no connectOutlets event anymore in your routes. Instead there are 3 events you can implement: model(), setupController() & renderTemplate().
A little explanation on the hooks:
model(): Is called once when your route is entered via URL. This should return your model, which should become the content of your controller.
setupController(): Here you can get your controller and set its content how you may like. The default implementation sets the controller, that is name matching your route to the result of model().
renderTemplate(): Inside this hook you should use the new render method of routes to do the rendering. The render method is somehow the method that matches the old connectOutlets the most. There is also default implementation. Therefore it is also not implemented in the pre4 version of todomvc.
As Milkyway stated, you realy have to read the guides, but i hope this gets you started a little bit better.