How to expand table row in Ember 1.8? - ember.js

I would like to make my row expanded on click. Similar effect takes place here. The problem is that I am getting tr inside tr when using each on new handlebars 1.8.
{{#each positions itemController='position' itemView='url'}}
<td>{{position}}
{{#if showExpanded}}
{{render 'positionDetails' this}}
{{/if}}
{{/each}}
App.UrlView = Ember.View.extend
tagName: 'tr'
App.PositionDetailsView = Ember.View.extend
tagName: 'tr'
Results:
<tr class="ember-view" id="ember7755">
<td>1</td>
<tr class="position-details-view">
</tr>
How can I make it works?
I've manged to create only expending function
App.PositionDetailsView = Ember.View.extend
tagName: 'tr'
classNames: 'expanded'
didInsertElement: (->
parent = #.$().parent()
#.$().show().detach().insertAfter(parent)

With your existing code, you will always have one tr nested inside the other because the itemView for the each has tagName: 'tr', while the positionDetailsView nested inside of it also has the tagName set to 'tr'.
For the results you want, you need a (non-tr) item view that contains both of the <tr> elements. However because it's a table, the default div element for Ember views won't work. Fortunately we can nest the rows in a tbody, and (more importantly) we can have multiple tbody elements in the same table.
That would lead us to this:
{{#each positions itemController='position' itemView='url'}}
<tr>
<td>{{position}}</td>
</tr>
{{#if showExpanded}}
{{render 'positionDetails' this}}
{{/if}}
{{/each}}
App.UrlView = Ember.View.extend
tagName: 'tbody'
App.PositionDetailsView = Ember.View.extend
tagName: 'tr'
This assumes you want to keep UrlView as the entire section for a single element of positions. If not, you can keep the old UrlView, define a separate view to be the tbody, and wrap the position td above with {{#view 'App.UrlView'}}...{{/view}} instead of tr.
In any event, I would not recommend manually manipulating the DOM when using Ember views, as suggested by your edited question. But that code should no longer be necessary with these changes.

Related

Change class on tr element after action on tr

With Ember 2.0 coming down the pipeline and the move away from itemControllers & views, what is the best way to apply the selected class to the currently selected tr element?
Originally there was just the each loop within the controller template that set itemControllers on each tr element. The itemController would then hold the isSelected property as well as hoisting it into the parentController upon select.
Selection currently is working without issues with the bindings of pumpSelected to a property passed into the component.
Although the code is a bit cleaner after the refactor, it's just pushed the need for the itemController lower to me. Any help appreciated.
Component .hbs snippet:
{{#each pump in results}}
<tr {{action 'select' pump}} {{bind-attr class='isSelected:selected'}}>
<td>{{pump.modelNumber}}</td>
</tr>
{{/each}}
Component Definition:
PumpResultComponent = Ember.Component.extend
tagName: 'table'
classNames: ['table', 'table-striped']
actions:
select: (selectedPump)->
#set 'pumpSelected', selectedPump
The way I'm doing this right now is by wrapping the content in a list and defining the selected state in each item. In your template you can than loop over this wrapper list, so in your case:
PumpResultComponent = Ember.Component.extend({
pumpList: function() {
var selected = this.get('pumpSelected');
return this.get('results').map(function(pump) {
return {
isSelected: selected && pump.get('id') === selected.get('id'), // or some other equality property
pump: pump
};
});
}.property('pumpSelected','results')
});
{{#each item in pumpList}}
<tr {{action 'select' item.pump}} {{bind-attr class='item.isSelected:selected'}}>
<td>{{item.pump.modelNumber}}</td>
</tr>
{{/each}}

How do I render a collection of models each with variable view and controller?

I have a collection of models in my Ember.js app, which I would like to render. The catch is that I want to be able to specify a specialized view and controller for each of the models.
The controller part seems to be easy: I would just wrap the array in an ArrayController and implement itemController method. The view part is where it gets tricky. I don't see an obvious idiomatic way of doing this.
The best way we came up with is the combination of ArrayController and CollectionView with an overridden createChildView. For instance:
createChildView: function(viewClass, attrs) {
var viewInstance,
widgetType = attrs.content.get('type');
// lookup view, if found, use it, if not, pass empty view
var viewDefined = this.container.lookup('view:' + widgetType);
var createWidgetType = viewDefined ? widgetType : 'empty';
// create view instance from widgetType name
// it causes lookup in controller
viewInstance = this._super(createWidgetType, attrs);
// if `attrs.content` is controller (item of `ComponentsController`)
// set it as controller of newly created view
if(attrs.content.get('isController')) {
viewInstance.set('controller', attrs.content);
}
return viewInstance;
}
This feels unnecessarily convoluted, I don't like that I have to connect the view with the controller manually like that. Is there a cleaner way?
You can create a component, which will act as controller and have a view associated with it:
App.XItemComponent = Ember.Component.extend({
controllerProperty: '!',
tagName: 'li'
});
Then, you can just do:
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each model }}
{{ x-item item=this }}
{{/each}}
</ul>
</script>
http://emberjs.jsbin.com/wehevixolu/1/edit?html,js,output
I'd use the {{render}} helper. It'll create a view and controller for each instance.
{{#each item in model}}
{{render "item" item}}
{{/each}}
Example: http://emberjs.jsbin.com/vuwimu/2/edit?html,js,output
Render helper guide: http://emberjs.com/guides/templates/rendering-with-helpers/#toc_the-code-render-code-helper
Additionally:
In your comment you mentioned you want different controller/view types for particular model types. This could be done like this:
{{#each item in model}}
{{#if item.typeX}}
{{render "itemX" item}}
{{/if}}
{{#if item.typeY}}
{{render "itemY" item}}
{{/if}}
{{/each}}
or if you'd choose to go with components:
{{#each item in model}}
{{#if item.typeX}}
{{component-x item=item}}
{{/if}}
{{#if item.typeY}}
{{component-y item=item}}
{{/if}}
{{/each}}
Without knowing what you are trying to accomplish in more detail it’s hard to tell what the best solution is.

Ember.js add class to action helper template

How can I specify a class to an item when clicked with Ember. I am using a Handlebars action on the elements of a table to sort by properties. I want to add a class to the property being sorted so I can show the user the current property being sorted. How can I do this?
I have an ember controller shown below:
App.UsersController = Ember.ArrayController.extend
title: 'Users'
count: Ember.computed.alias 'length'
sortProperties: ['username']
actions:
sort: (property) ->
console.log property
if #get('sortProperties')[0] is property
#set('sortAscending', !#get 'sortAscending')
else
#set 'sortProperties', [property]
#set('sortAscending', true)
The controller allows me to click on headings in a table to sort the table. The html is shown below:
<thead>
<tr>
<th>Action</th>
<th class="sort" {{action sort 'last'}}>Last</th>
<th class="sort" {{action sort 'first'}}>First</th>
<th class="sort" {{action sort 'username'}}>Username</th>
<th class="sort" {{action sort 'email'}}>Email</th>
</tr>
</thead>
Create a currentSort property (optional)
First, I created a currentSort property on your App.UsersController which cleans a little bit the code. We'll use it later.
App.UsersController = Ember.ArrayController.extend
sortProperties: ['username']
currentSortBinding: 'sortProperties.firstObject'
actions:
sort: (sort) ->
if sort is #get('currentSort')
#toggleProperty 'sortAscending'
else
#setProperties
currentSort: sort
sortAscending: true
Define a custom view for each <th>
You'll then have to define a custom view for the <th> which will do 2 things:
Have a class active-sort when the sort of the view is the current
Change the controllers current sort when clicking the view
It will looks like this:
App.SortView = Ember.View.extend
template: Ember.Handlebars.compile('{{view.sortName}}')
tagName: 'th'
sortName: null # (will be different for each <th> : `last`, `first`,..)
classNameBindings: [ ':sort', 'isCurrent:active-sort' ]
isCurrent: (->
#get('sortName') is #get('controller.currentSort')
).property('sortName', 'controller.currentSort')
click: ->
var newSort = #get('sortName');
#get('controller').send('sort', newSort);
Here we customized the view class names, and we handled click event on the view.
Insert a custom view for each sort
This is really simple to insert views in templates:
<thead>
<tr>
{{view App.SortView sortName="default"}}
{{view App.SortView sortName="price"}}
{{view App.SortView sortName="alphabetical"}}
</tr>
</thead>
You can test all of this in a working JSBin
I don't know if this is the best workaround, but you could have some "sort flag" that you could bind CSS on.
In your controller (in "classic" javascript) :
sortedByLast : function() { return this.get("sortProperties")[0] === "last" }.property("sortProperties.#each")
// etc ...
In your template :
<th {{bind-attr class=":sort sortedByLast:current-sort"}} {{action sort 'last'}}>Last</th>
So the sort class would always be on, and the current-sort would only be there if it match its proper flag.

Emberjs action in collectionView

I have managed to use the collectionView like this :
App.GroupList = Ember.ArrayController.create({
content: this.getGroupList(),
update: function() {
this.setProperties({content: getGroupList()});
}
})
App.GroupListView = Ember.CollectionView.extend({
tagName: 'tbody',
contentBinding: "App.GroupList"
})
<table class="table-history">
{{#collection App.GroupListView}}
<td class="col-left">
{{view.content.groupName}}
<span class="col-number-contact">{{view.content.participantCount}}</td>
</td>
<td>
<div class="ph-img"></div>
</td>
<td>
<a {{action "editGroup" view.content}}>
<div class="ed-img"></div>
</a>
</td>
{{/collection}}
</table>
And as you can see I have put an action helper {{action "editGroup" view.content"}} and this is working well to handle the action in the controller. But I need to handle this action in the view.
When I put the helper like this {{action "editGroup" view.content target=view}} it doesn't work. I have no error, but I also can't catch the event in my view.
My template is called home, and my view App.HomeView = Ember.View.extend({}).
I really don't understand...
I have create a fiddle to show you that it doesn't work : http://jsfiddle.net/NQKvy/902/
If you remove the target=view in the action helper it will trigger the controller handler but nothing happend in the view..
[edit]: Otherwise, is it possible to access a view variable from a controller ? to edit or update a view variable from a controller.
In your case target="view" will target anonymous view created for each GroupListView's item. You'll probably want something like:
App.GroupListView = Ember.CollectionView.extend({
tagName: 'tbody',
contentBinding: "App.GroupList",
actions: {
editGroup: function() {
alert("You Clicked - in GroupListView");
}
}
})
<a href="#" {{action "editGroup" view.content target="view.parentView"}}>
LINK
</a>
Then you can handle your clicks in GroupListView. If you really need it to be in IndexView, you can add another layer of indirection: target="view.parentView.parentView". However, code becomes really brittle then, so I wouldn't recommend it.
At this point, I should warn you that the way you're using views and controllers is not following Ember's guidelines (controller naming, manual controller creation and then binding to a hard-coded instance, ...). Before digging yourself deeper, I would suggest you go through Ember's docs once again and reexamine your code before it calcifies in its current shape.

custom Handlebars helper - parameter is not resolved

# here is CreditCards controller context
{{#with controllers.currentCardCategory}}
{{#each property in cardProperties}}
{{#is property.symbol 'your_savings'}}
<td class="td">{{yourSavings}}</td>
{{else}}
<td class="td">{{cardProperty this property.symbol}}</td>
{{/is}}
{{/each}}
{{/with}}
I create the table dynamically. All content comes from ArrayController except one that is a computed property that comes from the controller. symbol' field is underscored likeannual_fee' and belongs to CreditCardProperty. Each card category has different set of card properties. Only two categories have properties (category has many card properties) and one record has computed field set to true. That means that the template should look up the corresponding controller.
As symbol (e.g age_to_apply) relates to one of the CreditCard fields (ageToApply) all I could to figure out was to use the cardProperty helper which takes current card in the context (this) and resolves property.symbol, e.g:
camelizedProperty = categoryProperty.camelize()
card.get 'camelizedProperty'
Ideally I'd like to do without the helper and use it somehow like this:
# *** {{property.symbol}} should look up this context
# and become e.g {{annual_fee}} or {{annualFee}} - is it possible?
{{#with controllers.currentCardCategory}}
{{#each property in cardProperties}}
<td class="td">{{property.symbol}}***</td>
{{/each}}
{{/with}}
But the problem is that I don't know how can I render that '{{yourSavings}}' part. The helper you can see comes from swag collection of Handlebars helpers. The helper, unfortunately does not resolve properties so that property.symbol becomes a string.
Here it is:
Handlebars.registerHelper 'is', (value, test, options) ->
if value is test then options.fn(#) else options.inverse(#)
I think it is possible but with the right helper - don't know which one, though.
What I do would like to avoid is to resort to computed property like if isYourSavings.
I am not certain about the context of your code, but it seems like you are looking for registerBoundHelper with a block helper. This isn't supported. You will run into this warning,
Assertion failed: registerBoundHelper-generated helpers do not support use with Handlebars blocks.
An alternative way to do what you are doing is to use a view helper instead. A view helper is like a helper but can render with a custom template.
For instance a CardItemView would be,
App.CardItemView = Em.View.extend({
templateName: 'cardItemTemplate'
});
Em.Handlebars.registerHelper('cardItem', App.CardItemView);
Where the cardItemTemplate is,
<script type='text/x-handlebars' data-template-name='cardItemTemplate'>
{{#if view.property.symbol}}
<td class="td">{{yourSavings}}</td>
{{else}}
<td class="td">{{cardProperty view.property.symbol}}</td>
{{/if}}
</script>
And you could use the helper like so,
{{#with controllers.currentCardCategory}}
{{#each property in cardProperties}}
{{cardItem property=property etc}}
{{/each}}
{{/with}}
You can pass in any number of properties as attributes. These will be bound to the CardItemView. And since it's a view anything a view does, like custom computed properties, can be done in CardItemView.