Ember.js/rendering nested array content on handlebars - ember.js

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/

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.

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.

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}}

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.

How to create an external link from emberjs loop?

I'm looping through several items in a model. For each item I would like to create an external link to a page in my application that is not using emberjs. I thought this would be trivial but its not working the way I thought it would work.
This is what I have:
<tbody>
{{each model itemViewClass=App.ColorView}}
</tbody>
<script type="text/x-handlebars" id="colorTemplate">
<tr>
<td>{{date}}</td>
<td>{{name}}</td>
</tr>
</script>
App.ColorView = Em.View.extend({
templateName: 'colorTemplate'
});
I thought this would create links like these:
/myapp/colors/5/shades
/myapp/colors/45/shades
/myapp/colors/6/shades
...etc.
However, the link is being created like this:
localhost:8080/myapp/colors/%3Cscript%20id='metamorph-33-start'%20type='text/x-placeholder'%3E%3C/script%3E56%3Cscript%20id='metamorph-33-end'%20type='text/x-placeholder'%3E%3C/script%3E/shades
You Should implement a itemController to the {{#each}} collection view. In that itemController you can use a computed property to generate the url as
url : function () {
return "/myapp/colors/"+this.get('id')+"/shades";
}.property()
Here is a Sample Bin
Hope it helps
you should use {{bindAttr href="url"}} for this. url is a method that generates url