Access ReactiveVar // Template.instance() from helper (indirectly) - meteor

How can I access ReactiveVar in the construction like below?
Template.profile.helpers({
user: {
...
gender: () => Template.instance().genderFlag.get(), //returns Uncaught TypeError: Cannot read property 'genderFlag' of null
...
},
});
Returns error because Template.instance() inside gender: is null (when I console.log(Template.instance());)
This is how I create ReactiveVar:
Template.profile.onCreated( function() {
this.genderFlag = new ReactiveVar(0);
});
P.S. gender is inside user because I use user context i.e. {{#with user}} in my .html.
UPDATE:
Spacebars part:
<button type="button" name="Male" class="{{#if gender '1'}}gender--selected{{/if}}">Male</button>
<button type="button" name="Female" class="{{#if gender '2'}}gender--selected{{/if}}">Female</button>
so I am passing arg to the Template.helper gender:
user: {
...
gender: (arg) => arg == Template.instance().genderFlag.get(),
...
}

Because you are creating a function within the helper it seems to be picking up the wrong data context and therefore the Template variable is not available a the time it is called, the following code works, it moves the function to the helper itself so the context is correct and it can access Template.instance() correctly, then you can return an object for user as needed for your #with
Template.profile.helpers({
user: () => {
return {
...
gender: Template.instance().genderFlag.get()
...
};
}
});
EDIT:
In line with the update to your question, couple of options but neatest I would say is:
Template.profile.helpers({
user: () => {
var gend = Template.instance().genderFlag.get();
return {
...
gender: gend,
isMale: gend===1?"gender--selected":"",
isFemale: gend===2?"gender--selected":"",
...
};
}
});
And in your handlebars
<button type="button" name="Male" class="{{isMale}}">Male</button>
<button type="button" name="Female" class="{{isFemale}}">Female</button>

Related

Publish and subscribe to a single object Meteor js

How to publish single objects seems not clear enough to me. Please what's the best way to handle this. This code snippet does not display anything on the view.
Helper file
singleSchool: function () {
if (Meteor.userId()) {
let myslug = FlowRouter.getParam('myslug');
var subValues = Meteor.subscribe('SingleSchool', myslug );
if (myslug ) {
let Schools = SchoolDb.findOne({slug: myslug});
if (Schools && subValues.ready()) {
return Schools;
}
}
}
},
Publish file
Meteor.publish('SingleSchool', function (schoolSlug) {
check( schoolSlug, Match.OneOf( String, null, undefined ) );
user = Meteor.users.findOne({_id:this.userId})
if(user) {
if(user.emails[0].verified) {
return SchoolDb.findOne({ slug: schoolSlug, userId: {$lt: this.userId}});
} else {
throw new Meteor.Error('Not authorized');
return false;
}
}
});
template file
<template name="view">
{{#if currentUser}}
{{#if Template.subscriptionsReady }}
{{#with singleSchool}}
{{singleSchool._id}}
{{singleSchool.addschoolname}}
{{/with}}
{{/if}}
{{/if}}
</template>
As you said "This code snippet does not display anything on the view." well, inside Meteor.publish you need to return cursor, not array or any other object.
So use this code:
Meteor.publish('SingleSchool', function (schoolSlug) {
check( schoolSlug, Match.OneOf( String, null, undefined ) );
var user = Meteor.users.findOne({_id:this.userId});
if(!user || !user.emails[0].verified) {
throw new Meteor.Error('Not authorized');
}
return SchoolDb.find({ slug: schoolSlug, userId: {$lt: this.userId}},{limit:1});
});
I would definitely recommend you to go through How to avoid Common Mistakes
When I am concerned only for a single object, I implement this using a meteor method:
Meteor.methods({
"getSingleSchool":function(schoolSlug) {
//... check args and user permissions
return SchoolDb.findOne({ slug: schoolSlug, userId: {$lt: this.userId}});
},
});
Then in the template I run this method in the onCreated autorun part:
Template.view.onCreated(function(){
const instance = this;
instance.state = new ReactiveDict();
instance.autorun(function(){
let my slug = FlowRouter.getParam('myslug');
// load if no loaded yet
if (my slug && !instance.state.get("singleSchool")) {
Meteor.call("getSingleSchool", mySlug, function(err, res){
//handle err if occurred...
this.state.set("singleSchool", res);
}.bind(instance)); //make instance available
}
});
});
The helper then just returns a value, if the school is loaded:
singleSchool: function () {
return Template.instance().state.get("singleSchool");
},

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>

Error in Autoform insert

I am having a problem getting my Insert Autoform to work properply. I am trying to have it similar to the example http://autoform.meteor.com/insertaf and my code is below. I have already removed insecure and autopublish
client/templates/venues/venue_submit.html
<template name="venueSubmit">
<!-- {{> quickForm collection="Venues" id="venueSubmit" type="insert"}} -->
{{#if isSuccessfulvenueSubmit }}
<h2>Thanks for the Venue </h2>
{{else}}
{{#autoForm id="insertVenueForm" type="insert" collection=Collections.Venues omitFields="createdAt" resetOnSuccess=true}}
{{> afQuickField name="Venue"}}
<div class="form-group">
<button type="submit" class="btn btn-primary">Add Venue</button>
<button type="reset" class="btn btn-default">Reset Form</button>
</div>
{{/autoForm}}
{{/if}}
</template>
client/templates/venues/venue_submit.js
Schemas = {};
Template.registerHelper("Schemas", Schemas);
Schemas.Venue = new SimpleSchema({
Venue: {
type: String,
label: "Venue Name",
max: 200,
autoform: {
placeholder: "Name of the Venue"
}
},
....
});
AutoForm.debug()
var Collections = {};
Template.registerHelper("Collections", Collections);
Venues = Collections.Venues = new Mongo.Collection("Venues");
Venues.attachSchema(Schemas.Venue);
Venues.allow({
insert: function (userId, doc) {
return true;
},
remove: function (userID, doc, fields, modifier) {
return true;
},
remove: function (userId, doc) {
return true;
}
});
if (Meteor.isClient) {
Meteor.subscribe("Venues")
};
server/Publications.js
Meteor.publish('venue', function () {
return Venues.find(id);
});
The AutoForm insert type generates a document and inserts in on the client. Without the autopublish and insecure packages installed you need to make sure to subscribe to the appropriate collection on the client. It does not exist if you do not subscribe to it.
if (Meteor.isClient) {
Meteor.subscribe("Venues")
}
Your problem is that you have venue_submit.js inside the client folder, but its contents are not intended solely for the client.
You can leave venue_submit.html exactly as it is.
Change venue_submit.js to:
Template.registerHelper("Schemas", Schemas);
AutoForm.debug();
Template.registerHelper("Collections", Collections);
Meteor.subscribe("Venues");
You only need the lines here that relate to the client: the two template helpers, the AutoForm debug (which you don't need except for debugging), and the subscribe to the Venues collection.
Change server/Publications.js to include everything related to the server side:
Meteor.publish('Venues', function () {
return Venues.find({});
});
Venues.allow({
insert: function (userId, doc) {
return true;
},
remove: function (userID, doc, fields, modifier) {
return true;
},
remove: function (userId, doc) {
return true;
}
});
It includes the publish function and the permissions on the collection.
Now create lib/schema.js:
Schemas = {};
Schemas.Venue = new SimpleSchema({
Venue: {
type: String,
label: "Venue Name",
max: 200,
autoform: {
placeholder: "Name of the Venue"
}
}
});
Collections = {};
Venues = Collections.Venues = new Mongo.Collection("Venues");
Venues.attachSchema(Schemas.Venue);
Everything in lib will be available to both the client and server and the contents of this folder will be loaded first so the schema and collection definitions will be available to all the rest of the code. Note the lack of a var keyword for the Collections. Using var sets the scope to only within the file. Omitting it makes the Collections variable available throughout your code.

What is the best way of fetching data from server in Meteorjs?

I am making simple application where user can add some data in the website. Every time, when user adds new 'name' I want display the latest name automatically for every connected users.
I am not sure if my implementation of Template.names.name is a good idea, maybe I should use subscribe instead?
Here is my code:
<template name="names">
<p>What is your name ?</p>
<input type="text" id="newName"/>
<input type="button" id="nameSubmit" value="add new"/>
<p>Your name: {{name}}</p>
</template>
if (Meteor.isClient) {
Template.names.name({
'click input#nameSubmit': function () {
Meteor.call('newName', document.getElementById("newName").value);
}
});
Template.names.name = function () {
var obj = Names.find({}, {sort: {"date": -1}}).fetch()[0];
return obj.name;
};
}
if (Meteor.isServer) {
newName: function (doc) {
var id = Names.insert({
'name': doc,
'date': new Date()
});
return id;
}
}
I use meteorjs version 0.8.1.1.
The only thing I see inherently wrong with your code is your method.. To define methods you can use with Meteor.call you have you create them with a call to Meteor.methods
Meteor.methods({
newName: function (name) {
Names.insert({
'name': doc,
'date': new Date()
});
}
});
Another couple notes.. The Method should be defined in shared space, not just on the server unless there is some specific reason. That why it will be simulated on the client and produce proper latency compensation.
Also in your Template.names.name you can return the result of a findOne instead of using a fetch() on the cursor.
Template.names.name = function () {
return Names.findOne({}, {sort: {"date": -1}}).name;
};

How to conditionally set attribute on HTML-element in Meteor.js/Handlebars.js?

The code below shows one row from a table, and the JavaScript-code to handle the template. The code works, but it sets the disabled-attribute on all the buttons in the table. I only want it for the one button-element that is pushed.
Question: What is the best way to conditionally set the correct element as disabled in Meteor.js?
In my HTML-file:
<template name="userRow">
<tr>
<td>{{ username }}</td>
<td>
<select class="newRole">
{{{optionsSelected globalRoles profile.role }}}
</select>
</td>
<td>
Disabled: {{disabledAttr}}
{{#isolate}}
<button type="button" {{disabledAttr}} class="btn btn-xs btn-primary saveUser">Save</button>
{{/isolate}}
</td>
<td><button type="button" class="btn btn-xs btn-danger deleteUser">Delete</button></td>
</tr>
And in my .js-file:
var newUserRole;
var savedDep = new Deps.Dependency;
var saved;
var disabledDep = new Deps.Dependency;
var disabledAttr = "";
Template.userRow.saved = function () {
savedDep.depend();
return saved;
};
Template.userRow.disabledAttr = function () {
disabledDep.depend();
return disabledAttr;
};
Template.userRow.events({
'change .newRole' : function (event) {
newUserRole = event.target.value;
},
'click .saveUser' : function (event) {
disabledAttr = "disabled";
disabledDep.changed();
Meteor.call('updateUser',
{
userId: this._id,
role: newUserRole
},
function (error, result) {
if (error) {
saved = "NOT saved, try again!";
} else {
saved = "Saved!";
savedDep.changed();
};
});
return false;
}
});
To answer your question:
All of your rows are using the same Dependency object, so when one row changes the object, all the other rows respond.
To fix this you can create a new Dependency object for each row.
For example:
Template.userRow.created = function () {
this.disabledDep = new Deps.Dependency;
};
And update all of your code to use the template's disabledDep instead of the 'global' one. That should solve your problem.
But let's talk about your goal here:
It looks like you want to render your rows differently while saving, until they're confirmed server side.
A cleaner way to do this is to take advantage of Meteor's isSimulation method. For example:
// Put this in a file that will be loaded on both the client and server
Meteor.methods({
add_item: function (name) {
Items.insert({name: name,
unconfirmed: this.isSimulation});
}
});
This example is using inserts, but you can use the same technique for updates.
Now each document in your collection will have an 'unconfirmed' field, which you can use to change your view:
Template.userRow.disabledAttr = function () {
return this.unconfirmed ? "disabled" : "";
};

Resources