The following Ember Handlebars template renders the 1st row, but does not render the one inside the nested each (or inner each)
<table width="50%">
{{#each someData.items as |item|}}
<tr> <!-- This one RENDERS -->
<td width="25%"><span class="boldTxt">{{item.fld1}}</span></td>
<td width="25%">{{item.fld2}}</td>
</tr>
{{#each item.Reas as |rea|}}
<tr> <!-- This one does not RENDER -->
<td>{{rea}}</td>
</tr>
{{/each}}
{{/each}}
</table>
What is the issue??
I am using Ember version 1.13
Most likely, your problem is that you are using Ember2.0 or above (based on your outer each loop) so your inner each loop has a now invalid (formerly deprecated) format. Also, you are using the same variable name item for both loops, which won't work properly.
http://guides.emberjs.com/v2.1.0/templates/displaying-a-list-of-items/
Just use the same format as in the outer loop:
Change:
{{#each item in item.Reasons}}
To:
{{#each item.Reasons as |reason|}}
EDIT
If your Reas arrays look as you've described in the comments:
item.Reas = [null]; // arrays containing a single `null` value
Then handlebars will show an empty string for these values since Handlebars coerces undefined and null to an empty string.
{{reas}} {{!-- if reas is null then an empty string is printed --}
If you want to show null and undefined values, you can make a simple helper to do so:
// helpers/show-value.js
import Ember from "ember";
export default Ember.Helper.helper(function(params) {
let value = params[0];
if(value === undefined) { return 'undefined'; }
if(value === null) { return 'null'; }
return value;
});
EDIT 2
Based on your explanation in the comment:
Since you are using Ember 1.13, you need a work around to achieve this. Here is one way:
// components/each-keys.js
export default Ember.Component.extend({
object: null, // passed in object
items: Ember.computed('object', function() {
var object = Ember.get(this, 'object');
var keys = Ember.keys(object);
return keys.map(function(key) {
return { key: key, value: object[key]};
})
})
})
Usage:
{{#each-keys object=item.Reas as |key value|}}
<tr>
<td>{{key}}</td>
<td>{{value}}</td>
</tr>
{{/each-keys}}
Here is a running example
If you update to Ember 2.0, which should be pretty straightforward from 1.13 (since 2.0 is basically 1.13 without deprecations) you can use the each-in helper to iterate over an object and get access to both its keys and values. Here is a simple example:
{{#each-in items as |key value|}}
<p>{{key}}: {{value}}</p>
{{/each-in}}
Related
I have a custom filter called removeIndex which removes the first index of an array. I want to modify that so it first sorts the array, and then removes the first index. However, when I try to do that, my application freezes up. Here's what I have:
filters: {
removeIndex: function(value) {
sort(value); // this crashes my browser
return value.slice(1, value.length);
}
}
Why would that cause my browser to crash? Is there another way I should be doing this? I just want to sort the array before I slice it.
Update: When I do console.log(value), this is what I'm getting:
So it's not just a flat array, there's other stuff tied to it.
Here's how I am using it:
<tbody v-repeat="company in companies | filterBy searchText | orderBy 'name'">
<tr>
<td class="center aligned border" rowspan="#{{ company.applications.length }}" bgcolor="#F9FAFB"><strong>#{{ company.name }}</strong></td>
<td>#{{ company.applications[0] }}</td>
</tr>
<tr v-repeat="company.applications | removeIndex">
<td>#{{ $value }}</td>
</tr>
</tbody>
# signs because I am using this inside of a Laravel app.
assuming value is an array ?
filters: {
removeIndex: function(value) {
return value.sort().slice(1);
}
}
through you might want to supply your own sorting method, as the default one is a Unicode based implementation:
filters: {
removeIndex: function(value) {
return value.sort(function(a,b) { // return -1,0,1 as required})
.slice(1);
}
}
The issue was indeed an infinite loop. I found an issue on the Vue GitHub page which was very similar to mine:
https://github.com/yyx990803/vue/issues/1153
The solution was to first slice the array to create a copy, and then apply the sort function directly after, like so:
return value.slice().sort().slice(1)
I have a list of attribute names attrs that I would like to iterate over, creating an input with a binding to that attribute on the model for each for each. The below expresses the functionality I'm after:
{{#each attr in attrs}}
{{input value=model.get(attr) }}
{{/each}}
Of course this does not actually work, because you can't make method calls in a helper. I found this question, but the solution doesn't work in my version of Ember (1.11.0)-- I gather because this is undefined in a helper definition as of version 1.11.0 (see here).
How can I make this work?
If you want to just get a property from the model, named by the current value of your each loop, you could write a helper like:
Ember.Handlebars.helper('input-helper',
function(model, attr) {
return Ember.get(model, attr);
}
);
And then use it like:
{{#each attr in attrs}}
{{input-helper model attr}}
{{/each}}
This will print property named to whatever attr holds in that #each iteration from the model
If you want to bound that value to an input, instead of making a helper, you could make a component or a view that will do this for you:
// component that binds 'object.property' to an input field
var InputBinderComponent = Ember.Component.extend({
prop: null, // passed in
obj: null, // passed in
value: null, // local
onValue: function() {
var obj = this.get('obj');
var prop = this.get('prop');
var value = this.get('value');
Ember.set(obj, prop, value);
}.observes('value'),
});
With input-binder component's template to actually contain the input:
{{input type="text" value=value}}
And then use it in the template:
{{#each attr in attrs}}
{{input-binder prop=attr obj=model}}
{{/each}}
Here is an Example
For the actual call, I need something like this:
<script id="messagesTemplate" type="text/x-handlebars-template">
{{#each messages.messages}}
{{#each to}}
{{#ifCond username messages.sessionUserName}}
<h1>{{username}} is equal to {{messages.sessionUserName}}</h1>
{{else}}
<h1>{{username}} is not equal to {{messages.sessionUserName}}</h1>
{{/ifCond}}
{{/each}}
{{/each}}
Where, in the db, 'to' is an array of docs that each have a 'username'..that thus need === the messages.sessionUserName to then template/render HTML for certain values (e.g. {{#if read.marked}} )
"to" : [
{
"user" : ObjectId("53aada6f8b10eb0000ec8a90"),
"username" : "username1",
"updated" : ISODate("2014-07-01T19:39:45Z"),
"_id" : ObjectId("53b30e81b0eff5cb1e2ecb21"),
"read" : {
"marked" : true
}
}
]
Worth noting, both usernameTest & sessionUserName are values appended to the end of the res.json() via express, so they are accessible by messages.usernameTest & messages.sessionUserName, but they are not present in each document..these values are only available in the global parent doc.
res.json({
messages : messages,
sessionUserName: req.session.username,
usernameTest: usernameTest
});
This factor may be responsible for why each of these only render is equal to, but doesn't really make sense for the third (The ../ path segment references the parent template scope):
{{#each messages.messages}}
<h1>{{usernameTest}} is equal to {{sessionUserName}}</h1>
<h1>{{../usernameTest}} is equal to {{../sessionUserName}}</h1>
<h1>{{../messages.usernameTest}} is equal to {{../messages.sessionUserName}}</h1>
Drawing from https://stackoverflow.com/a/9405113/3095287 for the custom comparison helper, the template that follows {{#ifCond v1 v2}} doesn't seem to render upper-level scoped elements..
Handlebars.registerHelper('ifCond', function(v1, v2, options) {
if(v1 === v2) {
return options.fn(this);
}
return options.inverse(this);
});
The ifCond comparison does work outside of an {{#each}} block:
<script id="messagesTemplate" type="text/x-handlebars-template">
{{#ifCond messages.usernameTest messages.sessionUserName}}
<h1>{{messages.usernameTest}} is equal to {{messages.sessionUserName}}</h1>
{{else}}
<h1>{{messages.usernameTest}} is not equal to {{messages.sessionUserName}}</h1>
{{/ifCond}}
{{#each messages.messages}}
..
..as that renders:
username1 is equal to username1
However, it does not work inside of an {{#each}} block:
{{#each messages.messages}}
{{#ifCond messages.usernameTest messages.sessionUserName}}
<h1>{{messages.usernameTest}} is equal to {{messages.sessionUserName}}</h1>
{{else}}
<h1>{{messages.usernameTest}} is not equal to {{messages.sessionUserName}}</h1>
{{/ifCond}}
...
..as it only renders:
is equal to
Even with {{../element}}
{{#each messages.messages}}
{{#ifCond messages.usernameTest messages.sessionUserName}}
<h1>{{../messages.usernameTest}} is equal to {{../messages.sessionUserName}}</h1>
{{else}}
<h1>{{../messages.usernameTest}} is not equal to {{../messages.sessionUserName}}</h1>
{{/ifCond}}
...
..the rendering is:
is equal to
Ok so the main thing is you want to be able to gain access to your top level scope deeper down. Ive done this using a customer helper that adds a little extra to the normal each block.
so here is the normal handle bars each
Handlebars.registerHelper('each', function(context, options) {
var ret = "";
for(var i=0, j=context.length; i<j; i++) {
ret = ret + options.fn(context[i]);
}
return ret;
});
all i do is set 'this' to a property called root and pass it back with the result. To overcome nested loops i check for the existence of my 'root' property and if it exists I pass it along other wise root = this.
Handlebars.registerHelper("myEach", function(context, options) {
var ret = "";
for (var i = 0, j = context.length; i < j; i++) {
if (this.root) {
root = this.root;
} else {
root = this;
}
ret = ret + options.fn(_.extend({}, context[i], {
root: root
}));
}
return ret;
});
Now no matter how deep i am in my loops if i want to use something from the root i just use root.property.
A working codepen can be found here with a simplified version of your example.
EDIT: Ok so 5 minutes later after posting this i read about paths in another templating language and then realise handlebars also has paths. so you don't need to do the above you can just use the relative nested path in your template like below. I'm going to continue using the helper though as I think it is tidier to go root.property rather than adding n many "../" for how nested your are.
here is a working example using the paths
<script type="text/x-handlebars-template" id="messages-template">
Logged in user {{userSession}}
{{#each messages}}
<ul>
<li> Title: {{title}}</li>
<li> Conetent: {{content}}</li>
<li> TO:
<ul>
{{#each to}}
<li>{{user}} {{#ifvalue user ../../userSession}}
thats me
{{else}}
thats not me
{{/ifvalue}}</li>
{{/each}}
</ul>
</li>
</ul>
{{/each}}
</script>
In Handlebars, say I have a collection of names. How can I do
{{#each names}}
{{position}}
{{name}}
{{/each}}
where {{position}} is 1 for the first name, 2 for the second name, etc.? Do I absolutely have to store the position as a key in the collection?
You can do this with the built-in Handlebars #index notation:
{{#each array}}
{{#index}}: {{this}}
{{/each}}
#index will give the (zero-based) index of each item in the given array.
Please note for people using Handlebars with the Razor view engine you must use the notation ##index to avoid compilation errors.
For more built-in helpers see http://handlebarsjs.com/
Handlebars.registerHelper("counter", function (index){
return index + 1;
});
Usage:
{{#each names}}
{{counter #index}}
{{name}}
{{/each}}
While you can't do this with any native Handlebars helper, you can create your own. You can call Handlebars.registerHelper(), passing it a string with the name you want to match (position), and a function that would return the current position count. You can keep track of the position number in the closure where you call registerHelper. Here is an example of how you can register a helper called position that should work with your template example.
JavaScript:
// Using a self-invoking function just to illustrate the closure
(function() {
// Start at 1, name this unique to anything in this closure
var positionCounter = 1;
Handlebars.registerHelper('position', function() {
return positionCounter++;
});
// Compile/render your template here
// It will use the helper whenever it seems position
})();
Here is a jsFiddle to demonstrate: http://jsfiddle.net/willslab/T5uKW/1/
While helpers are documented on handlebarsjs.com, this took some effort for me to figure out how to use them. Thanks for the challenge, and I hope that helps!
only you have to use {{#index}}
example:
{{#.}}
<li class="order{{#index}}"> counter: {{#index}}</li>
{{/.}}
Here is my preferred solution. Register a helper that extends the context to include your position property automatically. Then just use your new block helper (ex. #iter) instead of #each.
Handlebars.registerHelper('iter', function (context, options) {
var ret = "";
for (var i = 0, j = context.length; i < j; i++) {
ret += options.fn($.extend(context[i], {position: i + 1}));
}
return ret;
});
Usage:
{{#iter names}}
{{position}}
{{name}}
{{/iter}}
adapted from http://rockycode.com/blog/handlebars-loop-index/
you can get value just from index inside the list.
{{#each list}}
#index
{{/each}}
Current method,
Since Handlebars API V2 they already include an #number
It's basically an index of the iterator start with 1.
So, This is what you could have done.
{{#foreach names}}
{{#number}}
{{name}}
{{/foreach}}
Reference: https://ghost.org/docs/api/v3/handlebars-themes/helpers/foreach/
This works for me
{{#each posts}}
<tr>
<td>{{#index}} </td>
<td>{{name}}</td>
</tr>
{{/each}}
I have following JavaScript array,
[{"unitPrice": 2499,"currency":"$","productId":1,"retailerId":1,"productName":"XX ","formattedPrice":"$ 2,499","productImage":"Images/2012_08_12_00_45_39_4539.jpg","productQuantity":"9","totalPrice":19992},
{"unitPrice": 4999,"currency":"$","productId":2,"retailerId":1,"productName":"XX","formattedPrice":"$ 4,999","productImage":"Images/2012_08_12_00_46_45_4645.jpg","productQuantity":2,"totalPrice":9998},
{"unitPrice":4555,"currency":"$","productId":1,"retailerId":1,"productName":"XXXXX","formattedPrice":"$ 4,555","productImage":"Images/2013_02_12_10_57_49_5749_9868.png","productQuantity":3,"totalPrice":13665}]
here is the relevent html,
<table>
<tbody data-bind="foreach: $root">
<tr>
<td><img width="45" height="45" alt="" data-bind="attr:{src: productImage}"/></td>
<td><span data-bind="html: productName"></span></td>
<td><span data-bind="html: formattedPrice"></span></td>
<td><input type="number" class="quantity" data-bind="value: productQuantity, attr:{'data-id': productId }" /></td>
<td><span data-bind="html: totalPrice"></span></td>
</tr>
</tbody>
</table>
Then I have created observable array as,
observableItems = ko.observableArray(items);
ko.applyBindings(observableItems);
Now I was able to get an specfic element using,
var obj = ko.utils.arrayFirst(list(), function (item) {
return item.productId === id;
});
However when I change,
item.productQuantity = 20;
But UI is not updating. Tried also,
item.productQuantity(item.productQuantity)
But getting error productQuantity is not a function
The above behavior is because only the array is an observable and not the individual elements within the array or the properties of each element.
When you do item.productQuantity = 20;, this will update the property but since it is not an observable, the UI does not get updated.
Similary, item.productQuantity(20) gives you an error because productQuantity is not an observable.
You should look at defining the object structure for each element of your array and then add elements of that type to your observable array. Once this is done, you will be able to do something like item.productQuantity(20) and the UI will update itself immediately.
EDIT Added the function provided by the OP :). This function will convert each property of the elements in an array to observables.
function convertToObservable(list)
{
var newList = [];
$.each(list, function (i, obj) {
var newObj = {};
Object.keys(obj).forEach(function (key) {
newObj[key] = ko.observable(obj[key]);
});
newList.push(newObj);
});
return newList;
}
END EDIT
If you are unable to change that piece of code, you can also do something like observableItems.valueHasMutated(). However, this is not a good thing to do as it signals to KO and your UI that the whole array has changed and the UI will render the whole array based on the bindings.
You can easily use ko.mapping plugin to convert object to become observable: http://knockoutjs.com/documentation/plugins-mapping.html
Convert regular JS entity to observable entity:
var viewModel = ko.mapping.fromJS(data);
Convert observable object to regular JS object:
var unmapped = ko.mapping.toJS(viewModel);