Why does my template not reactive? - meteor

If I first open http://localhost:3000/, then click the test link, the roles labels will be displayed.
But if I directly open http://localhost:3000/test(Input the url in Chrome's address bar and hit enter), the roles labels will not be displayed.
Here is my code:
In client startup I subscribe to something:
Meteor.publish("Roles", function(){
return Roles.find();
});
Meteor.startup(function() {
if(Meteor.isClient) {
Meteor.subscribe('Roles');
}
});
And roles template:
Template.roles.helper( {
allRoles: function() {
return Roles.find();
}
})
html
<template name="roles">
<div>
{{#each allRoles}}
<label>test label</label>
{{/each}}
</div>
</template>
The problem is sometime roles template is rendered before the Roles is ready.
So these is no role labels displayed.
But according to Meteor document, helpers is a reactive computation, and Database queries on Collections is reactive data source. So after Roles is ready, the {{#with allRoles}} is reactive and should be displayed.
Why does roles not be displayed?
And then I rewrite my code to:
Meteor.startup(function() {
if(Meteor.isClient) {
roles_sub = Meteor.subscribe('Roles');
}
});
Template.roles.helper( {
allRoles: function() {
console.log(2);
return Roles.find();
},
isReady: function() {
console.log(1);
console.log(roles_sub.ready());
return roles_sub.ready();
}
})
html
<template name="roles">
<div>
{{#if isReady}}
{{#each allRoles}}
<label>test label</label>
{{/each}}
{{/if}}
</div>
</template>
And still role labels cannot be displayed.
And console gives me:
1
false
1
false
1
true
2
Which means isReady() is reactive? but why my roles labels remains blank?
Can somebody explain this?

use Template.subscriptionsReady
server/publish.js
Meteor.publish("Roles", function(){
return Roles.find();
});
client/client.js
Meteor.startup(function() {
Meteor.subscribe('Roles');
});
Template.roles.helpers({ // --> .helper change to .helpers
allRoles: function() {
return Roles.find();
}
})
client/templates.html
<template name="roles">
<div>
{{# if Template.subscriptionsReady }}
{{#with allRoles}}
<label>{{> role }}</label>
{{/with}}
{{ else }}
loading....
{{/if}}
</div>
</template>
<template name="role">
<div>{{ _id }}</div>
</template>
A reactive function that returns true when all of the subscriptions
https://github.com/meteor/meteor/blob/9fe2f4b442ec84eac5352b476d014c977c5ae4a2/packages/blaze/template.js#L424

Related

Meteor template rendered twice

This Meteor client Template.payment.onRendered gets fired twice, which calls the server side method twice, How can I only get it to fire the backend method once if it must do the rendering twice, else how can it get it to render only once? thx
//client
Template.mainMenu.events({
'click .menuItem': (event) => {
let menuShortName = event.currentTarget.dataset.template;
Session.set('taskSelected', menuShortName);
Meteor.call('mainAction', menuShortName);
}
});
Template.index.helpers({
'taskInputs': function () {
let task = Session.get('taskSelected');
let tasks = task ? task.split(',') : [];
let data = DisplayCol.find({action: {$in: tasks}}, {sort: {createdAt: 1}}).fetch();
return {items: data};
}
});
//server
'mainAction': function (menuShortName) {
DisplayCol.remove({userId: this.userId});
lib.displayMakeUp({'action': menuShortName});
},
'displayMakeUp': (doc) => {
for (let i = 0; i < attrItems.length; i++) {
if (attrItems[i].action === doc.action || attrItems[i].action.indexOf(doc.action + '_') >= 0) {
let group = {};
group = attrItems[i];
group.userId = Meteor.userId();
console.log(JSON.stringify(group));
DisplayCol.insert(group);
}
}
},
Template.payment.onRendered(function () {
Meteor.call('getClientToken', function (error, clientToken) {
if (error) {
console.log(error);
} else {
braintree.setup(clientToken, "dropin", {
container: "payment-form", // Injecting into <div id="payment-form"></div>
onPaymentMethodReceived: function (response) {
var nonce = response.nonce;
console.log(nonce);
}
});
}
});
});
Templates:
<body>
{{> header}}
{{#if currentUser}}
{{#if isVerified}}
{{> index}} <-------------------------- (1)
{{else}}
<br><br><br><br>
<p>Check your email for your verification link!</p>
{{/if}}
{{else}}
{{> terms}}
{{/if}}
</body>
<template name="index">
<div id="main">
{{#if (display 'mainMenu')}}
{{> mainMenu}}
{{else}} {{#if (display 'content')}}
{{> Template.dynamic template="content" data=taskInputs}} <------------------- (2)
{{#if (session 'showFooter')}}
{{> footer}}
{{/if}}
{{/if}}{{/if}}
</div>
</template>
<template name="content">
{{> subMenu}}
<div id="main">
<div id="content">
<form>
<button type="submit" style="display:none"></button>
{{#if Template.subscriptionsReady}}
{{#each this.items}}
{{> sub}} <---------------------------------- (3)
{{/each}}
{{/if}}
</form>
</div>
</div>
</template>
<template name="sub">
{{#if isEqual element "payment"}}
{{> payment}} <--------------------------------------- (4)
{{/if}}
</template>
<template name="payment">
<form role="form">
<div class="row">
<div class="col-md-6 col-xs-12">
<br>
<div id="payment-form"></div>
<button type="submit" class="btn btn-success">Submit</button>
</div>
</div>
</form>
</template>
Based on what I see, it looks like you are rendering the template inside an each block. This will render one instance of your template for each each iteration.
{{#each this.items}}
{{> sub}} <---------------------------------- (3)
{{/each}}
Keep in mind that each template instance will fire all its callbacks (including onRendered). If you only want one instance then take it out of the each
Try using this package to limit rendering. You can also yield sub to payment (instead of checking the element) try checking if this is the first run inside your route.
//html
{{> yield sub}}
//router
if (Tracker.currentComputation.firstRun) {
this.render("payment",{to:"sub"});
}

meteor test equality of a input value to a document field

Hello: I’m using the Tutorials todo.
I’ve been trying to compare an Input value
<input type="text" name="text" placeholder="Type Or Scan to add new Name" />
To the field named ‘text’ within a Document named ‘Tasks’.
Have tried EasySearch and {{#if $eq a b}} ... {{ /if }}.
May because I’m new to Meteor not setting up <templates> correctly.
Was hoping there is a short template or helper to compare or check the values to.
You can do this using a reactive variable.
Here's some example code:
JS:
Template.yourTemplateName.onCreated( function() {
this.input = new ReactiveVar(""); // Declare the reactive variable.
});
Template.yourTemplateName.helpers({
tasks() {
return Tasks.find(); // Find all tasks as an example.
},
isInputEqualToTaskText( taskId ) {
var task = Tasks.findOne({ _id: taskId });
if( task && task.text ) {
return task.text == Template.instance().input.get();
}
}
});
Template.yourTemplateName.events({
'change input': function( event, template ) {
template.input.set(event.target.value);
}
});
HTML:
<template name="yourTemplateName">
{{#each task in tasks}}
{{#if isInputEqualToTaskText task._id}}
<p>I was equal: {{task.text}}</p>
{{/if}}
{{/each}}
<input type="text">
</template>

Meteor stopped running template `rendered` functions

Im trying to run some jquery code after the template renders. But it's not working. When I run the code in the console manually, it works. Here's the js :
Template.PanelLayout.helpers({
restricted: function() {
return !Meteor.user();
},
authInProcess: function() {
return Meteor.loggingIn();
},
canShow: function() {
return !!Meteor.user();
}
});
Template.PanelLayout.rendered = function() {
$(document).ready(function() { // This is the code I want to run
$(".button-collapse").sideNav();
$('.collapsible').collapsible();
});
}
Template code :
<template name="PanelLayout">
{{#if restricted}}
{{> NotFound}}
{{else}}
{{#if authInProcess}}
{{> spinner}}
{{else}}
{{#if canShow}}
<header>
{{> PanelMainNav}}
</header>
<main id="panel-content">
{{> Template.dynamic template=content }}
</main>
{{/if}}
{{/if}}
{{/if}}
</template>
Im not sure but I think it's because I have added the if else statements to load the content only when the user is logged in? How can I fix this?
Probably it's because rendered function is called after document is ready, therefore your hook document.ready won't work, you should remove it, so it will look like this(also rendered function is deprecated, use onRendered instead):
Template.PanelLayout.onRendered(function() {
$(".button-collapse").sideNav();
$('.collapsible').collapsible();
});

Meteor: Blaze.Each example

How can one use Blaze.Each? For example, I'd like to emulate this:
{{#each Items}}
{{this}}
{{/each}}
in JS. I need to keep reactivity.
Something like:
Blaze.Each(Items.find(), function(item) {
console.log(item);
});
I don't get previous answer
This
html
<template name="yourTemplate">
<div id="each-area">
</div>
</template>
js
Template.yourTemplate.onRendered(function () {
var renderableContent = Blaze.Each(Collection.find({}), function(){
return Template.templateToRender;
});
Blaze.render(renderableContent, this.find("#each-area"));
});
Is complete equivalent of this:
html
<template name="yourTemplate">
<div id="each-area">
{{#each data}}
{{>templateToRender}}
{{/each}}
</div>
</template>
js
Template.yourTemplate.helpers({
"data": function(){
return Collection.find({})
}
});
this.autorun(function(){
var items = Items.find();
_.each(items.fetch(), function(item) {
console.log(item);
});
});
This works. It console.logs everytime an Item is added.
w/o underscore and autorun:
Blaze.Each(Data.find().fetch(), function(item){
console.log(item)
})

Template reuse in meteor

I'm trying to reuse some control elements in my Meteor app. I'd like the following two templates to toggle visibility and submission of different forms.
<template name='addControl'>
<img class='add' src='/images/icon-plus.png' />
</template>
<template name='okCancelControl'>
<img class='submit' src='/images/icon-submit.png' />
<img class='cancel' src='/images/icon-cancel.png' />
</template>
I'll call these templates in another:
<template name='insectForm'>
{{#if editing}}
<!-- form elements -->
{{> okCancelControl}}
{{else}}
{{> addControl}}
{{/if}}
</template>
editing is a Session boolean.
What's a good way to wire up the controls to show, hide and "submit" the form?
The main problem is finding the addInsect template (where the data is) from the control templates (where the "submit" event fires). Here's what I did:
First, the controls:
<template name='addControl'>
<section class='controls'>
<span class="add icon-plus"></span>
</section>
</template>
<template name='okCancelControl'>
<section class='controls'>
<span class="submit icon-publish"></span>
<span class="cancel icon-cancel"></span>
</section>
</template>
Now the javascripts. They simply invoke a callback when clicked.
Template.addControl.events({
'click .add': function(event, template) {
if (this.add != null) {
this.add(event, template);
}
}
});
Template.okCancelControl.events({
'click .cancel': function(event, template) {
if (this.cancel != null) {
this.cancel(event, template);
}
},
'click .submit': function(event, template) {
if (this.submit != null) {
this.submit(event, template);
}
}
});
I then connected the callbacks using handlebars' #with block helper.
<template name='addInsect'>
{{#with controlCallbacks}}
{{#if addingInsect}}
<section class='form'>
{{> insectErrors}}
<label for='scientificName'>Scientific Name <input type='text' id='scientificName' /></label>
<label for='commonName'>Common Name <input type='text' id='commonName' /></label>
{{> okCancelControl}}
</section>
{{else}}
{{> addControl}}
{{/if}}
{{/with}}
</template>
And the corresponding javascript that creates the callbacks relevant to this form.
Session.set('addingInsect', false);
Template.addInsect.controlCallbacks = {
add: function() {
Session.set('addingInsect', true);
Session.set('addInsectErrors', null);
},
cancel: function() {
Session.set('addingInsect', false);
Session.set('addInsectErrors', null);
},
submit: function() {
var attrs, errors;
attrs = {
commonName: DomUtils.find(document, 'input#commonName').value,
scientificName: DomUtils.find(document, 'input#scientificName').value
};
errors = Insects.validate(attrs);
Session.set('addInsectErrors', errors);
if (errors == null) {
Session.set('addingInsect', false);
Meteor.call('newInsect', attrs);
}
}
};
Template.addInsect.addingInsect = function() {
Session.get('addingInsect');
};
Template.addInsect.events = {
'keyup #scientificName, keyup #commonName': function(event, template) {
if (event.which === 13) {
this.submit();
}
}
};
In the submit callback I had to use DomUtils.find rather than template.find because template is an instance of okCancelControl, not addInsect.
You can use Session for this. You Just need a template helper that returns a boolean flag that indicates whether you are editing the form fields. And manipulate the DOM based on the Session value set by this template helper.
Assume you have one text input, now when you are entering text in it, set the Session flag as true. This will trigger the helper to return true flag, Based on that, one of your two templates will be rendered in the DOM.
The isEditing is the helper that triggers whenever you change the Session value.
This helper function is the main part here, it returns true/false based on the session value you have set.
Template.insectForm.isEditing = function(){
if(Session.get('isEditing')){
return true;
}
else{
return false;
}
}
Remember to set the Session to false at the start-up as:
$(document).ready(function(){
Session.set('isEditing', false);
})
This will render the default add template in the html, Now when you click on ADD, you need to display another template, for that, set Session to true as:
'click .add' : function(){
Session.set('isEditing', true);
}
Accordingly when you click on CANCEL, set the session to false, this will make the isEditing to return false and the default add template will be displayed.
So your complete html will look something like this:
<template name='insectForm'>
{{#if isEditing}}
<!-- form elements -->
<input type="text" id="text" value="">
{{> okCancelControl}}
{{else}}
{{> addControl}}
{{/if}}
</template>
<template name='addControl'>
<img class='add' src='/images/icon-plus.png' />
</template>
<template name='okCancelControl'>
<img class='submit' src='/images/icon-submit.png' />
<img class='cancel' src='/images/icon-cancel.png' />
</template>
[UPDATE]
To get the instance of the template, you'll need to pass the additional parameter in the event handler that represents the template.
So update your event handler as:
Template.insectForm.events = {
'click .submit' : function(event, template){
//your event handling code
}
}
The parameter template is the instance of the template from which the event originates.
Note that, although the event fires form the image that is inside the okCancelControl template, the parameter will still contain the instance of the insectForm template. This is because we are calling the event handler as Template.insectForm.events = {} .
Also see this answer for template instances.

Resources