Access Template.TemplateName.helpers from Template.helpers and vice versa - meteor

Is there a way to gain access to the Template instance from a global helper and vice versa?
/lib/route.js (with Iron Router):
Router.route('/', {name: 'home.view', controller: 'homeController'});
homeController = RouteController.extend({
template: 'home',
waitOn: function () {
Meteor.subscribe("Person", Meteor.userId());
},
data: function () {
// return some data;
}
});
homeController.helpers({
templateInstanceHelper: function () {
// Access a "global" helper here
}
});
/client/helpers.js:
Template.helpers("globalHelper", function () {
// Access the template instance helper here
});

Have you considered defining a global method instead? Instead of registering with Meteor Templates, just define it as
globalHelperFunc = function(templateVar) {
// do work
}
Note that this need to be in "lib" folder, so maybe (/lib/helpers.js)
Reference: Global function for Meteor template helper

Related

Iron router RouteController gives a not defined error

This works fine:
Router.route('/', function () {
this.render('home');
});
Router.route('/about', function () {
this.render('aboutView');
});
However, this gives an error (RouteController 'HomeController' not defined):
var HomeController, MainController;
HomeController = RouteController.extend({
action: function() {
this.render('home', {
});
}
});
MainController = RouteController.extend({
action: function () {
this.render('aboutView', {
});
}
});
Router.route('/', {name: 'home', controller: 'HomeController'});
Router.route('/about', {name: 'about', controller: 'MainController'});
I've tried various permutations (taken from the IronRouter documentation) but there is still the same error.
What am I doing wrong?
Either remove this line :
var HomeController, MainController;
To make these global variables.
Or remove the quotes when specifying controllers :
Router.route('/', {name: 'home', controller: HomeController});
Router.route('/about', {name: 'about', controller: MainController});
Why is that so ?
When you define controllers with iron:router you can use 2 different syntax, using a variable identifier of a string.
If you use a variable identifier, the controller has to be declared as a local (file-scoped) variable using the var keyword.
However if you use a string, the controller will get looked as a global variable (a property of the window object), and global variables in Meteor are defined without the var keyword.
Usually you will define your controllers in different files (that's the point of controllers, externalizing routing logic) so it is more common to use the string syntax.

Session object inside global template helpers

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.

Iron Router: How do I send data to the layout?

Using Iron Router, I understand how to set data for a template. But how do I send data to a layout that was globally defined?
I set the router layout by:
Router.configure({ layoutTemplate: 'NAME' })
This will set the layout for all my routes.
However from my individual routes, I'd like to send data to the layout template.
The layout uses the data context defined with data in the route option. Here is an excerpt from Iron Router documentation:
Router.route('/routeName', {
...
// A data function that can be used to automatically set the data context for
// our layout. This function can also be used by hooks and plugins. For
// example, the "dataNotFound" plugin calls this function to see if it
// returns a null value, and if so, renders the not found template.
data: function () {
return Posts.findOne({_id: this.params._id});
},
...
}
We can also set the data context of the layout with this.layout like this:
Router.route('/routeName', function () {
this.layout('layoutName', {
data: function() {
return CollectionName.find();
}
});
});
In addition, we can refer to an existing layoutTemplate option like this:
Router.route('/routeName', function () {
this.layout(this.lookupOption('layoutTemplate'), {
data: function() {
return CollectionName.find();
}
});
});
Router.configure({
layoutTemplate: 'NAME',
data: function() {
return CollectionName.find();
}
});
As described in the Global Default Options in the iron:router docs:
You can set any of the above options on the Router itself...
Where 'above options' is referring to any of the Route Specific Options
You can also subscribe to a published Collection:
Router.configure({
layoutTemplate: 'NAME',
subscriptions: function() {
this.subscribe('CollectionName');
}
});

Iron Router BaseController undefined when deployed?

My app runs fine locally, but when I deploy it on xxxx.meteor.com, I'm getting the Iron Router "Organize your Meteor application" splash page. Additionally, once the browser hits the first Basecontroller.extend({, I get a ref. error that "BaseController is not defined". Maybe something to do with the load order of the route controller specific to deployment? This is my code:
client/main/routes.js
Router.route('/', {
name: 'splash',
controller: 'SplashController'
});
Router.route('/login', {
name: 'login',
controller: 'LoginController'
});
Router.route('/home', {
name: 'home',
controller: 'HomeController'
//...etc
client/main/controller.js
BaseController = RouteController.extend({
layoutTemplate: 'layout',
requiresAuth: false,
publicOnly: false,
// Called before anything happens
onBeforeAction: function () {
if (!Meteor.userId() && this.requiresAuth) {
return this.redirect('/login');
}
if (Meteor.userId() && this.publicOnly) {
return this.redirect('/home');
}
}
});
client/(all templates)/controller.js
TemplateController = BaseController.extend({
requiresAuth: false,
publicOnly: true,
action: function () {
this.render('template', {
to: 'content'
});
}
});
The issue belong in loading only. So, code which should always load first, try put inside <app-root>\lib directory.
Like your case, put route.js and controller.js inside lib directory.
Read more about loading order in meteor - http://docs.meteor.com/#/full/structuringyourapp

Meteor: rendered function called before documents are loaded

I have a template with a rendered function like this:
Template.config.rendered = function () {
var barfoo = BarFoo.findOne({....});
if (barfoo) {
$('[start-date]').datepicker({ ... });
}
}
The problem is that the collection BarFoo is still empty at the time the rendered function is called.
I'm using Iron Router so I can wait until specific subscriptions have loaded their documents:
HomeController = RouteController.extend({
...
waitOn: function () {
return Meteor.subscribe('barfoo');
}
});
So the question is, how is it possible that the rendered function is called before the data of barfoo is loaded ?
You could use onData in your routecontroller. If you use the data param:
data: function() {
bar: BarFoo.findOne({....})
}
onData: function() {
if(BarFoo.findOne({....})) {
$('[start-date]').datepicker({ ... });
}
}
onData is run every time the return value of data changes, so you can check if there is a value there first, it can also be reactively changed if your subscription changes.
Another option is to add a loading template, which is shown while your subscription completes:
loadingTemplate : 'template'

Resources