Is there a way to prevent the loadingTemplate from showing up on "fast" routes? - meteor

Most of the time, my search returns so fast that it's not worth flashing the loading template to the user...(in fact, it's distracting, as people are fine with a blank screen if the results are coming a split second later)...Is there any way to prevent the loading template from showing if the waitOn is only waiting for a short amount of time?
Here's my configuration
Router.route('/search', {
waitOn: function () {
return searchSubsManager.subscribe("search", Session.get('searchString'));
},
action: function () {
this.render('searchResults');
}
});
I saw that with this package:
https://github.com/Multiply/iron-router-progress
you can control whether it shows up for fast routes, but I don't need all that functionality, nor do I want the progress bar it provides... I'm just wondering if the basic iron router/ waitOn functionality can provide this ability.

There is not configuration to use on the waitOn function, but Why don't you create another layout template, and use it to show that fast routes?
<template name="noLoading">
{{> yield}}
</template>
Router.map(function () {
this.route('fastRoutes', {
path: '/someRoutes',
template: 'myHomeTemplate',
layoutTemplate: 'noLoading',
});
});
Update
or use the sacha:spin package and change the class name depending on the duration of the query.
if(queryDuration){
Meteor.Spinner.options = {
className: 'none'
}
}else{
Meteor.Spinner.options = {
className: 'spinner'
}
}

Related

Meteor Iron-Router: Wait for Subscription sequential

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.

Meteor Iron Router. One Route, Multiple Templates. How?

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.

Meteor Iron Router not working on certain links

I have a route like this:
Router.route('/box', function () {
this.render('boxCanvasTpl');
},{
name: 'box',
layoutTemplate: 'appWrapperLoggedInTpl',
waitOn: function() {
console.log("Box route ran ok.");
return [
Meteor.subscribe('item_ownership_pub', function() {
console.log("subscription 'item_ownership_pub' is ready.");
}),
Meteor.subscribe('my_items', function() {
console.log("subscription 'my_items' is ready.");
})
];
}
});
... and I am clicking a link in a Template like this:
My Link
I receive the 'Box route ran ok.' message, but some reason the page does not navigate to the given URL. I have added console.log code in the funciton that is run when the 'boxCanvasTpl' is rendered, but these aren't showing in the browser console. It seems that something inbetween is stopping the templkate from re-rendering, but can't put my finger on it - any ideas?
There are some properties of Iron Router that you need to be aware of.
Say that the user is currently already on /boxes and there is a box template that renders for that path. If you:
click on a link Click Me
or
click on a link Click Me
Iron Router will NOT re-render the template because it already exists on the page. It will also NOT re-render the template if the box template happens to be a partial template that is already rendered on the page that you're on and also exists on the page that you want to navigate to.
Since it doesn't re-render, any code you have inside Template.box.onRendered will also not run again.
This behavior is most common in your layout, header, and footer templates. For many users, these templates are used for all of a website's pages, regardless of path. Because the layout, header, and footer template is rendered on a person's first visit to the site, they won't be re-rendered ever again if the user decides to navigate to other parts of the site using the same templates, so the code inside Template.layout/header/footer.onRendered won't fire.
Also note - even if a reactive Spacebars helper changes the physical look of the layout / header / footer, it doesn't qualify as an actual render, so reactive updates to the template do not trigger the onRendered callback.
The lack of re-rendering is what gives Meteor that "snappy" feel.
EDIT
Try to code in a reactive, event-driven style. Try not to think too much in a render / re-render sense.
You go to /box
You click on a link for /box?box=2342
Get your params or query in Iron Router
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#route-parameters
In Iron Router use the data from the params or query to set the data context for the template.
Grab stuff from the data context as needed inside of the template's .onRendered, .events, and .helpers callbacks.
Set Session vars as necessary and use them in helpers to give reactive changes to the page without having to re-render a template. Also use events to trigger updates to the session vars to, again, trigger reactive changes to the page.
Try this:
afterwards, go to /test?BUNNIES=lalalala
check out the console logs
test.html
<template name="test">
{{myData}}
</template>
test.js
Template.test.helpers({
myData: function() {
console.log("data context accessed from test.helpers: ", this);
console.log("this.BUNNIES accessed from test.helpers: ", this.BUNNIES);
return this.BUNNIES;
}
});
Template.test.onRendered(function() {
console.log("data context accessed from test.onRendered: ", this.data);
});
Template.test.events({
'click': function(){
console.log("data accessed from test.events: ", this);
}
});
router.js
Router.route('/test', function() {
console.log("routed!");
this.render('test');
}, {
name: 'test',
data: function(){
//here I am setting the data context
// for /test?BUNNIES=1234
var query = this.params.query;
console.log("query: ", query);
return query;
},
waitOn: function() {
console.log("waitOn is running (should see this message once for each subscription)");
return [
Meteor.subscribe('item_ownership_pub'),
Meteor.subscribe('my_items')
];
}
});
way cleaner way of writing router
Router.route('/test', {
waitOn: function() {
console.log("waitOn is running (should see this message once for each subscription");
return [
Meteor.subscribe('item_ownership_pub'),
Meteor.subscribe('my_items')
];
},
data: function(){
var query = this.params.query;
console.log("query: ", query);
return query;
},
action: function(){
console.log("this will re-render if url params changed");
this.render();
}
})

How to pause Iron Router template rendering until all data is available?

Current common Iron Router pattern is to display a loading template while waiting for data to be available. But I would prefer simply to wait on the previous rendered template/data context until data is available and then trigger rerendering. Data is quickly available, so that short flicker of loading template is worse than a short delay user will experience after a link click.
Does this pattern work for you?
Router.route('/', {
name: 'nameOfTemplate',
data: function() { return CollectionName.find({title: 'nameOfMongoDBQuery'}); },
waitOn: function() { return Meteor.subscribe('nameOfSubscription'); } // waits until resources arrive before rendering page
});
You'll want to specify an explicit action function like so:
action: function() {
if (this.ready()) {
this.render();
}
}
This will just not render anything until the data is around.

In iron router, how to avoid recompute the entire data field when only a subset is changed

In Iron-router, we can pass the data to a page in the data field. For example:
Router.map(function () {
this.route('myroute', {
path: '/route',
template: 'myTemplate',
data: function () {
return {
title: getTitle(),
description: getDescription(),
}
}
});
});
In the template, title and description are actually some value passed to subtemplates:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>
Since the data field in the iron-router is reactive, whenever a session variable change, the data field is recalculated.
In this case, however, the session variable in getTitle function only changes the template "titleTemplate", and the session variable in getDescription() function only changes the template "descriptionTemplate".
If the session variable in the getTitle() function changes, I would like to only execute the getTitle() function, and do not execute the getDescription() function. If possible, I would also like to only render the "titleTemplate" and do not render "descriptionTemplate".
I wonder whether that is possible. If this is not the right way of writing the Meteor application, what is a better way to do it?
Thanks.
This is an interesting situation. Despite the fact that the getTitle and getDescription functions may be dependent on completely different reactive variables, they will both be recomputed whenever either one of them changes.
One possible solution is to pass the functions themselves instead of the result of calling the functions. That may or may not be convenient depending on how they are used in the sub-templates, but it will prevent them from both being run every time. Here is a simple example:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>
<template name="titleTemplate">
<p>{{excitedTitle}}</p>
</template>
<template name="descriptionTemplate">
<p>{{this}}</p>
</template>
var getTitle = function() {
console.log('computed title');
return Session.get('title');
};
var getDescription = function() {
console.log('computed description');
return Session.get('description');
};
Router.map(function() {
this.route('home', {
path: '/',
template: 'myTemplate',
data: function() {
return {
title: getTitle,
description: getDescription
};
}
});
});
Meteor.startup(function() {
Session.setDefault('title', 'title1');
Session.setDefault('description', 'description1');
});
Template.titleTemplate.excitedTitle = function() {
return "" + (this.toUpperCase()) + "!";
};
From the console you can change the session variables (title and description) and you will see that only one function will be run at a time. I hope that helps.
One way to solve this is to not use the data context, but just use template specific helpers. Since I don't know what your getTitle and getDescription function do, I can't tell whether that is an option for you. It depends on whether you need to use the this object in those functions and need this to refer to the route object or not. If not, then the following seems like the better solution:
JS:
Router.map(function () {
this.route('myroute', {
path: '/route',
template: 'myTemplate'
});
});
Template.myTemplate.title = getTitle;
Template.myTemplate.description = getDescription;
HTML:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>

Resources