Iron-router + Fast-render, why is this an endless loop? - meteor

why is this an endless loop? [ Iron Router + Fast Render + Blaze]
Router.route("/:cycle_uri", {
name: "cycle"
,template: "home"
,onBeforeAction: function () {
console.log("is there a loop here?") // this is what confirms that it's a continuous loop
var cycle = Cycles.findOne({
"uri": this.params.cycle_uri
});
if (typeof cycle === "undefined") {
this.render("notFound"); return;
} else {
ActiveCycle.set(cycle); // if I remove this, there is no continuous loop anymore... but if I remove it I don't see how to have this info in the client
this.render("home");
}
}
,waitOn: function () {
Meteor.subscribe('featuredListsPub', {
'program_id': this.params.cycle_uri
});
}
,fastRender: true
});
I was trying to update ActiveCycle variable so I can read it in the frontend but it's not actually working... I'm certainly doing something wrong, but would like to first understand why updating the reactive var is creating a loop.
I've also tried
if (ActiveCycle.get() !== cycle) {
ActiveCycle.set(cycle);
}
but it also enters a loop... which I don't understand why

for your question in the comments:
How do you subscribe to two publications:
here is my answer:
waitOn: function () {
return [
Meteor.subscribe('subscription1'), Meteor.subscribe('subscription2')
];
}
However, i strongly recommend:
Create on publication and return two cursors
Use Template level subscriptions
Good Luck!
An example of Template level subscriptions:
Template.templatename.onCreated(function () {
Template.autorun(function () {
var subscription = Meteor.subscribe('some_publication');
if (subscription.ready()) {
// do something
}
});
});
and within the template
<template name="templatename">
{{#if Template.subscriptionsReady}}
<div>Your Template here...</div>
{{else}}
<p>Loading...</p>
{{/if}}
</template>
A nice article is right here.

Related

Template specific subscriptions in an array

Template.recent.created = function () {
this.autorun(function () {
this.subscriptions = [
this.subscribe('users'),
this.subscribe('posts'),
this.subscribe('comments')
];
}.bind(this));
};
Template.recent.rendered = function () {
this.autorun(function () {
var allReady = _.every(this.subscriptions, function (subscription) {
return subscription.ready();
});
...
Is this the correct way to subscribe to more than one DB source in a template? When I render this template again while it's still loading, then it seems to go into infinite loading state.
Related doc: https://www.discovermeteor.com/blog/template-level-subscriptions/
There is no need to wrap your subscriptions in a Tracker.autorun. In fact, each sub has a onReady callback that you can use:
this.subscribe('subName', {onReady: function() {
//Do something when ready
}});
But besides that, there is a subscriptionsReady() function that returns true when all your template subs are ready (see the doc):
So your code become:
Template.recent.onCreated(function () {
this.subscriptions = [
this.subscribe('users'),
this.subscribe('posts'),
this.subscribe('comments')
];
if(this.subscriptionsReady()) {
//do something when all subs are ready
}
});
And in your template you can also check if all template's subs are ready:
<template name="templateName">
{{#if Template.subscriptionsReady}}
Everything is ready!
{{else}}
Loading...
{{/if}}
</template>

Meteor publication not working

I have Iron Router and quite simple pub/sub.
When publication just returns some specific item - everything works fine. But when it does some logic inside (looping thru another collection) - it doesn't work (Iron Router's loading template keeps showing forever, and looks like no data is coming thru DDP from this publication).
The code of pub:
Meteor.publish('ordersWithState', function(orderState) {
// if uncommented, this line works just fine
//return Orders.find({name:"C02336"});
var temp = Workflows.findOne({name:"CustomerOrder"});
if (temp) {
var stateUuid;
_.each(temp.state, function (state) {
if (state.name == orderState) {
return Orders.find({stateUuid: state.uuid});
}
});
}
});
Router config (if needed):
this.route('ordersList', {
path: '/orders/list/:orderState?',
loadingTemplate: 'loading',
waitOn: function() {
console.log("in ordersList waitOn");
var orderState = this.params.orderState || "Требуется закупка";
return [
Meteor.subscribe('ordersWithState', orderState),
Meteor.subscribe('allSuppliersSub'),
Meteor.subscribe('tempCol'),
Meteor.subscribe('workflows')
];
},
data: function () {
return Orders.find({});
},
onBeforeAction: function (pause) {
this.next();
}
});
The problem is with the logic of your publication here:
if (temp) {
var stateUuid;
_.each(temp.state, function (state) {
if (state.name == orderState) {
return Orders.find({stateUuid: state.uuid});
}
});
}
You are returning something from your inner _.each function, but you are not returning anything from the publication function. So the publication is not returning anything to Iron Router or responding with this.ready();.
It is not exactly clear to me what you want to publish - an array of cursors or perhaps an Orders.find() with an $in: [arrayOfItems]? In any case Iron Router should work fine once the publication is fixed.

iron router syntax: onAfterAction

Router.route('/courses/:_catalog', function () {
var courseCatalog = this.params._catalog.toUpperCase();
Meteor.subscribe("courseCatalog", courseCatalog);
this.render('CourseDetail', {
to: 'content',
data: function () {
return Courses.findOne({catalog: courseCatalog});
}
});
}, {
onAfterAction: function() {
if (!Meteor.isClient) {
return;
}
debugger
var course = this.data(); <======
SEO.set({
title: "course.catalog"
});
}
});
In the above code, please look at the debugger statement. I want to access the data but it seems I am doing something wrong because this.data doesn't exist. I also tried Courses.find().fetch() but I only get an empty array inside onAfterAction. What's the right syntax and what am I missing?
It needs to be inside a this.ready() block:
onAfterAction: function() {
if (this.ready()) {
var course = this.data();
...
}
}
You need to subscribe to data first. Have a look at the waitOn function to do this. The server only sends the documents you subscribed to, and since you didn't subscribe, Courses.find().fetch() returns an empty array.
Also, don't put SEO stuff in onAfterAction. Put it in onRun which is guaranteed to only run once.

Session object inside global template helpers

Session.set('coursesReady', false); on startup.
UPDATE:
I made it into a simpler problem. Consider the following code.
Inside router.js
Router.route('/', function () {
Meteor.subscribe("courses", function() {
console.log("data ready")
Session.set("coursesReady", true);
});
}
and inside main template Main.js
Template.Main.rendered = function() {
if (Session.get('coursesReady')) {
console.log("inject success");
Meteor.typeahead.inject();
}
The message "inject success" is not printed after "data ready" is printed. How come reactivity does not work here?
Reactivity "didn't work" because rendered only executes once (it isn't reactive). You'd need to wrap your session checks inside of a template autorun in order for them to get reevaluated:
Template.Main.rendered = function() {
this.autorun(function() {
if (Session.get('coursesReady')) {
console.log("inject success");
Meteor.typeahead.inject();
}
});
};
Probably a better solution is to wait on the subscription if you want to ensure your data is loaded prior to rendering the template.
Router.route('/', {
// 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('courses');
},
action: function () {
this.render('Main');
}
});
And now your rendered can just do this:
Template.Main.rendered = function() {
Meteor.typeahead.inject();
};
Don't forget to add a loading template.
To Solve Your Problem
Template.registerHelper("course_data", function() {
console.log("course_data helper is called");
if (Session.get('coursesReady')) {
var courses = Courses.find().fetch();
var result = [ { **Changed**
name: 'course-info1',
valueKey: 'titleLong',
local: function() {
return Courses.find().fetch();
},
template: 'Course'
}];
Session.set('courseResult', result); **New line**
return Session.get('courseResult'); **New line**
,
Explanation
The answer is at the return of the helper function needs to have be associated with reactivity in order for Blaze, template renderer, to know when to rerender.
Non-reactive (Doesn't change in the DOM as values changes)
Template.Main.helpers({
course_data: UI._globalHelpers.course_data ** Not reactive
});
Essentially: UI._globalHelpers.course_data returns an array of objects which is not reactive:
return [
{
name: 'course-info1',
valueKey: 'titleLong',
local: function() {
return Courses.find().fetch();
},
template: 'Course'
},
Reactive
From Meteor Documentation:
http://docs.meteor.com/#/full/template_helpers
Template.myTemplate.helpers({
foo: function () {
return Session.get("foo"); ** Reactive
}
});
Returning Session.get function to Blaze is reactive; thus, the template will change as the values changes.

How can I delay a reactive Meteor template from updating an HTML template variable, when a Meteor.call() updates the database instantly?

I'm developing a keno game. When the user presses the start button a Meteor.Call() executes everything for that card pick. Including updating the user balance. I have a setTimeout for the winning numbers, so that they display over a period of about 20 seconds. The problem is that when the call is made, the balance updates instantly, and then the numbers start displaying with the delay. I not familiar with how to solve this. I appreciate any help.
server-side:
Meteor.methods({
process: function(){
// generate numbers
// update user balance
}
});
client-side:
Template.keno.events({
'click #start' : function(){
Meteor.call('process',function(err,numbers){
//setTimeout on displaying numbers
// as setTimeout displays numbers, balance already updated. I need to delay
// the balance update, until all numbers are displayed.
// otherwise, the player see that they won before all numbers come out.
});
}
});
** Update **
The only help I need is to understand how to make a variable like {{balance}} unreactive, until I finish the setTimeout, and then have it update. Should I be using sessions? Should I not use a template variable and instead, insert the balance with jquery? It's just a simple solution, the difficulty is that I don't know what function / method I'm looking for that can help me turn off the reactivity for a set amount of time, and then update, after the Meteor.call() for then numbers finishes it's setTimeout.
If I understand your situation correctly, you need the template {{balance}} expression to be set at a time you decide vs. when the collection gets a result from the server. So you could use Session to set a value when you like. Below is an example:
<body>
{{> game}}
</body>
<template name="game">
<button id="process">Process</button>
<div>{{firstNumber}}</div>
<div>{{secondNumber}}</div>
<div>balance: {{balance}}</div>
</template>
if (Meteor.isClient) {
Template.game.events({
'click #process': function (e, tmpl) {
Meteor.call('process', function (err, result) {
Session.set('firstNumber', result[0]);
setTimeout(function () {
Session.set('secondNumber', result[1]);
Session.set('balance', result[0] + result[1]);
}, 2000);
});
}
});
Template.game.helpers({
firstNumber: function () { return Session.get('firstNumber'); },
secondNumber: function () { return Session.get('secondNumber'); },
balance: function () { return Session.get('balance'); }
});
}
if (Meteor.isServer) {
function randomNumber () {
return Math.floor(Math.random() * 100);
}
Meteor.methods({
process: function () {
return [randomNumber(), randomNumber()];
}
});
}
Try to wrap your Meteor.call() inside the setTimeout() itself, like:
Template.keno.events({
'click #start' : function(){
setTimeout(function(){
Meteor.call('process',function(){
//do something.
});
}, 20000);
}
});
Maybe the solution is to use reactivity on a duplicated collection :
You set your main collection on server side only.
You create another collection on the client side that will be a duplicate collection but used only for display
Then you pusblish the main collection to the client.
On the client side, you set all required observer on it that will replicate all modification on the duplicated collection. But this way you can manage animation or any other wished features. All actions on client side will do call on server-side but it won't affect immediatly the templates because the templates only use the duplicated collections.
I hope it will help you.
OK, so I threw this demo together in 5 mins, works though.
Here's the demo: http://keno-test.meteor.com/
Of course it needs LOT's more work, but the delayed thing works.
HTML:
<head>
<title>keno-test</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
<input id="callCardThing" type="button" value="Start card-thing" />
<h1>Here are the cards!</h1>
<ul>
{{#each cards}}
<li>{{value}}</li>
{{/each}}
</ul>
</template>
JS:
Cards = new Meteor.Collection('cards');
if (Meteor.isClient) {
Deps.autorun(function () {
Meteor.subscribe("cards");
});
Template.hello.events({
"click #callCardThing": function (event) {
Meteor.call("doCardThingOnServer");
}
});
Template.hello.helpers({
cards: function () {
return Cards.find({});
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
Meteor.publish("cards", function () {
return Cards.find({});
});
});
Meteor.methods({
doCardThingOnServer: function () {
// I remove all the cards every time just for the demo…
Cards.remove({});
var numberOfcards = 10;
var counter = Meteor.setInterval(function () {
Cards.insert({value: 'whatever! no: '+numberOfcards });
numberOfcards--;
if (numberOfcards < 1) Meteor.clearInterval(counter);
}, 1500);
}
});
}
Ok, so how about conditional {{balance}} rendering?
var shouldRender = false;
setTimeout(function () {
shouldRender = true;
}, 2000);
Template.template_name.shouldRender = function () {
return shouldRender;
}
{{#if shouldRender}}
{{>balance}}
{{/if}}
Have a look at the Atmosphere animation package : https://atmosphere.meteor.com/package/animation!
I've just done this package to explore one way of doing animation on database reactivity.
You have to register a cursor and the template to animate. There is a project that will show you how to do that.
I hope it will help you.

Resources