I am using Handlebar and try to use if condition with equal like if(value.A==value.B).
Below is the code where i am trying this.
<div>
{{#each Data}}
{{#ifCond value.A value.B}}
<tr>
<td>{{value.C}}</td>
<td>{{value.D}}</td>
<td>{{value.E}}</td>
</tr>
{{/ifCond}}
{{/each}}
</div>
I have used below code but it is giving TypeError: hbs.registerHelper is not a function
The code is in app.js :-
var expressHbs = require('express-handlebars');
app.engine('.hbs', expressHbs.engine({ defaultLayout: 'layout', extname: '.hbs'}));
app.set('view engine', '.hbs');
var hbs = expressHbs.create({});
hbs.registerHelper('ifCond', function(v1, v2, options) {
if(v1 === v2) {
return options.fn(this);
}
return options.inverse(this);
});
There are a few problems here.
First, as your error indicates, registerHelper is not a function on the instance object returned by expressHbs.create({}). The returned object is an instance of an ExpressHandlebars object and this is not the same thing as the Handlebars library which has a registerHelper function.
The way to register helpers on an instance of ExpressHandlebars is via the configuration object passed to .create():
var hbs = expressHbs.create({
helpers: {
ifCond(v1, v2, options) {
if (v1 === v2) { return options.fn(this); }
return options.inverse(this);
},
}
});
Unfortunately, this change alone will not suffice. It will lead to the Error:
Missing helper: "ifCond"
The issue here is that we have created the ifCond helper on the hbs instance, but the hbs instance is not the one we are setting as our Express View Engine.
Our Express View Engine is set with the result of a call to expressHbs.engine with some configuration properties.
Express-Handlebars gives us the option of using the .engine function to construct an engine object or the .create function to create instances of ExpressHandlebars, each with its own .engine object.
In your example, you are not doing anything with the hbs instance. Your helpers need to be added to the configuration passed in your expressHbs.engine call:
app.engine(
".hbs",
expressHbs.engine({
defaultLayout: "layout",
extname: ".hbs",
helpers: {
ifCond(v1, v2, options) {
if (v1 === v2) { return options.fn(this); }
return options.inverse(this);
},
},
})
);
var hbs = expressHbs.create({}); can be removed.
Related
How can I use reactive template variables (from Template.data) in an anonymous function within the template rendered function? (I want to keep it reactive).
Template.templateName.rendered = function() {
function testFunction(){
//Log the field 'title' associated with the current template
console.log(this.data.title);
}
});
Not sure exactly what you are trying to do (like printing this.data.title whenever it changes?), but you should:
use a Reactive variable (add reactive-var package, then create a var myVar = new ReactiveVar()
If necessary, wrap your function with Tracker.autorun (or this.autorun in a template creation / rendered event).
So you could have like:
Parent template HTML:
<template name="parentTemplateName">
{{> templateName title=myReactiveVar}}
</template>
Parent template JS:
Template.parentTemplateName.helpers({
myReactiveVar: function () {
return new ReactiveVar("My Title!");
}
});
Template JS:
Template.templateName.onRendered(function() {
// Re-run whenever a ReactiveVar in the function block changes.
this.autorun(function () {
//Print the field 'title' associated with the current template
console.log(getValue(this.data.title));
});
});
function getValue(variable) {
return (variable instanceof ReactiveVar) ? variable.get() : variable;
}
What worked for me was simple using autorun() AND using Template.currentData() to grab the values from within autorun():
let title;
Template.templateName.rendered = function() {
this.autorun(function(){
title = Template.currentData().title;
});
function testFunction(){
console.log(title);
}
});
Template.templateName.onRendered(function(){
console.log(this.data.title);
});
I currently use meteor for a microproject of mine to get a bit usage experience with it. Shortly after setting up I ran into some trouble getting Data i recieve from an API call to a third party site to the client into the template. I checked the usual places for answers and found some information but nothing seems to get it working for me.
So I have a simple API Call to the Steam Web Api:
Meteor.methods({
getPlayerStats: function() {
return HTTP.get("http://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v0002/?appid=730&key=XXXXXXXXXXXXXXX&steamid=XXXXXXXX");
}
});
(Api key and steam id removed for anonymity purpose, but the call indeed returns a valid response)
So I use Iron Router for template rendering.
Router.route('/profile/:steamid', {
name:'profile',
template: 'profile',
data: function() {
Meteor.call('getPlayerStats', function(err, res) {
if(err) {
return {err: err, stat: null};
} else {
var redata = JSON.parse(res.content);
var stats = redata.playerstats.stats;
console.log({err: null, stats: stats});
return {err: null, stats: stats};
}
});
}
});
So as you can see i return an object in the data method containing either err or a parsed version of the result i get from the api call. The console.log actually returns everything as intended in the client browser, that is an object like this:
{err: null, stats: [{name: "xx", value: "XY"},...]}
And now the part that actually gets me wondering, the template:
<template name="profile">
<p>{{err}}</p>
<ul>
{{#each stats}}
<li>{{name}} - {{value}}</li>
{{/each}}
</ul>
</template>
Which fails to render anything, not the err (which is null and therefor not very important) but neither the stats array.
Anyone has any idea where I went wrong on this one?
You cannot return data from asynchronous call. Instead, You can do it in the template's created function by using ReactiveVar or Session Variable like this
Template.profile.created = function () {
// or Template.profile.onCreated(function () {
var template = this;
template.externalData = new ReactiveVar(null);
Meteor.call('getPlayerStats', function(err, res) {
if(err) {
template.externalData.set({err: err, stat: null});
} else {
var redata = JSON.parse(res.content);
var stats = redata.playerstats.stats;
console.log({err: null, stats: stats});
template.externalData.set({err: null, stat: stats});
}
});
};
// }); //comment the above line and use this, if you used onCreated instead of created.
Then in your helpers,
Template.profile.helpers({
externalData: function () {
var template = Template.instance();
return template.externalData.get();
}
});
Then in your template html,
<template name="profile">
{{#if externalData}}
{{#if externalData.err}}
<p>There is an error. {{externalData.err}}</p>
{{else}}
<ul>
{{#each externalData.stats}}
<li>{{name}} - {{value}}</li>
{{/each}}
</ul>
{{/if}}
{{/if}}
</template>
Session.set('coursesReady', false); on startup.
UPDATE:
I made it into a simpler problem. Consider the following code.
Inside router.js
Router.route('/', function () {
Meteor.subscribe("courses", function() {
console.log("data ready")
Session.set("coursesReady", true);
});
}
and inside main template Main.js
Template.Main.rendered = function() {
if (Session.get('coursesReady')) {
console.log("inject success");
Meteor.typeahead.inject();
}
The message "inject success" is not printed after "data ready" is printed. How come reactivity does not work here?
Reactivity "didn't work" because rendered only executes once (it isn't reactive). You'd need to wrap your session checks inside of a template autorun in order for them to get reevaluated:
Template.Main.rendered = function() {
this.autorun(function() {
if (Session.get('coursesReady')) {
console.log("inject success");
Meteor.typeahead.inject();
}
});
};
Probably a better solution is to wait on the subscription if you want to ensure your data is loaded prior to rendering the template.
Router.route('/', {
// this template will be rendered until the subscriptions are ready
loadingTemplate: 'loading',
waitOn: function () {
// return one handle, a function, or an array
return Meteor.subscribe('courses');
},
action: function () {
this.render('Main');
}
});
And now your rendered can just do this:
Template.Main.rendered = function() {
Meteor.typeahead.inject();
};
Don't forget to add a loading template.
To Solve Your Problem
Template.registerHelper("course_data", function() {
console.log("course_data helper is called");
if (Session.get('coursesReady')) {
var courses = Courses.find().fetch();
var result = [ { **Changed**
name: 'course-info1',
valueKey: 'titleLong',
local: function() {
return Courses.find().fetch();
},
template: 'Course'
}];
Session.set('courseResult', result); **New line**
return Session.get('courseResult'); **New line**
,
Explanation
The answer is at the return of the helper function needs to have be associated with reactivity in order for Blaze, template renderer, to know when to rerender.
Non-reactive (Doesn't change in the DOM as values changes)
Template.Main.helpers({
course_data: UI._globalHelpers.course_data ** Not reactive
});
Essentially: UI._globalHelpers.course_data returns an array of objects which is not reactive:
return [
{
name: 'course-info1',
valueKey: 'titleLong',
local: function() {
return Courses.find().fetch();
},
template: 'Course'
},
Reactive
From Meteor Documentation:
http://docs.meteor.com/#/full/template_helpers
Template.myTemplate.helpers({
foo: function () {
return Session.get("foo"); ** Reactive
}
});
Returning Session.get function to Blaze is reactive; thus, the template will change as the values changes.
I'm retrieving a Collection document based on a Session variable, and then passing this as a variable called store through an iron:router data context. The problem is that it sometimes returns undefined, as if it had not prepared itself in time for the helper to execute. How do I ensure the variable is always defined before the helper/template runs?
Here's my route, you can see that the data context includes retrieving a doc from a collection based on an _id stored in a Session variable:
Router.route('/sales/pos', {
name: 'sales.pos',
template: 'pos',
layoutTemplate:'defaultLayout',
loadingTemplate: 'loading',
waitOn: function() {
return [
Meteor.subscribe('products'),
Meteor.subscribe('stores'),
Meteor.subscribe('locations'),
Meteor.subscribe('inventory')
];
},
data: function() {
data = {
currentTemplate: 'pos',
store: Stores.findOne(Session.get('current-store-id'))
}
return data;
}
});
And here is the helper which relies on the store variable being passed to the template:
Template.posProducts.helpers({
'products': function() {
var self = this;
var storeLocationId = self.data.store.locationId;
... removed for brevity ...
return products;
}
});
This is a common problem in Meteor. While you wait on your subscriptions to be ready, this does not mean your find function had time to return something. You can solve it with some defensive coding:
Template.posProducts.helpers({
'products': function() {
var self = this;
var storeLocationId = self.data.store && self.data.store.locationId;
if (storeLocationId) {
... removed for brevity ...
return products;
}
}
});
I have a Template named movies, that has a method that returns a list of objects from a collection.
The query to generate that list of objects is created dynamically using data from another template method.
I would like to re-render the template, or just the components associated with that specific template method, whenever the filter data changes.
Here are the two methods used:
Template.movies.filter = function () {
if (Session.equals("filter", undefined)) {
return {};
}
return Session.get("filter");
};
Template.movies.movies = function () {
return Movies.find(Template.movies.filter(), {sort: [["Poster", "desc"]]});
};
On the HTML side it's a simple {{#each movies}}{{> movie}}{{/each}} to show the results from the movies method.
The problem is that when Session.get("filter") changes and therefore so does Template.movies.filter(), the HTML component relying on Template.movies.movies() data won't be updated with the new query results.
How would I achieve that behavior?
The easiest way is to just make a javascript function that both helpers utilize:
var getFilter = function() {
if (Session.equals("filter", undefined)) {
return {};
}
return Session.get("filter")
}
Template.movies.filter = function() { return getFilter(); }
Template.movies.movies = function () {
return Movies.find(getFilter(), {sort: [["Poster", "desc"]]});
};
This will react as you expect.