Ember.js add class to action helper template - ember.js

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.

Related

ember.js list template keeps getting bigger on each visit

Summary
I have a problem with a list displayed by Ember which keeps displaying extra rows each time it is visited. The extra rows are duplicates of those which were initially displayed.
Detail
In an Emberjs 2.13.0 app I have a model that looks like this :
import DS from 'ember-data';
export default DS.Model.extend({
cceIdentifierParent: DS.attr('string'),
cchCceIdParent: DS.attr('string'),
nodeType: DS.attr('number')
});
I have a route, 'diagcctreetoplevelonly', which looks like this :
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('diagcctreetoplevelonly');
}
});
And a template that looks like this :
{{diag-warningbanner}}
{{#if model.length}}
<table>
<thead>
<tr>
<th>
cceIdentifierParent
</th>
<th>
cchCceIdParent
</th>
<th>
nodeType
</th>
</tr>
</thead>
<tbody>
{{#each model as |treenode|}}
<tr>
<td>
{{treenode.cceIdentifierParent}}
</td>
<td>
{{treenode.cchCceIdParent}}
</td>
<td>
{{treenode.nodeType}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
<p id="blankslate">
No Tree Nodes found
</p>
{{/if}}
{{outlet}}
That works fine the first time that 'diagcctreetoplevelonly' is visited - 12 rows are rendered - but on subsequent visits (without the underlying data having changed) the table rendered by the template has 12 extra rows for each time it has been visited.
Can anyone explain what i'm doing wrong ? Thank you.
EDIT: Thanks to the input from #Jeff and #Subtletree I was able to resolve this.
The problem was that the data returned had no 'id' attribute and when I created one the problem went away.
Because of the peculiar nature of the data it didn't actually matter what the id was and I didn't want to make changes to the backend so I created an id dynamically once the data had arrived on the client by creating a model level serializer and overriding the extractId method like this :
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
extractId(modelClass, resourceHash) {
var arrId = [];
arrId.push(resourceHash['attributes']['cceIdentifierParent']);
arrId.push(resourceHash['attributes']['cchCceIdParent']);
arrId.push(resourceHash['attributes']['nodeType']);
var id = arrId.join('|');
return id == null || id === '' ? null : id+'';
},
});
It wouldn't have worked in all (perhaps most ?) situations but for my case this was good enough and resolved the problem.
To provide credit where it's due I got the idea for how to do this from the answer by #Casey here https://stackoverflow.com/a/35738573/364088 .
When ember-data receives records from a server it tries to match them to records already in the store by their id. If no id's are present then it can't find a match so instead of updating them it will just add them.
You could add an id to each record or could fetch the data with ajax and not use ember-data for this model.

How would you bind a dynamic value to a dynamic component in Handlebars/EmberJS

I'm creating a dynamic table component (one row per model), that will include components dynamically (one column for each object in config, each object relates to a key in a model).
I'm trying to bind the model key to the dynamic model.
Any ideas on how to do that given the following?
Config object:
deployment.js (controller)
EDConfig: {
controller: this,
modelType: 'EscalationDetailModelGroup',
table: {
cols: [{
header: 'Escalation Time',
cname: 'form-input-text',
content: {
value: model.escalationTime //obviously this wont work
}
},{
header: 'Most Complex Alarm Level',
field: 'mostComplexAlarmLevelDispatched',
cname: 'form-input-text',
content: {
value: model.escalationTime //obviously this wont work
}
}]
}
};
Router Model:
deployment.js (router)
modelRange: [{
id: 1,
escalationTime: '3 hours',
mostComplexAlarmLevelDispatched: 'N/A'
}, {
id: 2,
escalationTime: '45 minutes',
mostComplexAlarmLevelDispatched: 'Level 3'
}]
Templates:
deployment.hbs
<h2>Deployments</h2>
{{table-list
config=EDConfig
data=model.escalationDetailModelGroups
}}
table-list.hbs
<table>
<thead>
<tr>
{{#each col in config.table.cols}}
<th>{{col.header}}</th>
{{/each}}
</tr>
</thead>
<tbody>
{{#each record in modelRange}}
<tr>
{{#each col in config.table.cols}}
<td>
{{component col.cname content=col.content}}
</td>
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
I'm still not sure how are you trying to merge/link the data, but I doesn't seem to be really important.
I don't think its necessary to pass two data sources to your table-list, the relationships between config and model are not something that you should be doing in the templates. Its more of a data-decoration process and that type of thing should be done at the controller level.
How about something like:
// controller
tableRows: function() {
var config = this.get('config');
var model = this.get('model');
config.forEach(function(col) {
// give each col a model reference
});
return config;
}.property('config', 'model')
// template
{{table-list data=tableRows}}
I just typed that off the top of my head, tweaks would be needed most likely, but the idea should be clear.

How to expand table row in Ember 1.8?

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.

Ember Ember.Select binding

I am trying to create a simple page where I loop through a list of phone numbers associated with a Contact. Each phone number has a "number" and a "phone_type".
I've created a View that extends Ember.Select that populates itself with a the list of phone_types. Other than that, is's just a plain Ember.Select:
export default Ember.Select.extend({
thestore: '',
optionLabelPath: 'content.code',
optionValuePath : 'content.code',
didInsertElement: function() {
var vtype = this.get("valuetype");
var vls = this.get("thestore").filter('valuelist', { type: 'phone_type' }, function(vv) {
return vv.get("type") == vtype;
});
this.set("content",vls);
}
});
Here is my code in the template using the "valuelist" view defined above.
{{#each phonenumber in model}}
<tr>
<td> {{phonenumber.number}}</td>
<td>{{phonenumber.phone_type}}</td>
<td>{{view 'valuelist' thestore=store valuetype='phone_type'
selection="{{phonenumber.phone_type}}"
value="phonenumber.phone_type" }}</td>
</tr>
{{/each}}
What I cannot figure out is how to bind the value in the dropdown to the field in each model record I am iterating through in the template. You can see I've tried various things in the code above without any luck.
The property you need is value. However, in your attempts above, you were filling it with literal strings. This happens when you provide a value wrapped in quotes ('...' or "..."). What you need is to give it an identifier, which is value without quotes. So, try this:
{{#each phonenumber in model}}
<tr>
<td>{{phonenumber.number}}</td>
<td>{{phonenumber.phone_type}}</td>
<td>{{view 'valuelist' thestore=store valuetype='phone_type'
value=phonenumber.phone_type }}</td>
</tr>
{{/each}}
As an aside, this is a very unortodox way of doing things. A view shouldn't be tied to a store. Also, I think this will cause your select to be unusable while the values load asynchronously (and potentially crash your app if there is an error).
A conventional way to do this would be to load the list of all phone_types in your setupController hook and then provide it as an argument to Select view.
Controller:
App.MyRoute = Ember.Route.extend({
//...
setupController: function (c, m) {
c.set("model", m);
c.set("phoneTypes", [
"home", "office"
// or whatever, load it from store in model hook and setup here
]);
}
});
Template:
{{#each phonenumber in model}}
<tr>
<td>{{phonenumber.number}}</td>
<td>{{phonenumber.phone_type}}</td>
<td>{{view Ember.Select
content=phoneTypes
value=phonenumber.phone_type }}</td>
</tr>
{{/each}}

Ember.js/rendering nested array content on handlebars

I have a model 'transaction' in which an array of subCategories is declared. This array is populated with transaction type objects whenever the method 'add_subcagtegory' of transactionsController is called. Now when i try to render subcategories in a nested loop(#collection), i do not get it done. The outer loop(#each) that is rendering the array controller objects is working fine. Can anyone tell how to render the subCategories array?
app.js
App.transaction=Em.Object.extend({
account:null,
date:null,
source:null,
description:null,
category:null,
flag_for_later:null,
amount:null,
category_id:null,
record_index:null,
isSubCategory:null,
subCategories:[]
});
App.transactionsController = Em.ArrayController.create({
content: [],
add_subcategory: function(param){
var records=this.toArray();
if (typeof(records[param.value -1].subCategories) === "undefined") {
records[param.value -1].subCategories = new Array();
}
var category=App.transaction.create({
account:"//",
date:"//",
source:"//",
description:"//",
category:" ",
flag_for_later:" ",
amount:null,
category_id:records[param.value -1].subCategories.length + 1,
isSubCategory:true
});
records[param.value -1].subCategories.push(category);
App.transactionsController.set('content',[]);
App.transactionsController.pushObjects(records);
App.array.push(obj1);
}
});
and the template:
<table>
{{#each App.transactionsController}}
<tr>
<td>{{account}}</td>
<td>{{date}}</td>
<td>{{source}}</td>
<td>{{view App.TextField class="span12" style="border:0px;" objcount=record_index fieldname="description" value=description}}</td>
<td>{{view App.TextField class="span12" style="border:0px;" objcount=record_index fieldname="category" value=category }}</td>
<td><button onclick="App.transactionsController.add_subcategory(this);" value="{{unbound record_index}}">+</button></td>
<td>{{view App.TextField class="span6" style="border:0px;" objcount=record_index fieldname="flag_for_later" value=flag_for_later }}</td>
<td>{{amount}}</td>
</tr>
{{#collection contentBinding="App.transactionsController.subCategories"}}
<b>content does,nt not render</b>
{{/collection}}
{{/each}}
</table>
in the template under collection,How can I access subCategories?
http://jsfiddle.net/KbN47/29/
Does simply binding the content of the {{collection}} helper to this.subcategories (this is a transaction in your context) work ?
{{#collection contentBinding="this.subcategories"}}
Update
Here is a jsfiddle: http://jsfiddle.net/Sly7/tRbZC/
Please note the ember version is the latest-one. You should update, as the 0.9.5 is very old.
I didn't have a look of the <select> behavior, but if it does'nt work, I think you have now all the keys to make it works :)
I modified the ember version from latest to pre 1.0 and the clicking on the + works.
http://jsfiddle.net/y3YX9/