Add additional model to model hook triggered from link-to - ember.js

I'm happy that multiple models can be loaded into a route using something like this (although feel free to correct if there's a better way!):
model: function() {
var self = this;
return Ember.RSVP.hash({
foos: self.store.find('foos'),
bars: self.store.find('bars'),
});
However, if the model hook is set by a model being passed by, for example, a link-to helper, how should I add the additional model?
Thanks!
EDIT
So in my case I have a unit and objective, which are related. A unit can have many objectives - although its a one way relationship.
In my units route, I click on a unit which links-to units/unit:ID route. So the unit is set as the model, but I also want all objectives loaded into the model, so that I can select and add these to the unit.

Not everything in your page must be in the route's model. For example, you may have a Route that contains a Unit object. To get a list of all Objective objects, you could add a simple property to your controller:
allObjectives: function() {
return this.store.find('objective');
}.property()
Your template could then render the model as usual:
<div>Unit name: {{model.name}}</div>
<div>All objectives:
<ul>
{{#each objective in allObjectives}}
<li>{{objective.name}}</li>
{{/each}}
</ul>

Related

How to pass parameters to an Ember Route

Please see: http://emberjs.jsbin.com/xakok/1/edit
How do I do the following? The categories display. When user clicks on a category, I want to bring up a list of links that belong to the category that was clicked. Ember seems to be by-passing my LinkRoute all together. Thanks
When you provide a model to the link-to helper it will skip the model hook (it built the url based on the model, and assumes that's the model that would be used for that route).
That being said you need to handle the case where you refresh the page instead of hitting the page using the link-to.
Solving the first, we can now assume the category model is being sent to the links route as its model. So we can update the template to iterate over the links on the category sent in. (you could also say each link in model.links, where the category is your model).
{{#each link in links}}
{{link.title}}<br/>
{{/each}}
But you need to be able to handle the case where we refresh the page as well. So we change the link route to mimic the behavior and return the same type of model the link-to is passing in.
App.LinkRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('category', params.category_id);
}
});
Lastly, using the fixture adapter, when you define a hasMany or belongsTo as 3 or [1,2,3] you need to specify those relationships as async.
App.Category = DS.Model.extend({
name: DS.attr('string'),
links: DS.hasMany('link', {async:true})
});
Example: http://emberjs.jsbin.com/fexelera/1/edit
Lastly, thanks for providing source and all that was necessary for your problem, the jsbin really helps people answer questions easily.

How to use itemControllerClass to achieve a singleton like experience with ember.js

I've got a fairly simple form but it should never carry any state with it. I started reading an older discussion about how you can use itemControllerClass to get a "singleton" class created each time you enter the route.
If I want to use this how would I plug this into the template and wire it up from the parent controller?
Here is what I'm guessing you would do from the javascript side
App.FooController = Ember.Controller.extend({
itemControllerClass: 'someclass_name'
});
The only "must have" is that I need to have access to the parent route params from the child singleton controller.
Any guidance would be excellent -thank you in advance!
Update
Just to be clear about my use case -this is not an ArrayController. I actually just have a Controller (as shown above). I don't need to proxy a model or Array of models. I'm looking for a way to point at a url (with a few params) and generate a new instance when the route is loaded (or the parent controller is shown).
I'm doing this because the template is a simple "blank form" that doesn't and shouldn't carry state with it. when the user saves the form and I transition to the index route everything that happened in that form can die with it (as I've cached the data in ember-data or finished my $.ajax / etc)
Anyone know how to accomplish this stateless behavior with a controller?
I'm betting you're talking about this discussion. It's one of my personal favorite discoveries related to Ember. The outcome of it was the itemController property of an ArrayController; I use it all the time. The basic gist of it is, when you're iterating over an array controller, you can change the backing controller within the loop. So, each iterating of the loop, it will provide a new controller of the type you specify. You can specify the itemController as either a property on the ArrayController, or as an option on the {{#each}} handlebars helper. So, you could do it like this:
App.FooController = Ember.ArrayController.extend({
itemController: 'someclass'
});
App.SomeclassController = Ember.ObjectController.extend({});
Or, like this:
{{#each something in controller itemController='someclass'}}
...
{{/each}}
Within the itemController, you can access the parent controller (FooController, in this case), like:
this.get('parentController');
Or, you can specify the dependency using needs, like you ordinarily would in a controller. So, as long as the params are available to the parentController, you should be able to access them on the child controller as well.
Update
After hearing more about the use case, where a controller's state needs to reset every time a transition happens to a particular route, It sounds like the right approach is to have a backing model for the controller. Then, you can create a new instance of the model on one of the route's hooks; likely either model or setupController.
From http://emberjs.com/api/classes/Ember.ArrayController.html
Sometimes you want to display computed properties within the body of an #each helper that depend on the underlying items in content, but are not present on those items. To do this, set itemController to the name of a controller (probably an ObjectController) that will wrap each individual item.
For example:
{{#each post in controller}}
<li>{{title}} ({{titleLength}} characters)</li>
{{/each}}
App.PostsController = Ember.ArrayController.extend({
itemController: 'post'
});
App.PostController = Ember.ObjectController.extend({
// the `title` property will be proxied to the underlying post.
titleLength: function() {
return this.get('title').length;
}.property('title')
});
In some cases it is helpful to return a different itemController depending on the particular item. Subclasses can do this by overriding lookupItemController.
For example:
App.MyArrayController = Ember.ArrayController.extend({
lookupItemController: function( object ) {
if (object.get('isSpecial')) {
return "special"; // use App.SpecialController
} else {
return "regular"; // use App.RegularController
}
}
});
The itemController instances will have a parentController property set to either the the parentController property of the ArrayController or to the ArrayController instance itself.

Ember.js - How do I insert a controller?

So my understanding from the Ember docs is that the pattern for views/controllers/models is as follows:
[view] <- [controller] <- [model]
(with views consuming controllers consuming models)
In my previous experience using Ember, I'd set up a view to consume a model, like so:
{{#with blogpost}}
{{#view MyApp.BlogPostView contentBinding="this"}}
<h1>{{title}}</h1>
<p>{{content}}</p>
{{/view}}
{{/with}}
Now say I create a controller:
MyApp.BlogPostController = Ember.BlogPostController.extend()
Where do I initialize this controller?
Looking at the Ember docs, it seems like this happens automatically if the controller is associated with a route, but what if I just want an ad-hoc controller which ties together a view and a model? This could be for an arbitrary component on my page.
Am I responsible for instanciating the controller? Should I use some kind of controllerBinding attribute? Will it be instantiated automatically with my model, or with my view?
Any advice appreciated; I'm comfortable with the model/view pattern in Ember, but I'm having some difficulty working out where controllers fit in.
Looking at the Ember docs, it seems like this happens automatically if the controller is associated with a route
This is correct, a controller associated with a route will be automatically instantiated by ember when needed.
but what if I just want an ad-hoc controller which ties together a view and a model? This could be for an arbitrary component on my page. Am I responsible for instanciating the controller? Should I use some kind of controllerBinding attribute? Will it be instantiated automatically with my model, or with my view?
There are different way's to get your arbitrary controller instantiated automatically by ember without the needs of doing it yourself.
For the examples, let's assume you have a controller which is not associated with any routes called LonelyController,
App.LonelyController = Ember.ArrayController.extend({
content: ['foo', 'bar', 'baz']
});
Approach 1
Let's assume you have a route and you hook into setupController, if you try here to request you LonelyController with this.controllerFor('lonely'); this will make ember instantiate it for you:
App.IndexRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controllerFor('lonely').get('content');
// the above line will retrive successfully
// your `LonelyController`'s `content` property
}
});
Approach 2
Another possible way to get your LonelyController automatically instantiated by ember would be by defining a dependence with the needs API in another controller:
App.IndexController = Ember.ObjectController.extend({
needs: 'lonely',
someAction: function() {
this.get('controllers.lonely').get('content');
// the above line will retrive successfully
// your `LonelyController`'s `content` property
}
});
Using the needs API you could also doing something like this:
App.IndexController = Ember.ObjectController.extend({
needs: 'lonely',
lonelysContentBinding: 'controllers.lonely.content',
someAction: function() {
this.get('lonelysContent');
// the above line will retrive successfully
// your `LonelyController`'s `content` property
}
});
There are also some other combinations of the mentioned methods to get your LonelyController automatically instantiated, but I guess this should be more clear by now.
One last tip: to get a clue of what ember creates automatically under the hood you could also enable the generation logging to observe this in your console, which is very helpful, by doing:
var App = Ember.Application.create({
LOG_ACTIVE_GENERATION: true
});
Hope it helps.

itemController for hasMany

I've been struggling with the following question for a while: How do I assign an itemController to child elements in a hasMany relationship?
My use case is the following: I have an Ember.View (ProjectView) in which I manipulate areas on the map using the Google Maps API. I have a model for the Area and the Project model "hasMany" areas.
I do not have any save buttons or the like in my app, but rather sync changes to the backend when a change occurs (using a debounce function). In order to avoid nasty inFlight errors, I am using a modified version of the Ember.AutoSaving plugin https://github.com/gaslight/ember-autosaving, which buffers my changes and synchronizes them with the model when it's ready. However, in order to use this, I need to apply an itemController using this Mixin to every Area in my hasMany relation. How do I go about this?
The Handlebars {{each}} helper has an itemController option. When this option is specified each object will be wrapped by a controller instance. So something like this should work:
//from your project template
{{#each area in areas itemController="area"}}
<pre>
area is an AreaController: {{area}}
area.content is a reference to the model: {{area.content}}
{{/each}}
See the [handlebars {{each}} API docs] (http://emberjs.com/api/classes/Ember.Handlebars.helpers.html#method_each) for more details
EDIT: Option 2
As an alternative to using the {{each}} helper, use an ArrayController to represent the collection and set it's itemController property. For example:
App.AreasController = Ember.ArrayController.extend({
itemController: 'area'
});
App.AreaController = Ember.ObjectController.extend( Ember.AutoSaving, {
bufferedFields: ['title', 'body'],
instaSaveFields: ['postedAt', 'category'],
titleLength: function() {
return this.get('title').length;
}.property('title')
});
// In your project route:
setupController: function(controller, model) {
this.controllerFor('areas').set('content', model.areas);
}
Now the areas controller will wrap each item in an AreaController proxy.
See Ember ArrayController API docs

Ember.js arraycontroller call from view

I might be using this all wrong, but:
I've got an ArrayController representing a collection of products. Each product gets rendered and there are several actions a user could take, for example edit the product title or copy the description from a different product.
Question is: how do you interact with the controller for the specific product you're working with? How would the controller know which product was being edited?
I also tried to create an Ember.Select with selectionBinding set to "controller.somevar" but that also failed.
I think the most important thing you need to do, is first move as much logic as you can away from the views, and into controllers.
Another thing that would be useful in your case, is to have an itemController for each product in the list. That way, you can handle item specific logic in this item controller.
I don't have enough information to understand your architecture, so I will make a few assumptions.
Given you have the following ProductsController:
App.ProductsController = Ember.ArrayController.extend();
You need to create a ProductController that will be created to wrap every single product on its own.
App.ProductController = Ember.ObjectController.extend();
You need to modify your template as follows:
{{#each controller itemController="product"}}
<li>name</li>
{{/each}}
Now every product in your list will have its own ProductController, which can handle one product's events and will act as the context for every list item.
Another option:
If you will only be handling one product at a time, you can use routes to describe which product you are working with:
App.Router.map(function() {
this.resource('products', { path: '/products' }, function() {
this.resource('product', { path: '/:product_id' }, function() {
this.route('edit');
});
});
});
And create a controller for editing a product:
App.ProductEditController = Ember.ObjectController.extend();
And your list items would link to that product route:
{{#each controller}}
<li>{{#linkTo "product.edit" this}}name{{/linkTo}}</li>
{{/each}}
If you define itemController on your ProductsController you don't need to specify that detail in your template:
App.ProductsController = Em.ArrayController.extend({
itemController: 'product',
needs: ['suppliers'],
actions: {
add: function() {
// do something to add an item to content collection
}
}
});
App.ProductController = Em.ObjectController.extend({
actions: {
remove: function() {
// do something to remove the item
}
}
});
Use a collection template like this:
<button {{action="add"}}>Add Item</button>
<ul>
{{#each controller}}
<li>{{name}} <button {{action="remove"}}>x</button></li>
{{/each}}
</ul>
The Ember documentation describesitemController here:
You can even define a function lookupItemController which can dynamically decide the item controller (eg based on model type perhaps).
The thing I found when rendering a collection wrapped in an ArrayController within another template/view is the way #each is used. Make sure you use {{#each controller}} as Teddy Zeeny shows otherwise you end up using the content model items and NOT the item controller wrapped items. You may not notice this until you try using actions which are intended to be handled by the item controller or other controller based content decoration.
When I need to nest an entire collection in another view I use the view helper as follows to set the context correctly so that any collection level actions (eg an add item button action) get handled by the array controller and not by the main controller setup by the route.
So in my products template I would do something like this to list the nested suppliers (assuming your route for 'product' has properly the 'suppliers' controller):
{{view controller=controllers.suppliers templateName="products/suppliers"}}
The suppliers template just follows the same pattern as the template I show above.