I have a controller with data about user accounts (icon, name, provider, etc.). Within the output of the each loop I have a view that will build a CSS class dynamically based on the provider passed in via that specific model.
<script type="text/x-handlebars" data-template-name="accountItem">
{{#each account in controller}}
{{#view App.AccountView}}
<h4>{{account.name}}</h3>
<img {{bindAttr src="account.icon"}} />
<i {{bindAttr class="account.provider"}}></i>
{{/view}}
{{/each}}
</script>
App.AccountView = Ember.View.extend({
tagName: 'a',
classNames: ['avatar-image'],
providerClass: function(el) {
// do something
}
});
The question I have is two-fold.
How do you pass in "account", or the currently iterated item, into the view?
After you pass it in, how do you reference it?
I'm sure this is something that happens quite often but I can't seem to find any examples. Can anyone offer some input on this please?
Views has a special content property in a view which allows a more simple approach: you just use a name of the model's property without the view.content. part.
Also, when you're iterating over controller, you can omit the name of loop variable and use this instead, like in this guide. This is not necessary but can make the code a bit cleaner.
Also, from within view's template you generally don't need to reference the outside variables although you can if you like..
{{#each controller}}
{{#view App.IndexView contentBinding="this"}}
<h4>{{name}}</h4>
<img {{bindAttr src="icon"}} />
<i {{bindAttr class="provider"}}></i>
<i> {{icon}} </i>
<i>{{provider}}</i>
{{/view}}
{{/each}}
And you can always access the content property from within the view with:
this.get('content');
The currently iterated item can be passed into the view with the help of property bindings and it can be refered as "{{view.property}}" in the template. For example:
{{#each account in controller}}
{{#view App.IndexView itemBinding="account"}}
<h4>{{view.item.name}}</h3>
<img {{bindAttr src="account.icon"}} />
<i {{bindAttr class="account.provider"}}></i>
<i> {{view.item.icon}} </i>
<i>{{view.item.provider}}</i>
{{/view}}
{{/each}}
I have created a simple jsfiddle for the above case. Do check it and let me know if you were able to resolve the issues.
Fiddle url : http://jsfiddle.net/nCyn6/3/
Related
I've created view App.TestView which works fine outside following each loop. The code is displaying properly test loop_element.
App.TestView = Ember.View.extend({});
{{#view App.TestView}}
test
{{/view}}
{{#each controller.positions itemController='url'}}
loop_element
{{/each}}
On the other hand, when I put child view inside each loop
{{#each controller.positions itemController='url'}}
loop_element
{{#view App.TestView}}
test
{{/view}}
{{/each}}
is not displaying anything. There isn't any error message. What can be wrong? Why can't I use views inside loop?
It looks like it's working to me. Are you sure you're looping the same thing? You wrote two different items that you were iterating. controller.positions is probably empty
App.TestView = Em.View.extend({
});
App.UrlController = Em.ObjectController.extend({
});
<script type="text/x-handlebars" data-template-name="index">
{{#each controller.positions itemController='url'}}
loop_element
{{#view App.TestView}}
test2
{{/view}}
{{/each}}
</script>
http://emberjs.jsbin.com/bapugowi/1/edit
The thing was that the loop was inside <tbody> tag. View was rendered in other tag than <tr> and it was breaking my web browser.
I have the following template where I loop over a list of objects and want to have a checkbox that is bound to a field isChecked for that object. This needs to be in a view helper in order to get the for tag to work (I think). When I do this I can't seem to figure out how to keep the binding with the isChecked field.
{{#each listEntry in listEntries}}
{{#view}}
{{view Ember.Checkbox viewName="checkboxView" checkedBinding="listEntry.isChecked"}}
<label {{bindAttr for="view.checkboxView.elementId"}}>Option 1</label>
{{/view}}
{{/each}}
Your question is similar to that, but that approach not work, I think is because the each helper.
But one of the comments say about nesting your component in the label.
I have done that and works.
{{#each listEntry in listEntries}}
<label>
{{view Ember.Checkbox viewName="checkboxView" checkedBinding="listEntry.isChecked"}}
Option 1
</label>
{{/each}}
I have created a jsfiddle showing
This is what I ended up doing. The problem I kept having was the need for the binding for the "for" attribute was not working in conjunction with the checked binding. Things were out of scope. If anyone has a better way to accomplish this, please let me know.
{{#each listEntry in ListEntries}}
{{#if ../isCheckable}}
{{#with ../listEntry}}
{{#view listEntryBinding="this"}}
{{view Ember.Checkbox viewName="checkboxView" checkedBinding="listEntry.isChecked"}}
<label {{bindAttr for="checkboxView.elementId"}}></label>
{{/view}}
{{/with}}
{{/if}}
{{/each}}
I have the following code in a ember.js template. userController is an ArrayController, with multipe "users" within.
{{#each CollaborativeEditor.userController}}
{{#view CollaborativeEditor.OnlineUserView userBinding="this"}}
<div class="avatar">
<div class="avatar_name">{{name}}</div>
<div class="avatar_status">{{status}}</div>
</div>
<div id="dropdown-1">
<ul>
<li><a href="#" {{action startChat target="onlineUser"}}>Talk to </a></li>
</ul>
</div>
{{/view}}
{{/each}}
This is the code of the respective view:
CollaborativeEditor.OnlineUserView = Ember.View.extend({
tagName: 'li',
startChat : function() {
console.log(this.get('user'));
}
});
Although, the name and status is set correctly for each user, the startChat action attached to the link always prints the first user of the array to the console.
What is wrong with the binding ?
Thanks a lot for your request, to put it in a jsfiddle !
While I was trying to reproduce the error there, I realized the problem, and it has nothing to do with ember.
The div with id="dropdown-1" was called from another link and it was always the same id, therefore always the same action with its user-binding.
Now I've bound the Id to the user-object and it works perfectly.
Controller:
games : Ember.A(["1", "2", "3"]),
View:
{{#each view.games}}
{{view Ember.TextField valueBinding="this"}}
{{/each}}
when I change the value of the textfield, it didn't change the array at the same time?
Hum, I feel weird on this... If you use objects in the array, then the bindings works well, with strings, as you do, effectively it's not working...
<script type="text/x-handlebars">
<h5> work with object </h5>
{{#each plop in App.controller1}}
{{view Em.TextField valueBinding="plop.name"}} {{plop.name}}
{{/each}}
<h5> don't work with strings </h5>
{{#each plop in App.controller2}}
{{view Em.TextField valueBinding="plop"}} {{plop}}
{{/each}}
</script>
window.App = Ember.Application.create({
controller1: Ember.ArrayController.create({
content: [{name:"aaaa"}, {name:"bbbbb"}, {name:"ccccc"}]
}),
controller2: ["aaaa", "bbbbb", "cccc"]
});
http://jsfiddle.net/Sly7/SQ5g8/
EDIT: Thanks to Kris Selden, who explains me that is intented behavior, since two ways bindings work through obj[key], obviously, a string has no such key. And to conclude, there is no binding based on array position.
I'm triyng to use the view helper inside my {{#each}} template blocks without using global paths (my controllers create and destroy their own views).
Examples. Given a view with a myList array property, and an itemButton child view:
This will work
<script type="text/x-handlebars" name="my-list-view">
{{#each myList}} <!-- App.myListView.myList -->
{{view App.myListView.itemButton}} {{title}}
{{/each}}
</script>
This will not:
<script type="text/x-handlebars" name="my-list-view">
{{itemButton}} <!-- works fine outside the each -->
{{#each myList}}
{{view itemButton}} {{title}} <!-- itemButton view not found -->
{{/each}}
</script>
I do not appear to be able to access the parent view from the each view helper (or in fact access anything other than the properties of the objects being iterated).
The hacky workarounds I've come up with are:
Add the view I want to use to the items I'm iterating over.
or
Creating a collectionView in App.myListView
Create an itemViewClass view in that collection view class
Move the itemButton view inside the itemViewClass
Replace {{#each}} with {{#collection}}
or
Create a custom handlebars helper for iteration.
Both of these options seem horrible.
Surely there's a better alternative than creating 2 new classes (and nesting 4 views deep) just to iterate over a list, though. Is there a replacement handlebars helper I can use instead?
Workaround implementations
Option #1 : Modifing the content array
http://jsfiddle.net/FQEZq/3/
Disadvantages: Having to add the view to each model instance just for iteration.
Option #2 : Custom collection view
http://jsfiddle.net/ST24Y/1/
Disadvantages: Now you have two additional views that you do not need / want, and less control of markup. References from the child view to the parent instance now requires parentView.parentView.parentView.
#each is too limited for your requirements. You can make it work if you're willing to use a global path to the view you want to nest within the #each. Otherwise, your collection view approach is best. Adding the view to the model instance is likely to muddy your app design something fierce, so I would avoid that.
One idea to keep your templates clean is to take advantage of Ember.View properties like:
collectionView - Return the nearest ancestor that is an Ember.CollectionView
itemView - Return the nearest ancestor that is a direct child of an Ember.CollectionView
contentView - Return the nearest ancestor that has the property content.
The big thing here - options.
Hooks for how you wish to use a template are available. These are:
<-- render templates/name.js -->
{{partial 'name'}}
<-- render views/name.js -->
{{view 'name'}}
<-- render views/name1.js with controllers/name2.js -->
{{render 'name1' 'name2'}}
<!-- see also: -->
{{output}}
{{output 'named'}}
{{yield}}
An example variant of your initial template showing 4 different options:
<script type='text/x-handlebars' data-template-name='my-list-view'>
<h2>Option 1</h2>
{{#each myList}}
{{! the 2nd parameter will look for itemButtonController }}
{{render 'itembutton' itemButton}}
{{/each}}
<h2>Option 2</h2>
{{#each myList}}
{{! using an Ember Component }}
{{#item-button }}
some static text OR {{dynamic}}
{{/item-button}}
<!-- in component/item-button.hbs add {{yield}} for where you want the static content to output -->
{{/each}}
<h2>Option 3</h2>
{{#each myList}}
{{! if the button is to be a link }}
{{#link-to 'route' parameter tagName='button' classNames='btn'}}
{{title}}
{{/link-to}}
{{/each}}
<h2>Option 4</h2>
<p>Ludicrous example showing almost every reference option!</p>
{{! focus the context on subview data }}
{{#with someArrayOrCollectionOfView}}
{{! prepend type to add context - here returning back up to this view's properties }}
{{#each view.myList}}
{{! this is equivalent to someArrayOrCollectionOfView[x].name }}
{{name}}
{{! a button that hooks in route, model, and 2 controllers, and applies a target for the output when clicked }}
{{#link-to 'page' controllers.page.nextPage target='outlet' tagName='button' disabledWhen=controller.disableNext }}
{{model.buttonName}}
{{/link-to}}
{{/each}}
{{/with}}
{{! generic default target (automatic) }}
{{outlet}}
{{! example of a named target }}
{{outlet 'footerlinks'}}
</script>
Mmmm... reference for further reading:
Ember.Handlebars.helpers