Where should I keep selection state for a list? - list

I'm using ember 1.0.0-pre4.
I want to display a list of Model instances. The user should be able to select as many list entries by clicking a button or checkbox that is rendered within each row.
I managed to display the list. But I don't know how to manage selection state. When I put something like {{view Ember.Checkbox checkedBinding="isSelected"}} into the template then the selection state will be held in my model. But I don't think this is the best place. I think selection state belongs to the controller or view state.
Could you please tell me the best way to store and access (multiple) list selection state?

One way is to just keep a second list in the controller:
App.FooController = Ember.ArrayController.create({
selectedContent: [],
selectToggle: function(event) {
var selectedContent;
selectedContent = this.get(selectedContent);
if (selectedContent.contains(event.context)) {
return this.set('selectedContent', selectedContent.without(event.context));
} else {
return this.get('selectedContent').push(event.context);
}
}
});
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each foo in controller}}
<li {{action selectToggle foo}}>{{foo.name}}</li>
{{/each}}
</ul>
</script>
That just maintains a separate list in the controller and pushes/removes based on whether or not the item was selected.
You could also use an Ember.ObjectProxy to augment the values of the foo object with an "isSelected" property.
App.FooController = Ember.ArrayController.create({
selectedContent: #get('content').map(function(item){
Ember.ObjectProxy.create({
content: item
isSelected: false
});
});
});
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each foo in controller.selectedContent}}
<li {{bindAttr class= "foo.isSelected"}}{{action selectToggle foo}}>{{foo.name}}</li>
{{/each}}
</ul>
</script>
Then in your selectToggle method you just set foo's isSelected property appropriately.

Related

Assigning model to deeply nested view in Ember.js

I have a template that has a nested view. The nested view in turn has its own nested view. The views should all be rendered under the same route and use their own specific models. The browse template does not have an associated model.
The templates look like this:
<script type="text/x-handlebars" data-template-name="browse">
{{render "category" category}}
</script>
<script type="text/x-handlebars" data-template-name="category">
{{render "sort" sort}}
</script>
<script type="text/x-handlebars" data-template-name="sort">
<ul>
{{#each m in model}}
<li>{{m.sortType}}</li>
{{/each}}
</ul>
</script>
I'm returning all the models I need under the browse route:
App.BrowseRoute = Ember.Route.extend({
model: function () {
var store = this.store;
return new Em.RSVP.Promise(function (resolve, reject) {
new Em.RSVP.hash({
category: store.find('category'),
sort: store.find('sort')
}).then(function (results) {
resolve({
category: results.category,
sort: results.sort
});
});
});
}
});
I have no problem attaching the category model to the category template this way, but I'm unable to attach the sort model to the sort template. The sort model data is being returned, I just cannot figure out how to associate it with the sort template. Is it because the sort template is nested two levels deep?
Thanks!
Into the context of the view 'category' don't exist the variable 'sort',
you need to do something like this:
(in the view of category you can use the variable 'category')
<script type="text/x-handlebars" data-template-name="browse">
{{render "category" model}}
</script>
<script type="text/x-handlebars" data-template-name="category">
{{category}}
{{render "sort" sort}}
</script>
<script type="text/x-handlebars" data-template-name="sort">
<br/>{{model}}
<ul>
{{#each m in model}}
<li>{{m.sortType}}</li>
{{/each}}
</ul>
</script>
Sorry for my english
The answer is in the context.
The browse view's route retrieves the category and sort models, so it knows about them both. That's why I can pass the category model when I render the category template. However, the category template doesn't know about the sort model, so it can't pass it to the sort template, but the category template can reference its parent, so I was able to pass the sort model from category to the sort template be referencing the parent. Here's the piece of code that solved the problem:
<script type="text/x-handlebars" data-template-name="category">
{{render "sort" parentController.sort}}
</script>

How can I click to edit on a text field with ArrayController?

I was following the tutorial but the tutorial is for the object controller. In an Array controller how do I properly pass in the object for the text field so it triggers the update for that model object?
Right now I can double click, and then type in some value, and if I hit enter I get the value plus undefined method set.
Uncaught TypeError: Object asdasdasdasdasd has no method 'set'
I guess it's passing the raw value into the controller and then trying to run methods off of that. How do I get it to pass the actual model?
View:
<ul>
{{#each}}
<li {{bind-attr class="isEditing:editing"}} {{action "editWorkout" this on="doubleClick"}}>
{{#if isEditing}}
{{view Ember.TextField class='edit' action="updateWorkout"}}
{{else}}
{{#link-to 'workout' this}} {{title}} {{/link-to}}
{{/if}}
</li>
{{/each}}
<li>
{{newWorkoutName}}
</li>
</ul>
Controller:
EmberWorkouts.WorkoutsController = Ember.ArrayController.extend
actions:
editWorkout: (workout) ->
workout.set('isEditing', true)
createWorkout: ->
title = #get('newWorkoutName')
workout = #store.createRecord('workout', title: title)
#set('newWorkoutName', '')
workout.save()
updateWorkout: (workout) ->
workout.set('isEditing', false)
workout.save()
isEditing: false
Repo here if you want to investigate: https://github.com/ecl1pse/ember-workouts/tree/master/app
You can specify an itemController in your each and then use an ObjectController for each item in your list.
{{#each itemController="workout"}}
<li {{action editWorkout on="doubleClick"}}>
<!-- Other stuff goes here -->
</li>
{{/each}}
EmberWorkouts.WorkoutsController = Ember.ObjectController.extend({
editWorkout : function(){
this.set('isEditing', true);
}
});
Here's a JSBin of the general idea : http://jsbin.com/ucanam/1038/edit

How to get current model for a route from a controller or a view?

I want to implement item-list/item-detail pattern in Ember, but the nuance is that the detail view must appear next to the selected item. E.g:
<ul>
<li><div>Post 1<div></li>
<li><div>Post 2<div></li>
<li><div>Post 3<div></li>
<li>
<div>Post 4<div>
<div>
<ul>
<li>Comment 1</li>
<li>Comment 2</li>
<li>Comment 3</li>
</ul>
</div>
</li>
<li><div>Post 5<div></li>
</ul>
The Handlebars template I tried is:
<script type='text/x-handlebars' data-template-name='posts'>
<ul>
{{#each model}}
{{#linkTo 'post' this}}
<div>{{title}}</div>
{{/linkTo}}
{{#if isSelected}} <!-- How to implement isSelected ? -->
<div>{{!-- render selected post's comments --}}</div>
{{/if}}
{{/each}}
</ul>
</script>
I tried this in controller:
App.PostController = Em.ObjectController.extend({
isSelected: function() {
return this.get('content.id') === /* what to put here? */;
}
});
What I'm stuck with is how to implement isSelected in 'Ember'-way? Am I going in right direction?
You are on the right track. The trick is to use a different controller to wrap products in the item-list vs. the item-detail. So start by specifying that the handlebars {{each}} helper should wrap each entry in a ListedProductController
{{#each model itemController="listedProduct"}}
Now define ListedProductController, adding the isSelected function you'd been writing. Now it can reference the singleton ProductController via the needs array, comparing the product that was set by the router to the listed product.
App.ProductController = Ember.ObjectController.extend({});
App.ListedProductController = Ember.ObjectController.extend({
needs: ['product'],
isSelected: function() {
return this.get('content.id') === this.get('controllers.product.id');
}.property('content.id', 'controllers.product.id')
});
I've posted a working example here: http://jsbin.com/odobat/2/edit

Can't swap currentView of Ember.ContainerView in a #each

I'm trying to use the the currentView feature of an Ember.ContainerView in the context of a #each helper but it fails when currentView property is changed to another view.
My aim here is to allow editing an item of a list, by changing the regular view to an edit view when the user click a link.
Main template:
<ul>
{{#each itemController="person"}}
<li>{{view Ember.ContainerView currentViewBinding="cv"}}</li>
{{/each}}
</ul>
Template 'name' used to display a person :
{{firstName}} {{lastName}} <a {{action edit}}>edit</a>
Controller for the currentViewBinding property ('cv') and handling for the edit action.
App.PersonController = Ember.ObjectController.extend({
cv: Ember.View.extend({
templateName: 'name'
}),
edit: function() {
this.set('cv', Ember.View.extend({
templateName: 'nameEdit'
}));
}
})
'nameEdit' template corresponding to the view that needs to be displayed to edit the person object.
{{input type='text' value=firstName}} {{input type='text' value=lastName}}
The api guide says that:
When the currentView property is set to a view instance, it will be added to the ContainerView. If the currentView property is later changed to a different view, the new view will replace the old view.
But it's worse if I replace the cv property with a view instance (by using create() instead of extend()) as a re-render error is yield. See this question of mine.
Here is the jsFiddle to play with http://jsfiddle.net/fblanvil/tD3Ph/3/
I ended up not using ContainerView at all and using a simple if. But it doesn't explain why it's not possible to use a ContainerView this way in an #each helper. If someone thinks it's worth a Jira, put a comment and I'll do it.
<ul>
{{#each itemController="person"}}
<li>
{{#if editing}}
{{view templateName='nameEdit'}}
{{else}}
{{view templateName='name'}}
{{/if}}
</li>
{{/each}}
</ul>
Simple and effective after all...
App.PersonController = Ember.ObjectController.extend({
editing: false,
edit: function() {
this.set('editing', true);
}
})

EmberJS - How to call view method from inside a nested {{#each}}

I need to trigger the {{showAlias}} view method from within the {{#each content.activitytypes}} I have tried {{_parentView.showAlias}}. I believe I need to call it on the parent's parent, is that correct and how? I can call the {{showAlias}} outside of the {{#each...
Handlebars Template
<script type="text/x-handlebars">
<h2>Activities</h2>
<ul>
{{#each Km.activityController}}
{{#view Km.ActivityView contentBinding="this"}}
<li>
{{content.id}}:{{content.name}}
<ul>
{{#each content.activitytypes}}
<li>{{showAlias}}
{{name}} {{aliasname}}
</li>
{{/each}}
</ul>
</li>
{{/view}}
{{/each}}
</ul>
</script>
View
Km.ActivityView = Em.View.extend({
showAlias: function () {
var arr = this.getPath('content.activitytypes'),
show = false;
console.log(arr)
arr.forEach(function(item) {
var aliasArr = item.showaliasaccountid;
if (typeof aliasArr !== 'undefined') {
if (jQuery.inArray(2,aliasArr)) {
console.log(aliasArr);
show = true;
}
}
});
}.property('content.#each.activitytype.#each'),
});
{{parentView.showAlias}} will work, but in these situations with nested views and sub eachs I always find the code to be more maintainable with CollectionViews. Otherwise you end up stuffing too much inside of a single template/view.
http://docs.emberjs.com/#doc=Ember.CollectionView&src=false