itemController for hasMany - ember.js

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

Related

Retrieving model's property in controller

I have a model which contains a single data but it's inside an array. I want to retrieve this data from inside my controller and making it a property of the controller so I can use it in other controllers. For example :
App.CurrentsubuserController = Ember.ArrayController.extend({
currentsubuser: function() {
return this.get('model'); <-------** not working **
}.property()
});
Basically I want to get the whole associated model so I can access it's datas. What is the syntax I have to use? Thank you
I'm not 100% sure of what your goal is here, but from another controller you can do a someAttribute: Ember.computed.alias('controllers.someController.model')
There is no need to create any local attribute in the controller that is being provided model data unless you are transforming it in some way.
You will need to specify a needs in that controller to reference the one you are pulling model data from like (adjust for your global style javascript)
export default Ember.Controller.extend({
needs: ['someController'],
someAttr: Ember.computed.alias('controllers.someController.model')
})
I know that will work fine, but thats not to say you should be doing any of this. And, obviously, make sure the model data is in the originating controller as you expect. A quick way to validate this is tossing a logging helper into your handlebars like {{log model}} or using the Ember Inspector in the browser.
UPDATE: Based on your comment below, this will work
export default Ember.Controller.extend({
currentSubUser: Ember.computed.readOnly('model.firstObject'),
})
Then, in your template you can use {{ currentSubUser.foo }}
Maybe this will help:
Getting the model inside a controller emberjs
Basically the model is loaded asynchronously. You can use this.get('model').then(function(data) { ... }) to work with the data, once it's loaded. Although I suggest using Ember.computed macros, like .mapBy:
currentsubuser: Ember.computed.mapBy('model', 'subuserproperty')
http://emberjs.com/api/classes/Ember.computed.html#method_mapBy
There is no need to store the model into an attribute.
Check this about the dependencies between controllers.
But to answer your question, do so:
On the controller you want to retrieve the ** CurrentsubuserController** model you define the need of this controller:
export default Ember.ArrayController.extend({
needs: "currentsubusercontroller"
currentSubUserController: Ember.computed.alias("controllers.CurrentsubuserController")
});
And then you can access this controller and his model with this.get('currentSubUserController.model')

Why is that using itemController renders a collection of empty items?

I'm currently learning Ember while following the todomvc tutorial with ember-cli: http://thetechcofounder.com/getting-started-with-ember-js-using-ember-cli/
I'm in the section where in order to edit a todo, it's needed to add the editTodo action in the TodoController. So far so good, but it also says to use itemController on the each handlebars helper to tell each todo to use a specific controller
.
The thing is that when I add itemController to each in the template (using Emblem.js: each itemController='todo'), the template no longer renders the title of each item on the collection, it only renders them blank:
I cannot understand why this happens.
Template extract
section#main
ul#todo-list
each
li class={isCompleted:completed}
if isEditing
input.edit
else
= input class='toggle' type='checkbox' checked=isCompleted
label{action 'editTodo' on='doubleClick'}= title
button.destroy
input#toggle-all type='checkbox'
Controller extract
`import Ember from 'ember'`
TodoController = Ember.Controller.extend
actions:
editTodo: ->
#set 'isEditing', true
`export default TodoController`
An item controller must be an Ember.ObjectController to successfully render each item and its associated data. ObjectControllers are used to decorate individual items within an ArrayController. Use the itemController property in the 'TodosListController' ArrayController to declare the item controller:
itemController: 'todo',
Then, when creating the 'todo' item controller class definition as suggested in the referenced tutorial, observe that the Ember CLI 'generate controller' command will create a standard Ember Controller. Standard Controllers and ArrayControllers represent multiple items (like the 'TodosController' or 'TodosListController'). Thus, the TodoController should extend Ember.ObjectController to represent singular items:
`import Ember from 'ember'`
TodoController = Ember.ObjectController.extend
actions:
editTodo: ->
#set 'isEditing', true
`export default TodoController`
A standard Ember.Controller, as posted with the question, fails to display each of the individual todos properly, when passed via the 'each' helper, because the model for the standard controller is referencing a filtered set of all records of type 'todo', instead of a particular, single todo record.
I’ve created a JS Bin to illustrate - just toggle between using Ember.Controller and using Ember.ObjectController for the 'TodoController', to see the standard controller fail.
Also, not the cause of the issue, but just in case it was overlooked, the ‘isEditing:editing’ is missing from the list-item class attribute declaration:
section#main
ul#todo-list
each itemController='todo'
li class={isCompleted:completed, isEditing:editing} // <-- here
if ...

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.

How can I bind the element ID for an Ember View?

My model "content.id" contains a string, e,g "123":
{{view Em.TextArea idBinding="content.id"}}
Instead of just setting the id of this view to "123", I'd like it to be "message-123", basically customizing the string being used. Sadly, Ember does not allow bindings to be functions, which would solve my problem (I could define such a function on the controller).
What's the best way to achieve this?
You could define a computed property in the controller (or elsewhere):
The controller
MyApp.ApplicationController = Ember.Controller.extend({
content: "a-content",
editedContent: function() {
return "message-" + this.get('content');
}.property('content')
});
The view
MyApp.FooView = Ember.View.extend({
    tagName: 'p'
});
The template (where content is a String, here)
{{#view MyApp.FooView elementIdBinding="editedContent"}}
{{content}}
{{/view}}
And the JSFiddle is here.
EDIT
How can the view see the property editedContent since it belongs on the ApplicationController controller?
The router, after started, automatically render the ApplicationView, or its template when there is no ApplicationView defined. If you want more detail, I suggest you to read the Ember guide: Understanding the Ember.js Router: A Primer.
And {{editedContent}} directly get the controller editedContent property, because the default view context is its controller, as you can read in Ember Blog - 1.0 Prerelease:
The {{#view}} helper no longer changes the context, instead maintaining the parent context by default. Alternatively, we will use the controller property if provided. You may also choose to directly override the context property. The order is as follows:
Specified controller
Supplied context (usually by Handlebars)
parentView's context (for a child of a ContainerView)