Avoid repeating queries in meteor template helpers - meteor

I'm starting to use meteor for a project, and there are several concepts that are being pretty hard to me coming from angular+php.
I'm trying to handle two helpers, for displaying a list of records in a table, based in a date range that I'm storing on a session variable. My template then looks like this, I'm using a helper called noRecords and another called records, records store the actual document set, and in noRecords I'm trying to store as a boolean whether the record document set is empty or not.
div(class='col-md-8 col-md-offset-2')
if noRecords
p There are no records for the selected date
else
table(class='table table-bordered')
thead
tr
td Id
td Name
tbody
each records
+record
Unfortunately I've not being able to set records and noRecords at the same time without repeating the query, in my javascript code those helpers are now defined like this:
records : function(){
var startDate = Session.get('searchDate').setHours(0,0,0,0);
var endDate = Session.get('searchDate').setHours(23,59,59,999);
var matches = Records.find(
{date : {
$gte : new Date(startDate),
$lte : new Date(endDate)
}
});
return records;
},
noRecords : function(){
var startDate = Session.get('searchDate').setHours(0,0,0,0);
var endDate = Session.get('searchDate').setHours(23,59,59,999);
var matches = Records.find(
{date : {
$gte : new Date(startDate),
$lte : new Date(endDate)
}
});
return records.count() === 0;
}
The date session variable is set by an event.
I guess there must be a better way of doing this instead of executing the query twice, I've tried using reactive variables but I had no luck, since I can't make the reactive variable to update when the minimongo query executes.
Is there any way of achieving this without running two queries?

If you are repeating the same logic in multiple helpers one solution is to reduce them to a single helper and return an object from it, e.g.:
records : function(){
var startDate = Session.get('searchDate').setHours(0,0,0,0);
var endDate = Session.get('searchDate').setHours(23,59,59,999);
var cursor = Records.find(
{date : {
$gte : new Date(startDate),
$lte : new Date(endDate)
}
});
return {
cursor: cursor,
empty: cursor.count() === 0,
one: cursor.count() === 1
}
}
And in your template:
if records.one
p Found One!
Silly example but it can be used more broadly.
Note that in your example you wouldn't actually need the noRecords helper because your template can check for an empty cursor:
each records
p date
else
p No records!

If you were using Blaze, you could use the {{#each records}}...{{else}}...{{/each}} using your records helper only like this.
<template name="myTemplate">
<div class='col-md-8 col-md-offset-2'>
{{#each records}}{{> showRecord}}
{{else}}<p>There are no records for the selected date</p>
{{/each}}
</div>
</template>
You could also use {{#if records}}...{{else}}...{{/if}} since each will iterate over each item in the records cursor
<template name="myTemplate">
<div class='col-md-8 col-md-offset-2'>
{{#if records}}
<table class='table table-bordered'>
<thead>
<tr>
<td>Id</td>
<td>Name</td>
</tr>
</thead>
<tbody>
{{#each}}{{> showRecord}}{{/each}}
</tbody>
</table>
{{else}}<p>There are no records for the selected date</p>
{{/with}}
</div>
</template>

Related

Displaying items from collection in a template

The below code is expected to insert a document of 3 pairs in the FooterButtons collection, then those values are shown as labels on 3 buttons in the footer template.
But when the "click .menuItem" is called, it only insert "YES" in the collection. Any idea why it is broker and best way to fix it? Thanks
Server and client code
FooterButtons = new Mongo.Collection('footerButtons');
Server code
Meteor.publish('footerButtons', function(){
return FooterButtons.find();
});
Client code
Meteor.subscribe('footerButtons');
//---main_menu.js--------------------
Template.mainMenu.events({
'click .menuItem': function (event) {
FooterButtons.insert({button:"NO", button:"EXTRA", button:"YES"});
}
});
//---footer.html---------------
<template name="footer">
{{#each footerButtons}}
<h1>
<button class="col-xs-4" type="button">{{button}}</button>
</h1>
{{/each}}
</template>
//---footer.js---------------
Template.footer.helpers({
footerButtons: function(){
return FooterButtons.find();
}
});
This command is totally wrong for the mongo insertion
FooterButtons.insert({button:"NO", button:"EXTRA", button:"YES"});
If you create a javascript object,
var obj = {button:"NO", button:"EXTRA", button:"YES"};
since all the keys is duplicated, your object will only has one key which has the value of the last one: Yes
You need to insert one by one
FooterButtons.insert({button:"NO"});
FooterButtons.insert({button:"EXTRA"});
FooterButtons.insert({button:"YES"});

Meteor - Strange behavior when using {{#if}} to setup table rows

I'm trying to setup a table using alternating row colors. I'm having trouble getting Meteor to let me use the #if statement to start each row.
Here is the simple helper to determine if the row number is odd or even;
Template.drillDown.trx = function() {
return trx.find({userID: Meteor.userId()});
}
Template.drillDown.isEven = function(num) {
return !( num & 1 );
}
and I have this in the template named drillDown;
<table>
{{#each trx}}
{{#if isEven trx_num}}
<tr class="even">
{{else}}
<tr class="odd">
{{/if}}
<td>{{trx_num}}</td>
</tr>
{{/each}}
</table>
I get the error unexpected {{else}}. I've tried setting this up other ways where I pass in the entire tag like <tr class="even"> but then it throws an error when it see's the closing tag. Any suggestions on how to get past this problem?
The issue is that you are wrapping just the start tag in the {{#if}} block. In Spacebars, everything has to be a complete element with a start and end tag.
The best way to accomplish what you're looking for is:
Helper:
Template.drillDown.isEven = function() {
return !( this.trx_num & 1 );
}
HTML:
<tr class="{{#if isEven}}even{{else}}odd{{/if}}">
<td>{{trx_num}}</td>
</tr>
Also, you could avoid this whole matter entirely and use tr:nth-child(even) instead of even and odd classes: http://www.w3.org/Style/Examples/007/evenodd.en.html
I would change if helper like this:
Template.drillDown.isEven = function() {
return !( this.trx_num & 1 );
}
(I assume helper returns correct answer)
Then, if statement can look like this, cause I guess this line causes trouble
{{#if isEven}}
Also, if you have more helpers, better way to manage them is to keep them this way
Template.<template>.helpers({
isEven: function() {
return !( this.trx_num & 1 );
},
trx: function() {
return trx.find({userID: Meteor.userId()});
},
})

Meteor - Select Parent ID of element clicked

Using Meteor, I am trying to loop through and display a list of notes from a database with an option to delete each note.
Here is the HTML (using Handlebars.js)
<template name="Notes">
{{#each NoteArr}}
<div class="Note">
<h2>{{Title}}</h2>
<p>{{Body}}</p>
<span class="deleteNote">Delete</span>
</div>
{{/each}}
</template>
And here is the client Javascript
Template.Notes.events = {
"click .deleteNote" : function(){
noteID = $('.deleteNote').parent().attr("id");
Notes.remove({ID:noteID});
}
};
This grabs the first instance of .deleteNote, so unless I'm trying to delete the first one, that won't help. How can I grab the parent of the particular instance of .deleteNote that was clicked, not just the first one it finds?
The reason why the first element is deleted is.. in your .click event, you are accesssing the div directly as $('.deleteNote').parent() which grabs the first node in the html which has a class .deleteNode.
Now to remove the specific notes, from the collection: Every document in the collection has a unique _id attribute which is generated automatically. assign that unique _id of the document to the span element as <span id= "{{_id}}" class="deleteNote">Delete</span>.
So the cilck event will look like:
Template.Notes.events = {
"click .deleteNote" : function(e){
var noteID = e.currentTarget.id;
Notes.remove({_id:noteID});
}
};
And the template will look like:
<template name="Notes">
{{#each NoteArr}}
<div class="Note">
<h2>{{Title}}</h2>
<p>{{Body}}</p>
<span id= "{{_id}}" class="deleteNote">Delete</span>
</div>
{{/each}}
</template>
Untested code, but hope this will help solving your issue.
The _id of a note is stored in 'this' as well. In addition, the remove function accepts '_id' as a string. So this should work as well:
Template.Notes.events = {
'click .deleteNote': function(){ return Notes.remove(this._id)}
}
A few benefits here. Less querying the DOM for information. Less jQuery. Fewer lines of code to think about. Cleaner templates.

Is there a way to get index while iterating through a collection in Meteor? [duplicate]

This question already has answers here:
How can I get the index of an array in a Meteor template each loop?
(6 answers)
Closed 7 years ago.
The below example will generate a list of names of players, where players is a data set from a MongoDB database.
<template name="players">
{{#each topScorers}}
<div>{{name}}</div>
{{/each}}
</template>
However, I want to display four of them in a row, and after four players is printed, I want to divide the line by <hr /> and then continue. For instance,
<template name="players">
{{#each topScorers}}
<div style="float:left;">{{name}}</div>
{{if index%4==0}}
<hr style="clear:both;" />
{{/if}
{{/each}}
</template>
How can I do something like that while iterating through collections?
Another solution, in line with maintaining the reactivity of the collection, is to use a template helper with the map cursor function.
Here's an example showing how to return the index when using each with a collection:
index.html:
<template name="print_collection_indices">
{{#each items}}
index: {{ this.index }}
{{/each}}
</template>
index.js:
Items = new Meteor.Collection('items');
Template.print_collection_indices.items = function() {
var items = Items.find().map(function(doc, index, cursor) {
var i = _.extend(doc, {index: index});
return i;
});
return items;
};
There's no easy way to do this right now, the latest version of handlebars supports an #index field (which would do what you want), but it's not yet implemented in meteor's version - https://github.com/meteor/meteor/issues/489.
Certainly you could implement your own {{#each_with_index}} helper, it would look something like this:
Handlebars.registerHelper('each_with_index', function(items, options) {
var out = '';
for(var i=0, l=items.length; i<l; i++) {
var key = 'Branch-' + i;
out = out + Spark.labelBranch(key,function(){
options.fn({data: items[i], index: i});
});
}
return out;
});
The downside of this is you lose the niceness of meteor's {{#each}} helper, which doesn't reactively re-render the whole list when a single item changes.
EDIT: thanks #zorlak for pointer to https://github.com/meteor/meteor/issues/281#issuecomment-13162687

How to refer to Object of current iteration in Handlebars

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>
...

Resources