Content wrapped in currentUser re-rendering when user updated - meteor

I'm using Meteor and having an issue where my content is being re-rendered when I don't want it to.
I have my main content wrapped in a currentUser if statement which I feel is fairly standard.
{{#if currentUser}}
{{> content}}
{{/if}}
The problem with this is my content template is being re-rendered when I update my user object. Is there any way around this? I don't reference users anywhere inside the content template.
Thank you!
Here's a sample app to replicate my problem:
HTML
<head>
<title>Render Test</title>
</head>
<body>
{{loginButtons}}
{{> userUpdate}}
{{#if currentUser}}
{{> content}}
{{/if}}
</body>
<template name="userUpdate">
<p>
<input id="updateUser" type="button" value="Update User Value" />
User last update: <span id="lastUpdated">{{lastUpdated}}</span>
</p>
</template>
<template name="content">
<p>Render count: <span id="renderCount"></span></p>
</template>
JavaScript
if (Meteor.isClient) {
Meteor.startup(function() {
Session.set("contentRenderedCount", 0);
});
Template.content.rendered = function() {
var renderCount = Session.get("contentRenderedCount") + 1;
Session.set("contentRenderedCount", renderCount);
document.getElementById("renderCount").innerText = renderCount;
};
Template.userUpdate.events = {
"click #updateUser": function() {
Meteor.users.update({_id: Meteor.userId()}, {$set: {lastActive: new Date()}});
}
};
Template.userUpdate.lastUpdated = function() {
return Meteor.user().lastActive;
};
}
if (Meteor.isServer) {
Meteor.users.allow({
'update': function () {
return true;
}
});
}
Update:
I should've explained this example a little. After creating a user, clicking the Update User Value button, causes the render count to increment. This is because it's wrapped in a {{#if currentUser}}. If this is if is removed, you'll notice the render count remains at 1.
Also, you'll need to add the accounts-ui and accounts-password packages to your project.

Meteor will re-render any template containing reactive variables that are altered. In your case the {{currentUser}} is Meteor.user() which is an object containing the user's data. When you update the users profile, the object changes and it tells meteor to re-calculate everything reactive involving the object.
We could alter the reactivity a bit so it only reacts to changes in whether the user logs in/out and not anything within the object itself:
Meteor.autorun(function() {
Session.set("meteor_loggedin",!!Meteor.user());
});
Handlebars.registerHelper('session',function(input){
return Session.get(input);
});
Your html
{{#if session "meteor_loggedin"}}
{{> content}}
{{/if}}

Related

How to write a jQueryUI selectmenu with dynamic options into a Meteor Blaze template

I am having trouble making the jQueryUI selectmenu work in a Meteor app when the options are dynamic. I am using:
Meteor 1.4.1
jQuery 2.2.4
jQueryUI 1.11.4
lodash 4.15.0
physiocoder said on a different question, "The Meteor reactivity force you to choose who is in charge of DOM updates.".
I realize that this is fundamental to my problem here. Accordingly, there is no problem if a page/template can let Meteor load all the page content/data and then hand over DOM control to jQueryUI's widgets. However I have a case where I want to have my cake and eat it too -- I want to have Meteor reactively feed the options for a jQueryUI widget (specifically the selectmenu at the moment) but still let jQueryUI handle the styling/theming.
Initializing jQueryUI widgets in template onRendered functions works just fine, as does destroying jQueryUI widgets, as necessary, in template onDestroyed functions. Calling selectmenu('refresh') on the option template's onRendered function does refresh the selectmenu when new options are available. However, I cannot find anywhere I can effectively call refresh when options are reactively removed such that the selectmenu is refreshed to the new, correct UI state -- not at the end of the event which changes the Meteor data context, not on the option template's onDestroyed function, and not a Tracker.autorun function tied to the appropriate data source.
HTML:
<head>
<title>Proof of Concept</title>
</head>
<body>
<div id="myApp">
{{> myForm}}
</div>
</body>
<template name="myForm">
<div>
<div id="selectedEntries">
<h3>Selected Entries</h3>
<ul class="display-list">
{{#each entry in selectedEntries}}
{{> myForm_entry entry}}
{{/each}}
</ul>
</div>
<br/>
<form id="includeEntry">
<select name="entryToInclude" id="entryToInclude">
{{#each potentialEntry in availableEntries}}
{{> myForm_option potentialEntry}}
{{/each}}
</select>
<input type="submit" value="Include Entry">
</form>
</div>
</template>
<template name="myForm_entry">
<li>
<div class="button removeEntry" data-id="{{_id}}">X</div>
<span>{{name}}</span>
</li>
</template>
<template name="myForm_option">
<option value="{{_id}}">{{name}}</option>
</template>
JavaScript:
Template.myForm.helpers({
availableEntries: function () {
return _.filter(Session.get('someEntries'), function(o) {
return Session.get('selectedEntryIds').indexOf(o._id) == -1;
});
},
selectedEntries: function () {
return _.filter(Session.get('someEntries'), function(o) {
return Session.get('selectedEntryIds').indexOf(o._id) != -1;
});
}
});
Template.myForm.events({
'submit #includeEntry': function (event) {
event.preventDefault();
if (_.isEmpty(Session.get('selectedEntryIds'))) {
Session.set('selectedEntryIds', [$('#entryToInclude').val()]);
} else {
let selectedEntryIds = Session.get('selectedEntryIds');
selectedEntryIds.push($('#entryToInclude').val());
Session.set('selectedEntryIds', selectedEntryIds);
}
$('#entryToInclude').selectmenu('refresh')
},
'click .removeEntry': function (event) {
event.preventDefault();
let selectedEntryIds = Session.get('selectedEntryIds');
selectedEntryIds = _.pull(selectedEntryIds, $(event.target).parent().attr('data-id'));
Session.set('selectedEntryIds', selectedEntryIds);
}
});
Template.myForm.onCreated(function () {
let someEntries = [{
_id:'1',
name:'One'
},{
_id:'2',
name:'Two'
},{
_id:'3',
name:'Three'
},{
_id:'4',
name:'Four'
},{
_id:'5',
name:'Five'
},{
_id:'6',
name:'Six'
}];
Session.set('someEntries', someEntries);
Session.set('selectedEntryIds', []);
});
Template.myForm.onRendered(function () {
$('#entryToInclude').selectmenu();
$('input:submit').button();
});
Template.myForm_entry.onRendered(function () {
$('.button').button();
});
Template.myForm_option.onRendered(function () {
if ($('#entryToInclude').is(':ui-selectmenu')) {
$('#entryToInclude').selectmenu('refresh');
}
});
Template.myForm_option.onDestroyed(function () {
$('#entryToInclude').selectmenu('refresh');
});
Tracker.autorun(function () {
if (Session.get('selectedEntryIds')) {
if ($('#entryToInclude').is(':ui-selectmenu')) {
$('#entryToInclude').selectmenu('refresh');
}
}
});
The #entryToInclude selectmenu continues to include the entry that was just selected; selecting a second entry numbered as high or higher actually selects the subsequent entry (e.g. selecting 4 then 5 actually selects 4 and 6) except that selecting the last entry immediately after a successful selection does nothing but refresh the selectmenu.
Adding a refresh to the entry template's onRendered function makes this work.
Template.myForm_entry.onRendered(function () {
$('.button').button();
if ($('#entryToInclude').is(':ui-selectmenu')) {
$('#entryToInclude').selectmenu('refresh');
}
});
But a better approach to the entire problem would be appreciated.

How to make a session variable reactive?

I'm running Meteor 0.5.7, and trying to display a bootstrap alert message when user clicks submit.
client.js
Handlebars.registerHelper('isSuccessful',function(input){
return Session.get("success");
});
Template.form.events({
'click .submit' : function (event, template) {
if (condition) {
Session.set("success", true);
// hide warning, show success
$('#valid_form').show();
$('#invalid_form').hide();
} else {
Session.set("success", false);
// hide success, show warning
$('#valid_form').hide();
$('#invalid_form').show();
}
}
});
Template.form.rendered = function () { $('.alert').hide();};
page.html
<body>
{{> page}}
</body>
<template name='page'>
{{> form}}
</template>
<template name='form'>
<!-- Show Alerts Above Form -->
<div class="alert alert-success" id="valid_form">..</div>
<div class="alert" id="invalid_form">..</div>
{{#if isSuccessful}}
<div>SHOW CONFIRMATION PAGE</div>
{{else}}
<div>SHOW INPUT FIELDS</div>
{{/if}}
<div>
</template>
When the user INITIALLY clicks submit and the condition is not met, they have to click submit again in order to get the warning message. All other times the logic works.
I've looked at this - How does Meteor's reactivity work behind the scenes?, and re-read the Meteor docs on reactive programming parts, but something is still amiss.
Shouldn't the else statement in the client default to a session variable of success == false, and the handlebar template pick it up immediately in the {{else}} block? SLightly confused. Thanks,
The answer to my question appears to be that changing return Session.get("success") to return Session.equals("success",true) in the handlebar registered helper did the trick. From what I'm reading, it has less "invalidations"? - not sure what that's suppose to mean atm, but it's a start!
If your purpose is to hide/show the relevant alerts on a condition, you can just do this instead:
client.js
Template.form.rendered = function() {
// hide all alerts when elements are rendered
$('.alert').alert('hide');
}
Template.form.events({
'click .submit' : function (event, template) {
if (condition) {
// hide warning, show success
$('#valid_form').alert();
$('#invalid_form').alert('hide');
} else {
// hide success, show warning
$('#valid_form').alert('hide');
$('#invalid_form').alert();
}
}
});
page.html:
<body>
{{> page}}
</body>
<template name='page'>
{{> form}}
</template>
<template name='form'>
<div class="alert alert-success" id="valid_form">..</div>
<div class="alert" id="invalid_form">..</div>
</template>

How to create users client side?

I'm working on a intern document-sharing project for a small company. I want to do this with meteor. I'm very common with html/javascript but not with databases.
My problem is to handle the users. Because of my researches here I'm not sure if it's already possible to create users on client side. The official documentation shows some methodes how to deal with users but no examples.
I tried to create a list on server side like this:
Users = new Meteor.Collection("users");
Then I want to insert a user on startup like this:
//on Client side
if (Meteor.isClient) {
var username = "My Name";
Meteor.call("create_user", username, function(error, user_id) {
Session.set("user_id", user_id);
});
}
//on Server side
if(Meteor.is_server) {
Meteor.methods({
create_user: function(username) {
console.log("CREATING USER");
var USER_id = Users.insert({name: username});
return user_id;
},
});
}
But reading the username in the html template doesn't work...
Are there any good examples with a register and login?
Cheers
Adding the accounts functionality is very easy in Meteor.. may it be simple email, password, or by using facebook connect/twitter etc..
do the following to get a simple meteor app with user accounts set up..
meteor create simpleapp
cd simpleapp
add the accounts-ui and accounts-password packages
meteor add accounts-ui
meteor add accounts-password
you simply add other accounts related packages for implementing facebook/twitter/github/google login etc
to list other available meteor packages use this command
meteor list
now edit your simpleapp.html file to this to add login buttons etc..
<head>
<title>simpleapp</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
<h1>Hello World!</h1>
{{greeting}}
<input type="button" value="Click" />
{{loginButtons}}
</template>
I simply added {{loginButtons}} to the default html file to add the default login buttons..
now run the meteor app and go to localhost:3000
meteor
you implemented the login functionality without doing much work. 4-5 lines of code, it even takes care of things like forgot password, registering new user etc
next thing is you need to display a particular html when the user is signed in.
you do this using the {{currentUser}} global
you implement it accordingly
<template name="hello">
<h1>Hello World!</h1>
{{greeting}}
<input type="button" value="Click" />
{{loginButtons}}
{{#if currentUser}}
{{> loggedInTemplate}}
{{else}}
{{> loggedOutTemplate}}
{{/if}}
</template>
<template name="loggedInTemplate">
<!-- user is logged in -->
</template>
<template name="loggedOutTemplate">
<!-- user is logged out -->
</template>
You don't need to create a user system manually. Just use the accounts package:
The Meteor Accounts system builds on top of the userId support in publish and methods. The core packages add the concept of user documents stored in the database, and additional packages add secure password authentication, integration with third party login services, and a pre-built user interface.
Your code should work, though. But you're not exposing the username property to the client, so maybe that's why you can't see it in your template.
Ok thank you, that's easy. But so I can not modify the loggedInTemplate or the loggedOutTemplate.
I show you what I've got:
//the html
<head>
<title>myApp | documents</title>
</head>
<body>
<div id="headerBox">
{{> header}}
</div>
<div id="sideBox">
{{> side}}
</div>
<div id="contentsBox">
{{> contents}}
</div>
</body>
<template name="header">
<h1>Company name</h1>
</template>
<template name="contents">
{{#if currentUser}}
<p>Welcome to documents</p>
{{else}}
<h3>Hello! Please log in!</h3>
<p><input type="text" id="username" placeholder="Username"><input type="text" id="password" placeholder="Password"><input type="button" value="log me in!"></p>
{{/if}}
</template>
<template name="side">
<p>Hello {{ activeUser }}</p>
<p><input type="button" value="Create New Document" onclick="createPage()"></p>
<h3>Documents:</h3>
</template>
and
//client.js
Pages = new Meteor.Collection("pages");
Meteor.startup(function() {
Session.set("activeUser", "please log in!");
});
Template.side.activeUser = function() {
return Session.get("activeUser");
};
and
//server.js
Meteor.startup(function() {
Accounts.createUser({username: "MyName", email: "me#example.com", password: "123456"});
});
and I'm searching for a manual way to log this user created on startup in. And of course later to allow this user to create new users...
The problem is adding
// Returns an event_map key for attaching "ok/cancel" events to
// a text input (given by selector)
var okcancel_events = function (selector) {
return 'keyup '+selector+', keydown '+selector+', focusout '+selector;
};
// Creates an event handler for interpreting "escape", "return", and "blur"
// on a text field and calling "ok" or "cancel" callbacks.
var make_okcancel_handler = function (options) {
var ok = options.ok || function () {};
var cancel = options.cancel || function () {};
return function (evt) {
if (evt.type === "keydown" && evt.which === 27) {
// escape = cancel
cancel.call(this, evt);
} else if (evt.type === "keyup" && evt.which === 13) {
// blur/return/enter = ok/submit if non-empty
var value = String(evt.target.value || "");
if (value)
ok.call(this, value, evt);
else
cancel.call(this, evt);
}
};
};
Template.contents.events = {};
Template.contents.events[okcancel_events('#password')] = make_okcancel_handler({
ok: function (text, event){
var usernameEntry = document.getElementById('username');
var passwordEntry = document.getElementById('password');
Meteor.loginWithPassword({usernameEntry, passwordEntry});
event.target.value = "";
}
});
to the client doesn't work...

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.

returning findOne object to template

Having troubles understanding how to return and use an object from findOne().
my code is this:
Html:
<head>
<title>count</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
{{showcount}}
</template>
Js:
var Database = new Meteor.Collection("counters");
if(Meteor.is_client) {
Template.hello.showcount = function () {
var c = Database.findOne();
return c;
};
}
if (Meteor.is_server) {
Meteor.startup(function () {
if(Database.find().count() === 0)
{
Database.insert({name: "counter", value: 0});
}
});
}
Now I'm wondering if there is any way I can access the data from my object.
changing from {{showcount}} to {{showcount.name}} doesn't seem to work at all.
This same issue got me a few times when I started out with Meteor...
When the Meteor client connects to the server the template is being rendered before the collections have finished being synchronised. i.e. The client collection is empty at the point you are calling findOne.
To see this in action stick a console.log(c) after your findOne call then try reloading the page. You will see two log entries; Once on initial page load and then again when the collection has finished being synchronised.
To fix this all you need to do is update your hello template to handle the fact the collection might not have been synchronised.
{{#if showcount}}
{{showcount.name}}
{{/if}}
I tested your code with the above change and it works.
The proper way to do this is with the #with tag.
<template name="hello">
{{#with showcount}}
{{name}}
{{/with}}
</template>
See the documentation below for more info on the #with tag
https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md

Resources