Using Bindings with the EmberJS Router? - javascript

I'm working with Ember.Router, and one thing I can't figure out is how to bind objects to controllers that the Router is instantiating.
For instance, here is a controller class (extended) that the Router will instantiate for a specific route ('page'), as well as a controller object (created), say to handle user administration tasks on a part of the application outside of the Router:
// controller used by Router to render the "page" route
App.PageController = Em.ObjectController.extend({
content: Em.Object.extend({
foo: 'bar'
})
});
// global controller for users
App.usersController = Em.ObjectController.extend({
content: Em.Object.extend({
fooBinding: App.PageController.foo
// the above will not work since Em.Router
// instantiates the page controller dynamically
})
});
So when the router loads it will instantiate App.PageController into App.router.pageController, but that's after App.usersController is already created. So how can App.usersController access data in a controller that the Router is managing?
Any ideas?

There are a few mistakes in your sample.
First, you should never directly setup a property with an Object value at declaration time: this value would be shared across all instances of the class. Here, it does not really matter, but it's a bad practice. In this case, the good way of setting up the PageController content is to bind it in router, at connectOutlet call, like that:
connectOutlets: function (router) {
var theContainerController = router.get('theContainerController'),
objectWithFooBar = Ember.Object.create({
foo: 'bar'
});
theContainerController.connectOutlet('page', objectWithFooBar);
}
Second mistake is the naming of usersController: it should be UsersController, as it is a class, which will be injected in the router as usersController during initialize call. It seems also quite strange to have users pluralized & ObjectController. Certainly should be singularized...
Last, and certainly what will be the most interesting regarding the question, once you will have preceding remarks applied, you will be able to setup the binding using:
App.UserController = Em.ObjectController.extend({
fooBinding: 'App.router.pageController.foo'
});
App.router can be setup before your call to App.initialize. It is definitively a bad coupling to have UserController using a global symbol to directly access to PageController, but it does the job in your case.
A definitely yet better solution would also be to bind UserController's content in a connectOutlet call.

Related

Best practise to load content when Ember application boots

I need to load contents coming from my API when the application boots, then inject what I get into all routes and controllers to be able to access them whenever I want.
I was wondering where is the best place to do that in Ember?
In an initializer? I've heard that it's not a good practise to use the store from there...
In the application route? Then how can I access it from all routes and controllers? Using this.modelFor('application')? Is that a good practise?
Thanks.
The best place would be in the ApplicationRoute, you can do this in the beforeModel or afterModel/setupController hook as you like. Here's a beforeModel example:
ApplicationController = Ember.ObjectController.extend({
beforeModel:function() {
var self = this;
var rsvp = Ember.RSVP.hash({
fruits: self.store.find('fruit'),
candies: self.store.find('candy'),
meats: self.store.find('meats')
}};
rsvp.then(function(models) {
self.controllerFor('fruits').set('model',models.fruits);
self.controllerFor('candies').set('model',models.candies);
self.controllerFor('meats').set('model',models.meats);
});
}
});
The rsvp fetches all of the models together and waits for them to precede before continuing which happens on the then. We then assign all the found models to the model property on their matching controller.
To do this in the afterModel hook, it would look different.

Ember.js: dependencies between two controllers failing

I am trying to access one of two models in a controller that uses needs on a sibling controller. My router looks like the following:
App.Router.map(function() {
this.route('login');
this.route('mlb.lineups', {path: 'tools/mlb/lineups'})
this.resource('mlb.lineups.site', { path: 'tools/mlb/lineups/site/:site_id' });
});
The mlb.lineups route definition looks like the following:
App.MlbLineupsRoute = Ember.Route.extend({
model: function() {
var self = this;
return Ember.RSVP.hash({
sites: self.store.find('site')
})
},
setupController: function(controller, models) {
controller.set('model', models.get('sites'));
},
afterModel: function(models) {
var site = models.sites.get('firstObject');
this.transitionTo('mlb.lineups.site', site);
}
});
The reason I am using Ember.RSVP.hash({}) here is I plan on adding another model to be retrieved after I retrieve the site model.
Now in my MlbLineupsSiteController I am trying to access the sites model with the following:
App.MlbLineupsSiteController = Ember.ArrayController.extend({
needs: "mlb.lineups",
sites: Ember.computed.alias("controllers.models.sites")
});
This is the error I'm getting in my Ember console: needs must not specify dependencies with periods in their names (mlb.lineups)
What's the best way to make the sites model from the MlbLineups controller available in my MlbLineupsSiteController?
Note:
#NicholasJohn16's answer isn't valid anymore. It always gives an error that controller couldn't be found. Generally you should also never use needs property and always use Ember.inject.controller if you have to make your controllers dependent on each other. I'd also recommend using services instead of dependencies between controllers. It's easier to maintain code which contains communication between controllers through services, than controller directly accessing other controller's properties. You might not always be aware of such access, and using services gives you another layer of security.
Solution:
Tested in Ember.js 1.10.0-beta.4. Use following code in Controller to reference nested controller in needs:
needs: ['classic/about']
Then you can access it later using:
const aboutController = this.get('controllers.classic/about');
const aboutProperty = aboutController.get('customProperty');
Works as expected. Basically you need to replace dots with slashes.
It should be:
needs:" MlbLineupsSite "
Basically, the name of the controller you want to include, minus the word controller.
Everything else you posted should work.

Ember.js: questions concerning controllers, 'this', 'content' and model structure

I am getting a little deeper into my first functional app and need to better understand what it going on in my controller.
Here I have a controller that handles the action when a user clicks on an 'Option'. Looking at the this object raises a few questions:
What exactly is this? I would expect it to be an instance of my Option model, but it is missing some properties (like "identity: 'model: Option'").
If this is an instance of my Option model, why is the 'model' property undefined? Why doesn't it just know that?
What is this.content? It looks like some stuff is inside content (id and isSuppressed) and some is not (this.isSelected) - why is that?
Disclaimer: Though there aren't any presenting problems so far, there certainly could be errors in my ember app architecture.
Screenshot debugging controller:
Option Model & Controller
App.Option = Ember.Object.extend({
identity: 'model: Option',
id: '',
cost: '',
isSelected: false,
isSuppressed: false
});
App.OptionController = Ember.Controller.extend({
actions: {
toggleOption: function() {
this.set('isSelected', !this.get('isSelected'));
var id = this.get('content.id');
this.send('deselect', this.get('content.id'));
}
}
});
App.OptionsController = Ember.ArrayController.extend({
actions: {
deselect: function(exception) {
var opts = this.rejectBy('id', exception)
opts.setEach('isSuppressed', true);
}
}
});
It depends where this is, if your in the controller it's the controller. If your controller is an ObjectController/ArrayController it will proxy get/set calls down to the underlying model. content/model are the same thing in the context of the controller.
The properties rarely live directly on the instance, usually they are hidden to discourage accessing the properties without using the getters/setters.
In your code above there is a good chance your OptionController should be extending ObjectController. Unless the controller isn't backed by a model. If you use Ember.Controller.extend then it won't proxy getters/setters down to the model, it will store, retrieve properties from the controller itself.

In Ember.js, when you manually declare a view and its corresponding controller, is there a notion of scoping it to the current application state?

As an example, one particular application state may have a home view that just renders some background container,
App.EditView = Ember.View.extend({
templateName: 'edit-template',
})
App.EditController = Ember.ObjectController.extend({
title: 'Edit state',
})
Which are instantiated when I navigate to this state:
App.editRouter = Ember.Route.extend({
route: '/edit',
connectOutlets: function( router, context ){
router.get('applicationController').connectOutlet( 'mainOutlet', 'edit' )
}
})
Once here, the user may manually declare new div elements which map to new view and controller ( and model but that's no super relevant here ), the new div may or may not be a child of the div rendered by editView.
The current way I'm doing it
App.smallView1 = App.SmallView.create({
controller: App.smallController1
}).append()
App.smallController1 = App.SmallController.create()
As you see, nothing here indicates under what state are the view and controller declared.
What I'm confused about:
What is the relationship between this view-controller pair and the instance of EditView and EditController?
What is the relationship between the pair and the editRouter?
Should there be dependancies that need to explicitly specified?
This view-controller pair doesn't seem to be used by the router, so you'd have to create a route for them and connect the outlet with your view-controller pair, unless you want to append this view to an element that won't change per route. It also doesn't have any relationship with the other view-controller pair.
As for your questions:
What is the relationship between this view-controller pair and the
instance of EditView and EditController?
A: The code, as it stands, presents no direct relationship between the small view and controller with edit view and controller, but the difference is that the pair EditView and EditController are not "created", but "given" to the application, which will take care of instantiating that type when required through its own initialization logic (initialize) or when creating the instance of a view when it's required. The pair smallView1 and smallController1 will probably not be good for the router as their instance names end with "1" and I'm not sure if Ember expects that, but anyway, these are being instantiated and directly attached to the application as "living" objects, which is not required when using the router. Does this answer your question?
What is the relationship between the pair and the editRouter?
A: In your code, editRouter is the definition of what state (since Route extends State) your application is when you are on that route; this means that the framework understands that when you are on a given state you need some specific things to occur, for example, loading the view that state requires, loading the data that view should display, etc... this is done through connectOutlet, so for this particular route you don't have any relationship of any kind with smallView1 or smallController1 unless you use a different signature of connectOutlet to specify the viewClass and controller manually.
Should there be dependancies that need to explicitly specified?
A: Yes. When using the Router, your application must have a controller named ApplicationController, and when you call connectOutlet, the name you pass must be corresponding to one view and controller (I think the controller might not me required, but I'm not sure at the moment). So if you say connectOutlet('about'), the framework will look for a view named AboutView as per convention, then will instantiate this view and render on the appropriate outlet in the container view.
How will the controller access the router if it needs to?
A: At any point in your application you can access the router with App.router assuming your application is named "App" and your router was named "Router", so in any of your methods in your controller you can, for example, use the router to transition: App.router.transitionTo('root.index.home').

In Ember.js, how do you hook an object into a generic application namespace

Let's say I declared an application namespace:
App = Ember.Application.create();
and later I write an arrayController instance that creates objects and hook it onto the app namespace on user event:
App.objController = Ember.ArrayController.create({
content: [],
createObj: function(){
// instantiate new object
var newObj = Ember.Object.create({ ... })
//give obj a name
var newObjName = this._getObjName( someParam );
// hook object to an app namespace -> this is where I have an issue
App[newObjName] = newObj
},
...
});
See I explicitly use App[newObjName] = newObj to hook the object onto the namespace, ideally I would like some sort of generic way to name the application namespace in case I use the objController for a different application later.
There has to be some way to do this though I am just not familiar enough with Ember to have encountered it.
Note: on a scale of 1 to JFGI, this question is definitely not a 1. On the other hand it's a free resolved checkmark for anyone that has a moment.
During the initialization phase, Ember will instantiate all of your controllers and inject three properties into each of them - "target", "controllers", "namespace". The "namespace" property is your application.
That said, instead of hard-coding the top-level object:
App[newObjName] = newObj
you can do the following:
this.get("namespace").set(newObjName, newObj);
Note - in order for this to work, your application needs a router. Also, you should define controller classes, not instances. Ember will instantiate all controllers for you. So, this
App.objController = Ember.ArrayController.create({/* code here */});
should be written as
App.ObjController = Ember.ArrayController.extend({/* code here */});
Note the capital "O" in "ObjController".
Consider using injections, which is the preferred way to add dependencies.
Ember.Application.registerInjection({
name: 'fooObject',
before: 'controllers',
injection: function(app, router, property) {
if (property === 'FooObject') {
app.set('fooObject', app[property].create());
}
}
});
So if you define a class as follows:
App.FooObject = Ember.Object.extend({
// ...
});
the injection will create an instance into App.fooObject. Although we still use the namespace App, however only once. You could further do:
Ember.FooObject = Ember.Object.extend({
// ...
});
and then in your App, App.FooObject = Ember.FooObject but I'm not sure if its useful.

Categories