I have this issue that the rendered function is called while the elements are not yet in the DOM. Let me describe my situation. The templates looks like this:
<template name="barfoo">
<ol>
{{#each bars}}
<li item>{{title}}</li>
{{/each}}
</ol>
</template>
And the following javascript code
Template.barfoo.bars = function () {
return Bars.find({});
};
Tempalate.barfoo.rendered = function () {
var bars = Bars.find({}).fetch();
var list = $('[items]');
};
When the rendered function is called bars == list == [].
So to fix this I implemented Iron routes like this
Bars = new Meteor.Collection('bars');
// Routes
Router.configure({
layout: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound'
});
Router.map(function () {
this.route('/', {
controller: 'BarsController',
});
});
if (Meteor.isClient) {
App = {
subs: {
bars: Meteor.subscribe('bars'),
...
}
};
MainController = RouteController.extend({
template: 'barfoo',
before: function () { ... },
waitOn: function () {
return [App.subs.bars];
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
Meteor.publish('bars', function () {
return Bars.find({});
});
}
All basics, but when I go now to localhost:3000 I still get in the rendered function empty lists . Any suggestions what do wrong here ?
I assume your are working with the latest version of iron-router. If that's the case you will also need to add loading hook to your router, so:
Router.onBeforeAction('loading');
Also note that before hook is marked as deprecated.
Related
I'm a bit of a noob and having a bit of trouble getting my publications to work. In my data, I have a number of patients and would like to show the data of a single patient. This is how I have structured my publication:
Meteor.publish('patients.single', function (patientId) {
check(patientId, String);
return Patients.find({_id: patientId});
});
and this is how I have subscribed:
Router.route('/patients/:_id', {
layoutTemplate: 'ApplicationLayout',
yieldRegions: {
'single_patient': {to: 'content'}
},
subscriptions: function () {
return Meteor.subscribe('patients.single', this.params._id);
}
});
I have also tried to subscribe via the actual template to no avail:
Template.patient_details.onCreated(function () {
this.subscribe('patients.single', Session.get("currentPatient"));
});
Publications seem easy in theory, but I just can't seem to get them right. What am I doing wrong here?
It takes time for the subscription to get the data from the server to the mini mongo, so you have to wait for the subscription to be ready, before using the data that It will get for you.
If you are using Iron Router try using waitOn instead of subscribe, that will force the router to wait for the subscription to be ready and will render the loading template while its getting the subscription data.
Router.route('/patients/:_id', {
layoutTemplate: 'ApplicationLayout',
yieldRegions: {
'single_patient': {to: 'content'}
},
waitOn: function () {
return Meteor.subscribe('patients.single', this.params._id);
}
data: function () {
return Patients.findOne({_id: this.params._id});
},
});
You can also use the data property, that way you will have the data available in your template instance.data.
Try this:
Server side Js
Meteor.publish('patients.single', function (patientId) {
check(patientId, String);
return Patients.find({_id: patientId});
});
Router JS File
Router.route('/patients/:_id', {
layoutTemplate: 'ApplicationLayout',
yieldRegions: {
'single_patient': {to: 'content'}
},
waitOn: function () {
return Meteor.subscribe('patients.single', this.params._id);
}
});
In client JS File
Template.patient_details.helpers({
getData : function(){
return Collection.find().getch();
});
Don't forget to call the {{getData}} in the template html file.
I am fairly new to Meteor.
I have a template helper function which requires to work with data I am publishing from the server side. When the page loads, the value for profile below is undefined. But after that when I execute the same code snippet from browser's console, it works just fine. From what I understand, the template helper is being executed before the data is published. How can I wait until the data is published to run the UI helper?
Here is relevant code.
Helper Function
Template.header.helpers({
me: function() {
var user = Meteor.users.findOne(Meteor.userId());
if (user) {
return Profiles.findOne({
spid: user.profile.spid
});
}
return null;
}
});
HTML Template
<template name="header">
<header class="ui fixed inverted menu">
{{> thumb user=me}}
</header>
</template>
Thumbnail Template
<template name="thumb">
{{#with user}}
<div class="thumb">
<div class="text" style="background-color:{{color}}">
{{initials name}}
</div>
<div class="pic" style="background-image:url('{{pic}}')"></div>
</div>
{{/with}}
</template>
Publications
Meteor.publish('profiles', function() {
return Profiles.all();
});
Meteor.publish('departments', function() {
return Departments.all();
});
Meteor.publish('requestServiceIds', function() {
return Requests.getServiceIds();
});
Meteor.publish('relevantServices', function() {
return Services.getRelevant(this.userId, 5);
});
Router
Router.configure({
layoutTemplate: 'main',
waitOn: function() {
Deps.autorun(function() {
Meteor.subscribe('profiles', Partitioner.group());
Meteor.subscribe('departments', Partitioner.group());
});
}
});
Router.onBeforeAction(function() {
if (this.route.getName() === 'not-authorized') return this.next();
if (!Meteor.userId() || !Cookie.get('TKN')) {
this.render('login');
} else {
this.next();
}
});
Router.route('/', {
name: 'home',
waitOn: function() {
Deps.autorun(function() {
Meteor.subscribe('requestServiceIds', Partitioner.group());
Meteor.subscribe('relevantServices', Partitioner.group());
});
}
});
---
UPDATE 1
I updated the the router a bit. But it did not seem to have had made any difference.
Router.configure({
layoutTemplate: 'main',
waitOn: function() {
// Deps.autorun(function() {
// Meteor.subscribe('profiles', Partitioner.group());
// Meteor.subscribe('departments', Partitioner.group());
// });
return [
Meteor.subscribe('profiles', Partitioner.group()),
Meteor.subscribe('departments', Partitioner.group())
];
}
});
Router.route('/', {
name: 'home',
waitOn: function() {
// Deps.autorun(function() {
// Meteor.subscribe('requestServiceIds', Partitioner.group());
// Meteor.subscribe('relevantServices', Partitioner.group());
// });
return [
Meteor.subscribe('requestServiceIds', Partitioner.group()),
Meteor.subscribe('relevantServices', Partitioner.group())
];
}
});
Create a dumb loading template such as this one (using font awesome):
<template name="loading">
<div class="loading">
<i class="fa fa-circle-o-notch fa-4x fa-spin"></i>
</div>
</template>
And try replacing your Router.configure() part with something like this:
Router.configure({
layoutTemplate: 'main',
action: function() {
if(this.isReady()) { this.render(); } else {this.render("loading");}
},
isReady: function() {
var subs = [
Meteor.subscribe('profiles', Partitioner.group());
Meteor.subscribe('departments', Partitioner.group());
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
return {
params: this.params || {},
profiles: Profiles.find(),
departments: Departments.find()
};
}
}
});
I guess that could be achieved using waitOn, but since your way does not work, I give you a working recipe I use everyday. Code inspired from meteor Kitchen generated code.
To answer your comments:
Regarding the Action not being triggered, it might be because we tried to put in inside the Router.configure. I don't do it that way, here are some details on how I implement it.
First, I set a controller for each route inside a router.js file (where I have my Router.configure() function too. It looks like that:
Router.map(function () {
this.route("login", {path: "/login", controller: "LoginController"});
// other routes
}
Next, I create a controller that I store in the same folder than my client view template. It looks like that:
this.LoginController = RouteController.extend({
template: "Login",
yieldTemplates: {
/*YIELD_TEMPLATES*/
},
onBeforeAction: function() {
/*BEFORE_FUNCTION*/
this.next();
},
action: function() {
if(this.isReady()) { this.render(); } else { this.render("loading"); }
/*ACTION_FUNCTION*/
},
isReady: function() {
var subs = [
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
return {
params: this.params || {}
};
/*DATA_FUNCTION*/
},
onAfterAction: function() {
}
});
So this action function is working when I extend a route using a controller. Maybe it will solve your issue.
For completeness sake, here is how my Router.configure() looks like:
Router.configure({
templateNameConverter: "upperCamelCase",
routeControllerNameConverter: "upperCamelCase",
layoutTemplate: "layout",
notFoundTemplate: "notFound",
loadingTemplate: "loading",
//I removed the rest because it is useless.
});
I have set up my app with Iron Router and have a 'post' page where I would like to show a single post according to its ID. However, this page does not show a post, and instead the post template renders blank (although the url and nav are working fine). Here is my code:
router.js
Router.route('postDetail', {
path: '/posts/:_id',
notFoundTemplate: 'postNotFound',
waitOn: function() {
return [
Meteor.subscribe('post')
]
},
data: function() {
var idVar = this.params._id
return posts.findOne({_id: idVar});
}
});
postDetail.html
<template name="postDetail">
{{address}}
</template>
postsPub.js
Meteor.publish('posts', function () {
return posts.find();
});
Meteor.publish('post', function (id) {
return posts.find(id);
});
It looks like your publisher needs an id. Try this in your waitOn:
Meteor.subscribe('post', this.params._id)
I was able to solve this issue by simply removing the 'post' publish function, and including the following route which refers to the 'posts' publish function instead:
Router.route('postDetail', {
path: '/posts/:_id',
notFoundTemplate: 'postNotFound',
waitOn: function() { return Meteor.subscribe('posts'); },
data: function() { return posts.findOne(this.params._id); }
});
Router.route('/form/:_id', function () {
this.render('viewForm', {
data: function () {
return forms.findOne({id: this.params._id});
},
waitOn:function(){
return Meteor.subscribe("forms").ready();
}
});
}
,
{name:"forms.show",
layoutTemplate: 'generalLayout'
});
the data context is empty when i open the link on a new private window .
here's my template manager side
Meteor.subscribe("forms");
Template.viewForm.rendered = function(){
console.log("calling view form");
currentForm = this.data;
console.log("form id",currentForm.id);
}
when i reload the page the dat is retrieved .
here's my loading template with the spin package
<template name="loadingTemplate">
{{>spinner}}
</template>
Router.configure({
loadingTemplate: 'loadingTemplate',
layoutTemplate: 'generalLayout'
});
Change the subscribe method into the route,using waitOn function, like this, just to be sure that template wait until the collection is ready
waitOn:function(){
return Meteor.subscribe("forms");
}
Also don't forget to use a loading template with the waitOn function.
Router.configure({
loadingTemplate: 'loadingTemplate',
layoutTemplate: 'generalLayout',
waitOn:function(){
return Meteor.subscribe("forms");
}
});
I have autopublish on.
Template.play.helpers ({
title: function () {
wcount = Workouts.find().count();
console.log(wcount);
}
});
This gives me 0.
But if I'm in the developer console of my browser.
Workouts.find().count()
24
I have recently upgraded to 0.8
If I add waitOn to the Router then I get this behavior every other time I load the page.
Router.map(function() {
this.route('splash', {path: '/'});
this.route('play', {
path: '/play',
template: 'play',
waitOn: function() {
return Meteor.subscribe('Workouts')
}
});
After some help from #Christian Fritz it seems that my problem is that its not waiting on my subscription and if the helper doesn't return anything because its undefined then it doesn't get rerun when the data does get loaded.
I have now turned off autopublish. My server/publications.js is:
Meteor.publish('workouts', function() {
return Workouts.find();
});
My router is:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading'
});
Router.map(function() {
this.route('splash', {path: '/'});
this.route('play', {
path: '/play',
template: 'play',
waitOn: function() {
return Meteor.subscribe('workouts')
}
});
});
in play.js
var workoutsSubcription = Meteor.subscribe('workouts');
console.log("workoutsSubscription.ready() is ",workoutsSubcription.ready());
returns:
workoutsSubscription.ready() is false
once or twice then finally reruns when its fully loaded as true. But shouldn't the waitOn mean that it doesn't run that page until the data is there?
You probably want something like this:
var workoutsSubcription = Meteor.subscribe('workout_count');
Template.play.helpers ({
title: function () {
if(workoutsSubcription.ready()){
wcount = Workouts.find().count();
}
}
});
You would need to publish your subscription on the server as well
Meteor.publish("workout_count", function () {
return Workouts.find();
});