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

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

Related

Meteor - data not available with Iron Router

I am trying to achieve something pretty simple: load a template and fill it with data. However, the template is rendered several times, and the first time with null data.
I found various posts about the issue stating that rendering occurs every time the subscription adds a record to the clients database, and tried applying proposed solutions, but I still have a first rendering without data. How can I fix that?
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound'
});
Router.route('/team/:_id/cal', {
name: 'calendar',
waitOn: function() {
return [
Meteor.subscribe('userTeams',Meteor.user()._id),
Meteor.subscribe('teamUsers', this.params._id)];
},
action : function () {
console.log("action:"+this.ready());
if (this.ready() && Template.currentData != null) {
this.render();
}
},
data: function(){
var team = Teams.findOne({_id:this.params._id})
console.log(JSON.stringify(team));
return team;
}
}
);
Console output
Navigated to http://localhost:3000/team/PAemezbavGEmWBNMy/plan
router.js:33 undefined
router.js:33 {"_id":"PAemezbavGEmWBNMy","name":"superteam","createdAt":"2015-10-22T11:51:10.994Z","members":["T6MpawQj75J2HgPi6","4T3StXAaR9iF4sK99","dDe2dJq3wjL2rFB43"]}
router.js:26 action:true
router.js:33 {"_id":"PAemezbavGEmWBNMy","name":"superteam","createdAt":"2015-10-22T11:51:10.994Z","members":["T6MpawQj75J2HgPi6","4T3StXAaR9iF4sK99","dDe2dJq3wjL2rFB43"]}

Iron-Router checking document existence before route runs

I have a basic chat room App where you can create a room from the main page which calls this method :
createNewRoom: function (){
//Room containers unique ID for the object
var room = Rooms.insert({
createdAt: new Date()
});
return room;
},
The rooms are routed like this :
Router.route('/rooms/:_id', {
name: 'room',
template: 'chatRoom'
});
I am trying to set it up though so that you can't just type any random ID and get a room, it has to already exist. So I created this iron-router hook:
var checkRoomExists = function() {
var room = Rooms.findOne({_id : this.params._id});
if(typeof(room) == "undefined"){
Router.go('home');
}
else {
this.next();
}
}
Router.onBeforeAction(checkRoomExists, {
only : ['room']
});
room in the checkRoomExists always returns undefined though, even if I test the exact same statement elsewhere with the same _id and the room exists. So if I send the link to someone else it will redirect even if the room exists. Is this the wrong type of hook or is there a better way to accomplish this?'
Edit some additional information:
This is the code that creates a room the first time around :
Template.home.events({
'click #create-room' : function(event){
event.preventDefault();
Meteor.call('createNewRoom', function(error, result){
if (error){
console.log(error);
}else {
Session.set("room_id", result);
Router.go('room', {_id: result});
}
});
}
});
If I try to use the full link after, like http://localhost:3000/rooms/eAAHcfwFutRFWHM56 for example, it doesn't work.
I am trying to avoid users going to some random ID like
localhost:3000/rooms/asdasd if a room with that ID doesn't already
exist.
If you want to do this you can follow the next.
on the Layout configure add this.
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound' //add the notFoundTemplate.
});
and this simple onBeforeAction.
Router.onBeforeAction('dataNotFound', {only: 'room'});
Create some sample template like this.
<template name="notFound">
<span> Dam this route don't exist go back to home
</template>
OPTION
Im not sure if this still working but you can define this on the very last of the routes.js js
this.route('notFound', {
path: '*' //this will work like the notFoundTemplate
});
NOTE
If you don't have layout template use like this.
Router.route('/rooms/:_id', {
name: 'room',
notFoundTemplate: 'authorNotFound',
template: 'chatRoom'
});

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

What changes in the execution of an iron router route when coming in on a deep link?

I have a small meteor app going with iron router. Here's my routes.js, which is available to both client and server:
Router.configure({
layoutTemplate: 'defaultLayout',
loadingTemplate: 'loading'
});
// Map individual routes
Router.map(function() {
this.route('comicCreate', {
path: '/comic/create'
});
this.route('comicDetails', {
onBeforeAction: function () {
window.scrollTo(0, 0);
},
path: '/comic/:_id',
layoutTemplate: 'youtubeLayout',
data: function() { return Comics.findOne(this.params._id); }
});
this.route('frontPage', {
path: '/',
layoutTemplate: 'frontPageLayout'
});
this.route('notFound', {
path: '*',
where: 'server',
action: function() {
this.response.statusCode = 404;
this.response.end('Not Found!');
}
});
});
I need to feed some fields from my comic collection document into a package that wraps Youtube's IFrame API, and I'm doing this via the rendered function:
Template.comicDetails.rendered = function() {
var yt = new YTBackground();
if (this.data)
{
yt.startPlayer(document.getElementById('wrap'), {
videoIds: this.data.youtubeIds,
muteButtonClass: 'volume-mute',
volumeDownButtonClass: 'volume-down',
volumeUpButtonClass: 'volume-up'
});
}
};
This works great when I start by going to localhost:3000/ and then clicking on a link set to "{{pathFor 'comicDetails' _id}}". However, if I go directly to localhost:3000/comic/somevalidid, it doesn't, despite the fact that both routes end up pointing me at the same url.
The reason appears to be that when I go directly to the deep link, "this.data" is undefined during the execution of the rendered function. The template itself shows up fine (data correctly replaces the spacebars {{field}} tags in my template), but there's nothing in the "this" context for data when the rendered callback fires.
Is there something I'm doing wrong here?
try declaring the route in Meteor.startup ... not sure if that's the problem but worth a try

meteorjs iron-router waitOn and using as data on rendered

I try to get the returned data in my Template.rendered function.
The current code is:
this.route('editCat', {
layoutTemplate : 'layoutCol2Left',
template : 'modCategoriesEdit',
path : '/mod/categories/edit/:_id',
yieldTemplates : _.extend(defaultYieldTemplates, {
'navigationBackend' : {to : 'contentLeft'}
}),
waitOn : function () {
return Meteor.subscribe('oneCat', this.params._id);
},
data : function () {
return Categories.findOne({_id : this.params._id});
}
});
In this block i wait on the subscribtion of the Collection Document and return the Document as data.
Now i can use the returned Document in my Template like this:
<template name="modCategoriesEdit">
<h1>Edit {{name}}</h1>
</template>
My problem is that i have to use the returned data in my rendered function like this:
Template.modCategoriesEdit.rendered = function () {
console.log(this.data);
}
But this returns "null".
So my question is:
How is it possible to get access to the returned data in the rendered function ?
Solution:
Just add the following to your iron-router route() method.
action : function () {
if (this.ready()) {
this.render();
}
}
Than the Template will rendered after all is loaded correctly.
There are 3 solutions if you want to wait until the waitOn data is ready before rendering:
1- Add an action hook to each route
Router.map(function()
{
this.route('myRoute',
{
action: function()
{
if (this.ready())
this.render();
}
}
}
2- Use the onBeforeAction hook globally or on every route
Sample code for the global hook:
Router.onBeforeAction(function(pause)
{
if (!this.ready())
{
pause();
this.render('myLoading');
}
});
myLoading (or whatever name) must be a template you have defined somewhere.
Don't forget the this.render line, otherwise the problem will occur when leaving the route (while the original problem occurs when loading the route).
3- Use the built-in onBeforeAction('loading') hook
Router.configure(
{
loadingTemplate: 'myLoading',
});
Router.onBeforeAction('loading');
myLoading (or whatever name) must be a template you have defined somewhere.
Using the action hook to check for this.ready() works, but it looks like the official way to do this is to call the following:
Router.onBeforeAction("loading");
Reference: https://github.com/EventedMind/iron-router/issues/679
Like #Sean said, the right solution is to use a loading template:
Router.onBeforeAction("loading");
But if you don't want it, like me, I came up with this solution:
Template.xxx.rendered = function() {
var self = this;
this.autorun(function(a) {
var data = Template.currentData(self.view);
if(!data) return;
console.log("has data! do stuff...");
console.dir(data);
//...
});
}
Template.currentData is reactive, so in the first time it is null and in the second it has your data.
Hope it helps.
-- Tested on Meteor v1.0 with Iron Router v1.0

Resources