emberjs: add routes after app initialize() - ember.js

i run into the following problem when building a router based app with emberjs. My app is simplified structured as following:
var App = Em.Application.create({});
App.ApplicationController = Em.Controller.extend({});
App.ApplicationView = Em.View.extend({
template : Em.Handlebars.compile('APPLICATION TEMPLATE')
});
App.RootState = Em.Route.extend({
index : Em.Route.extend({
route : "/"
})
})
App.Router = Em.Router.extend({
root : App.RootState
});
// initialize the app here
App.initialize();
// extend the RootState from anywhere. eg. from a plugged widget
App.RootState.reopen({
login : Em.Route.extend({
route : "/state1"
})
});
//App.initialize(); //init the app a second time forces unexpected behaviour
App.RootState.reopen({
alarms : Em.Route.extend({
route : "/state2"
})
});
//App.initialize();
Like my app demonstrates, i try to extend the router with new routes at runtime. I know that there exists another thread with similar question exists, but the discussed example do not work for me.
How can i extend my router at runtime with additional routes, without calling initialize() after every ...reopen({ }).
The background for doing this is to decide at runtime how the application is look like, for example to plug in different wigets with their own routes.
Regards,
T

Related

Ember one init for ALL routes

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

Ember.js setting application property on load

I'm trying to fetch the current logged in user via my REST API and then set it as a property of the ApplicationController. This is how I'm trying to do it:
App.ApplicationController = Ember.Controller.extend({
init: function() {
this._super();
var self = this;
App.User.findCurrent().then(function(user) {
self.set('currentUser', user);
});
}
});
App.User = Ember.Object.extend({});
App.User.reopenClass({
findCurrent: function() {
return $.getJSON('/api/v1/users/current').then(
function(response) {
return response.user;
}
);
}
});
When I check the Chrome network tab, I see there's a call to the API and the JSON is returned, but when I try to access e.g. {{currentUser.name}} in my application template (or a partial of it), it doesn't return the name. No errors are given as well.
But in the application template it doesn't return it.
What am I missing?
Thanks!
Edit
When I create another controller, e.g. HelpController and visit /help, then {{currentUser.name}} does return the username:
App.HelpController = Ember.Controller.extend({
needs: ['application'],
currentUser: Ember.computed.alias('controllers.application.currentUser')
});
Edit 2
Sorry, I forgot to mention that I'm actually trying to use {{currentUser.name}} from a partial ({{partial 'sidebar'}}), but that shouldn't change anything, because that's the same scope, right?
Edit 3
I noticed something very strange. When I call {{currentUser.name}} in my application template (which is not what I want btw), then it also works in the {{partial 'sidebar'}}.
Edit 4
As per request:
DEBUG: Ember.VERSION : 1.0.0-rc.6 ember.js?body=1:361
DEBUG: Handlebars.VERSION : 1.0.0-rc.4 ember.js?body=1:361
DEBUG: jQuery.VERSION : 1.10.0
This isn't the correct place to put this logic. You can use the route hooks model and afterModel on the ApplicationRoute, to do this easily. In general in ember loading of data is done in the routes hooks. This allows the router pause while loading so by the time your controller and templates come into play, they are working with loaded data.
App.ApplicationRoute = function() {
model: function() {
return App.User.findCurrent();
},
afterModel: function(model) {
App.set('currentUser', model)
}
}

Connecting a View in connectOutlet with Ember RC1

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.

How to manually invoke a route in ember.js RC1

I have the following controller and I'd like to bubble up an event using send
App.PersonController = Ember.ObjectController.extend({
page: function(page) {
var model = PersonApp.Page.create({id: page});
this.send("person.page", model); //should bubble up ...
}
});
here is my route setup
PersonApp.Router.map(function(match) {
this.resource("person", { path: "/" }, function() {
this.route("page", { path: "/page/:page_id" });
});
});
here is the simple page model (shim basically)
PersonApp.Page = Ember.Object.extend({
});
although I'm using the route "person.page" and I'm passing a valid model I get the following error (seemingly the router does not have this route?)
Uncaught Error: Nothing handled the event 'person.page'.
If it helps debug the controller / router relationship I noticed inside my controller if I dump this.get('target') ...
_debugContainerKey: "router:main"
and if I dig further ... and print this
this.get('target').get('router')
I see a router w/ my route under the currentHandlerInfos array ... not sure if I should be this deep though
... another slight update
If I do this (full blown) it seems to modify the window.location but my model/setupController hooks on the route are never hit
this.get('target').get('router').transitionTo(route, model);
I think, send is just used for events of a route. Assuming your controller would call send like this:
//in the controller
this.send("personPage", model);
// a matching Route
App.PersonRoute = Ember.Route.extend({
events : {
personPage : function(page){
// this should be called
}
}
});
For your case you need to leverage transitionTo (your access on the router property was too much, i think. The router instance of Ember.Router has again a router property. Pretty confusing :-)).
this.get("target").transitionTo("person.page", model);

How to get an instance of a controller in Ember.js?

I'm trying to access an instance of a controller that has been wired automatically using App.initialize();
I've tried the below but it returns a Class not an instance.
Ember.get('App.router.invitesController')
I have a quick post about this exact subject on my Blog. It's a little big of a different method, but seems to work well for Ember.js RC1.
Check it out at: http://emersonlackey.com/article/emberjs-instance-of-controller-and-views
The basic idea is to do something like:
var myController = window.App.__container__.lookup('controller:Posts');
This answer works with RC1/RC2.
Now you can use the needs declaration in order to make the desired controller accessible. Here's an example:
Suppose I want to get something from my SettingsController from within my ApplicationController. I can do the following:
App.SettingsController = Ember.Controller.extend({
isPublic: true
});
App.ApplicationController = Ember.Controller.extend({
needs: 'settings',
isPublicBinding: 'controllers.settings.isPublic'
});
Now in the context of my ApplicationController, I can just do this.get('isPublic')
You can access a controller instance inside an action in the router via router.get('invitesController'), see http://jsfiddle.net/pangratz666/Pk4k2/:
App.InvitesController = Ember.ArrayController.extend();
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
route: '/',
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router, context) {
var invitesController = router.get('invitesController');
},
anAction: function(router) {
var invitesController = router.get('invitesController');
}
})
})
});​
You can access any controller instance by name using lookup method of Application instance.
To get Application instance you can use getOwner from any route or controller.
const controllerName = 'invites';
Ember.getOwner(this).lookup(`controller:${controllerName}`));
Works for me in Ember 2.4 - 3.4.