Ember - How to set a variable when in admin route - ember.js

I want to set some state variable which can be used to add a css class to my header template only when I am in the admin section of my app (i.e. any url beginning with /admin). I have the following routes:
this.resource('admin', { path: '/admin' }, function() {
this.route('dashboard', { path: '/' });
// more admin routes...
}
this.route('user')
// more routes...
Here is the setupController in my ApplicationRoute:
App.ApplicationRoute = App.Route.extend({
setupController: function(){
this.controllerFor('header').set('isInAdmin', false);
}
}
And the same in AdminRoute:
App.AdminRoute = App.Route.extend({
setupController: function(){
this.controllerFor('header').set('isInAdmin', true);
}
});
This works fine but once I have navigated into an admin route, and navigate back to a non-admin route, I'm not sure how to reset the isInAdmin variable.

The application controller have a computed property called currentPath. That property is equal the current path, for example, transitioning to admin/dashboard will return admin.dashboard in currentPath, going to /foo return foo etc.
So you can use the following code to know when a admin route is entered:
App.ApplicationController = Ember.Controller.extend({
isInAdmin: function() {
var currentPath = this.get('currentPath');
// if the first hierarchy is admin, so is a admin route
return currentPath.split('.')[0] === 'admin';
}.property('currentPath')
});
And in your application template isInAdmin will be avaliable:
<script type="text/x-handlebars" data-template-name="application">
...
<div {{bind-attr class=isInAdmin}}>
...
</div>
...
</script>
Give a look in that fiddle to see this in action http://jsfiddle.net/marciojunior/j5PRe/

I just read about the deactivate hook on Ember.Route:
http://emberjs.com/api/classes/Ember.Route.html#method_deactivate
This hook is executed when the router completely exits this route. It
is not executed when the model for the route changes.
So I added:
App.AdminRoute = App.Route.extend({
deactivate: function(){
this.controllerFor('header').set('isInAdmin', false);
}
});
which also works, however Márcio's answer is probably a little neater

Related

emberjs | save state of routes and nested resources

i am trying to build my first emberjs app and i wonder how i can save the state of a nested route to rebuild that state when the top route is revisted in the current session.
To give an example:
Lets Say a user switches from /overview/item1 to /info and then returns to
/overview/ and want to redirect him to /overview/item1
HTML
<div id="navigation">
{{#link-to 'info' class='link' }}Info{{/link-to}}
{{#link-to 'overview' class='link'}} Overview {{/link-to}}
</div>
JS
App.Router.map(function(){
this.route('info');
this.resource('overview', function () {
this.resource('item', { path : '/:item_id'});
});
});
it would be really nice if somebody could give me a hint to the right approach of this.
There are various ways for achieving your goal. Basically, you need to store state of last visited overview/:item_id route in the parent route or controller. Then, you need to check this state before resolving model of overview route. If state is not null (user was selected some item from overview/:item_id), abort current transition and start the new one (to
overview/:selected_item_id).
Schematic solution in code:
// 1st approach
App.OverviewController = Ember.ObjectController.extend({
selectedItem: null
});
App.OverviewRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (this.get('controller.selectedItem')) {
transition.abort();
this.transitionTo('overview.item', this.get('selectedItem'));
}
}
});
App.OverviewItemRoute = Ember.Route.extend({
afterModel: function(model) {
this.controllerFor('overview').set('selectedItem', model);
}
});
// 2nd approach (less code)
App.OverviewRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (this.get('controller.selectedItem')) {
transition.abort();
this.transitionTo('overview.item', this.get('selectedItem'));
}
},
setupController: function(controller) {
controller.reopen({ selectedItem: null });
}
});
App.OverviewItemRoute = Ember.Route.extend({
afterModel: function(model) {
this.controllerFor('overview').set('selectedItem', model);
}
});
It's important to keep the item itself, not it's id, because it'll way more easier to load overview/:item_id route in the future (passing stored model in this.transitionTo('overview.item', item)).

`needs` not waiting for data to be returned before rendering template

I am trying to implement a controller needing another (CampaignsNew needing AppsIndex), which looks like
App.CampaignsNewController = Ember.Controller.extend({
needs: ['appsIndex']
});
And in my CampaignsNew template I am showing it via
{{#if controllers.appsIndex.content.isUpdating}}
{{view App.SpinnerView}}
{{else}}
{{#each controllers.appsIndex.content}}
{{name}}
{{/each}}
{{/if}}
However controllers.appsIndex.content.isUpdating is never true. I.e. it attempts to show the data before it has been loaded.
My AppsIndex route has the model overridden:
App.AppsIndexRoute = Ember.Route.extend({
model: function(controller) {
var store = this.get('store').findAll('app');
}
...
});
I can get it to work if I put the same code within my CampaignsNew route and modify the template to each through controller.content. Which says to me that needs is not using the route? It also works if I go to the /apps page and it loads the data, and then navigate to the /campaigns/new page.
How do I get this to work? Thanks!
Edit:
As requested, the relevant parts of my router:
App.Router.map(function() {
this.resource('apps', function() {
...
});
this.resource('campaigns', function() {
this.route('new');
});
});
And the AppsIndex is accessed at /apps and CampaignsNew is at /campaigns/new
Edit2:
After implementing the suggestion by #kingpin2k, I've found that Ember is throwing an error. Below are the updated files and the error received.
App.CampaignsNewController = Ember.ObjectController.extend({
pageTitle: 'New Campaign'
});
App.CampaignsNewRoute = Ember.Route.extend({
model: function(controller) {
return Ember.RSVP.hash({
campaign: this.store.createRecord('campaign'),
apps: this.store.find('app')
});
// return this.store.createRecord('campaign');
},
setupController: function(controller, model){
controller.set('apps', model.apps);
this._super(controller, model.campaign);
}
});
Ember throws this error:
Error while loading route: Error: Assertion Failed: Cannot delegate set('apps', <DS.RecordArray:ember689>) to the 'content' property of object proxy <App.CampaignsNewController:ember756>: its 'content' is undefined.
I read online that this is because the content object doesn't exist. If I set it like so:
App.CampaignsNewController = Ember.ObjectController.extend({
content: Ember.Object.create(),
...
});
Then the page loads without error, and when inspecting the Ember Chrome extension, I can see the data has loaded. But it doesn't show on the page. Which I suppose happened because the content object existed and so Ember didn't wait for the model's promise to fulfill before rendering the template. Seems odd that you should have to define content in such a way though. Any insight on how to handle this?
Edit3: Question answered for me in another thread
Based on your router, apps isn't a parent of campaigns/new.
This means someone could hit #/campaigns/new and Ember would hit ApplicationRoute, CampaignsRoute, and CampaignsNewRoute to populate the necessary information for the url requested. Using needs as a way of communicating between controllers really only makes sense in an ancestral pattern (aka communicating with your parents, grandparents etc).
Just as another quick note, AppsIndex is a route of Apps, it won't be hit when your url includes a child. e.g.
Router
this.resource('apps', function() {
this.resource('chocolate', function(){
.....
});
});
Url being hit
#/apps/chocolate
Routes that will be hit
ApplicationRoute
AppsRoute
ChocolateRoute
ChocolateIndexRoute
The index route is only hit when you don't specify a route of a resource, and you are hitting that exact resource (aka nothing past that resource).
Update
You can return multiple models from a particular hook:
App.FooRoute = Em.Route.extend({
model: function(){
return Em.RSVP.hash({
cows: this.store.find('cows'),
dogs: this.store.find('dogs')
});
}
});
If you want the main model to still be cows, you could switch this up at the setupController level.
App.FooRoute = Em.Route.extend({
model: function(){
return Em.RSVP.hash({
cows: this.store.find('cows'),
dogs: this.store.find('dogs')
});
},
setupController: function(controller, model){
controller.set('dogs', model.dogs); // there is a property on the controller called dogs with the dogs
this._super(controller, model.cows); // the model backing the controller is cows
}
});
Check out the second answer here, EmberJS: How to load multiple models on the same route? (the first is correct as well, just doesn't mention the gotchas of returning multiple models from the model hook).
You can also just set the property during the setupController, though this means it won't be available when the page has loaded, but asynchronously later.
Which controller?
Use Controller if you aren't going to back your controller with a model.
App.FooRoute = Em.Route.extend({
model: function(){
return undefined;
}
});
Use ObjectController, if you are going to set the model of the controller as something, that isn't a collection.
App.FooRoute = Em.Route.extend({
model: function(){
return Em.RSVP.hash({
cows: this.store.find('cows'),
dogs: this.store.find('dogs')
});
}
});
Use ArrayController if that something is going to be a collection of some sort.
App.FooRoute = Em.Route.extend({
model: function(){
return ['asdf','fdsasfd'];
}
});
Note
If you override the setupController, it won't set the model of the controller unless you explicitly tell it to, or use this._super.
App.FooRoute = Em.Route.extend({
model: function(){
return Em.RSVP.hash({
cows: this.store.find('cows'),
dogs: this.store.find('dogs')
});
},
setupController: function(controller, model){
controller.set('cows', model.cows);
controller.set('dogs', model.dogs);
// uh oh, model isn't set on the controller, it should just be Controller
// or you should define one of them as the model
// controller.set('model', model.cows); or
// this._super(controller, model.cows); this does the default setupController method
// in this particular case, ArrayController
}
});

How to launch a modal from a url change with ember.js?

I've got a working "launch modal from an event" using the ApplicationRoute but I'd like to track the modal changes in the url if possible.
App.ApplicationRoute = Ember.Route.extend({
actions: {
modal: function() {
view = Ember.View.create({
templateName: "modal",
controller: this.controller,
content: []
}).appendTo('body');
}
}
});
If I change the url, how can I trigger a modal to show w/ a given context (using the url params to build it)?
Depending on how centralized you want this behavior to be, you could add a path observer to the application controller:
App.ApplicationController = Ember.Controller.extend({
//...
currentPathDidChange: function () {
switch(this.get('currentPath')){
case 'foo-route.index':
//trigger modal change
break;
}
}.observes('currentPath')
//...
});

How to show a loading screen while model in application route is being fetched

Given a application route like this:
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return new Ember.RSVP.Promise(function(resolve) {
setTimeout(function() {
resolve();
}, 3000);
});
}
});
How do I show a loading template while this model hook is waiting?
I tried something like this:
<script type="text/x-handlebars" id="loading">
<h3>Loading...</h3>
</script>
But this only displays when a sub route of the application is loading. How do I show a loading template when the application itself is still loading?
Thank you.
You could make use of some sort of loading overlay (which could be some static html/css) and the afterModel route hook:
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return new Ember.RSVP.Promise(function(resolve) {
setTimeout(resolve, 3000);
});
},
afterModel: function (model, transition) {
$('.loading-overlay').fadeOut();
}
});
You would have to determine the best place to put your overlay, but this should work.
Working example: http://jsbin.com/rixukune/3
Take a look at this hook's details here in the docs: http://emberjs.com/api/classes/Ember.Route.html#method_afterModel

View Controller not getting called

I have set up a view in ember and rendered it on the page like this
App.TestView = Ember.View.extend({
template: Ember.Handlebars.compile('<h1>Heading</h1>')
});
{{view App.TestView}}
But if I create the controller nothing happens
App.TestController = Ember.Controller.extend({
init: function() {
console.log('CONTROLLER HERE');
this._super();
}
});
Any ideas why this happens?
When you create a view manually (like you are doing) it doesn't use the test controller. If you hit a test route it will use the associated test controller and test view.
In your case based on your comments below you probably want to set up some routes and have them use the associated controllers and views.
Check out this: http://emberjs.com/guides/routing/defining-your-routes/
Maybe something like this
App.Router.map(function() {
this.resource('gallery', { path: '/gallery/:gallery_id' }, function() {
this.resource('photo', { path: 'photo/:photo_id' });
});
});
You are missing a route for your example to work: http://jsbin.com/IGIvuhe/2/edit
Add this and it will work:
App.Router.map(function(){
this.route("test", {path: '/'});
});
Hope it helps.