Is there any way to get the object of the current iteration in Handlebars?
code:
<script id="HandleBarTemplate1" type="text/x-handlebars-template">
{{#each objArr}}
<img src="{{objField1}}"/>
<strong>Name:</strong> {{objField2}}
<input type="button" onclick="processObject({{.}});"/>
{{/each}}
</script>
I've mentioned processObject({{.}}) That is incorrect. That's where I need a replacement/Solution.Hope you get what I'm tryin' to say.
The contents of objArr might look like
var objArr = [{objField1:"smith.jpg",objField2:"Smith"},{objField1:"jane.jpg",objField2:"Jane"},...]
Template compilation code is:
var source = document.getElementById("HandleBarTemplate1").innerHTML;
var compiledTemplate = Handlebars.compile(source);
var html = compiledTemplate({objArr:objArr});
If I could get the reference to the Object, then It's so easy to process the data. Rather than passing a field to the function and searching through entire array to get the required object and then process it.
I Prefer a solution without a custom block helper/custom expression helper but if none exists, I'd rather go for a custom block helper. Any solution without searching through the entire array is welcome!
I would suggest a different route.
Knowing that you already have a reference to objArr, make a global or name-spaced variable pointing to it.
For example: window.objArr = objArr;
Create your click handler that does whatever you wish:
function processObject(key){
}
call that with your key inside your template:
< script id="HandleBarTemplate1" type="text/x-handlebars-template">
{{#each objArr}}
<img src="{{objField1}}"/>
<strong>Name:</strong> {{objField2}}
<input type="button" onclick="processObject({{objField2}});"/>
{{/each}}
</script>
Other alternatives:
Customer Handler.
Other alternatives:
If you can't create a reference to objArray, you might could store the properties of the object within data- attributes if you're using html5. processObject could then retrieve them.
I do this with a Handlebars helper and an array of context objects.
Somewhere we have an array of context objects, which will store a reference to each context object we need to reference:
var ContextObjects = [];
We need to register a Handlebars helper function that stores the current context object in the array for us when we place "{{obj}}" in the template. The helper returns the index of the object in the array, which is what gets rendered:
// Must be function and not lambda to preserve the correct 'this'
Handlebars.registerHelper("obj", function ()
{
var contextObject = this; // the current context object
var index = ContextObjects.indexOf(contextObject);
if (index < 0)
{
index = ContextObjects.length;
ContextObjects[index] = contextObject;
}
return index;
});
Next we need a function to retrieve the object from the index on the element:
// Get the context object for the current event (e.g. click or context).
function GetContextObject(event)
{
var $e = $(event.currentTarget);
var index = $e.attr("data-obj");
return ContextObjects[index];
}
We tie them together in the template something like this:
{{#each Items}}
<div data-obj="{{obj}}" onclick="DoSomething(GetContextObject(event));">...</div>
{{/each}}
Which may render something like this:
<div data-obj="0" onclick="DoSomething(GetContextObject(event));">...</div>
<div data-obj="1" onclick="DoSomething(GetContextObject(event));">...</div>
<div data-obj="2" onclick="DoSomething(GetContextObject(event));">...</div>
...
Related
I am using a helper inside another helper. I am trying to pass a value ‘post_id’, that i am getting dynamically from ‘show_post’ helper.I want to pass it and then use it inside the query that is returning a set of result back to helper. for some reason , app is crashing. Can someone guide me through this.
{{#each show_post}}
{{posttext}}
{{postedBy}}
{{#each show_comment({{post_id}}) }}
//<--- i am passing the value to helper like this.
<span> <p>{{postedBy}}: <h5>{{commenttext}}</h5>{{createdAt}}</p></span>
{{/each}}
{{/each}}
Template.display_block.helpers({
show_post(){
return PostComment.find({parent: 0},{sort: {createdAt: -1}});
}
});
Template.display_block.helpers({
show_comment(e){
var t1 = e;
var now = PostComment.find({post_id:{$regex:new RegExp(’^’ + t1)}});
return now;
}
});
The first helper generates a array(Mongo Db result).I want to use this array’s elements(that are dynamic) in next helper. So i want to use a variable that can hold the value of array element of first helper and then pass it to next helper. In second helper, i want to pass this variable and use this variable to sort a result in Mongo query. I am new to this so i am unable to understand how this instance
Firstly you don't need to wrap helper arguments in double curly braces or parens.
{{#each show_comment post_id}}
Will do what you need.
You're also making life a bit more complicated for yourself than necessary. You can use the current data context through this in your code. Also unclear why you're using a regex unless you're concatenating something to the post _id.
This allows you to simplify down to:
html:
{{#each show_post}}
{{posttext}}
{{postedBy}}
{{#each show_comment}}
<span><p>{{postedBy}}: <h5>{{commenttext}}</h5>{{createdAt}}</p></span>
{{/each}}
{{/each}}
js:
Template.display_block.helpers({
show_comment(){
return PostComment.find({post_id: this._id);
}
});
I have a simple <firebase-query> tag, and I'd like to manipulate some of the data before having it displayed through a <dom-repeat>. For example, I need to turn some fields into links, and also parse some dates.
So, I need to get the data once it's ready, loop through each item, and change some of the values.
To do that, I have an observer on the data to detect when it's ready. However, I can't figure out how to loop through the data from that JavaScript function. For some reason, for(var i in items) doesn't work, although the items do exist.
Here is the component:
<dom-module id="cool-stuff">
<template>
<firebase-query id="query" path="/items" data="{{items}}"></firebase-query>
<template is="dom-repeat" items="{{items}}" as="item">
[[item.name]]<br />
[[item.date]]<br />
</template>
</template>
<script>
Polymer({
is: 'ix-table',
properties: {
items: {type: Object, observer: "_itemsChanged"},
}
itemsChanged: function(data) {
// how do I loop through the data received from firebase-query?
console.log(data);
}
});
</script>
</dom-module>
Ideally, all I'd want to do in the observer function is something like:
for(var i in data) {
obj = data[i];
obj.name = '<a href="/item/"+obj.key>'+ojb.name+'</a>';
}
But I can't seem to be able to loop through the data.
Inside the observer function, console.log(data) returns some weird stuff like this:
Array[o]
0: Object (which contains a proper item)
1: Object (same)
2: Object (same)
Update:
Here is a screenshot of what console.log(data) returns (from inside the observer):
The array seems to be populated with all the objects, but it shows as Array[0]. So it won't let me loop through them.
Update 2:
Thanks to arfost here is the solution:
<script>
Polymer({
is: 'ix-table',
properties: {
items: {type: Object},
}
observers: [
'_itemsChanged(items.splices)'
],
_itemsChanged: function(changeRecord) {
if (changeRecord) {
changeRecord.indexSplices.forEach(function(s) {
for (var i=0; i<s.addedCount; i++) {
var index = s.index + i;
var item = s.object[index];
console.log('Item ' + item.name + ' added at index ' + index);
// do whatever needed with the item here:
this.items[index].name = "New name";
}
}, this);
}
},
});
</script>
<firebase-query> results
Note that <firebase-query> results in an array of objects. Let's say your database contained the following items under /notes/<USER_ID>/:
Your <firebase-query> would look similar to this:
<firebase-query
id="query"
app-name="notes"
path="/notes/[[user.uid]]"
data="{{notes}}">
</firebase-query>
(where user is bound to <firebase-auth>.user).
Assuming the user is logged in, <firebase-query> would then populate its data property (i.e., bound to notes) with the following array:
Note how each object contains a $key property, which corresponds to the item's key seen in the Firebase console's Database view.
You could then iterate notes directly with <dom-repeat>:
<template is="dom-repeat" items="[[notes]]">
<li>
<div>key: [[item.$key]]</div>
<div>body: [[item.body]]</div>
<div>title: [[item.title]]</div>
</li>
</template>
Binding to HTML strings
You should be aware that the string data bindings are rendered literally in this case, so attempting to set name to obj.name = '<a href="...">' would render the literal string instead of an anchor. Instead, you should declare the tags in your template, and bind the key and name properties inside those tags. So, your observer could be replaced with this:
<template is="dom-repeat" items="{{items}}" as="item">
<a href$="/item/[[item.key]]">[[item.name]]</a><br />
[[item.date]]<br />
</template>
Iterating an array
The following note is only relevant if you prefer to mutate the data before displaying it...
When iterating an array, you should avoid for..in because it doesn't guarantee order of iteration, and because it may iterate over enumerable properties you might not necessarily care about. Instead, you could use for..of (assuming ES6 is available to your app):
for (let note of notes) {
note.title += ' ...';
}
or Array.prototype.forEach():
notes.forEach(function(note) {
note.title += ' ...';
});
I thinks I have run into the same issue as you.
It come from the way firebase query is getting the array, the way polymer obersvers works, and is hidden by the fact that the javascript console is reference based when it show the objects.
In fact what really happen here, is that firebase query is creating an empty array, which trigger your polymer observer.
So your function is called as soon as the array is created, but still empty and you can't iterate through, since it's empty. You then log it, where the primitives sub-properties are correctly displayed (array[0])
Then firebase begin to populate the array with the datas. The arrays reference stay the same, so polymer won't fire the observer again, and in the console, when it try to display the array it display the array referenced in the log, which now contains the datas.
I recommend that you use a array mutation observer in place of your simple one as follow
`properties: {
items: {type: Object},
},
,
observers: [
'_itemsChanged(items.splices)'
],`
It will fire every time an object is added to your array, and you would be able to do the work you need :)
I had the link for the documentation on array mutation observer :)
polymer array mutation observer
I hope this will solve your issue,
have a good day.
i don't think i can think of a scenario where you'd need to mutate the data by looping through the array rather than just using computed bindings. like this:
<template is="dom-repeat" items="{{items}}" as="item">
<child-el date="{{_computeDate(item.date)}}"></child-el><br />
<child-el attr1="{{_someOtherConversion(item.prop1)}}"></child-el><br />
<child-el attr2="{{_iPromiseAnyConversionCanBeDoneLikeThis(item.prop2)}}"></child-el><br />
</template>
<script>
_computeDate: function(item) {
//do date converstion
}
Let's say I have the following Blaze template helper that fetches some objects from a collection:
PackageInCart: function() {
PackageIds = Carts.findOne({SessionId: SessionID}).Packages;
var PackageObjects = Packages.find({ _id: { $in : PackageIds } } ).fetch();
return PackageObjects;
},
The PackageObjects variable contains objects that have a 'priceperday' property with a certain price value. In the Blaze template, I can easily print this value using:
{{#each PackageInCart}}
<div class="price">{{priceperday}}</div>
{{/each}}
However, what if I want to modify the 'priceperday' value from the Helper function before it gets printed in the template? What would be the correct way to do this?
One solution that came to mind was to make a for loop that iterates over the objects and does something like Object.defineProperty() to change the priceperday property into the new value.
I want to know if there's an easier or quicker way using Blaze methods to modify the object property that gets printed with the curly braces.
If you want to do this using blaze you could do this using another helper.
weeklyPrice: function(priceperday){
return priceperday * 7;
}
Which would be called like this
{{#each PackageInCart}}
<div class="price">{{weeklyPrice priceperday}}</div>
{{/each}}
More info about spacebars helper arguments in the docs
I'm trying to write a custom iterator in spacebars (I'm using meteor 1.1.3). The iterator is to be a sequential for loop (basically to replace my usage of #each when needed since I believe #each is not guaranteed to be sequential in its iteration).
I have tried the following:
In lib -
UI.registerHelper 'sequentialFor', () ->
ret = ""
for i in [0...#.length]
id = #[i]
ret = ret + Template.noop
ret
noop.html -
<template name="noop">
{{> UI.contentBlock this}}
<template>
main.html -
{{#sequentialFor ids}}
<div id="wow-{{this}}">stuff</div>
{{/sequentialFor}}
ids in the above is an array of strings passed from one of main's template helpers.
Right now it complains the the return from my UI helper is [object Object] [object Object].
For sanity's sake I know that if I replace my UI helper with:
UI.registerHelper 'sequentialFor', () ->
//ret = ""
//for i in [0...#.length]
// id = #[i]
// ret = ret + template
id = #[0]
Template.noop
I get that the div in my main.html shows up with the appropriate id as a part of its id attribute as desired. However, I can't seem to make the for loop work.
I can't simply return the div in main.html directly from the helper because I have a lot of divs that I need to wrap with my new iterator, each of which has very different attributes.
I guess the simple question is, how do I define my own block iterator (akin to #each) in spacebars?
The more difficult question may be, what is wrong with my approach above?
I have considered a wide array of resources but have only the found the following to be very helpful:
How to pass an object from to a block helper back to the block in meteor blaze?
https://github.com/meteor/meteor/wiki/Using-Blaze
https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md
Iterating over basic “for” loop using Handlebars.js
NOTE I'm using coffeescript
I managed to get a custom iterator using a recursive technique similar to what you might use in Haskell or Lisp:
<body>
{{#countdown n=5}}
<p>item {{this}}</p>
{{/countdown}}
</body>
<template name="countdown">
{{#if positive}}
{{> Template.contentBlock n}}
{{#countdown n=nMinusOne}}
{{> Template.contentBlock this}}
{{/countdown}}
{{/if}}
</template>
Template.countdown.helpers({
positive: function () {return this.n > 0;},
nMinusOne: function () {return this.n - 1;}
});
See meteorpad.
The performance is probably far worse than the usual {{#each}}.
It appears to me that you want to create a <div> for each of an array of IDs (correct me if I'm wrong). This is how I would go about it, no custom iterator necessary:
Template.registerHelper('ids', function(arrayWithIds) {
if (!arrayWithIds) return [];
// do some sorting or whatever with arrayWithIds, for example:
var arrayOfIds = _.map(arrayWithIds, function(obj) {
return obj._id;
});
return arrayOfIds;
});
Then in main.html:
{{#each ids someDataSetWithIds}}
// `someDataSetWithIds` is the helper's parameter
// `this` in each case is an ID
<div id="wow-{{this}}"></div>
{{/each}}
If your helper returns an object, you would use this._id in the template, instead. Did I misunderstand what you're trying to achieve?
I'm using meteor 0.6.4. The problem i have is that the data context when rendering a template is sometimes undefined, so that, 'this' object is a reference to Window:
Template.task.time_left = function(){
debugger;
var nDate = this.due_date.getTime();
Exception from Deps recompute: TypeError: Cannot call method 'getTime' of undefined
The html code is wrapped inside an {{each}} handlebars statement:
<template name="tasks_lists">
{{#each tasks_list}}
...
{{#each task}}
{{> task}}
{{/each}}
...
{{/each}}
</template>
<template name="task">
...
<div class="text">{{due_date}}</div>
...
</template>
I read that this bug was solved in an earlier version of Meteor. What can I do to aviod the function being called with 'this' as Window.
You should use template.xxx.helpers instead, ie:
Template.task.helpers({
nDate: function() {
return this.due_date.getTime();
}
});
When you use it within helpers, this is the data context.
I used 'helpers' function and I have the same problem. 'this' object is sometimes the window object:
Template.task.helpers({
...
'time_left': function(){
debugger;
var nDate = this.due_date.getTime();
...
When using any of the three Meteor template callback functions, including the onRendered function, the this object is actually a template instance object. Although it is possible to retrieve the data context of the template through this object, it is recommended that you reference the template data context through the use of the Template.currentData() function. The documentation for this function can be found here.
The this inside a template helper will always points to window object.
You can access the data context in Template.rendered() or the event handler function. In the event handlers, it is passed as a second argument as function( event, template ), where the template is the current template object.
However I suggest you to use the template instance functions such as find(), findAll(), firstNode(), lastNode() rather than data context.
Template.task.rendered = function() {
if( !this.window ){ //check that 'this' is not a 'window' object
var el = this.find( 'div.text' ); // the div that holds due_date
//do something
}
}