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 ...
Related
Hi is there a way to customize the id of a component (i know it can be done for views ...but views have be deprecated since ember 1.13):
E.g. the following worked for the view:
export default Ember.View.extend({
classNames: ['music-js', 'vjs-default-skin', 'center-x','center-block'],
attributeBindings: ['id'],
id: 'musicOne',
However when I attempt to use id binding for the component i get the exception in the console logs:
export default Ember.Component.extend({
classNames: ['music-js', 'vjs-default-skin', 'center-x','center-block'],
attributeBindings: ['id'],
id: 'musicOne',
Uncaught TypeError: The element or ID supplied is not valid.
2 ways:
In the component itself:
export default Ember.Component.extend({
elementId: 'the-id'
});
Or specifying it in the component call itself:
{{my-component id="the-id"}}
I think the reason for NOT being able to do this is that the component is automatically assigned an ID by the Ember framework itself. You can see that if you inspect the HTML when you run your app:
<div id="ember428" class="ember-view">
But you can get a handle on that auto-generated ID and pass that to the JQuery plugin, instead of creating your own ID as per Mikko's answer.
See this to learn how to do that.
I think this is the preferred way, since components should be 'isolated' from external dependencies. By having to pass in the ID from the template defeats that (as per Mikko's suggestion) - since any consumer of a component would have to know what ID to pass in for the component to work.
However, Mikko has now edited his answer, so setting your own ID inside the component, also satisfies the 'isolation' requirement (ie. using elementID: 'the-id')
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')
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.
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
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)