Meteor - Helper comparing two different cursors - meteor

I'm using a template helper that returns a comparison between a specific cursor and every iteration of the documents from another cursor. The 'inside' value is stored inside the 'City' collection.
I know that storing a unique 'inside' value on each of the documents inside the 'Places' collection would solve this problem, but you only can be 'inside' one place in the each 'City', this would be a performance issue.
Helpers:
Template.listPlaces.helpers({
places: function () {
return Places.find({});
},
insidePlace: function () {
return City.findOne({_id: this._id}).inside === places._id;
}
]);
Template:
<ul>
{{#each places}}
{{#if insidePlace}}Active{{else}}Inactive{{/if}}
{{/each}}
</ul>
I know a solution would be to have a cursor observer running that would update a Session variable with the 'inside' value each time City.inside gets updated, but I would like to know if there is a better solution.

Have you considered using a transform?
Template.listPlaces.helpers({
places: function () {
var transform = function(doc) {
var city = Cities.findOne({_id: doc._id});
doc.insidePlace = (city.inside == doc._id)
return doc;
}
return Places.find({}, {transform: transform});
},
]);

Related

Meteor Helper using query based on reactive variable

I'm trying to use a helper that should return a Collection specifying a subset of the whole Collection with $in using a reactive array from templates:array.
I have
var tags = new ReactiveArray();
and on some event I change the contents of the array, something along the lines of
tags.pushArray(note.tags);
(or maybe I should use .set()?)
My helper is
Template.editor.helpers({
tagslist() {
return Tags.find({ _id: { $in : tags }});
},
});
But then I get an exception in meteor.js:1010 which looks like this
if (allArgumentsOfTypeString)
console.log.apply(console, [Array.prototype.join.call(arguments, " ")]);
In the stack there is compileValueSelector. This seems to indicate that the compilation of the helper is not content with what it finds.
I've also tried to make tags a template local instance, and adding .get() to the tags in the helper query. But with the same result.
Where should I start looking? Am I using ReactiveArray correctly? Is it possible to do what I want, namely have a reactive query based on an ReactiveArray?
I personally have not used ReactiveArray but I assume this same pattern would work. I stick to using ReactiveVar so here is an example that should get you going in the right direction.
Template.editor.onCreated(function () {
const instance = this;
instance.tags = new ReactiveVar([]);
});
Template.editor.helpers({
tagslist() {
const tags = Template.instance().tags.get();
return Tags.find({ _id: { $in : tags }});
}
});
Template.editor.events({
'click .tag'(event, instance){
const tag = this;
const tags = instance.tags.get();
tags.push(tag);
instance.tags.set(tags);
}
});

Search and Sort on the same page

I'm trying to implement sort and search to my items, so i started with sort and it works:
Template
<button class="sort">Sort</button>
{{#each cvs}}
{{> Interviu}}
{{/each}}
JS:
Template.Interviuri.onCreated(function () {
var self = this
self.autorun(function () {
self.sortOrder = new ReactiveVar(-1)
})
Template.Interviuri.helpers({
cvs() {
const instance = Template.instance()
return Cvs.find({}, { sort: { createdAt: instance.sortOrder.get() } })
},
})
Template.Interviuri.events({
'click .sort'(event, instance) {
instance.sortOrder.set(instance.sortOrder.get() * -1)
Next i wanted to implement Search on the same page. So the best way i could found was EasySearch.
But using EasySearch, it means i must change the way my items are being displayed. And then the sort doesn't work anymore.
Template
<div class="searchBox pull-right">
{{> EasySearch.Input index=cvsIndex attributes=searchAttributes }}
</div>
{{#EasySearch.Each index=cvsIndex }}
{{> Interviu}}
{{/EasySearch.Each}}
Collection
CvsIndex = new EasySearch.Index({
collection: Cvs,
fields: ['name'],
engine: new EasySearch.Minimongo()
})
JS
cvsIndex: () => CvsIndex,
How can i have both search and sort working at the same time?
With EasySearch you can use two methods on your index, namely getComponentDict() and getComponentMethods().
With getComponentDict() you can access search definition and options:
index.getComponentDict().get('searchDefinition');
index.getComponentDict().get('searchOptions');
You also have the corresponding setters to change the search definition/option.
getComponentMethods has mehods like
index.getComponentMethods().loadMore(integer);
index.getComponentMethods().hasMoreDocuments();
index.getComponentMethods().addProps(prop, value);
index.getComponentMethods().removeProps([prop])
From that you can set your prop, say index.getComponentMethods().addProp('sort', -1) and then on the index definition, in your MongoDB engine, set the sort from that prop:
index = new EasySearch.index({
// other parameters
engine: new EasySearch.MongoDB({
sort: function(searchObject, options) {
if(options.search.props.sort) {
return parseInt(options.search.props.sort);
}
return 1;
}
})
});
See EasySearch Engines for more info.

Meteor: Access Template Helper (or variable) from another helper

How can I reference a template helper from another one? For example...
Template.XXX.helpers({
reusableHelper: function() {
return this.field1 * 25 / 100; //or some other result
},
anotherHelper: function() {
if (this.reusableHelper() > 300) //this does not work
return this.reusableHelper() + ' is greater than 300';
else
return this.reusableHelper() + ' is smaller than 300';
}
});
I have also tried Template.instance().__helpers.reusableHelper - all with no luck.
Alternatively is there a way to define reactive Template instance variables?
XXX is a sub-template that renders multiple times on the same page.
You can but only with global template helpers.
Blaze._globalHelpers.nameOfHelper()
Here is an example calling Iron:Router's pathFor global helper.
Template.ionItem.helpers({
url: function () {
var hash = {};
hash.route = path;
hash.query = this.query;
hash.hash = this.hash;
hash.data = this.data;
var options = new Spacebars.kw(hash);
if (this.url){
return Blaze._globalHelpers.urlFor(options)
} else if( this.path || this.route ) {
return Blaze._globalHelpers.pathFor(options)
}
}
});
EDIT: To your second question. You can call the same template as many times as you like on a page and pass different data attributes directly into it and/or use #each block template wrapper to iterate over data. #each will call a template many times giving it a different data context each time.
#each Example
<template name="listOfPosts">
<ul>
{{#each posts}}
{{>postListItem}} <!--this template will get a different data context each time-->
{{/each}}
</ul>
</template>
Attributes Example
<template name="postDetails">
{{>postHeader title="Hello World" headerType="main" data=someHelper}}
{{>postHeader title="I am a sub" headerType="sub" data=newHelper}}
{{>postBody doc=bodyHelper}}
</template>
This like using of common code, you can make another javascript function which contains the your reusable code and call it from wherever you required.
Like in your code-
function calcField(field){
return field * 25 / 100
}
and in you template helper-
Template.XXX.helpers({
reusableHelper: function() {
return calcField(this.field1);
},
anotherHelper: function() {
if (calcField(this.field1) > 300)
return calcField(this.field1) + ' is greater than 300';
else
return calcField(this.field1) + ' is smaller than 300';
}
});
and
Alternatively is there a way to define reactive Template instance
variables?
you can use Session variables or Reactive variable
Disclaimer: This may not answer your question directly, but it might be helpful for people stuck with a similar use case:
Sometimes it's easy to get locked into the "Meteor way", that standard Javascript rules are forgotten.
Two use cases that sound similar to what you're trying to do:
1. For helpers/events that you can access anywhere on the client-side, simply set a global helper.
Put this in, say, client/helpers.js:
Helpers = {
someFunction: function(params) {
/* Do something here */
}
}
Now Helpers.someFunction() is available to all templates.
If you want to bind the local template instance to it for some reason, again, it's standard JS:
var boundFunction = Helpers.someFunction.bind(this);
2. To create reusable Blaze helpers inside of templates, use Template.registerHelper
For example, this function uses the "numeral" library to format numbers:
Template.registerHelper('numeral', function(context, opt) {
var format = (opt.hash && opt.hash.format) || '0,0.00';
return numeral(context || 0).format(format);
});
You can use this in any template like so:
{{numeral someNumberVariable format='0,0'}}
I found a better solution with collection hooks:
Item = new Mongo.Collection('Items');
Item.helpers({
isAuthor: function(){
return this.authorId == Meteor.userId();
},
color: function(){
if(this.isAuthor())
return 'green';
else
return 'red';
}
});
I then becomes functions of this, usable in both helpers and templates.
i had something similar -- i had 2 helpers in the same template that needed access to the same function. however, that function 1) needed access to a reactive var in the template, and 2) is a filter function, so i couldn't just pass in the data of that reactive var.
i ended up defining the filter function in the templates onCreated() and stored it in a reactive var, so the helpers could access it.
Template.Foo.onCreated(function () {
this.fooData = new ReactiveVar();
function filterFoo(key) {
var foo = Template.instance().fooData.get();
// filter result is based on the key and the foo data
return [true|false];
}
this.filterFoo = new ReactiveVar(filterFoo);
});
Template.Foo.helpers({
helper1: function() {
var filterFn = Template.instance().filterFoo.get();
return CollectionA.getKeys().filter(filterFn);
},
helper2: function() {
var filterFn = Template.instance().filterFoo.get();
return CollectionB.getKeys().filter(filterFn);
},
});
Since this answer is currently missing - I wanted to add an update
In the current meteor version, you should be able to call:
var TEMPLATE_NAME = //the name of your template...
var HELPER_NAME = //the name of your helper...
Template[TEMPLATE_NAME].__helpers[' '+HELPER_NAME]
You should call it like this, if you want to make sure the helper has access to this:
var context = this;
Template[TEMPLATE_NAME].__helpers[' '+HELPER_NAME].call(context,/* args */);
But be careful - this could break in future Meteor versions.
Adding on to Nils' answer, I have been able to access Template level helpers in events using the following code:
'click a#back': (event, instance) ->
if instance.view.template.__helpers[' complete']() && instance.view.template.__helpers[' changed']()
event.preventDefault()
this just came up again at work, and this time we used modules. in this case, we had a number of large, related functions that had to maintain data across calls. i wanted them outside the template file but not totally polluting the Meteor scope. so we made a module (polluting the Meteor scope 1x) and called the functions therein from the template.
lib/FooHelpers.js:
FooHelpers = (function () {
var _foo;
function setupFoo(value) {
_foo = value;
}
function getFoo() {
return _foo;
}
function incFoo() {
_foo++;
}
return {
setupFoo: setupFoo,
getFoo: getFoo,
incFoo: incFoo
}
})();
FooTemplate.js:
Template.FooTemplate.helpers({
testFoo: function() {
FooHelpers.setupFoo(7);
console.log(FooHelpers.getFoo());
FooHelpers.incFoo();
console.log(FooHelpers.getFoo());
}
});
console output is 7, 8.

The most concise and elegant way to break the reactivity of helpers in meteor

I want to use my collection players in an helper... for several reasons I would like this collection will be not reactive. I would like just a first call to the database to display the collection. I tried to use reactivates:false option but in this case the collection remains empty after loading and nothing display.
Template.myGame.helpers({
players: function () {
return Players.find({}, {reactive: false});
}
})
<ul>
{{#each players}}
{{> player}}
{{/each}}
</ul>
You're effectively looking for a way to return data non-reactively, but only once it's ready. This can be achieved with a subscription handle (assuming you've removed "autopublish").
playersSub = Meteor.subscribe('players', ...);
Template.myGame.helpers({
players: function() {
if (playersSub.ready()) {
return Players.find({}, {reactive: false});
} else {
return [];
}
}
});
NB - it is actually possible to get the subscription readiness (non-reactively) without having a handle, by using Players._connection._subscriptions.[SUB_ID].ready, but I wouldn't recommend this as it's not part of the public API.

Is it possible to use session.get() as part of a selector in Meteor event maps?

I would like to add specific event only to elements that match a certain criteria, in this case, if the element is selected in the session or not.. Here's some example code:
Template.leaderboard.events({
'click Session.get("selected_team") .win': function () {
Teams.update(Session.get("selected_team"), {$inc: {won: 1, score : 5, played: 1}});
}
});
This looks for the selected team in the session & then updates that item. Does that make sense? Is there a better way to achieve what I want?
In the leaderboard example, the selected player is given a css class of "selected", so all you need to do is:
Template.player.events({
'click .selected': function () {
console.log('clicked on the selected player:', this.name);
}
});
You can use the same pattern for other elements that you might want to trigger events on conditionally: assign a particular css class to them (or not) depending on the condition.
If you prefer not to add a css class to elements through the templates (for whatever reason), you're better off simply checking your condition in Javascript:
Template.player.events({
'click': function () {
if (Session.get("selected_player") === this._id) {
console.log('clicked on the selected player:', this.name);
}
}
});
It won't work the way you're writing it since you've included the Session.get statement as part of the string. Something like this might work:
Template.leaderboard.events({
'click ' + Session.get("selected_team") + ' .win': function() {}
});
...but I wouldn't recommend it. Instead you should probably do something like this:
Template.leaderboard.events({
'click .team': function() {
Teams.update(this.id, {...});
}
});
Template.leaderboard.teams = function() {
return Teams.find({});
}
In your view:
<template name="leaderboard">
{{#each teams}}
<div class="team">{{team}}</div>
{{/each}}
</template>
Each .team still remembers its context within the leaderboard template, referred to as this inside the event handler, so you can just pass this.id to the query.

Resources