Iron router RouteController gives a not defined error - meteor

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.

Related

Helper variable undefined from router data

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;
}
}
});

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

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

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

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