trying to debug something. On our client, we have a Accounts.createUser() call, which include a callback that looks like:
function(err) {
if (err) {
Session.set('entryError', err.reason);
}
else {
return Router.go('/');
}
}
With this setup, a normal signup (i.e. with no errors) works fine. However, if there's an error, the template is destroyed, created, and rendered, twice. I found this question, and went on a hunt for potential reactive variables triggering this. The only thing I can find that changes between the two template destroyed/created/rendered calls is Meteor.loggingIn() (including session variables). That doesn't seem to be causing this, because when I removed all references to it and dependencies on it, the problem continued.
Any pointers?
ETA: per the request below.
signUpPage.html:
<template name='signUpPage'>
<main id="signUpPage">
<h1 class="accountsTitle">Sign Up</h1>
<p id="signInPrompt">If you already have an account sign in.</p>
<form id='signUpForm'>
{{> entryError}}
<div class="form-group">
<label for="usernameInput">Username</label>
<input id="usernameInput" type="string" class="form-control" value=''>
</div>
<div class="form-group">
<label for="emailInput">Email Address</label>
<input id="emailInput" type="email" class="form-control" value=''>
</div>
<div class="form-group">
<label for="passwordInput">Password</label>
<input id="passwordInput" type="password" class="form-control" value=''>
</div>
<div class="form-group">
<label for="confirmPasswordInput">Confirm Password</label>
<input id="confirmPasswordInput" type="password" class="form-control" value=''>
</div>
<div class="form-group">
<label for="signupCodeInput">Signup Code</label>
<input id="signupCodeInput" class="form-control" value=''>
</div>
<button type="submit" class="btn btn-primary btn-block">Sign up</button>
</form>
</main>
</template>
signUpPage.js
Template.signUpPage.events({
'submit #signUpForm': function(event, template) {
event.preventDefault();
// Get the input values from the form
var username = template.find("#usernameInput").value.trim();
var email = template.find("#emailInput").value.trim();
var signupCode = template.find("#signupCodeInput").value;
var password = template.find("#passwordInput").value;
var confirmPassword = template.find("#confirmPasswordInput").value;
// Log the signup attempt
var signupAttempt = {'type':'signup', 'username':username, 'email':email, 'password':password, 'signupCode':signupCode};
Logins.insert(signupAttempt);
// Validate username presence
if (!username.length)
return Session.set('entryError', 'Username is required');
// Validate email presence
if (!email.length)
return Session.set('entryError', 'Email address is required');
// Validate password
var passwordErrors = validatePassword(password, confirmPassword);
if (passwordErrors.length) {
var errorsString = passwordErrors.join("\r\n");
Session.set('entryError', errorsString);
return;
}
// Validate signup code
if (!signupCode.length)
return Session.set('entryError', 'Signup code is required');
Meteor.call('createAccount', username, email, password, signupCode, function(err, data) {
if (err) {
Session.set('entryError', err.reason);
return;
}
else {
Meteor.loginWithPassword(username, password);
return Router.go('/');
}
});
}
});
Template.signUpPage.destroyed = function () {
Session.set("entryError", null);
};
Template.signUpPage.created = function() {
document.title = "Grove OS | Sign Up";
};
The following are js files where entryError is used:
Template.signInPage.events({
'click #signInButton': function(evt, template) {
evt.preventDefault();
// Making lowercase here, check on the server should be case insensitive though
var email = template.find('#emailInput').value.trim().toLowerCase();
var password = template.find('#passwordInput').value;
if (!email.length)
return Session.set('entryError', 'Email is blank');
if (!password.length)
return Session.set('entryError', 'Password is blank');
var loginAttempt = {'type':'login', 'email':email, 'date':new Date()};
return Meteor.loginWithPassword(email, password, function(error) {
if (error) {
loginAttempt.status = 'error';
Logins.insert(loginAttempt);
return Session.set('entryError', error.reason);
} else{
loginAttempt.status = 'success';
Logins.insert(loginAttempt);
return Router.go("/");
}
});
},
'click #signUpButton': function(evt, template) {
evt.preventDefault();
Router.go("signUpRoute");
},
'click #forgotPasswordButton': function(evt, template) {
evt.preventDefault();
Router.go("forgotPasswordRoute");
}
});
Template.signInPage.destroyed = function () {
Session.set("entryError", null);
};
Template.signInPage.created = function() {
document.title = "Grove OS | Sign In";
};
-
Template.resetPasswordPage.helpers({
error: function() {
return Session.get('entryError');
}
});
Template.resetPasswordPage.events({
'submit #resetPasswordForm': function(event, template) {
event.preventDefault();
var password = template.find('#passwordInput').value;
var confirmPassword = template.find('#confirmPasswordInput').value;
// Validate password
var passwordErrors = validatePassword(password, confirmPassword);
if (passwordErrors.length) {
var errorsString = passwordErrors.join("\r\n");
return Session.set('entryError', errorsString);
} else {
return Accounts.resetPassword(Session.get('resetToken'), password, function(error) {
if (error) {
return Session.set('entryError', error.reason || "Unknown error");
} else {
Session.set('resetToken', null);
return Router.go("/");
}
});
}
}
});
Template.resetPasswordPage.created = function () {
document.title = "Grove OS | Reset Password";
};
-
Template.forgotPasswordPage.events({
'submit #forgotPasswordForm': function(event, template) {
event.preventDefault();
var email = template.find("#forgottenEmail").value;
if (!email.length) {
return Session.set('entryError', 'Email is required');
} else {
return Accounts.forgotPassword({
email: email
}, function(error) {
if (error)
return Session.set('entryError', error.reason);
else
return Router.go("/");
});
}
}
});
Template.forgotPasswordPage.created = function () {
document.title = "Grove OS | Forgot Password";
};
-
Router code (combined from 2 files):
//--------------------------------------------------------------
// Global Configuration
Router.configure({
layoutTemplate: 'appBody',
notFoundTemplate: 'notFoundPage',
loadingTemplate: 'loadingPage',
yieldTemplates: {
'appHeader': {to: 'header'},
'appFooter': {to: 'footer'}
}
});
// Have to sign in to access all application pages
Router.onBeforeAction(function() {
console.log("Global router onBeforeAction calle");
// if (!Meteor.loggingIn() && !Meteor.user()) {
if(!Meteor.user()){
this.redirect('/sign-in');
}
this.next();
}, {
// whitelist which routes you don't need to be signed in for
except: [
'signUpRoute',
'signInRoute',
'forgotPasswordRoute',
'signOutRoute',
'resetPasswordRoute',
'pageNotFoundRoute'
]
});
// Subscriptions application-wide
Router.waitOn(function() {
if (Meteor.loggingIn() || Meteor.user())
return Meteor.subscribe("userGroups");
});
//--------------------------------------------------------------
// Root route
Router.route('landingRoute', {
path: '/',
onBeforeAction: function(){
console.log("other global one is being called");
// if (!Meteor.loggingIn() && !Meteor.user()){
if(!Meteor.user()){
this.redirect('/sign-in');
}else{
this.redirect('/grove/dashboard');
}
this.next();
}
});
//--------------------------------------------------------------
// Static Routes
Router.route('/glossary', {
path: '/glossary',
template: 'glossaryPage'
});
Router.route('/eula', {
path: '/eula',
template: 'eulaPage'
});
Router.route('/privacy', {
path: '/privacy',
template: 'privacyPage'
});
Router.route('/about', {
path: '/about',
template: 'aboutPage'
});
Router.route("signUpRoute", {
path: "/sign-up",
template: "signUpPage"
});
Router.route("signInRoute", {
path: "/sign-in",
template: "signInPage"
});
Router.route('signOutRoute', {
path: '/sign-out',
template: "signOutPage",
onBeforeAction: function() {
Meteor.logout();
Router.go('/');
this.next();
}
});
Router.route("forgotPasswordRoute", {
path: "/forgot-password",
template: "forgotPasswordPage",
});
Router.route('resetPasswordRoute', {
path: 'reset-password/:resetToken',
template: "resetPasswordPage",
data: function() {
Session.set('resetToken', this.params.resetToken);
return ;
}
});
Router.route("404Route", {
path: "/page-not-found",
template: "notFoundPage",
});
Router.route("dashboardRoute", {
path: "/dashboard",
template: "dashboardPage"
});
Router.route('createShrub', {
path: '/tools/createShrub',
template: 'upsertShrubPage',
waitOn: function(){
},
data: function () {
},
});
// TODO figure out how to dynamically render templates
// based on if the slug is a group, user, or nothing
// and also conditionally subscribe
Router.route('profileRoute', {
path: '/:ownerSlug',
template: 'myProfilePage'
});
Router.route('groveRoute', {
path: '/:ownerSlug/:groveSlug',
template: 'grovePage',
data: function () {
return Groves.findOne({ slug: this.params.groveSlug });
},
});
Router.route('shrubRoute', {
path: '/:ownerSlug/:groveSlug/:shrubSlug',
template: 'shrubStaticPage',
data: function() {
Session.set("currentShrub", this.params.shrubSlug);
return ShrubTemplates.findOne({ slug: this.params.shrubSlug });
},
action: function() {
if (this.ready())
this.render();
else
this.render('loadingHolder');
}
});
Well, it turned out that as part of the router there was this function:
Router.waitOn(function() {
if (Meteor.loggingIn() || Meteor.user())
return Meteor.subscribe("userGroups");
});
The change in Meteor.loggingIn() caused this function to run, which we saw with our loading page getting displayed briefly. We're changed the if statement so that it no longer contains the Meteor.loggingIn() check. A little embarrassing, really--I swear I looked at that line before. Not sure what happened in my thought process to miss this. At least now I have a better understanding of Iron Router.
Related
how to use a remote back-end to check if a given username is already taken or not.
check and validate locally without using database or web-server
This is latest version of bootstrapValidator, jquery, html5, bootstrap.
<script src="js/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery.bootstrapvalidator/0.5.2/js/bootstrapValidator.min.js"></script>
<form id="registrationForm" class="form-horizontal">
<div class="form-group">
<label class="col-lg-3 control-label">Username</label>
<div class="col-lg-5">
<input type="text" class="form-control" name="username" />
</div>
</div>
</form>
and custom js is
$(document).ready(function() {
$('#registrationForm').bootstrapValidator({
fields: {
username: {
message: 'The username is not valid',
validators: {
remote: {
message: 'The username is not available',
url: 'default.php',
data: {
type: 'username'
}
}
}
}
}
});
});
back-end default.php file is
<?php
$isAvailable = true;
switch ($_POST['type']) {
case 'username':
default:
$username = $_POST['username'];
data = 'admin','root';
$isAvailable = true;
break;
}
echo json_encode(array(
'valid' => $isAvailable,
));
username already exists or not from default.php file, error
not showing.
$(document).ready(function() {
$('#registrationForm').bootstrapValidator({
fields: {
username: {
message: 'The username is not valid',
validators: {
callback: {
callback: function(value, validator) {
var data = "user1";
if (value === data) {
return {
valid: false,
message: 'The name is not available'
}
}
return true;
}
}
}
}
}
});
});
Instead of using php function, I have used call back function for this it's working fine.
I am running this through Ionic live reload and for some reason when I click the Verify button, the the identify() function run's twice except for when I do certain things defined below.
If anyone has any suggestions, it'd be greatly appreciated.
If I click enter, it only run's once.
If I comment out the following code, it also only run's once
FOLLOWING CODE IN CONTROLLER:
//if(resp == false) $cordovaDialogs.alert('ID Not Found', 'Not Found', 'Try Again');
//else $cordovaDialogs.alert('We were not able to load up the user database.', 'Error', 'Try Again');
Here is my code.
HTML
<div id="loginBox">
<form name="id_frm" ng-submit="id_frm.$valid && identify()" novalidate>
<input name="id" ng-model="id" type="text" placeholder="type id number" required />
<button name="submit" type="submit">{{ identify_button || "Verify" }}</button>
</form>
</div>
Identification Controller
.controller('identification', function($scope, $rootScope, $http, $state, $stateParams, $cordovaNetwork, $cordovaDialogs, $q, $pouchDB) {
$scope.identifying = false;
$scope.identify = function()
{
if(!$scope.identifying)
{
$scope.identify_button = "Verifiying..";
$scope.identifying = true;
$q.when($pouchDB.findUser($scope.id))
.then(function(resp){
if(resp) {
localStorage['id'] = $scope.id;
$rootScope['id'] = $scope.id;
$state.transitionTo('home');
}
else {
if(resp == false) $cordovaDialogs.alert('ID Not Found', 'Not Found', 'Try Again');
else $cordovaDialogs.alert('We were not able to load up the user database.', 'Error', 'Try Again');
console.log("FALSE");
$scope.identifying = false;
$scope.identify_button = "Verify";
}
});
}
}
})
POUCHDB SERVICE
self.findUser = function(idnum) {
return $q.when(self.db.query('filters/users'))
.then(function(resp) {
var found = false, found_id;
angular.forEach(resp.rows, function(u, id) { if(u.value == idnum) { found = true; found_id = u.id; } });
if(found) return found_id; else return false;
})
.catch(function(resp) { console.log(JSON.stringify(resp)); return null; })
}
I am implementing a very simple React login page. I have started with the following component, Account.
var Account = React.createClass({
getInitialState: function() {
return {
showSignUp: false,
showLogin: true
}
},
update: function(data) {
this.setState(data);
},
render: function() {
if(this.state.showSignUp) {
return <SignUp/>
}
else {
return <Login update={this.update}/>
}
}
});
As expected, the Login component is displayed and renders the following:
return (
<div>
<p><input type="text" placeholder={Language.languagePack.account.username} onChange={this.usernameChange}/></p>
<p><input type="password" placeholder={Language.languagePack.account.password} onChange={this.passwordChange}/></p>
<p><a onClick={this.performLogin}>{Language.languagePack.account.login}</a></p>
<p><a onClick={this.handleSignUp}>{Language.languagePack.account.signUp}</a></p>
<p>{failedMessage}</p>
</div>
)
This all works fine. The application is picking up on the changes via the onChange hook. If the user clicks "Sign Up" though, then the following code is called:
handleSignUp: function() {
this.props.update({showSignUp: true, showLogin: false})
},
Which calls the update method in the Account class, which updates the state and causes a re-render. This is what causes it to switch to the SignUp component.
return (
<div id="signUp">
<p><input type="text" placeholder={Language.languagePack.account.username} onChange={this.usernameChange} /></p>
<p><input type="password" placeholder={Language.languagePack.account.password} onChange={this.passwordChange} /></p>
<p><input type="email" placeholder={Language.languagePack.account.email} onChange={this.emailChange} /></p>
<p><a onClick={this.handleSignUp}>{Language.languagePack.account.signUp}</a></p>
</div>
)
And for some reason, none of the events are firing on this. onChange or onClick doesn't seem to be registered. I think this is related to my implementation of switching components based on a state change that renders different components. My question is, why is this happening and what part of React have I misunderstood to make this happen?
Full Classes
Login Component
var Login = React.createClass({
getInitialState: function() {
return {
username: '',
password: '',
failed: false
}
},
usernameChange: function(event) {
this.setState({
username: event.target.value,
failed: false
});
},
passwordChange: function(event) {
this.setState({
password: event.target.value,
failed: false
});
},
performLogin: function() {
var username = this.state.username;
var password = this.state.password;
console.log("Attempting login with username " + username + " and password " + password);
var _this = this;
Api.login(username, password, function(response) {
_this.props.update({user: response, loggedIn: true});
},
function(response) {
_this.setState({failed: true});
})
},
handleSignUp: function() {
this.props.update({showSignUp: true, showLogin: false})
},
render: function() {
var failedMessage = null;
if(this.state.failed) {
failedMessage = <div className="failed-auth">{Language.languagePack.account.invalidCredentials}</div>;
}
return (
<div>
<p><input type="text" placeholder={Language.languagePack.account.username} onChange={this.usernameChange}/></p>
<p><input type="password" placeholder={Language.languagePack.account.password} onChange={this.passwordChange}/></p>
<p><a onClick={this.performLogin}>{Language.languagePack.account.login}</a></p>
<p><a onClick={this.handleSignUp}>{Language.languagePack.account.signUp}</a></p>
<p>{failedMessage}</p>
</div>
)
}
});
Signup Component
var SignUp = React.createClass({
getInitialState : function() {
return {
username: '',
password: '',
email: ''
}
},
usernameChange: function(event) {
this.setState({
username: event.target.value
});
},
passwordChange: function(event) {
this.setState({
password: event.target.value
});
},
emailChange: function(event) {
this.setState({
email: event.target.value
});
},
handleSignUp : function() {
var username = this.state.username;
var password = this.state.password;
var email = this.state.email;
console.log("Signing up with username=" + username + " and password=" + password + "and email=" + email);
},
handleLogin : function() {
console.log("Fired!");
},
render: function () {
return (
<div id="signUp">
<p><input type="text" placeholder={Language.languagePack.account.username} onChange={this.usernameChange} /></p>
<p><input type="password" placeholder={Language.languagePack.account.password} onChange={this.passwordChange} /></p>
<p><input type="email" placeholder={Language.languagePack.account.email} onChange={this.emailChange} /></p>
<p><a onClick={this.handleSignUp}>{Language.languagePack.account.signUp}</a></p>
</div>
)
}
});
Your code does work; However, I did remove references to language.LanguagePack, since that's not defined in the code you provided. If you have a javascript error, it will prevent code from running.
https://jsfiddle.net/tqz3skcr/2/
var SignUp = React.createClass({
getInitialState : function() {
return {
username: '',
password: '',
email: ''
}
},
usernameChange: function(event) {
console.log('username Changed');
this.setState({
username: event.target.value
});
},
passwordChange: function(event) {
console.log('password Changed');
this.setState({
password: event.target.value
});
},
emailChange: function(event) {
console.log('email changed');
this.setState({
email: event.target.value
});
},
handleSignUp : function() {
var username = this.state.username;
var password = this.state.password;
var email = this.state.email;
console.log("Signing up with username=" + username + " and password=" + password + "and email=" + email);
},
handleLogin : function() {
console.log("Fired!");
},
render: function () {
return (
<div id="signUp">
<p><input type="text" onChange={this.usernameChange} /></p>
<p><input type="password" onChange={this.passwordChange} /></p>
<p><input type="email" onChange={this.emailChange} /></p>
<p><a onClick={this.handleSignUp}></a></p>
</div>
)
}
});
ReactDOM.render(
<SignUp />,
document.getElementById('container')
);
I don't see anything obvious but you could try this pattern to show/hide the components. Toggle showing and hiding components in ReactJs.
First of all, make your life easier and don't use indicators like:
{
showSignUp: true,
showLogin: false
}
something like this would be much simpler and would produce less errors:
{
formToShow: "signUpForm" // or "loginForm"
}
I would say, if you start coding in this way the issue will resolve by "clean code magic" ))
I am trying to build a contact form which is very similar to the one used in the keystone demo but i have hit a road block, While trying to save to db, I get the following errors
{ message: 'Validation failed',
name: 'ValidationError',
errors:
{ name:
{ name: 'ValidatorError',
path: 'name',
message: 'Name is required',
type: 'required' } } }
I have checked the fields on the form and also the request in the backend by doing a console.log but for some reason i still keep on getting the same error.
Here is what I have in my jade file
section#contact-container
section#contact.contact-us
.container
.section-header
// SECTION TITLE
h2.white-text Get in touch
// SHORT DESCRIPTION ABOUT THE SECTION
h6.white-text
| Have any question? Drop us a message. We will get back to you in 24 hours.
if enquirySubmitted
.row
h3.white-text.wow.fadeInLeft.animated(data-wow-offset='30', data-wow-duration='1.5s', data-wow-delay='0.15s') Thanks for getting in touch.
else
.row
form#contact.contact-form(method="post")
input(type='hidden', name='action', value='contact')
.wow.fadeInLeft.animated(data-wow-offset='30', data-wow-duration='1.5s', data-wow-delay='0.15s')
.col-lg-4.col-sm-4(class=validationErrors.name ? 'has-error' : null)
input.form-control.input-box(type='text', name='name', value=formData.name, placeholder='Your Name')
.col-lg-4.col-sm-4
input.form-control.input-box(type='email', name='email', value=formData.email, placeholder='Your Email')
.col-lg-4.col-sm-4
div(class=validationErrors.enquiryType ? 'has-error' : null)
input.form-control.input-box(type='text', name='enquiryType', placeholder='Subject', value=formData.enquiryType)
.col-md-12(class=validationErrors.message ? 'has-error' : null)
.col-md-12.wow.fadeInRight.animated(data-wow-offset='30', data-wow-duration='1.5s', data-wow-delay='0.15s')
textarea.form-control.textarea-box(name='message', placeholder='Your Message')= formData.message
button.btn.btn-primary.custom-button.red-btn.wow.fadeInLeft.animated(data-wow-offset='30', data-wow-duration='1.5s', data-wow-delay='0.15s', type='submit') Send Message
and this is how my schema and route file looks like
var keystone = require('keystone'),
Types = keystone.Field.Types;
var Enquiry = new keystone.List('Enquiry', {
nocreate: true,
noedit: true
});
Enquiry.add({
name: { type: Types.Name, required: true },
email: { type: Types.Email, required: true },
enquiryType: { type: String },
message: { type: Types.Markdown, required: true },
createdAt: { type: Date, default: Date.now }
});
Enquiry.schema.pre('save', function(next) {
this.wasNew = this.isNew;
next();
});
Enquiry.schema.post('save', function() {
if (this.wasNew) {
this.sendNotificationEmail();
}
});
Enquiry.schema.methods.sendNotificationEmail = function(callback) {
var enqiury = this;
keystone.list('User').model.find().where('isAdmin', true).exec(function(err, admins) {
if (err) return callback(err);
new keystone.Email('enquiry-notification').send({
to: admins,
from: {
name: 'Wheatcroft Accounting',
email: 'contact#abc.com'
},
subject: 'New Enquiry for **',
enquiry: enqiury
}, callback);
});
};
Enquiry.defaultSort = '-createdAt';
Enquiry.defaultColumns = 'name, email, enquiryType, createdAt';
Enquiry.register();
This is the route file
var keystone = require('keystone'),
async = require('async'),
Enquiry = keystone.list('Enquiry');
exports = module.exports = function(req, res) {
var view = new keystone.View(req, res),
locals = res.locals;
locals.section = 'contact';
locals.formData = req.body || {};
locals.validationErrors = {};
locals.enquirySubmitted = false;
view.on('post', { action: 'contact' }, function(next) {
var newEnquiry = new Enquiry.model();
var updater = newEnquiry.getUpdateHandler(req);
updater.process(req.body, {
flashErrors: true,
fields: 'name, email, enquiryType, message',
errorMessage: 'There was a problem submitting your enquiry:'
}, function(err) {
if (err) {
locals.validationErrors = err.errors;
console.log(err);
} else {
locals.enquirySubmitted = true;
}
next();
});
});
view.render('contact');
}
I believe the problem has to do with the way KeystoneJS handles Types.Name field types internally.
In your jade file, you should reference your name field using a path to its virtual name.full property. Internally name.full has a setter that splits the name into name.first and name.last. So, if you wish to have separate input for the first and last name you should use name.first and name.last. If you want a single input to enter the full name you should use name.full.
Try replacing the input control for your name field:
input.form-control.input-box(type='text', name='name', value=formData.name, placeholder='Your Name')
with this:
input.form-control.input-box(type='text', name='name.full', value=formData['name.full'])
I want to call a rest service (post) when I press on the button login but it doesn't launch any service it just add a "?" at the end of the url of my application.
here is my js :
(function ($) {
var authentication = Backbone.Model.extend({
defaults: {
Username: "",
Password: ""
},
url:'../../rest/login'
});
var LoginView = Backbone.View.extend({
model: new authentication(),
el: $("#login-form"),
events: {
"click button#login": "login"
},
login: function(){
alert("ici");
this.model.save({username: this.$el.find("#inUser")}, {
password: this.$el.find("#inPswd")}, {
success: function() {
/* update the view now */
},
error: function() {
/* handle the error code here */
}
});
}
})
})
(jQuery);
And here is my form :
<form class="form-inline">
<div class="form-group">
<input type="text" class="form-control" placeholder="Username" id="inUser"></input>
<input type="password" class="form-control" placeholder="Password" id="inPswd"></input>
<button id="login">Login</button>
</div>
</form>
You have a problem with your .save() method call because you send username and password in two different objects.
Also to stop adding question mark ? sign (stop submitting your form) you need to add event.preventDefault(); and/or return false; to your button click handler.
Here is a fix:
login: function(event) {
event.preventDefault();
alert("ici");
this.model.save({
username: this.$el.find("#inUser"),
password: this.$el.find("#inPswd")
}, {
success: function() {
/* update the view now */
},
error: function() {
/* handle the error code here */
}
});
return false;
}