According to this question, it was possible to do something like this with Handlebars rc1:
{{#each links}}
<li>{{#index}} - {{url}}</li>
{{/each}}
{{#index}} would basically give you the iteration index, which is really useful when creating tables.
When I try this with Ember.js rc3, I get an unexpected token error. Does this not work anymore? Did it ever work? Is there another way to get the iteration index?
It looks like it was possible. Can't get it to work with HBS RC3. Probably, is deprecated.
Here's a "hand written" HBS helper.
This can help you gettin the index with {{index}} and side by side you can know if the iteration in on first or last object of the Array with {{first}} and {{last}} respectively.
Ember.Handlebars.registerHelper("foreach", function(path, options) {
var ctx;
var helperName = 'foreach';
if (arguments.length === 4) {
Ember.assert("If you pass more than one argument to the foreach helper, it must be in the form #foreach foo in bar", arguments[1] === "in");
var keywordName = arguments[0];
options = arguments[3];
path = arguments[2];
helperName += ' ' + keywordName + ' in ' + path;
if (path === '') {
path = "this";
}
options.hash.keyword = keywordName;
} else if (arguments.length === 1) {
options = path;
path = 'this';
} else {
helperName += ' ' + path;
}
options.hash.dataSourceBinding = path;
// Set up emptyView as a metamorph with no tag
//options.hash.emptyViewClass = Ember._MetamorphView;
// can't rely on this default behavior when use strict
ctx = this || window;
var len = options.contexts[0][path].length;
options.helperName = options.helperName || helperName;
options.contexts[0][path].map(function(item, index) {
item.index = index;
item.first = index === 0;
item.last = index === len - 1;
})
if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) {
new GroupedEach(ctx, path, options).render();
} else {
return Ember.Handlebars.helpers.collection.call(ctx, Ember.Handlebars.EachView, options);
}
});
and this can be tested like
{{#foreach array}}
{{log index first last}}
{{/foreach}}
i had the same problem recently i finish by writing a bound helper and passing them objects via Binding for example item here is an ember DS.Store object and content is a 'content' of the controller. hope it
Ember.Handlebars.registerBoundHelper 'isPair', (content, options)->
item = options.hash.item
content_name = options.hash.content || 'content'
if #get(content_name).indexOf(item) % 2 == 0 then 'is-pair' else 'is-unpair'
and in you view you call it
{{isPair content itemBinding='order'}}
i don't know if it is what you looking for but it might give you some ideas how to use it in your project.
btw. Ember overwrites #each helper that's why there it no #index i suppose
Related
var totalList_grps = element.all(by.css('p.group-name-text'));
totalList_grps.getText().then(function(text){
console.log('Total list of joined groups : ' + text);
});
Tried the above code for printing list of group names.
Got Output :Total list of joined groups : Party,Innovation,capsLock,Gym,Sunrisers
AW,Big Boss.
Now i need to search for a particular name using if else condition and i tried the second set of code, but its not displaying any output not even a error.
totalList_grps.getText().then(function(itemList) {
expect(itemList).toContain('Big Boss');
});
Here is developers code
1) use by.cssContainingText():
var bigBoss = element(by.cssContainingText('p.group-name-text', 'Big Boss'));
// then you can call click(), getText(), getAttribute('') on found element as following:
bigBoss.click();
2) use elements.filter():
var bigBoss = element.all(by.css('p.group-name-text'))
.filter(function(it){
return it.getText().then(function(txt){
console.log('txt: ' + txt);
return txt === 'Big Boss' || txt.includes('Big Boss');
});
})
.first();
3) use await with combination of if/else
var allNames = element.all(by.css('p.group-name-text'));
var length = await allNames.count();
var matchedIndex = -1;
for(var i=0;i<length;i++) {
var name = await allNames.get(i).getText();
if (name === 'Big Boss' || name.includes('Big Boss')) {
matchedIndex = i;
console.log('matchedIndex = ' + matchedIndex);
break;
}
}
var bigBoss = allNames.get(matchedIndex);
We can implement option 3 without using await, but the code will be not easy readable and more complex than current.
FYI, If you want to use await/async, you need to disable protractor promise management (know as control flow). You can't use both in your code at same time.
I've been cracking my head for the last several days, trying to understand what am I doing wrong.
I'm implementing an infrastructure of lists for my app, which can include paging/infinite scroll/filtering/grouping/etc. The implementation is based on extending controllers (not array controllers, I want to be Ember 2.0 safe), with a content array property that holds the data.
I'm using Ember.computed.sort for the sorting, and it's working, but i have a strange behavior when i try to change the sorter. the sortedContent is not updating within the displayContent, even though the sortingDefinitions definitions are updated.
This causes a weird behaviour that it will only sort if I sort it twice, as if the sorting was asynchronous.
I am using Ember 1.5 (but it also happens on 1.8)
(attaching a snippet of code explaining my problem)
sortingDefinitions: function(){
var sortBy = this.get('sortBy');
var sortOrder = this.get('sortOrder') || 'asc';
if (_.isArray(sortBy)) {
return sortBy;
}
else {
return (sortBy ? [sortBy + ':' + sortOrder] : []);
}
}.property('sortBy', 'sortOrder'),
sortedContent: Ember.computed.sort('content', 'sortingDefinitions'),
displayContent: function() {
var that = this;
var sortBy = this.get('sortBy');
var sortOrder = this.get('sortOrder');
var list = (sortBy ? this.get('sortedContent') : this.get('content'));
var itemsPerPage = this.get('itemsPerPage');
var currentPage = this.get('currentPage');
var listItemModel = this.get('listItemModel');
return list.filter(function(item, index, enumerable){
return ((index >= (currentPage * itemsPerPage)) && (index < ((currentPage + 1) * itemsPerPage)));
}).map(function(item) {
var listItemModel = that.get('listItemModel');
if (listItemModel) {
return listItemModel.create(item);
}
else {
return item;
}
});
}.property('content.length', 'sortBy', 'sortOrder', 'currentPage', 'itemsPerPage')
Edit:
fixed by adding another dependency to the displayContent (sortedContent.[]):
displayContent: function() {
....
}.property('content.length', 'sortBy', 'sortOrder', 'currentPage', 'itemsPerPage' , 'sortedContent.[]')
Your sort function is watching the whole array sortingDefinitions instead of each element in the array. If the array changed to a string or some other variable it would update but not if an element in the array changes.
To ensure your computed property updates correctly, add a .[] to the end of the array so it looks like this: Ember.computed.sort('content', 'sortingDefinitions.[]')
I'm trying to do something which must be really simple to accomplish in Ember.
I want to show a button in my template based on the boolean state of a property:
{{#if canFavoriteTag}}
{{d-button action="favoriteTag" label="tagging.favorite" icon="star-o" class="admin-tag favorite-tag"}}
{{else}}
{{d-button action="unFavoriteTag" label="tagging.unfavorite" icon="star-o" class="admin-tag favorite-tag tag-unfavorite"}}
{{/if}}
I have created a property called canFavoriteTag with a function which I want to return true or false to the template based on whether the user can favorite the tag or not:
export default Ember.Controller.extend(BulkTopicSelection, {
canFavoriteTag: function() {
const self = this;
var ticker = this.get('tag.id');
console.log('checking can fav stock:' + ticker);
Discourse.ajax("/stock/get_users_favorite_stocks", {
type: "GET",
}).then(function(data) {
var favable = true;
for (var i = data.stock.length - 1; i >= 0; i--) {
var stock = jQuery.parseJSON(data.stock[i]);
if(ticker.toLowerCase() == stock.symbol.toLowerCase()) { console.log(ticker + ' is a favorite stock: ' + stock.symbol.toLowerCase()); favable = false; }
}
console.log(favable);
return favable;
});
}.property('canFavoriteTag') <-- unsure about this?
...
When the page loads, the wrong button shows (always the "false" one).. I see in the console that the favable variable gets set to false when the ajax call completes, but the button never changes. How do I get it to show the right button based on the function? Do I need to use a promise? If so, how?
I get cannot call method 'set' of undefined object when I remove item from ArrayController.
start: function () {
this.registerModel('listController', Ember.ArrayController.create());
this._super();
},
My view looks like,
{{#each item in marketingListCriteriaList}}
{{view Select valueBinding="item.entity" contentBinding="controller.allEntities" optionLabelPath="content.Name" optionValuePath="content.Name" }}
{{/each}}
I have a observer method which observes
.observes('listController.#each.entity')
The above observer gets called when i remove object from array controller using removeObject() method.
Are there any other ways to remove objects from array?
entityChangeObserver: function (thisModule) {
var thisModule = this;
var criteria = thisModule.get('listController.content');
if (criteria != undefined && criteria.length > 0 && criteria[criteria.length - 1].entity != undefined) {
var presentObject = criteria[criteria.length - 1];
$.each(thisModule.get('allEntities'), function (index, item) {
if (presentObject.entity === item.Name) {
presentObject.set('allAttributes', item.Attributes);
}
});
}
}.observes('listController.#each.entity'),
attributeChangeObserver: function (thisModule) {
var thisModule = this;
var criteria = thisModule.get('listController.content');
if (criteria != undefined && criteria.length > 0 && criteria[criteria.length - 1].attribute != undefined) {
var presentObject = criteria[criteria.length - 1];
$.each(presentObject.get('allAttributes'), function (index, item) {
if (presentObject.attribute === item.Name) {
thisModule.setDefaulsVisibility(presentObject);
if (item.Type === '1') {
presentObject.set('textVisible', true);
}
else if (item.Type === '2') {
presentObject.set('selectVisible', true);
presentObject.set('allValues', item.Values);
}
else if (item.Type === '3') {
presentObject.set('multiSelectVisible', true);
presentObject.set('allValues', item.Values);
}
else if (item.Type === '4') {
presentObject.set('dateVisible', true);
}
}
});
}
}.observes('listController.#each.attribute'),
You can also remove array elements using "remove" instead of "removeObject" however, you might want to double check your logic in your observer which gives undefined error when you remove an object. I would recommend sticking to remove object and just fixing the error within the observer. Also, do note that using "remove" will not instantly update handlebar templates if you are looping over the array.
Firstly sorry for the late post.
I figured the solution for the problem "calling set on destroyed object".
In my control definition of didInsertElement I made a check for if (!me.isDestroyed) for every set operation.
Are there any helpers for making templates aware of when to use plural words?
In the example below, how do you make the template output "2 dogs have..."?
The code:
Ember.View.create({dog_count: 2})
The template:
{{dog_count}} (dog has)/(dogs have) gone for a walk.
I know this is old, but I needed it today, so here goes.
Ember.Handlebars.registerBoundHelper('pluralize', function(number, opts) {
var single = opts.hash['s'];
Ember.assert('pluralize requires a singular string (s)', single);
var plural = opts.hash['p'] || single + 's';
return (number == 1) ? single : plural;
});
Usage:
{{questions.length}} {{pluralize questions.length s="Question"}}
or
{{dog_count}} {{pluralize dog_count s="dog has" p="dogs have"}} gone for a walk.
The plural (p=) option is only necessary when you don't want the standard +s behavior.
There is a I18n library for Ember: zendesk/ember-i18n.
There is a handlebars helper t which handles the internationalization by looking up string from Em.I18n.translations:
Em.I18n.translations = {
'dog.walk.one': '1 dog has gone for a walk.',
'dog.walk.other': '{{count}} dogs have gone for a walk.'
};
And you can then use the string in your Handlebars template via:
{{t dog.walk countBinding="dogCount"}}
The code above is untested and just taken from the documentation in the README.
Another JS I18n library I found is Alex Sexton's messageformat.js.
It depends on the complexity of you app, but you can also use a computed property for that, see http://jsfiddle.net/pangratz666/pzg4c/:
Handlebars:
<script type="text/x-handlebars" data-template-name="dog" >
{{dogCountString}}
</script>
JavaScript:
Ember.View.create({
templateName: 'dog',
dogCountString: function() {
var dogCount = this.get('dogCount');
var dogCountStr = (dogCount === 1) ? 'dog has' : 'dogs have';
return '%# %# gone for a walk.'.fmt(dogCount, dogCountStr);
}.property('dogCount')
}).append();
If you use Ember Data you can use Ember.Inflector.
var inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
inflector.pluralize('person') //=> 'people'
You can register a new helper with:
Handlebars.registerHelper('pluralize', function(number, single) {
if (number === 1) { return single; }
else {
var inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
return inflector.pluralize(single);
}
});
More details at http://emberjs.com/api/data/classes/Ember.Inflector.html
It looks like you got an answer from wycats himself, but I didn't see it mentioned in this thread, so here it is:
Handlebars.registerHelper('pluralize', function(number, single, plural) {
if (number === 1) { return single; }
else { return plural; }
});
I recently found this library http://slexaxton.github.com/Jed/ which seems to be a nice tool for JS i18n. I guess you can pretty easily create your own implementation by registering a handlebars helper using this library.
I do not know of any Ember specific functions that will do this for you. However, generally when you pluralize a word, the single version only shows up when the count is one.
See this for an example: http://jsfiddle.net/6VN56/
function pluralize(count, single, plural) {
return count + " " + (count == 1 ? single : plural);
}
pluralize(1, 'dog', 'dogs') // 1 dog
pluralize(10, 'dog', 'dogs') // 10 dogs
pluralize(0, 'dog', 'dogs') // 0 dogs