I have a Meteor template in HTML-file:
<template name='main'>
</template>
I rendered it using Iron router:
Router.route('/', function () {
this.render('main');
});
Now I want to render another template to replace 'main' template. How to do it?
Obviously you do not want another route?
If not you can use a reactive var in the router. When you change the variable it will run again and render your other template.
See http://eventedmind.github.io/iron-router/#hooks
var OnBeforeActions;
OnBeforeActions = {
whichMain: function() {
if (reactiveVar) {
this.render('otherMain');
}
else
this.next() ;
}
};
Router.onBeforeAction(OnBeforeActions.whichMain, {
only: ['Main']
});
Alternatively use a dynamic template inside your main router.
https://www.discovermeteor.com/blog/blaze-dynamic-template-includes/
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);
});
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');
}
});
I would like to load a meteor template using iron-router but the template that I'm loading needs to be dynamic, I have tried a couple different approaches but none are working.
My router
Router.route('/forms/add-form/:template', {
name: 'addForm',
layoutTemplate: 'layoutApp',
waitOn: function() {
return Meteor.subscribe('producersList');
},
data: function() {
return Producers.find();
}
});
The router go
Router.go('addForm', {template: this.template});
The url is fine now I first tried having this in my router
name: this.params.template,
But that doesn't work I'm now trying the following in my addForm template
{{> UI.dynamic template=formToLoad data=myDataContext}}
formToLoad:function(){
console.log('aaaa ' + this.template);
}
});
You can pass data into template in route:
Router.route('/forms/add-form/:template', {
name: 'addForm',
layoutTemplate: 'layoutApp',
waitOn: function() {
return Meteor.subscribe('producersList');
},
data: function() {
return {
producers: Producers.find(),
formToLoad: this.params.template //here pass the template name
}
}
});
and in your template:
<template name="addForm">
{{> Template.dynamic template=formToLoad}}
</template>
Now if we run:
Router.go('addForm', {template: 'someTemplateName'});
It should load template with name 'someTemplateName'. Use camelCase syntax for template names because you will get syntax error with 'some-template-name' when you will define helpers or events for template.
This came out recently: https://meteorhacks.com/server-side-rendering.html but there doesn't seem to be a full fledged example of how to use this with iron-router.
If I had a template like:
/private/post_page.html
{{title}}
{{#markdown}} {{body}} {{/markdown}}
How would I populate it with a single records attributes from a request for a specific ID ?
E.g page requested was localhost:3000/p/:idofposthere how to populate it with data and render it in iron-router for that route / server side?
Actually a bit easier than you think (I just followed Arunoda's SSR example and converted it to iron-router for you):
if(Meteor.isServer) {
Template.posts.getPosts = function(category) {
return Posts.find({category: category}, {limit: 20});
}
}
Router.map(function() {
this.route('home');
this.route('view_post', {
path: 'post/:id',
where:"server",
action : function() {
var html = SSR.render('posts', {category: 'meteor'})
var response = this.response;
response.writeHead(200, {'Content-Type':'text/html'});
response.end(html);
}
});
});
It only gets tricky if you share the same route for client and server side for example, if you want it to render client-side based on user agent.
Source: We use this strategy on our own apps.
UPDATE
While the above code is simply what the question is asking for, we can get this working to follow Google's Ajax spec by checking the ?_escaped_fragment_= query string before we reach the client..
Basically, what we mostly don't know about Iron-Router is that if you have identical routes declared for server and client, the server-side route is dispatched first and then the client-side.
Here's the main javascript (with annotations):
ssr_test.js
Router.configure({
layout: 'default'
});
Posts = new Mongo.Collection('posts');
// Just a test helper to verify if we area actually rendering from client or server.
UI.registerHelper('is_server', function(){
return Meteor.isServer ? 'from server' : 'from client';
});
myRouter = null;
if(Meteor.isServer) {
// watch out for common robot user-agent headers.. you can add more here.
// taken from MDG's spiderable package.
var userAgentRegExps = [
/^facebookexternalhit/i,
/^linkedinbot/i,
/^twitterbot/i
];
// Wire up the data context manually since we can't use data option
// in server side routes while overriding the default behaviour..
// not this way, at least (with SSR).
// use {{#with getPost}} to
Template.view_post_server.helpers({
'getPost' : function(id) {
return Posts.findOne({_id : id});
}
});
Router.map(function() {
this.route('view_post', {
path: 'post/:id', // post/:id i.e. post/123
where: 'server', // this route runs on the server
action : function() {
var request = this.request;
// Also taken from MDG's spiderable package.
if (/\?.*_escaped_fragment_=/.test(request.url) ||
_.any(userAgentRegExps, function (re) {
return re.test(request.headers['user-agent']); })) {
// The meat of the SSR rendering. We render a special template
var html = SSR.render('view_post_server', {id : this.params.id});
var response = this.response;
response.writeHead(200, {'Content-Type':'text/html'});
response.end(html);
} else {
this.next(); // proceed to the client if we don't need to use SSR.
}
}
});
});
}
if(Meteor.isClient) {
Router.map(function() {
this.route('home');
this.route('view_post', { // same route as the server-side version
path: 'post/:id', // and same request path to match
where: 'client', // but just run the following action on client
action : function() {
this.render('view_post'); // yup, just render the client-side only
}
});
});
}
ssr_test.html
<head>
<title>ssr_test</title>
<meta name="fragment" content="!">
</head>
<body></body>
<template name="default">
{{> yield}}
</template>
<template name="home">
</template>
<template name="view_post">
hello post {{is_server}}
</template>
<template name="view_post_server">
hello post server {{is_server}}
</template>
THE RESULT:
I uploaded the app at http://ssr_test.meteor.com/ to see it in action, But it seems to crash when using SSR. Sorry about that. Works fine if you just paste the above on Meteorpad though!
Screens:
Here's the Github Repo instead:
https://github.com/electricjesus/ssr_test
Clone and run!
SSR is lacking real life examples, but here is how I got it working.
if (Meteor.isServer)
{
Router.map(function() {
this.route('postserver', {
where: 'server',
path: '/p/:_id',
action: function() {
// compile
SSR.compileTemplate('postTemplate', Assets.getText('post_page.html'));
// query
var post = Posts.findOne(this.params._id);
// render
var html = SSR.render('postTemplate', {title: post.title, body: post.body});
// response
this.response.writeHead(200, {'Content-Type': 'text/html'});
this.response.write(html);
this.response.end();
}
});
});
}
Assets are documented here: http://docs.meteor.com/#assets.
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