Iron router return data is in template but I can't use it.
For example I have db with jobs, where every job has a position (e.g. jobs.position):
ExistJobPostController = RouteController.extend({
layoutTemplate: 'existJob',
data:function() {return Posts.findOne(this.params._id); }
})
Router.map(function() {
this.route('existJob', {
path: '/jobs/:_id',
controller: ExistJobPostController,
});
});
<template name="existJob">
{{position}}
</template>
And nothing happens, I think that it's my fault, but I really can't understand how to fix this.
Can anybody help?
You should first check that the correct data is even being set on your template data context. Here's a quick general summary of how to set the data context and how to access it from various locations:
Router.map(function() {
this.route('index', {
path: '/index',
data: function(){
var obj = {
fname: "Tom",
lname: "Smith"
};
return obj;
}
});
});
Template.index.onRendered(function(){
console.log(this.data.fname);
});
Template.index.events({
'click body': function(e, tmpl){
console.log(tmpl.data.fname);
}
});
Template.index.helpers({
lastName: function(){
return this.lname;
}
});
<template name="index">
You have to use `this` when directly accessing template data from spacebars.
{{this.firstName}}
The following comes from Template.index.helpers:
{{lastName}}
</template>
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.
Why isn't this reactive? And more importantly how can it be made reactive?
I'd like the data to be saved in Mongo and used in the template. I could use a ReactiveVar or ReactiveDict. Do I need two copies of the data?
Doesn't Suspects.findOne('bruce') return a reactive object already? I tried putting the human answer directly on Bruce, but it didn't trigger an update.
The events fire, log(this) shows bruce's answer was changed, but the template doesn't re-render. What's the good way to do this?
http://meteorpad.com/pad/KoH5Qu7Fg3osMQ79e/Classification
It's Meteor 1.2 with iron:router added:
<head>
<title>test</title>
</head>
<template name="question">
{{#unless isAnswered 'human'}} <!-- :-< I'm not reacting here -->
<div>Sir, are you classified as human?</div>
<button id="no">No, I am a meat popsicle</button>
<button id="smokeYou">Smoke you</button>
{{else}}
<div> Classified as human? <b>{{answers.human}}</b></div>
{{/unless}}
</template>
And the JavaScript:
// Why isn't this reactive?
if (Meteor.isClient) {
Template.question.helpers({
isAnswered: function (question) { // :-< I'm not reactive
var suspect = Template.instance().data;
return (typeof suspect.answers[question] !== 'undefined');
}
});
Template.question.events({
'click #no': function () {
this.answers.human = "No"; // :-< I'm not reactive
console.log(this);
},
'click #smokeYou': function() {
this.answers.human = "Ouch"; // :-< I'm not reactive
console.log(this);
}
});
}
// Collection
Suspects = new Meteor.Collection('suspects');
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Suspects.upsert('bruce', { quest: 'for some elements', answers: {}});
});
Meteor.publish('suspects', function() {
return Suspects.find({});
});
}
// Iron Router
Router.route('/', {
template: 'question',
waitOn: function() {
return Meteor.subscribe('suspects');
},
data: function() {
return Suspects.findOne('bruce');
}
});
Thanks :-)
The events are not actually updating the reactive data source (the db record). Instead of doing:
Template.question.events({
'click #no': function () {
this.answers.human = "No";
}
});
The event needs to perform a database action, either through a direct update or through a Meteor.call() to a Meteor.method. For example:
'click #no': function(){
Suspects.update('bruce', {'answers': {'human': 'no'}});
}
If you use this pattern, you will also need to set the correct allow and deny rules to permit the update from client code. http://docs.meteor.com/#/full/allow. Methods generally end up being a better pattern for bigger projects.
Also, I'm not sure off the top of my head that Template.instance().data in your helper is going to be reactive. I would use Template.currentData() instead just to be sure. http://docs.meteor.com/#/full/template_currentdata
Very close you just need to use ReactiveVar by the sound of it it pretty much explains what it's :) http://docs.meteor.com/#/full/reactivevar
and here's how to use it
if (Meteor.isClient) {
Template.question.onCreated(function () {
this.human = new ReactiveVar();
});
Template.question.helpers({
isAnswered: function (question) {
return Template.instance().human.get();
}
});
Template.question.events({
'click #no': function (e, t) {
t.human.set('No');
console.log(t.human.get());
},
'click #smokeYou': function(e, t) {
t.human.set('Ouch');
console.log(t.human.get());
}
});
}
UPDATE: if you're using a cursor I usually like to keep it on the template level not on iron router:
if (Meteor.isClient) {
Template.question.helpers({
isAnswered: function (question) {
return Suspects.findOne('bruce');
}
});
Template.question.events({
'click #no': function (e, t) {
Suspects.update({_id: ''}, {$set: {human: 'No'}});
},
'click #smokeYou': function(e, t) {
Suspects.update({_id: ''}, {$set: {human: 'Ouch'}});
}
});
}
I have db with posts and all of them have bollean flagged
I have one template and navigation like (Read, Dont Read).
Problem is that I see all posts (when I must see posts with flagged false or flagged true), and I dont understand why, I think problem in publish/subscribe
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
});
AllPostsController = RouteController.extend({
template: 'timeTable',
waitOn: function() {
return Meteor.subscribe('allPosts');
}
});
readPostController = AllPostsController.extend({
waitOn: function() {
return Meteor.subscribe('readPosts');
}
});
dontreaderPostController = AllPostsController.extend({
waitOn: function() {
return Meteor.subscribe('dontreadPosts');
}
});
Router.map(function() {
this.route('timeTable', {path: '/',
controller: AllPostsController
});
this.route('readPosts', {path: '/read',
controller: readPostsController
});
this.route('dontreaderPosts', {
path: '/dontreader',
controller: dontreaderPostController
});
});
Meteor.publish('allPosts', function(){
return Posts.find({},{ sort: { createdAt: -1 }});
});
Meteor.publish('readPosts', function(){
return Posts.find({read:true},{ sort: { createdAt: -1 }});
});
Meteor.publish('dontreadPosts', function(){
return Posts.find({read:false},{ sort: { createdAt: -1 }});
});
If someone need more code, Just ask me
Anybody help
EDIT : David solved problem for regular tasks. Main problem that I have specific return Posts.find(...) in my tamplate helper.
<template name="timeTable">
{{#if posts_exist_week}}
{{> table posts=week}}
{{/if}}
{{#if posts_exist_month}}
{{> table posts=month}}
{{/if}}
</template>
<template name="table">
<table class="main-table table">
{{#each posts}}
{{> post}}
{{/each}}
</table>
</template>
You solved my problem if I did not have template timeTable (that show posts for last week and month) Because here it Template helper
Template.timeTable.helpers({
week: function() {
//...
return Posts.find({createdAt: {$gte: weekstart, $lt: yesterday}},{ sort: { createdAt: -1 }}); //return posts that was created in this week
},
month: function() {
//...
return Posts.find({createdAt: {$gte: monthstart, $lte: weekstart}},{ sort: { createdAt: -1 }});
}
});
And now you see that if I choose your decision (David) I will have 2 !!
return
first - in router
second - in template helper
I recreated this locally and found that extend causes the parent controller's waitOn to run. So whenever you go to the /read route it will actually activate both subscriptions and you'll end up with all of the documents on your client. A simple fix is to refactor your controllers like so:
PostController = RouteController.extend({
template: 'timeTable'
});
AllPostsController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('allPosts');
}
});
readPostController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('readPosts');
}
});
dontreaderPostController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('dontreadPosts');
}
});
That being said, you don't want to build your app in a way that it breaks when extra subscriptions happen to be running. I would rewrite the controllers to select only the documents that pertain to them. For example:
dontreaderPostController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('dontreadPosts');
},
data: {selector: {read: false}}
});
And now your helpers can use the selector like this:
Template.timeTable.helpers({
week: function() {
var selector = _.clone(this.selector || {});
selector.createdAt = {$gte: weekstart, $lt: yesterday};
return Posts.find(selector, {sort: {createdAt: -1}});
}
});
Also note that sorting in the publish functions may not be useful - see common mistakes.
I'm facing a strange problem.
I'm using Iron Router controller to pass data to template:
Router.route('/wards/add/:_id?', {name: 'wards.add', controller: 'WardAddController'});
WardAddController = RouteController.extend({
action: function() {
this.render('addWard', {
data: function(){
return { hospitals : Hospitals.find({}), hospital_id : this.params._id }
}
});
}
});
I return a variable 'hospitals', that should contain all the collection data.
Template:
<div class="jumbotron">
{{#each hospitals}}
{{name}}<br>
{{/each}}
</div>
At first page load, if I type the directly the url of the page, there are no items.
If I type Hospitals.find({}).fetch() (insecure is active) in the browser console, it return an empty object.
But if i change pages, navigating on the website a while, and return the the listing page, items appears.
Any idea?
In the server folder, add publish.js and inside it add:
Meteor.publish('hospitals', function() {
return Hospitals.find({});
});
Then try subscribing to hospitals from your controller:
WardAddController = RouteController.extend({
action: function() {
this.render('addWard', {
waitOn: function() {
return [
Meteor.subscribe('hospitals')
];
},
data: function(){
return { hospitals : Hospitals.find({}), hospital_id : this.params._id }
}
});
}
});
I have a iron route that searches for a collection item based on the url param. If it finds it, it returns the item as a data context, otherwise it renders a notFound template. The code looks like this:
this.route('profileView', {
path: list_path + '/profiles/:_id',
fastRender: true,
waitOn: function() {
if (Meteor.user()) {
return [Meteor.subscribe('singleProfile', this.params._id, Session.get("currentListId"))];
}
},
data: function() {
var profile = Profiles.findOne({
_id: this.params._id
});
if (!profile) {
this.render("notFound");
} else
return profile;
}
});
The problem is the notFound template gets loaded briefly prior to profile getting returned, although I thought the waitOn function would have handled that. What's the correct pattern to have the desired result using iron router? Thanks.
Is it possible that you forgot to configure the loading and dataNotFound hooks?
Router.onBeforeAction('loading');
Router.onBeforeAction('dataNotFound');
If you want to understand what is actually going on here, please look here.
I had to check for this.ready() in data. Updated code
this.route('profileView', {
path: list_path + '/profiles/:_id',
fastRender: true,
waitOn: function() {
if (Meteor.user()) {
return [Meteor.subscribe('singleProfile', this.params._id, Session.get("currentListId"))];
}
},
data: function() {
if(this.ready()){
var profile = Profiles.findOne({
_id: this.params._id
});
if (!profile) {
this.render("notFound");
} else
return profile;
}
}
});