Have just upgraded my application to 1.0.0-pre.4 and am in the process of changing my router to the new router API, however I cannot seem to be able to create a binding between my controllers anymore.
So in my main ApplicationController, I have the following:
App.ApplicationController = Em.ArrayController.extend({
user: App.User.create()
});
And then in v1 of the router API, I had the following:
App.IndexController = Ember.ArrayController.extend({
userBinding: 'App.router.applicationController.user',
});
However, with changing over to v1 of the router API, App.router is no longer defined. Everything I try does not seem to work, even setting userBinding to 'App.ApplicationController.user' does not work - it's as if the applicationController no longer is working.
What I am trying to achieve is to create an instance of my user model and then share it across a number of routes/views.
Any ideas would be appreciated.
Unfortunately Ember have hidden all instances of the singleton controllers to prevent users from implementing bad practice code. You shouldn't be referencing controllers explicitly, and instead you should be decoupling everything and using dependency injection to pass in things to your controller.
In the previous releases of Ember, we had connectControllers which allowed you to connect controllers to one another, but now with this latest release of Ember, we just use "set" in the router to pass in other controllers.
In your example you have a an IndexController and a UserController, to get access to the userController from within the indexController, you'll need to do something like the following:
(Bear in mind that all of this takes place in Ember's Router, which you can read more about here: http://emberjs.com/guides/routing/setting-up-a-controller/)
App.UserRoute = Ember.Route.extend({
setupController: function(controller) {
this.controllerFor('index').set('userController', controller);
}
});
Your indexController will now have the ability to read information from the userController. In a template this may look like the following:
{{controller.userController.name}}
There is another workaround using "needs" in ObjectControllers.
Here is a reference on how to use this.
http://eviltrout.com/2013/02/04/ember-pre-1-upgrade-notes.html
Related
I'm very new to EmberJS 2.0 and trying to slowly understand it by building my own website with it. Anyways, I've managed to get Firebase integrated with Ember and my controller is able to authenticate correctly. However, I'd like to understand why when I execute:
this.send('toggleModal');
inside the authenticate action property function (.then()) it doesn't work but if I execute it outside then everything works fine.
1) Is the 'this' keyword getting confused with something other than the Ember controller?
Here is the sample:
// /app/controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
isShowingModal: false,
actions: {
toggleModal: function() {
this.toggleProperty('isShowingModal');
},
authenticate: function(username, pass) {
this.get('session').open('firebase', {
provider: "password",
email: username,
password: pass
}).then(function (data) {
console.log(data.currentUser);
console.log(session.isAuthenticated); //Why is 'session' not defined?
this.send('toggleModal'); //This doesn't work. Throws an error.
});
this.send('toggleModal'); //This works.
},
logOut: function() {
this.get('session').close();
}
}
});
2) Also, I've noticed that when using Emberfire I'm able to use the property 'session.isAuthenticated' within the template application.hbs however, shouldn't 'session' be an object that is injected to all routes and controllers using Torii? Why is that property inaccessible/undefined within the application.js controller? I'm using https://www.firebase.com/docs/web/libraries/ember/guide.html#section-authentication as a reference.
3) In the guide above the actions for authentication are put inside the route. However, according to this quora post the route should only handle template rendering and model interfacing. Is this post incorrect? The authentication logic should reside in the application.js controller correct? https://www.quora.com/What-is-the-best-way-to-learn-Ember-js
1) Is the 'this' keyword getting confused with something other than the Ember controller?
Yes. This is one of the most common sticking points of Javascript. There's a lot of articles out there about it, but this one looked pretty good. To solve it you'll either need to use an arrow function, bind the function to the current context, or save the context in a local variable. (Read that article first though.)
2) Also, I've noticed that when using Emberfire I'm able to use the property 'session.isAuthenticated' within the template application.hbs however, shouldn't 'session' be an object that is injected to all routes and controllers using Torii? Why is that property inaccessible/undefined within the application.js controller? ...
That's because the template pulls the property from the current context (your controller). Inside of your controller you'll have to use this.get('session') instead. (After you fix the issue I mentioned above.)
3) ... Is this post incorrect? ...
I wouldn't say incorrect, just a bit oversimplified. I would follow whatever conventions the library uses as that's probably the best way given the library's requirements.
You're partially right about this although it's not really confused. this (where you're modal call doesn't work) isn't scoped to the Controller anymore, because it's inside a function. Either:
replace the function (data) call with data => if you're using ember cli. Or
var _self = this; up top and reference _self instead.
This should at least get you started.
I have an app with many similar views which I instantiate programmatically to "DRY-up" my app.
The problem is that controllers instantiated programmatically do not delegate actions in the actions hash further. This is clear because there is nothing from which the controller can derive the hierarchy. There should be a way, however, to tell a controller which parent controller it has to use for event bubbling. Does anyone know it?
You shouldn't be initializing controller's on your own. All controller initialization should be handled by Ember itself. Another interesting note, controller's are intended to be singletons in the application. The only exception to this being the itemController when looping over an ArrayController. You can read more about it in the guides. Quote from the guides:
In Ember.js applications, you will always specify your controllers as
classes, and the framework is responsible for instantiating them and
providing them to your templates.
This makes it super-simple to test your controllers, and ensures that
your entire application shares a single instance of each controller.
Update 1:
An example of how to do routing for a wizard:
App.Router.map(function() {
this.resource('wizard', function() {
this.route('step1');
this.route('step2');
this.route('step3');
});
});
This way, you can have a separate controller/view/template per step of the wizard. If you have logic around how much of each step should be completed prior to transitioning to the next one, you can handle that in the individual routes.
Update 2:
In the event that the number of steps aren't predetermined, but are based on the data being fed to the app, you can make a WizardController that is an ArrayController where each item in the array is a step in the wizard. Then, use the lookupItemController hook on the ArrayController, kind of like this:
App.WizardRoute = Ember.Route.extend({
model: function() {
return [
{controllerName: 'step1', templateName: 'step1'},
{controllerName: 'step2', templateName: 'step2'},
{controllerName: 'step3', templateName: 'step3'}
];
}
});
App.WizardController = Ember.ArrayController.extend({
lookupItemController: function(modelObject) {
return modelObject.controllerName;
}
});
{{#each step in controller}}
{{view Ember.View templateName=step.templateName}}
{{/each}}
As another, probably better, alternative, you can override the renderTemplate hook in the route where you're pulling down the model for the next step in the wizard and pass in the appropriate templateName and controller in the render call, kind of like you see here.
Point being, I think it should be possible to do this without having to instantiate controllers yourself.
I asked a question similar to this, here, specifically about how to implement specific settings for a specific controller. In short, I wanted to implement checkInSettings for the whole CheckInController so that my index, settings, and reports templates and controllers have access to the checkInSettings.
I did get my answer to that; however, I think that specific settings might be limiting and it would be better served by making a settings object or store, and defining something like settings.checkIn for the check in settings.
I've looked for resources online but haven't come up with many answers... So, how should I best go about creating application wide settings, with sub settings for specific areas of my app?
A note: I would like to refrain from using Ember Data since it is not Production Ready yet, and this app will eventually be consumer facing.
Thank you!
Ember Data is a different beast. Store them on the application controller. Or if you don't want o clutter the application controller, create a singleton instance of a settings controller and store them there. (The same thing can be done just on the application controller, just use application instead of settings).
App.SettingsController = Ember.Controller.extend({
someSettingOn: false,
someOtherSetting: null
});
And then in other routes/controllers:
App.AnyRoute = Ember.Route.extend({
anyMethod: function(){
this.controllerFor('settings').toggleProperty('someSettingOn');
}
})
App.AnyController = Ember.Controller.extend({
needs: ['settings'],
anyMethod: function(){
var setting = this.get('controllers.settings.someOtherSetting');
console.log(setting);
},
anyProperty: function(){
if(this.get('controllers.settings.someSettingOn')){
return 'yes';
}
return 'no';
}.property('controllers.settings.someSettingOn')
})
In my ember application I want to have modules on different namespaces. I have an App Namespace and for each module there is an App.ModuleName namespace.
So far so good, I can access everything so far using App/ModuleName/SomeResource syntax. Now I have a controller that has a dependency on a controller in one of the module namespaces. I put up the controllers like this:
App.ModuleName.FooController = Ember.Controller.extend({
fooVal: 42
});
App.SomeController = Ember.Controller.extend({
needs: ['App/ModuleName/Foo', 'bar']
});
That seem to work so far telling by ember not complaining that the needed controller doesn't exist.
Now, how do I acces the controller in my handlebars template? For the bar controller its easy, just using controllers.bar.someProperty but I cannot access the App.ModuleName.FooController. I tried:
{{controllers.App/ModuleName/Foo.fooVal}}
{{controllers.App.ModuleName.Foo.fooVal}}
{{controllers.Foo.fooVal}}
and so on, every combination I could think of but it didn't work. Is it possible (and how) to get this running? Can someone please enlighten me?
And by the way: Even on the controllers I use needs successfully, if I debug them (logging them into the console, from my code directly or with a handlebars helper) the controllers property is always undefined. Is there a way to check the needed controller references?
Many thanks in advance.
To my knowledge, Ember's Container looks up stuff via the resolver. And it only looks for stuff on the application's namespace.
Some examples from the DefaultResolver docs.
'controller:post' //=> App.PostController
'controller:posts.index' //=> App.PostsIndexController
'controller:blog/post' //=> Blog.PostController
'controller:basic' //=> Ember.Controller
To achieve what you need you will need to provide a custom Resolver that looks up stuff in the different namespace when creating the Application.
App = Ember.Application.create({
resolver: MyNewResolver
});
For everyone who is interested in how I solved the issue and what I tried and learned:
I did some diggin in the code and tried a few things. I got the desired output using
a) A custom Handlebars helper that returns the property of the controller like this:
{{ myhelper 'App.ModuleName.FooController' 'fooVal'}}
with the following (quick and dirty) helper:
Ember.Handlebars.registerBoundHelper('myhelper', function(arg1, arg2, opts) {
if (opts && opts['data']) {
var ctrl = opts.data.properties[0];
var prop = opts.data.properties[1];
var controller = this.get('container').lookup('controller:' + ctrl.split('.').join('/'));
if (controller) {
return controller.get(prop);
}
}
});
The problem with this is, I couldn't get databinding to work, so if I changed my underlying model, the view didn't update.
b) Overriding the Controllers init() method to get the controller instance like this:
init: function() {
this.module = this.get('container').lookup('controller:App/Module/Foo');
}
and then in the template:
{{module.fooVal}}
In the second way, also Databinding worked, but It didn`t feel correct, so I did some googlin and came across this post on the emberjs issue list:
https://github.com/emberjs/ember.js/issues/683#issuecomment-5122333
As stated by tomdale, nested namespaces are not really something that is encouraged in ember, but multiple 'toplevel' namespaces are ok. So I decided to put up a toplevel namespace for every module and use a custom resolver to resolve them. Works like a charm now and feels like the right way to do it.
Any additional suggestions and comments about the pro's and con's of my second and final way of solving the problem would be much appreciated.
This might be a simple question, but I can't seem to find any current (RC1) example on how to achive this.
So let's say I've got the following two routes:
App.PostRoute = Ember.Route.extend({
});
App.PostCommentRoute = Ember.Route.extend({
});
How do I ensure that the controller of the PostRoute is loaded when I access the PostComment route directly. I.e. by calling #/post/comment directly from the browser?
You need the PostRoute model hook to return a promise. If you are using ember-data, this is done automatically for you.
The router checks, and if the object returned by the model hook implements the then function (which means it's a promise), it will transition the router into a loading state until the promise is resolved (which means the data was fetched). Then it will continue to the PostCommentRoute.