Ember.js: ArrayController undefined in template - ember.js

Problem:
I am kind of struggling with the organization of my first ember app. The current issue is that the my Items ArrayController is not defined in my dashboard template:
<script type="text/x-handlebars" data-template-name="dashboard">
{{#if controllers.items}}
<p class="alert alert-error">Dashboard can access item's info - Nice!</p>
{{else}}
<p class="alert alert-error">Dashboard cannot access items... :-/</p>
{{/if}}
</script>
Likely cause: *
**EDIT: after talking with #conrad below, I'm kind of questioning this:*
I had a similar issue in an earlier post and kingpin2k suggested the cause was that I:
"never created anything that uses the options controller".
This is probably the case here as well. This quick screencast shows that a breakpoint on my ArrayController is not hit on page load - but it is hit when I inspect the Items controller in the Ember inspector tool (eg, Ember creates the ArrayController object right then for the first time).
Apparent non-solutions:
My Dashboard controller says it needs the Items controller. I guess that isn't enough to instantiate the ArrayController?
App.ItemsController = Ember.ArrayController.extend({
len: function(){
return this.get('length');
}.property('length'),
totalCost: function() {
return this.reduce( function(prevCost, item){
return parseInt(item.get('values').findBy('type', 'cost').price, 10) + prevCost;
}, 0);
}.property('#each.values')
[more computed properties...]
});
App.DashboardController = Em.Controller.extend({
needs: ['items'],
itemsLength: Ember.computed.alias('controllers.items.len'),
itemsTotalCost: Ember.computed.alias('controllers.items.totalCost'),
[more computed properties...]
});
Furthermore, each item in Items is being rendered in my items template. I guess that does not create the missing controllers.items either...
<script type="text/x-handlebars" data-template-name="items">
{{#each}}
[these render fine]
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="display">
<!-- DISPLAY TEMPLATE -->
{{!- DASHBOARD -}}
{{render dashboard}}
{{!- ITEMS -}}
{{render 'items' items}}
</script>
So then.. what?
I can imagine many possible avenues, but haven't gotten any of them to work yet:
Specify the Items ArrayController in {{render dashboard}}?
Some configuration in a Route?
Maybe my templates/routes are not correctly arranged?

You could make sure that the ItemController is instantiated in the dashboard template by calling it in the DashboardController's init function:
App.DashboardController = Em.Controller.extend({
needs: ['items'],
init: function() {
this._super();
this.get('controllers.items.length');
}
});
/edit:
removed the part that was not helpful

Related

Inheriting singular controller with render helper

I am trying to render a set of tabs for a set of objects (conversations) using the render helper for each. This is not part of a route as it is a persistent part of the interface. I have run into a problem where only the view with the same name as the model gets the intended controller (i.e. the panel contents and not the tab headers).
I have a Chat model, object controller and array controller (deliberately simplified here):
App.Chat = DS.Model.extend({ });
App.ChatsController = Ember.ArrayController.extend({
needs: 'application',
content: Ember.computed.alias('controllers.application.currentChats'),
});
App.ChatController = Ember.ObjectController.extend({ });
The ArrayController needed the needs/content properties because the chats are loaded in the application controller. I used the currentChats name as other routes may load non-current chats.
App.ApplicationController = Ember.Controller.extend({
init: function(){
this.store.find('chat', {"current": true});
this.set('currentChats', this.store.all('chat'));
}
});
I have no difficulty rendering the chat contents with the appropriate controller (into the 'chat' template). However, the chat tabs are given the default ObjectController, and therefore can't fire actions.
<script type="text/x-handlebars" id="application">
<!--application template-->
{{outlet chats}}
</script>
<script type="text/x-handlebars" id="chats">
<div id="chats">
<ul id="chat-tabs">
{{#each}}
{{render 'chatTab' this}}
{{/each}}
</ul>
{{#each}}
{{render 'chat' this}}
{{/each}}
</div>
</script>
<script type="text/x-handlebars" id="chatTab">
<!--tab template-->
</script>
<script type="text/x-handlebars" id="chat">
<!--chat template-->
</script>
The application router is as follows:
App.ApplicationRoute = Ember.Route.extend({
model: function(){ },
renderTemplate: function(){
this.render('application', { });
this.render('chats', {
into: 'application',
outlet: 'chats',
controller: 'chats'
});
}
});
This seems to come solely down to naming of the templates. The template called 'chat' inherits the correct controller, but chatTab doesn't despite receiving a chat as the model. Is there any way to force the view to inherit the correct controller? Or am I going about this in an idiosyncratic way.
Many thanks for your help to this Ember novice.
Andrew
It goes solely off the name provided to the render. The easiest way is to just create the other controller and extend the chat controller.
App.ChatTabController = App.ChatController.extend();

getting back reference to a specific model using Ember's Array Controller

I'm new to Ember and am finding some of their concepts a bit opaque. I have a app that manages inventory for a company. There is a screen that lists the entirety of their inventory and allows them to edit each inventory item. The text fields are disabled by default and I want to have an 'edit item' button that will set disabled / true to disabled / false. I have created the following which renders out correctly:
Inv.InventoryitemsRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON("/arc/v1/api/inventory_items/" + params.location_id);
}
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled="true"}}</p>
<p>{{input type="text" value=detail disabled="true"}}</p>
<button {{action "editInventoryItem" data-id=id}}>edit item</button>
<button {{action "saveInventoryItem" data-id=id}}>save item</button>
</div>
{{/each}}
</script>
So this renders in the UI fine but I am not sure how to access the specific model to change the text input from disabled/true to disabled/false. If I were just doing this as normal jQuery, I would add the id value of that specific model and place an id in the text input so that I could set the textfield. Based upon reading through docs, it seems like I would want a controller - would I want an ArrayController for this model instance or could Ember figure that out on its own?
I'm thinking I want to do something like the following but alerting the id give me undefined:
Inv.InventoryitemsController=Ember.ArrayController.extend({
isEditing: false,
actions: {
editInventoryItem: function(){
var model = this.get('model');
/*
^^^^
should this be a reference to that specific instance of a single model or the list of models provided by the InventoryitemsRoute
*/
alert('you want to edit this:' + model.id); // <-undefined
}
}
});
In the Ember docs, they use a playlist example (here: http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/) like this:
App.SongsRoute = Ember.Route.extend({
setupController: function(controller, playlist) {
controller.set('model', playlist.get('songs'));
}
});
But this example is a bit confusing (for a couple of reasons) but in this particular case - how would I map their concept of playlist to me trying to edit a single inventory item?
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled="true"}}</p>
<p>{{input type="text" value=detail disabled="true"}}</p>
<button {{action "editInventoryItem" this}}>edit item</button>
<button {{action "saveInventoryItem" this}}>save item</button>
</div>
{{/each}}
</script>
and
actions: {
editInventoryItem: function(object){
alert('you want to edit this:' + object.id);
}
}
Is what you need. But let me explain in a bit more detail:
First of all, terminology: Your "model" is the entire object tied to your controller. When you call this.get('model') on an action within an array controller, you will receive the entire model, in this case an array of inventory items.
The {{#each}} handlebars tag iterates through a selected array (by default it uses your entire model as the selected array). While within the {{#each}} block helper, you can reference the specific object you are currently on by saying this. You could also name the iteration object instead of relying on a this declaration by typing {{#each thing in model}}, within which each object would be referenced as thing.
Lastly, your actions are capable of taking inputs. You can declare these inputs simply by giving the variable name after the action name. Above, I demonstrated this with {{action "saveInventoryItem" this}} which will pass this to the action saveInventoryItem. You also need to add an input parameter to that action in order for it to be accepted.
Ok, that's because as you said, you're just starting with Ember. I would probably do this:
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled=headerEnabled}}</p>
<p>{{input type="text" value=detail disabled=detailEnabled}}</p>
<button {{action "editInventoryItem"}}>edit item</button>
<button {{action "saveInventoryItem"}}>save item</button>
</div>
{{/each}}
</script>
with this, you need to define a headerEnabled property in the InventoryitemController(Note that it is singular, not the one that contains all the items), and the same for detailEnabled, and the actions, you can define them also either in the same controller or in the route:
App.InventoryitemController = Ember.ObjectController.extend({
headerEnabled: false,
detailEnabled: false,
actions: {
editInventoryItem: function() {
this.set('headerEnabled', true);
this.set('detailEnabled', true);
}
}
});
that's just an example how you can access the data, in case the same property will enable both text fields, then you only need one, instead of the two that I put . In case the 'each' loop doesn't pick up the right controller, just specify itemController.

emberjs Cannot clone an Ember.Object that does not implement Ember.Copyable

I am using ember 1.3.1 and ember-data 1.0.0-beta.5. On creating new mode I get following error
Assertion failed: Cannot clone an Ember.Object that does not implement Ember.Copyable
Following is my model code
App.myModel = DS.Model.extend({
name : DS.attr('string'),
age : DS.attr('string')
});
In my create route model function
return Em.Object.create({});
and finally on save I do following
this.store.createRecord('property', this.get('model'));
Although despite the error, my backend service is called successfully and new model is saved.
Please guide.
Thanks
I had the same issue which I fixed by doing the following:
In the model function of the route replace
return Em.Object.create({});
with
return this.store.createRecord('myModel');
and on save replace
this.store.createRecord('myModel', this.get('model'));
with
this.get('model').save();
For the sake of completeness, in the scenario described by #acidleaf this is the solution offered by Yehuda Katz from the ember core team in this video:
Off the Menu: Building a Client-Side With Ember and Rails - Yehuda Katz # Rails Israel 2013
In the route from which you're returning a list of resources to display (i.e the plural version of the resource StoriesRoute, PostsRoute, etc..), you'll returned a filtered list containing those which are not new:
model: function() {
this.store.find('myModel');
return this.store.filter('myModel',function(myModel){
return !myModel.get('isNew');
});
}
I am quite new to Ember and still trying to catch all problems caused when migrating to newer versions of Ember and Ember Data, but...
On one hand I think you have a mistake in last code block and that it should be:
this.store.createRecord('myModel', this.get('model'));
// myModel instead of property
But on the other hand I dont think this will be the problem :-/
anyway, try to look (and compare) to changes for Ember data here: https://github.com/emberjs/data/blob/master/TRANSITION.md
and also on this http://discuss.emberjs.com/t/createrecord-using-this-get-model-throws-an-error/3968 or similiar
hope it helps!
J.
I have ran into this problem while learning Ember. The accepted answer works, but it first creates a new empty record in the store. This was not desired in my application as it displays the empty record in my view.
My Solution
Router
App.ItemsNewRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('content', {});
}
});
Controller
App.ItemsNewController = Ember.ObjectController.extend({
actions: {
save: function() {
this.store.createRecord('item', {
title: this.get('newTitle'),
category: this.get('newCategory')
}).save();
this.transitionToRoute('items');
}
}
});
Template
<script type="text/x-handlebars" data-template-name="items">
<ul class="list-group">
{{#each}}
<li class="list-group-item">{{title}} - {{category}}</li>
{{/each}}
{{outlet}}
<li class="list-group-item">{{#link-to "items.new"}}Add{{/link-to}}</li>
</ul>
</script>
<script type="text/x-handlebars" data-template-name="items/new">
<li class="list-group-item">
{{input class="form-control" value=newTitle placeholder="Title"}}
{{input class="form-control" value=newCategory placeholder="Category"}}
<button class="btn btn-default" {{action "save"}}>Save</button>
</li>
</script>

How to access a property in an itemController via the needs API in Ember.js

How can I access the properties of an itemController when I'm accessing the content of a controller via the needs API?
App.PostsIndexController = Ember.ArrayController.extend
itemController: 'post'
someAction: -> console.log("i'm melting!")
App.PostController = Ember.ObjectController.extend
someComputedProperty: (-> true ).property()
App.IndexController = Ember.Controller.extend
needs: ['postsIndex']
<script type="text/x-handlebars" data-template-name="index">
{{#each post in controllers.postsIndex.content}}
{{someComputedProperty}}
<a {{action someAction target="postsIndex"}}>click</a>
{{/each}}
</script>
I tried {{controllers.postsIndex.someComputedProperty}} and {{someComputedProperty}} in the index template.
How can I access the properties of an itemController when I'm accessing the content of a controller via the needs API?
First you should pass the controllers.postsIndex to the {{each}} helper instead of controllers.postsIndex.content. Otherwise the {{each}} helper will be looping over your model objects without going thru the PostsIndexController. So like #MilkyWayJoe suggested, try something like:
<script type="text/x-handlebars" data-template-name="index">
{{#each post in controllers.postsIndex}}
{{someComputedProperty}}
<a {{action someAction target="postsIndex"}}>click</a>
{{/each}}
</script>
I tried that, but it seems that someComputedProperty doesn't update when it changes, although it calculates correctly on page load.
Ember computed properties don't update unless you specify a list of dependencies:
App.PostController = Ember.ObjectController.extend
someComputedProperty: (-> true ).property('title', 'someotherproperty', 'etc')

using emberjs new-router-V3 and #with helper to access events defined in a different route

I have this working jsfiddle. The EmBlog.PostsEditRoute has a destroyPost event which I want to call with an action helper in the 'post/show.hbs' which is the template for the EmBlog.PostsShowRoute.
I am using #with helper to change scope in the template as suggested here. It doesn't destroy the object and throws no error.
<script type="text/x-handlebars" data-template-name="posts/show">
{{#with EmBlog.PostsEditController}}
<a href='#' {{action destroyPost this}}> Destroy</a>
{{/with}}
</script>
EmBlog.PostsShowRoute = Ember.Route.extend({
});
EmBlog.PostsEditRoute = Ember.Route.extend({
events: {
destroyPost: function(context) {
var post = context.get('content');
post.deleteRecord();
post.get('store').commit();
this.transitionTo('posts');
}
}
});
I think this is basically because you have to define your event handler in the EmBlog.PostsShowRoute or in the PostsRoute if you want it to be accessible in an other PostsXXX view. see http://emberjs.com/guides/templates/actions/ for details.
(the use of the #with helper here seems wrong here BTW, as your reference is about something quite old). I would simply do
<script type="text/x-handlebars" data-template-name="posts/show">
<a {{action destroyPost content}}> Destroy</a>
</script>
Here is the modified fiddle: http://jsfiddle.net/Qn3ry/4/
Note that when you try to destroy a post which is in the fixtures, it reappears when you transition to posts/index. This is simply because the post is not destroyed in the fixtures, and when entering to the PostIndexRoute, App.Post.find() will reload it again.