Is there a way to get positional index during iteration in ember.js?
{{#each itemsArray}}
{{name}}
{{/each}}
I'm looking for a way to have something like:
{{#each itemsArray}}
{{name}} - {{index}}th place.
{{/each}}
Update:
As per the comment by #ebryn the below code works without using a nested view for each item:
<script type="text/x-handlebars">
{{#collection contentBinding="App.peopleController"}}
Index {{contentIndex}}: {{content.name}} <br />
{{/collection}}
</script>
http://jsfiddle.net/WSwna/14/
Although to have something like adjustedIndex, a nested view would be required.
It's old question but still gets a lot of hits. In the current version of Ember.JS one can use _view.contentIndex to display current index inside the loop.
{{#each}}
Index: {{_view.contentIndex}}
{{/each}}
If you need an adjusted index (for instance starting from 1) then there is a possibility of creating reusable helper increment
Ember.Handlebars.registerBoundHelper('increment', function(integer) {
return integer + 1;
});
then you would use it in the following way
{{#each}}
Index: {{increment _view.contentIndex}}
{{/each}}
Update
Starting with ember 1.11 you can do as well
{{#each people as |person index|}}
Index: {{increment index}}
{{/each}}
In RC6 CollectionView provides contentIndex propery for each rendered view of collection. Each helper uses CollectionView in its implementation so uou can access index in this way:
{{#each itemsArray}}
{{_view.contentIndex}}
{{/each}}
Actually yes you can get the position of the current index using the {{each}} helper. You have to create a view for every item in a list and then use {{_parentView.contentIndex}}.
<script type="text/x-handlebars">
{{#each App.peopleController}}
{{#view App.PersonView contentBinding="this"}}
Index {{_parentView.contentIndex}}: {{content.name}} {{adjustedIndex}} <br />
{{/view}}
{{/each}}
</script>
App = Ember.Application.create();
App.peopleController = Ember.ArrayController.create({
content: [ { name: 'Roy' }, { name: 'Mike' }, { name: 'Lucy' } ]
});
App.PersonView = Ember.View.extend(Ember.Metamorph, {
content: null,
// Just to show you can get the current index here too...
adjustedIndex: function() {
return this.getPath('_parentView.contentIndex') + 1;
}.property()
});
See this jsFiddle for a working example.
As of Ember 9.8 and pre-1.0 you can wrap the "contentIndex" with a view in order to get at the virtual parent (the {{#each}}). If you don't add the view, your context ends up being either the main template, an item in your list or whatever you manually set with your {{#with}}. It is not impossible to get at the {{#each}} from the JS side but it is a lot more of a pain flipping through those child views.
{{#each App.peopleController}}
{{#view}}
Index {{view._parentView.contentIndex}}: {{name}} <br />
{{/view}}
{{/each}}
...OR...
{{#each people in App.peopleController}}
{{#view}}
Index {{view._parentView.contentIndex}}: {{people.name}} <br />
{{/view}}
{{/each}}
Just in-case you would like a fiddler.
DEMO
Note: You can create a view and do a this.get("_parentView.contentIndex") to get at the index if you want to modify the number at all.
This isn't currently a feature of Handlebars or Ember.Handlebars. We have contentIndex available inside #collection/Ember.CollectionView. I think it's useful to support in #each too. Please file an issue at the GitHub repository: https://github.com/emberjs/ember.js/issues
As of Ember 1.11.0, index is an optional parameter in each blocks:
{{#each items as |item index|}}
{{item.name}} is at index {{index}}
{{/each}}
I have modified a bit ud3323 solution using collection.
Check here: http://jsfiddle.net/johngouf/WSwna/13/
<script type="text/x-handlebars">
{{#collection contentBinding="App.peopleController"}}
{{#view App.PersonView contentBinding="this"}}
Index {{_parentView.contentIndex}}: {{content.name}} {{adjustedIndex}} <br />
{{/view}}
{{/collection}}
</script>
App = Ember.Application.create();
App.peopleController = Ember.ArrayController.create({
content: [ { name: 'Roy' }, { name: 'Mike' }, { name: 'Lucy' } ]
});
App.PersonView = Ember.View.extend({
content: null,
// Just to show you can get the current index here too...
adjustedIndex: function() {
return this.getPath('_parentView.contentIndex') + 1;
}.property()
});
I think you could probably do something like this too
//add index property to all each queries
Handlebars.registerHelper('each', function(context, block) {
var ret = "";
for(var i=0, j=context.length; i<j; i++) {
context[i].index = i;
ret = ret + block(context[i]);
}
return ret;
});
Related
What I'm having here is basically look like this:
filter
|_ todo
|_ todo
filter
|_ todo
filter
|_ todo
Several filterView which have todoView nested inside.
So first I'm creating instances of filterView and pass in all the params.
<ul id="todo-list">
{{view 'filter' control=controller.beforeFilter title="Before" }}
{{view 'filter' param='0' control=controller.todayFilter title="Today"}}
{{view 'filter' param='1' control=controller.tomorrowFilter title="Tomorrow" }}
</ul>
This is how it look like in filterView:
App.FilterView = Ember.View.extend({
classNames: ['filter-container'],
templateName: 'datefilter',
title: 'Today',
param: null,
control: null,
isHide: false,
click: function(){
this.toggleProperty('isHide');
}
});
and the corresponding template:
<div class="filter-bar">
<label class="filter-title">{{view.title}}</label>
<label class="filter-date">{{generateDate view.param}}</label> <!-- This is a handlebar's helper -->
<div class="filter-right-container">
<div class="filter-count">
<label> count </label> <!-- Show number of todos in this filter -->
</div>
</div>
</div>
<div class="filter-box" {{bind-attr class=view.isHide:hide}}>
{{#each todo in view.control}} <!-- So this will turn to in controller.someFunction -->
{{view 'todo'}}
{{/each}}
</div>
And this will be the TodoView
App.TodoView = Ember.View.extend({
templateName: 'todolist',
contentBinding: 'this',
classNames: ['todo-box']
})
And finally the controller
App.TodosController = Ember.ArrayController.extend({
beforeFilter: function(){
return this.get('model').filter(function(todo, index){
var date = todo.get('date');
if(moment(date).isBefore(moment(), 'days')){
return todo;
}
});
}.property('model.#each.date'),
todayFilter: function(){
return this.get('model').filter(function(todo, index){
var date = todo.get('date');
if(moment().isSame(date, 'days')){
return todo;
}
});
}.property('model.#each.date'),
tomorrowFilter: function(){
return this.get('model').filter(function(todo, index){
var date = todo.get('date');
if((moment().add(1, 'days')).isSame(date, 'days')){
return todo;
}
});
}.property('model.#each.date'),
});
So the TodoView will be created according to the return filtered record, but sometimes nothing will get returned. So the question is how to hide the filterView if no TodoView is created?
Is there something like
{{#each todo in view.control}}
{{view 'todo'}}
{{else}}
{{'Set filterView isVisible to false'}}
{{/each}}
or I could easily get this done using collectionView? but how?
Really appreciate to any help
Here is complete solution.
To sum it up :
An ArrayController to hold all your events
Each event holds a date
Header elements hold a date with truncated hours and a boolean for display
In your template, simply iterate over your array and display the header as you like (this is my controller context):
{{#each randDate in this}}
<div {{bind-attr class=":border-row randDate.isHeader:header"}}>{{formatDate randDate.date isHeader=randDate.isHeader}}</div>
{{/each}}
To differenciate whether there is a date following or not, an easy choice would be to put all your events objects into a [LinkedList][2] data structure and not just a simple Array. This way, each event knows the one after himself and knows if it should be displayed. There are tons of implementations of this kind of list, so just pick one where an element knows its next element (the Doubly for instance, but maybe its not the best suited for your case). Then, you could do something like that (this is pseudo code) :
// inside the each loop
{{#if randDate.isHeader && randDate.next.isHeader}} // not sure this && operator is supported by handlebars at the moment
// here you have 2 headers one after the other, do nothing
{{else}}
// one of the 2 is not a header, display your header/event as usual
{{/if}}
Does it help ?
So what I did is instead of return directly from the controller, I check the length and save it in another variable:
beforeFilter: function(){
var data = this.get('model').filter(function(todo, index)
{
var date = todo.get('date');
if(moment(date).isBefore(moment(), 'days')){
return todo;
}
});
this.set('beforeCount', data.length);
return data;
}.property('model.#each.date')
When creating new instance of view, I'll pass one more param in (the controller.variable which save the length):
{{view 'filter' control=controller.beforeFilter countControl=controller.beforeCount title="Before" }}
And in the view, we can first check the length, and if theres nothing, we will hide the header:
dataChanged: function(){
var count = this.get('countControl'); //<-- this will get the length of the return data
if(count<1){
this.set('isVisible', false);
}
}.observes('countControl').on('didInsertElement')
I currently have a view setup that renders the template depending on the model:
<ul>
{{#each controller.sortedAll}}
{{view App.ScoreView}}
{{/each}}
</ul>
.
App.ScoreView = Ember.View.extend({
templateName: function(){
var item = this.get('context')
if (item.sort < 8){
return 'low'
} else {
return 'high'
}
}.property(),
})
I'm struggling with assigning a specific controller for each separate view. The jsbin is: http://jsbin.com/tahag/5/edit
Is it possible to specify the controller in App.ScoreView using controller:? Or would I be better off trying to set an item controller:
{{view App.ScoreView itemController="VAL"}}
And pass the VAL from the parent controller as a property?
The controller is inherited from the current scope (defining itemController on the view won't do anything). You should do it on the each.
{{#each controller.sortedAll itemController='val'}}
{{view App.ScoreView}}
{{/each}}
Then within the view you can do this.get('controller')...
Also you can do an if statement and do {{render 'high' this}} if you want to have different types of controllers on each item.
{{#each controller.sortedAll}}
{{#if isBlue}}
{{render 'blue' this}}
{{/if}}
{{#if isGreen}}
{{render 'green' this}}
{{/if}}
{{/each}}
Personal recommendations:
To avoid making your template super convoluted I would just use a single controller.
{{#each controller.sortedAll itemController='score'}}
{{input value=sort}}
{{view App.ScoreView}}
{{/each}}
Add what the computed property is dependent on in order for it to automagically update:
App.ScoreView = Ember.View.extend({
templateName: function(){
var sort = this.get('controller.sort');
if (sort < 8){
return 'low';
} else {
return 'high';
}
}.property('controller.sort'),
});
Take advantage of the computed helpers
App.ScoreController = Ember.ObjectController.extend({
isVeryHigh: Em.computed.gt('sort', 20),
isVeryLow: Em.computed.lt('sort', 4)
});
Example: http://jsbin.com/sidebozi/1/edit
http://jsbin.com/qoyudape/1/edit
Despite using .pushObject() template doesn't update. I've noticed it DOES update, if instead this I use model or content in template;
What is this in view is referring to if not model ? Is it possible to get it working using this and not model or content ?
var App = Ember.Application.create();
App.ApplicationRoute = Ember.Route.extend({
model: function(){
return Ember.A();
}
});
App.ApplicationController = Ember.ArrayController.extend({
actions: {
update: function(){
this.get("model").pushObject( Ember.Object.create({a:"b"}) );
console.log( this.get("model") );
}
}
});
template:
<script type="text/x-handlebars">
<button {{action "update"}}>update</button>
<br><br>
{{#if this}}
array not empty
{{else}}
array empty
{{/if}}
</script>
this is referring to the controller. btw, an easy way to find that out is to do {{log this}} in your template see also: http://emberjs.com/guides/understanding-ember/debugging/.
I'm not actually sure what it's checking to be truthy/falsy, but you can always just use length. I'll update once I find it.
{{#if this.length}}
array not empty
{{else}}
array empty
{{/if}}
http://jsbin.com/qoyudape/3/edit
I wanna render a block in Ember Handlebars only, if a specific route is active.
So, how can I create a 'ifRoute' helper, with the same conditons then the 'active' class on the 'linkTo' helper?
I want this, because I've a two layer navigation. So, I want to show the sub-navigation only, if the head navigation point is active. I dont wanna use the 'active' class, because I use lazy loading and I only want to load the sub navigation when the head navigation point is active.
So, what I want to do is:
<ul>
{{#each assortmentGroups}}
<li>
{{#linkTo "assortmentGroup" this}} {{description}} {{/linkTo}}
{{#ifRoute "assortmentGroup" this}}
<ul>
{{#each itemCategories}}
<li>{{#linkTo "itemCategory" this}} {{description}} {{/linkTo}}</li>
{{/each}}
</ul>
{{/ifRoute}}
</li>
{{/each}}
<ul>
How can I do this or is there a better solution?
Thanks
Just add to the controller:
needs: ['application'],
isCorrectRouteActive: Ember.computed.equal('controllers.application.currentRouteName', 'correctRoute')
Similarly:
isCorrectPathActive: Ember.computed.equal('controllers.application.currentPath', 'correct.path')
isCorrectURLActive: Ember.computed.equal('controllers.application.currentURL', 'correctURL')
I am quite sure latest Ember does the rest
Here are two possible options, although for both you first have to save the currentPath in your ApplicationController to have access to it whenever you need it:
var App = Ember.Application.create({
currentPath: ''
});
App.ApplicationController = Ember.ObjectController.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
}.observes('currentPath')
});
Using a computed property
Then in the controller backing up the template, let's say you have a NavigationController you create the computed property and define also the dependency to the ApplicationController with the needs API to gather access, then in the CP you check if the currentPath is the one you want:
App.NavigationController = Ember.Controller.extend({
needs: 'application',
showSubMenu: function(){
var currentPath = this.get('controllers.application.currentPath');
return (currentPath === "assortmentGroup");
}.property('controllers.application.currentPath')
});
So you can use a simple {{#if}} helper in your template:
...
{{#linkTo "assortmentGroup" this}} {{description}} {{/linkTo}}
{{#if showSubMenu}}
<ul>
{{#each itemCategories}}
<li>{{#linkTo "itemCategory" this}} {{description}} {{/linkTo}}</li>
{{/each}}
</ul>
{{/if}}
</li>
...
Using a custom '{{#ifRoute}}' helper
But if your really want a custom helper to deal with your condition then this is how you could do it, note that the currentPath stored on your application is still needed since we need a way to get the value of the current route:
Ember.Handlebars.registerHelper('ifRoute', function(value, options) {
if (value === App.get('currentPath')) {
return options.fn(this);
}
else {
return options.inverse(this);
}
});
And then you could use it like this:
...
{{#linkTo "assortmentGroup" this}} {{description}} {{/linkTo}}
{{#ifRoute "assortmentGroup"}}
<ul>
{{#each itemCategories}}
<li>{{#linkTo "itemCategory" this}} {{description}} {{/linkTo}}</li>
{{/each}}
</ul>
{{/ifRoute}}
</li>
...
See here also a simple Demo of the "custom helper" solution: http://jsbin.com/izurix/7/edit
Note: with the second solution there is a catch! Since bound helpers do not support blocks (in embers handlebars customization) I used a simple helper that does not reevaluate the condition depending on bindings which is may not what you want.
Hope it helps.
After investigating the ember code for the linkTo and if helpers, the answer from intuitivepixel and a blog post about writing my own bound block helper, I've found a solution:
var resolveParams = Ember.Router.resolveParams;
var resolvedPaths = function(options) {
var types = options.options.types.slice(1),
data = options.options.data;
return resolveParams(options.context, options.params, { types: types, data: data });
};
Ember.Handlebars.registerHelper('ifRoute', function(name) {
var options = [].slice.call(arguments, -1)[0];
var params = [].slice.call(arguments, 1, -1);
var theResolvedPaths = resolvedPaths({ context: this, options: options, params: params });
var router = options.data.keywords.controller.container.lookup('router:main');
var self = this;
var evaluateIsCurrentRoute = function() {
self.set('current_route_is_active_bool_for_ifroute', (function() {
return router.isActive.apply(router, [name].concat(theResolvedPaths)) ||
router.isActive.apply(router, [(name + '.index')].concat(theResolvedPaths));
})());
};
evaluateIsCurrentRoute();
router.addObserver('url', evaluateIsCurrentRoute);
options.contexts = null;
return Ember.Handlebars.helpers.boundIf.call(this, 'current_route_is_active_bool_for_ifroute', options);
});
I found an easy way to check if a route is active, but to get this into a computed property may not be so easy.
// Test if you are currently in a route by it's lowercase name
App.isInRoute = function(name) {
return App.Router.router.currentHandlerInfos.mapProperty('name').contains(name);
}
To use:
App.isInRoute('posts.show'); // true if in the route
I've got an app with basic functionality built out. I'm not going through and adding additional features. In this case I need to convert a simple button, currently using linkTo, to a View. Problem is that I'm not sure how to convert one to the other and still keep the link intact.
How do I do this conversion? Here's the code I have now:
<script type="text/x-handlebars" data-template-name="accountItem">
{{#each account in controller}}
{{#linkTo "account" account}}
<img {{bindAttr src="account.icon"}} />
{{/linkTo}}
{{/each}}
</script>
and here's the code I'm going to have:
<script type="text/x-handlebars" data-template-name="accountItem">
{{#each account in controller}}
{{#view "Social.AccountButtonView"}}
<img {{bindAttr src="account.icon"}} />
{{/view}}
{{/each}}
</script>
Social.AccountButtonView = Ember.View.extend({
tagName: 'a',
classNames: ['item-account'],
click: function(){
// do something
}
});
I would assume that I'd be building on top of the click handler in the View, but I'm not sure how to pass the reference to item being iterated over, nor how to reference the correct route within the View.
Assistance please?
Update 1
The first version renders an href attribute with a value of #/accounts/4 based on the Router I have set up:
Social.Router.map(function() {
this.resource('accounts', function(){
this.resource('account', { path: ':account_id'});
});
});
When I convert the current code to a view, how do I mimic the functionality that linkTo provides?
You can define a property binding for account in your handlebars template.
This binding works like this:
<script type="text/x-handlebars">
<h1>App</h1>
{{#each item in controller}}
{{#view App.AccountView accountBinding="item"}}
<a {{bindAttr href="view.account.url"}} target="_blank">
{{view.account.name}}
</a>
{{/view}}
{{/each}}
</script>
Note that I added accountBinding, so the general rule is propertyName and Binding as a suffix. And remember that when you add a property to a view, you will not be able to access it directly, instead you will have to access it with view.propertyName as shown above.
Just keep in mind that you must have a View class when using the {{view}} helper:
window.App = Em.Application.create();
App.AccountView = Em.View.extend(); // this must exist
App.ApplicationRoute = Em.Route.extend({
model: function() {
return [
{id: 1, name: 'Ember.js', url: 'http://emberjs.com'},
{id: 2, name: 'Toronto Ember.js', url: 'http://torontoemberjs.com'},
{id: 3, name: 'JS Fiddle', url: 'http://jsfiddle.com'}];
}
})
Working fiddle: http://jsfiddle.net/schawaska/PFxHx/
In Response to Update 1:
I found myself in a similar scenario, and ended up creating a child view to mimic the {{linkTo}} helper. I don't really know/think it's the best implementation tho.
You can see my previous code here: http://jsfiddle.net/schawaska/SqhJB/
At that time I had created a child view within the ApplicationView:
App.ApplicationView = Em.View.extend({
templateName: 'application',
NavbarView: Em.View.extend({
init: function() {
this._super();
this.set('controller', this.get('parentView.controller').controllerFor('navbar'))
},
selectedRouteName: 'home',
gotoRoute: function(e) {
this.set('selectedRouteName', e.routeName);
this.get('controller.target.router').transitionTo(e.routePath);
},
templateName: 'navbar',
MenuItemView: Em.View.extend({
templateName:'menu-item',
tagName: 'li',
classNameBindings: 'IsActive:active'.w(),
IsActive: function() {
return this.get('item.routeName') === this.get('parentView.selectedRouteName');
}.property('item', 'parentView.selectedRouteName')
})
})
});
and my Handlebars looks like this:
<script type="text/x-handlebars" data-template-name="menu-item">
<a {{action gotoRoute item on="click" target="view.parentView"}}>
{{item.displayText}}
</a>
</script>
<script type="text/x-handlebars" data-template-name="navbar">
<ul class="left">
{{#each item in controller}}
{{view view.MenuItemView itemBinding="item"}}
{{/each}}
</ul>
</script>
I'm sorry I can't give you a better answer. This is what I could come up with at the time and haven't touched it ever since. Like I said, I don't think this is the way to handle it. If you are willing to take a look into the {{linkTo}} helper source code, you'll see a modular and elegant implementation that could be the base of your own implementation. I guess the part you're looking for is the href property which is being defined like so:
var LinkView = Em.View.extend({
...
attributeBindings: ['href', 'title'],
...
href: Ember.computed(function() {
var router = this.get('router');
return router.generate.apply(router, args(this, router));
})
...
});
So I guess, from there you can understand how it works and implement something on your own. Let me know if that helps.