Iron router allows to render different content into layout template, or load smaller templates into yields, but when user goes to different path, a content from previous yields does not unload templates inside them, until you force router to render null as a template into yields.
I have written a simple loop, which unload all yields before rendering new view, but i think there should be easier solution.
Router.route('/docs/container/', function () {
// render base layout for view
this.render('view');
// clear all parts
var yields = [
'viewSideLeft',
'viewSideRight',
'viewFooter',
'viewTopbar',
'pageContent',
'pageSideLeft',
'pageSideRight',
'pageFooter',
'pageTopbar'
];
for (var i = 0; i < yields.length; i++) {
this.render(null, {to: yields[i]});
}
// and render only this parts which you need:
this.render('viewDocsSideLeft', {to: 'viewSideLeft'});
this.render('viewDocsSideRight', {to: 'viewSideRight'});
this.render('viewDocsFooter', {to: 'viewFooter'});
this.render('viewDocsTopbar', {to: 'viewTopbar'});
this.render('pageDocsContainer', {to: 'pageContent'});
// this.render('pageDocsContainerSideLeft', {to: 'pageSideLeft'});
// this.render('pageDocsContainerSideRight', {to: 'pageSideRight'});
// this.render('pageDocsContainerFooter', {to: 'pageFooter'});
// this.render('pageDocsContainerTopbar', {to: 'pageTopbar'});
});
Here is a github issue related to this problem and it seems it has not been solved yet. https://github.com/iron-meteor/iron-router/issues/174
Related
I have the following route defined in my iron-router:
this.route("/example/:id", {
name: "example",
template: "example",
action: function () {
this.wait(Meteor.subscribe('sub1', this.params.id));
this.wait(Meteor.subscribe('sub2', <<data of sub1 needed here>>));
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
I want to wait for sub1 and sub2 before rendering my actual template. The problem is that I need a piece of data which is part of the result of sub1 for the sub2 subscription.
How can I wait sequential for subscriptions? So that I can split the wait in two steps and wait for my first subscription to be finished. Then start the second subscription and then set this.ready() to render the template?
A workaround that I thought of was to use Reactive-Vars for the subscriptions and dont use .wait and .ready which is provided by iron-router. But I would like to use a more convenient solution provided by iron-router or Meteor itself. Do you know a better solution for this?
Thanks for your answers!
Publish Composite Package:
If the second subscription is reactively dependent on certain fields from the first dataset -- and if there will be a many-to-many "join" association, it might be worth looking into reywood:publish-composite package:
It provides a clean and easy way to manage associated subscriptions for collections with hierarchical relations.
Publication:
Meteor.publishComposite('compositeSub', function(id) {
return {
find: function() {
// return all documents from FirstCollection filtered by 'this.params.id' passed to subscription
return FirstCollection.find({ _id: id });
},
children: [
find: function(item) {
// return data from second collection filtered by using reference of each item's _id from results of first subscription
// you can also use any other field from 'item' as reference here, as per your database relations
return SecondCollection.find({ itemId: item._id });
}
]
}
});
Subscription:
Then you can just subscribe in the router using:
Meteor.subscribe('compositeSub', this.params.id);
Router hooks:
As a suggestion, hooks in iron-router are really useful, as they take care of a lot of things for you. So why not use the waitOn hook that manages this.wait and loading states neatly?
this.route('/example/:id', {
name: "example",
template: "example",
// 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('compositeSub', this.params.id);
// FYI, this can also return an array of subscriptions
},
action: function () {
this.render();
}
});
You can use the configure option to add a template for loading event:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading'
});
Note regarding the comment in question:
If both subscriptions only depend on the same id parameter passed to it, you can use the following, as mentioned by #abj27 in the comment above -- however, this does not seem to be the case, going by your example:
Publication:
Meteor.publish("subOneAndTwo", function (exampleId) {
check(exampleId, String);
return [
FirstCollection.find({ _id: exampleId });
SecondCollection.find({ firstId: exampleId })
];
});
Subscription:
Meteor.subscribe('subOneAndTwo', this.params.id);
So just check what you need and use a solution accordingly.
https://github.com/kadirahq/subs-manager
With this package, you can assign a subscription to a variable. Then, you can check that variable's ready state. I just got this working... after years of trying to understand.
Here is my code snippet that works, but oddly I had to wrap it in a 1ms timeout to work...
```
Router.route('/activity/:activityId', function (params) {
var params = this.params;
setTimeout(function(){
var thePage = window.location.href.split("/");;
window.thePage = thePage[4];
dbSubscriptionActivites.clear();
window.thisDbSubscriptionActivites = "";
window.thisDbSubscriptionActivites = dbSubscriptionActivites.subscribe("activityByPage", window.thePage);
Tracker.autorun(function() {
if(window.thisDbSubscriptionActivites.ready()) {
dbSubscriptionComments.clear();
window.thisDbSubscriptionComments = "";
window.thisDbSubscriptionComments = dbSubscriptionComments.subscribe('fetchComments', "activity", Activities.findOne({})._id);
BlazeLayout.render("activity");
$('body').removeClass("shards-app-promo-page--1");
}
});
},1); // Must wait for DOM?
});
```
Examine: window.thisDbSubscriptionActivites = dbSubscriptionActivites.subscribe("activityByPage", window.thePage);
I'm setting as a window variable, but you could do a const mySub = ...
Then, you check that in the autorun function later.
You can see there is where I am doing subscriptions.
I suppose I really should move the BlazeLayout render in to another .ready() check for the comments.
I'm using the latest Meteor and Iron Router. Imagine have a 'customComputer' collection. Computers have three different states: 'ordering', 'building', 'shipped'.
Right now, I'm using three different routes for this, each with a different template
/o/_id
/b/_id
/s/_id
A computer can't be in 2 states at once, so I'd like to have one route. How do I wrangle the templates?
/c/_id
The best I can come up with is to make a "main" template that links to the others. Is this a best practice?
{{#if isOrder}}{{>orderTemplate}}{{/if}}
{{#if isBuilding}}{{>buildingTemplate}}{{/if}}
{{#if isShipped}}{{>shippedTemplate}}{{/if}}
Or dynamic templates
Here's the route:
Router.route('order', {
path: '/o/:b/:computerId',
onAfterAction: function() {
if (this.title) document.title = this.title;
},
data: function() {
if(!this.ready()) return;
var o = Computer.findOne(this.params.computerId);
if(!o) throw new Meteor.Error('Computer not found');
return o;
},
waitOn: function() {
if (!Meteor.userId()) return this.next(); // Don't subscribe if not logged in.
return [
Meteor.subscribe('computerById', this.params.computerId),
Meteor.subscribe('myProfile'),
];
},
});
Is there a better way?
I'd do your main template idea, or the dynamic template.
dynamic template tends to be better when you have quite a few options that can be dynamically configured.
But the main template I think ends up being more obvious when you only have a couple of choices.
Either way can be converted easily to the other if you think you need the other option.
How can I set a layout when my data function returns null.
For example, in the route below, when chefs is null, I would like to render my 'notFound' template.
Router.route('/vendors/chefs/:_url', {
template: 'chefs',
data: function() {
var chefs = Chef_db.findOne({url: this.params._url});
return chefs;
}
});
Take a look at the notFoundTemplate in the documentation: https://github.com/iron-meteor/iron-router/blob/devel/Guide.md
You can apply it globally:
Router.plugin('dataNotFound', {notFoundTemplate: 'notFound'});
Or you can apply it to specific routes using the except/only options:
Router.plugin('dataNotFound', {
notFoundTemplate: 'NotFound',
except: ['server.route']
// or only: ['routeOne', 'routeTwo']
});
There's a built-in plugin for that. It's called dataNotFound. It's mentioned in the iron:router guide.
Is there a way to set a reactive layout in meteor with iron router?
For example:
Router.configure({
loadingTemplate: 'loading',
layoutTemplate: Session.get('fullscreen') ? 'layoutFull' : 'layout'
});
Then a link in both layouts with:
Toggle Fullscreen
And then in both layouts something like this:
Template.layoutFull.events({
'click [data-action=toggleFullscreen]': function() {
Session.set('fullscreen', !Session.get('fullscreen'));
}
});
Template.layout.events({
'click [data-action=toggleFullscreen]': function() {
Session.set('fullscreen', !Session.get('fullscreen'));
}
});
The issue I'm running into is Router.configure isn't setting layoutTemplate reactively. Is there a way to do this so it affects all routes?
It has to be a function to be reactive:
layoutTemplate: function(){
return Session.get('fullscreen') ? 'layoutFull' : 'layout'
}
Also, why two functions?
You need just one since this is the negation of clicked Session
I want to add multiple Bootstrap Modals in one Yield.
Router.route('test', {
yieldTemplates: {
'contentTemplate': {to: 'content'},
'modal1Template': {to: 'modal'},
'modal2Template': {to: 'modal'}
}
});
This code adds only the last template to the yield -modal-. Is it possible to add multiple templates to one yield or do anybody know a best practice for this situation?