Why isn't this template reactive? - meteor

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'}});
}
});
}

Related

Meteor Publications/Subscriptions not working

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.

Meteor pub/sub issues

This below is my collection code
Competitions = new Mongo.Collection("competitions");
var CompetitionsSchema = new SimpleSchema({
year: {
type: String
},
division: {
type : String,
allowedValues: ['Elite', '1st','2nd','3rd','4th','Intro']
},
teams:{
type : [TeamSchema],
allowedValues: (function () {
return Teams.find().fetch().map(function (doc) {
return doc.name;
});
}()) //here we wrap the function as expression and invoke it
}
});
In the allowedValues function
Teams.find is empty.
In the router I am subscribing to the publication as follows
this.route('competitions', {
path: '/admin/competitions',
layoutTemplate: 'adminLayout',
waitOn: function () {
return [
Meteor.subscribe('teams')
];
}
});
This is my publish function
Meteor.publish('teams', function() {
return Teams.find({},{sort: {
points: -1,
netRunRate : -1
}});
});
Do I have to do subscription some where else as well?
Your problem is in this piece of code:
allowedValues: (function () {
return Teams.find().fetch().map(function (doc) {
return doc.name;
});
}()) //here we wrap the function as expression and invoke it
This gets called when the page loads. At that point the Teams collection will still be empty on the client side. You need to wait until the data is ready. Since you are using waitOn in iron-router, it might be enough to just move this code to the onRendered callback.

MeteorJs: Return data Iron:router

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>

Meteor helper as temp storage

I'm trying to use a helper as a reactive data source to store something until I insert it into the database. I'm trying to do this:
Template.test.helpers({
tempStorage: []
})
Template.test.events({
'click .something': function() {
tempStorage.push('value');
}
})
And then in my jade I want to be able to do:
+each tempStorage
+someTemplateTheUsesTheData
However I can't seem to push to tempStorage from my events JavaScript
First off, helpers should be functions (although apparently values can be assigned, thanks #saimeunt), and you cannot use them as storage.
What you can do is install the reactive variables package and attach your tempStorage to your template, like so:
Template.test.onCreated(function () {
this.tempStorage = new ReactiveVar();
this.tempStorage.set([]);
});
Template.test.events({
'click .something': function(event, template) {
var tempStorage = template.tempStorage.get();
tempStorage.push('value');
template.tempStorage.set(tempStorage);
}
});
Template.test.helpers({
tempStorage: function () {
return Template.instance().tempStorage.get();
}
});
If you want to, you can use Session instead of a reactive variable, but your tempStorage will persist accross templates (not so temporary anymore!):
Template.test.onRendered(function () {
Session.set('tempStorage', []);
});
Template.test.events({
'click .something': function() {
var tempStorage = Session.get('tempStorage');
tempStorage.push('value');
Session.set('tempStorage', tempStorage);
}
});
Template.test.helpers({
tempStorage: function () {
return Session.get('tempStorage');
}
});

Multiple Data Contexts in router - Meteor

I'm building a meteor app and on one route I'm adding multiple data context like so -
this.route('orgPage', {
path: '/org/:title',
data: {
orgs: function () {Orgs.findOne(this.params._id)},
projects: function() {Meteor.subscribe('projects', this.params._id)}
}
The only problem is that when I try to access this data in my templates js file, I can't access the _id or any of the attributes of orgs.
I've tried several approaches, but it always returns undefined. If I use a single data context, it works perfectly. Here is the function that doesn't function properly -
Template.orgPage.events({
'click #newProject': function(e) {
$('#npModal').modal();
},
'submit #npModal form': function(e, template) {
e.preventDefault();
if(!$(e.target).find('[name=newTitle]').val()) {
var projectTitle = 'Untitled'
} else {
var projectTitle = $(e.target).find('[name=newTitle]').val()
}
var theid = this._id;
var newProject = {
title: projectTitle,
organization: theid
}
Meteor.call('project', newProject, function(error, id) {
if (error)
return alert(error.reason);
$('#npModal').modal('hide');
$('#npModal').on('hidden.bs.modal', function (e) {
Router.go('newFields', {});
})
});
});
Anyone have any ideas? Thanks!!
You have missed a return statement. function () {Orgs.findOne(this.params._id)} should be function () {return Orgs.findOne(this.params._id)}. Further more, this inside this function won't refer to what you want, so you can't use this.params. And why do you subscribe to a subscription as a data context property? Do it in the waitOn function instead.

Resources