ArrayController's property for each item in the model in ember - ember.js

I've an Array controller which has the property "isChecked" (boolean property). In my controller I want to get the collection of elements which are "checked" (I mean selected). I'm not sure how to access the controller's property in the model.
My controller is as follows:
App.ExampleController = Ember.ArrayController.extend({
isChecked: false,
totalElements: function()
{
return this.model.get('length');
}.property('#each'),
selectedElements: function()
{
var content = this.get('content');
console.log(content.filterBy('isChecked'));
return content.filterBy('isChecked');
}.property('isChecked'),
});
I linked the "isChecked" property to a checkbox inside each helper as follows..
<ul>
{{#each model}}
<li>
{{input type="checkbox" checked=isChecked}}
{{name}}
</li>
{{/each}}
</ul>
I will display all the items in the model with a checkbox associated with it. The user can select few items from it. So I want those items.
Now I want to get the list of elements which are "checked". Either as a computed property or under any action.
Thank you.

I think you need to move the isChecked property onto an ObjectController, then reference that controller in the array controller with the itemController property.
Array Controller:
App.IndexController = Ember.ArrayController.extend({
itemController: 'color',
totalElements: function() {
return this.get('length');
}.property('[]'),
selectedElements: Ember.computed.filterBy('#this', 'isChecked', true)
});
(The #this means that the computed property will reference the array of item controllers.)
Item Controller:
App.ColorController = Ember.ObjectController.extend({
isChecked: false
});
http://emberjs.jsbin.com/tazojejuwi/1/edit
Hope that helps.

Related

Ember: Update ObjectController property from ArrayController action?

Disclaimer: I'm quite new to Ember. Very open to any advice anyone may have.
I have a action in a ArrayController that should set an ObjectController property. How I can access the right context to set that property when creating a new Object?
Here is abbreviated app code show my most recent attempt:
ChatApp.ConversationsController = Ember.ArrayController.extend({
itemController: 'conversation',
actions: {
openChat: function(user_id, profile_id){
if(this.existingChat(profile_id)){
new_chat = this.findBy('profile_id', profile_id).get('firstObject');
}else{
new_chat = this.store.createRecord('conversation', {
profile_id: profile_id,
});
new_chat.save();
}
var flashTargets = this.filterBy('profile_id', profile_id);
flashTargets.setEach('isFlashed', true);
}
},
existingChat: function(profile_id){
return this.filterBy('profile_id', profile_id).get('length') > 0;
}
});
ChatApp.ConversationController = Ember.ObjectController.extend({
isFlashed: false
});
The relevant template code:
{{#each conversation in controller itemController="conversation"}}
<li {{bind-attr class="conversation.isFlashed:flashed "}}>
<h3>Profile: {{conversation.profile}} Conversation: {{conversation.id}}</h3>
other stuff
</li>
{{/each}}
I don't see why you need an object that handles setting a property for all the elements in your list. Have each item take care of itself, this means components time.
Controllers and Views will be deprecated anyway, so you would do something like:
App.IndexRoute = Ember.Route.extend({
model: function() {
return [...];
}
});
App.ConversationComponent = Ember.Component.extend({
isFlashed: false,
actions: {
// handle my own events and properties
}
});
and in your template
{{#each item in model}}
{{conversation content=item}}
{{/each}}
So, whenever you add an item to the model a new component is created and you avoid having to perform the existingChat logic.
ArrayController and ItemController are going to be depreciated. As you are new to Ember I think that it would be better for you not to use them and focus on applying to coming changes.
What I can advice you is to create some kind of proxy object that will handle your additional properties (as isFlashed, but also like isChecked or isActive, etc.). This proxy object (actually an array of proxy objects) can look like this (and be a computed property):
proxiedCollection: Ember.computed.map("yourModelArray", function(item) {
return Object.create({
content: item,
isFlashed: false
});
});
And now, your template can look like:
{{#each conversation in yourModelArray}}
<li {{bind-attr class="conversation.isFlashed:flashed "}}>
<h3>Profile: {{conversation.content.profile}} Conversation: {{conversation.content.id}}</h3>
other stuff
</li>
{{/each}}
Last, but not least you get rid of ArrayController. However, you would not use filterBy method (as it allows only one-level deep, and you would have the array of proxy objects, that each of them handles some properties you filtered by - e.g. id). You can still use explicit forEach and provide a function that handles setting:
this.get("proxiedCollection").forEach((function(_this) {
return function(proxiedItem) {
if (proxiedItem.get("content.profile_id") === profile_id) {
return proxiedItem.set("isFlashed", true);
}
};
})(this));

EmberJS: Property Scopes in an ArrayController?

this is probably a grossly simple question to answer, so I apologize if I am cluttering this forum in advance.
I am displaying a list of items that share the same model and controller.
I made these items editable via a <button {{ action 'edit' }}> next to each item which toggles a boolean value of a property "isEditable" in the controller.
However clicking this button causes all items in the list to become editable because they all share the controller property "isEditable". The desired effect is to make a single item editable at a time instead of all items at once.
A simplified version of my template looks like this:
{{#if isEditing}}
<p>{{input type="text" value=title}}</p>
<button {{action 'doneEditing'}}>Done</button>
{{else}}
<span class="title">{{title}}</span>
<button {{action 'edit'}}><span class="edit"</span></button>
{{/if}}
and the controller looks like this
App.ItemController = Ember.ArrayController.extend({
isEditing : false,
actions : {
edit : function(){
this.set('isEditing', true);
},
doneEditing : function(){
this.set('isEditing', false);
},
}
});
Anybody know how to accomplish this? Is it because each item shares the "isEditable" property? If so, how do I get around this? I don't want to put this into the model because it's purely a display thing, even though I know I can get it to work doing that.
Thanks :)
By default the controller lookup within an {{#each}} block will be the controller of the template where the {{#each}} was used. If each item needs to be presented by a custom controller (to hold it's own state for example) you can provide a itemController option which references a controller by lookup name. Each item in the loop will be then wrapped in an instance of this controller and the item itself will be set to the content property of that controller.
So, I assume you are displaying the list of items using the {{#each}} helper. Therefore you can specify an itemController in the {{#each}} helper to hold the isEditable state on a per item basis. This would look something like this:
{{#each item in controller itemController="item"}}
...
{{/each}}
Moreover you should define the defined itemController of type Ember.ObjectController like:
App.ItemController = Ember.ObjectController.extend({
isEditing : false,
actions : {
edit : function(){
this.set('isEditing', true);
},
doneEditing : function(){
this.set('isEditing', false);
},
}
});
And for the list you should then have an App.ItemsController of type Ember.ArrayController:
App.ItemsController = Ember.ArrayController.extend({});
See here for more info on the mentioned itemController support for the {{#each}} helper: http://emberjs.com/api/classes/Ember.Handlebars.helpers.html#method_each
Hope it helps.

Setting itemController on a filtered subset of an ArrayController's model

Problem Summary: While I can get the children of a collection (defined on an ArrayController) to use a specific object controller for the individuals, this doesn't work on filtered subsets of the children.
Short context: I've got Subcriptions, which have Items. I'd like to filter the subscriptions in my view by type, and have the items within those subscriptions sort by timestamp. Here's the SubscriptionsController:
Social.SubscriptionsController = Ember.ArrayController.extend({
itemController: 'subscription',
announcements: function() {
return this.get('model').filterBy('kind', 'announcement');
}.property('model.#each.kind'),
user_sites: function() {
return this.get('model').filterBy('kind', 'user');
}.property('model.#each.kind')
});
I've defined SubscriptionController thusly:
Social.SubscriptionController = Ember.ObjectController.extend({
items: function() {
return Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, {
sortProperties: ['post_timestamp'],
sortAscending: false,
content: this.get('content.items')
});
}.property('content.items'),
});
And here's the relevant bit of my handlebars template:
{{#each controller}}
<li>{{controller.description}} {{controller.kind}} {{controller.feed_url}} {{controller.base_url}}</li>
<ul>
{{#each item in controller.items}}
<li>{{item.post_timestamp}}: {{{item.summary}}}</li>
{{/each}}
</ul>
{{/each}}
That code more-or-less does what I want: it renders the items, sorted by item.post_timestamp, as SubscriptionController defines it.
The problem is if I change {{#each controller}} to {{#each site in user_sites}}, the itemController property doesn't seem to magically apply to the sublist. Is there some kind of Sorcery I should use to inform Ember in my filters that I'd rather return the controller for the objects rather than the objects themselves?
EDITed to add: I know I can just add a new property like sorted_items on the Subscription model itself, but this feels wrong, design-wise. The model holds the data, the view shows the data, and the controller deals with sorting / filtering and all that jazz. Or at least that's part of how I think about MVC separation.
You can manually set the itemController for loops. You might try this in your template:
{{#each site in user_sites itemController="subscription"}}
...
{{/each}}

Set a Ember.CollectionView's selected item

I have a collection view like this (CoffeeScript):
App.SearchSuggestionsList = Ember.CollectionView.extend
tagName: 'ul'
contentBinding: 'controller.controllers.searchSuggestionsController.content'
itemViewClass: Ember.View.extend
template: Ember.Handlebars.compile('{{view.content.title}}')
isSelected: (->
/* This is where I don't know what to do */
this.index == controller's selected index
).property('controller.controllers.searchSuggestionsController.selectedIndex')
emptyView: Ember.View.extend
template: Ember.Handlerbars.compile('<em>No results</em>')
As you can see there's some pseudo-code inside the isSelected method. My goal is to define the concept of the currently selected item via this yet-to-be-implemented isSelected property. This will allow me to apply a conditional className to the item that is currently selected.
Is this the way to go? If it is, then how can this isSelected method be implemented? If not, what's another way around this to achieve the same thing?
I think it solves the case that you are looking for, with a changing list.
What we do is similar to the solution above, but the selected flag is based on the collection's controller, not the selected flag. That lets us change the "selected" piece via click, url, keypress etc as it only cares about what is in the itemController.
So the SearchListController references the items and item controllers (remember to call connectController in the router)
App.SearchListController = Em.ObjectController.extend
itemsController: null
itemController: null
App.SearchListView = Em.View.extend
templateName: "templates/search_list"
The individual items need their own view. They get selected added as a class if their context (which is an item) matches the item in the itemController.
App.SearchListItemView = Em.View.extend
classNameBindings: ['selected']
tagName: 'li'
template: Ember.Handlebars.compile('<a {{action showItem this href="true" }}>{{name}}</a>')
selected:(->
true if #get('context.id') is #get('controller.itemController.id')
).property('controller.itemController.id')
the SearchList template then just loops through all the items in the itemsController and as them be the context for the single item view.
<ul>
{{each itemsController itemViewClass="App.SearchListItemView"}}
</ul>
Is that close to what you're looking for?
The trivial (or the most known) way to do this is to have your child view (navbar item) observe a "selected" property in the parent view (navbar) which is bound to a controller, so in your route you tell the controller which item is selected. Check this fiddle for the whole thing.
Example:
Handlebars template of the navbar
<script type="text/x-handlebars" data-template-name="navbar">
<ul class="nav nav-list">
<li class="nav-header">MENU</li>
{{#each item in controller}}
{{#view view.NavItemView
itemBinding="item"}}
<a {{action goto item target="view"}}>
<i {{bindAttr class="item.className"}}></i>
{{item.displayText}}
</a>
{{/view}}
{{/each}}
</ul>
</script>
your navbar controller should have a "selected" property which you'll also bind in your view
App.NavbarController = Em.ArrayController.extend({
content: [
App.NavModel.create({
displayText: 'Home',
className: 'icon-home',
routeName: 'home',
routePath: 'root.index.index'
}),
App.NavModel.create({
displayText: 'Tasks',
className: 'icon-list',
routeName: 'tasks',
routePath: 'root.index.tasks'
})
],
selected: 'home'
});
Then you have a view structure similar to this, where the child view checks if the parent view "selected" has the same name of the child
App.NavbarView = Em.View.extend({
controllerBinding: 'controller.controllers.navbarController',
selectedBinding: 'controller.selected',
templateName: 'navbar',
NavItemView: Em.View.extend({
tagName: 'li',
// this will add the "active" css class to this particular child
// view based on the result of "isActive"
classNameBindings: 'isActive:active',
isActive: function() {
// the "routeName" comes from the nav item model, which I'm filling the
// controller's content with. The "item" is being bound through the
// handlebars template above
return this.get('item.routeName') === this.get('parentView.selected');
}.property('item', 'parentView.selected'),
goto: function(e) {
App.router.transitionTo(this.get('item.routePath'), e.context.get('routeName'));
}
})
});
Then, you set it in your route like this:
App.Router = Em.Router.extend({
enableLogging: true,
location: 'hash',
root: Em.Route.extend({
index: Em.Route.extend({
route: '/',
connectOutlets: function(r, c) {
r.get('applicationController').connectOutlet('navbar', 'navbar');
},
index: Em.Route.extend({
route: '/',
connectOutlets: function (r, c) {
// Here I tell my navigation controller which
// item is selected
r.set('navbarController.selected', 'home');
r.get('applicationController').connectOutlet('home');
}
}),
// other routes....
})
})
})
Hope this helps

Does it make sense to use ObjectController and ArrayController together?

I have a list of object, stored in an arrayController and rendered on the view using the #each macro
{{#each item in controller}}
{{view App.ItemView}}
{{/each}}
Each item view has class name binding that depends on the user action. For exemple :
App.ItemView = Ember.View.extend {
classNameBindings: ['isSelected:selected']
}
isSelecteddepends on the state of each Item : I have to store the selected item somewhere, and compare it to the new selected item if a click event is triggered.
The question is: where should I compute this isSelectedproperty ? In the itemsController ? In an itemController? Directly in each itemView ?
To me, it does make sense to put it into the view as, moreover, it is really a display concern.
You've got an example here: http://jsfiddle.net/MikeAski/r6xcA/
Handlebars:
<script type="text/x-handlebars" data-template-name="items">
{{#each item in controller}}
{{view App.ItemView contentBinding="item"}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="item">
Item: {{item.label}}
</script>
​JavaScript:
App.ItemsController = Ember.ArrayController.extend({
selected: null
});
App.ItemsView = Ember.View.extend({
templateName: 'items'
});
App.ItemView = Ember.View.extend({
templateName: 'item',
classNameBindings: ['isSelected:selected'],
isSelected: function() {
var item = this.get('content'),
selected = this.getPath('controller.selected');
return item === selected;
}.property('item', 'controller.selected'),
click: function() {
var controller = this.get('controller'),
item = this.get('content');
controller.set('selected', item);
}
});
App.ItemsView.create({
controller: App.ItemsController.create({
content: [{ label: 'My first item' },
{ label: 'My second item' },
{ label: 'My third item' }]
})
}).append();
​
It sounds like you need two things - an isSelected property on the item itself (the model) which answers the question, "Is this item selected?", and a selectedItem property on the itemsController, which answers the question, "Which item is selected?" The property on the model is just a get/set; you could compute itemsController.selectedItem by filtering the list of items for one where isSelected is true, or you could set it explicitly with some code to un-select previously unselected items.