Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADD: Client-side Sign Up form validation #7086

Merged
merged 4 commits into from
Jan 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
//= require wikis.js
//= require header_footer.js
//= require keybindings.js
//= require realtime_username_validation.js
//= require jquery-validation/dist/jquery.validate.js
//= require validation.js
//= require submit_form_ajax.js
25 changes: 0 additions & 25 deletions app/assets/javascripts/realtime_username_validation.js

This file was deleted.

243 changes: 201 additions & 42 deletions app/assets/javascripts/validation.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
$(document).ready(function () {
$(document).ready(function() {
$("#edit-form").validate({
rules: {
email: {
Expand Down Expand Up @@ -32,47 +32,206 @@ $(document).ready(function () {
form.submit();
}
});
});

$("#create-form").validate({
rules: {
username: {
required: true,
minlength: 3
},
email: {
required: true,
email: true
},
password1: {
required: true,
minlength: 8
},
password2: {
equalTo: "#password1"
}
},
messages: {
username: {
required: "Please enter username",
minlength: "Username should be minimum 3 characters long"
},
password1: {
required: "Please enter password",
minlength: "Password should be minimum 8 characters long"
},
email: {
required: "Please enter email",
email: "Invalid email address"
},
password2: {
required: "Please enter password",
minlength: "Password should be minimum 8 characters long",
equalTo: "Passwords doesn't match"
$(document).ready(function () {
// The two forms have same ID
var forms = document.querySelectorAll("#create-form");

// Sign up modal form
forms[0].classList.add("signup-modal-form");
var signUpModalForm = new SignUpFormValidator(".signup-modal-form");

// publiclab.org/register form
if (forms[1]) {
forms[1].classList.add("signup-register-form");
var signUpRegisterForm = new SignUpFormValidator(".signup-register-form");
}
});

// Form validation class
function SignUpFormValidator(formClass) {
var signUpForm = document.querySelector(formClass);

if (!signUpForm) return;

this.validationTracker = {};
this.submitBtn = document.querySelector(formClass + ' [type="submit"');

this.isFormValid();

var usernameElement = document.querySelector(
formClass + " [name='user[username]']"
);
var emailElement = document.querySelector(
formClass + " [name='user[email]']"
);
var passwordElement = document.querySelector(
formClass + " [name='user[password]']"
);
var confirmPasswordElement = document.querySelector(
formClass + " [name='user[password_confirmation]']"
);

// Every time user types something, corresponding event listener are triggered
usernameElement.addEventListener(
"input",
validateUsername.bind(usernameElement, this)
);
emailElement.addEventListener(
"input",
validateEmail.bind(emailElement, this)
);
passwordElement.addEventListener(
"input",
validatePassword.bind(passwordElement, confirmPasswordElement, this)
);
confirmPasswordElement.addEventListener(
"input",
validateConfirmPassword.bind(confirmPasswordElement, passwordElement, this)
);
}

// Typing the form triggers the function
// Updates UI depending on the value of <valid> parameter
SignUpFormValidator.prototype.updateUI = function(element, valid, errorMsg) {
var elementName = element.getAttribute("name");

if (valid) {
this.validationTracker[elementName] = true;
styleElement(element, "form-element-invalid", "form-element-valid");
removeErrorMsg(element);
} else {
this.validationTracker[elementName] = false;
styleElement(element, "form-element-valid", "form-element-invalid");
renderErrorMsg(element, errorMsg);
}

this.isFormValid();
};

SignUpFormValidator.prototype.disableSubmitBtn = function() {
this.submitBtn.setAttribute("disabled", "");
};

SignUpFormValidator.prototype.enableSubmitBtn = function() {
this.submitBtn.removeAttribute("disabled");
};

SignUpFormValidator.prototype.isFormValid = function() {
// Form is valid if all elements have passsed validation successfully
var isValidForm =
Object.values(this.validationTracker).filter(Boolean).length === 4;

if (isValidForm) this.enableSubmitBtn();
else this.disableSubmitBtn();
};

function validateUsername(obj) {
var username = this.value;
var self = this;

if (username.length < 3) {
restoreOriginalStyle(this);
} else {
$.get("/api/srch/profiles?query=" + username, function(data) {
if (data.items) {
$.map(data.items, function(userData) {
if (userData.doc_title === username) {
obj.updateUI(self, false, "Username already exists");
} else {
obj.updateUI(self, true);
}
});
} else {
obj.updateUI(self, true);
}
},
submitHandler: function(form) {
form.submit();
}
});
});
}
}

function validateEmail(obj) {
var email = this.value;
var emailRegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var isValidEmail = emailRegExp.test(email);

obj.updateUI(this, isValidEmail, "Invalid email");
}

function validatePassword(confirmPasswordElement, obj) {
var password = this.value;

if (!isPasswordValid(password)) {
obj.updateUI(
this,
false,
"Please make sure password is atleast 8 characters long with minimum one numeric value"
);
return;
}

if (password === confirmPasswordElement.value) {
obj.updateUI(confirmPasswordElement, true);
}

obj.updateUI(this, true);
}

VladimirMikulic marked this conversation as resolved.
Show resolved Hide resolved
function validateConfirmPassword(passwordElement, obj) {
var confirmPassword = this.value;
var password = passwordElement.value;

if (confirmPassword !== password) {
obj.updateUI(this, false, "Passwords must be equal");
return;
}

obj.updateUI(this, true);
}

// Password is valid if it is at least 8 characaters long and contains a number
// Password's validation logic, no UI updates
function isPasswordValid(password) {
var doesContainNumber = /\d+/g.test(password);
var isValidPassword = password.length >= 8 && doesContainNumber;

return isValidPassword;
}

function renderErrorMsg(element, message) {
if (!message) return;

// Error messages are rendered inside of a <small> HTML element
var errorMsgElement = element.nextElementSibling;
if (!errorMsgElement) {
// On publiclab.org/register invalid elements are wrapped in a div.
errorMsgElement = element.parentElement.nextElementSibling;
}

errorMsgElement.textContent = message;
errorMsgElement.style.color = "red";
errorMsgElement.classList.remove("invisible");
}

function removeErrorMsg(element) {
var errorMsgElement = element.nextElementSibling;
if (!errorMsgElement) {
errorMsgElement = element.parentElement.nextElementSibling;
}

errorMsgElement.classList.add("invisible");
}

function restoreOriginalStyle(element) {
element.classList.remove("form-element-valid");
element.classList.remove("form-element-invalid");
}

// Makes input element red or green
function styleElement(element, classToRemove, classToAdd) {
if (element.classList.contains(classToRemove)) {
element.classList.remove(classToRemove);
}

});
element.classList.add(classToAdd);
}
19 changes: 19 additions & 0 deletions app/assets/stylesheets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,25 @@ a .fa-white,
}

/* Styles for specific areas of the site */
/* Remove blue background on Chrome autocomplete */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
-webkit-box-shadow: 0 0 0 30px white inset !important;
}

.form-element-valid {
background-color: white !important;
box-shadow: none !important;
border: green 2px solid !important;
}

.form-element-invalid {
background-color: white !important;
box-shadow: none !important;
border: red 2px solid !important;
}

.note-show .main-image {
margin-bottom: 14px;
Expand Down
17 changes: 10 additions & 7 deletions app/views/users/_create_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div style="padding:10px;">
<%= form_for :user, :as => :user, :url => "/register", :html => {:class => "container", :id => "create-form"} do |f| %>
<%= form_for :user, :as => :user, :url => "/register", :html => {:class => "container", :id => "create-form", :'data-toggle' => "validator"} do |f| %>
<% if f.error_messages != "" %><div class="alert alert-danger"><%= f.error_messages %></div><% end %>

<input type="hidden" name="return_to" class="login-modal-redirect" value="<%= params[:return_to] || request.fullpath %>" />
Expand All @@ -23,15 +23,16 @@
<div class="form-group" id="username_div">
<label for="username"><%= t('user_sessions.new.username') %></label>
<%= f.text_field :username, { tabindex: 1, placeholder: "Username", class: 'form-control', id: 'username-signup' } %>
<label class="username-check"></label>
<small class="invisible">Placeholder</small>
</div>

<div class="form-group">
<label for="email"><%= t('users._form.email') %></label>
<%= f.text_field :email, { tabindex: 3, placeholder: "you@email.com", class: 'form-control', id: 'email' } %>
<small class="invisible">Placeholder</small>
</div>
</div>

<div class="col-md-6" style="display:flex; justify-content: center;">
<div class="propic">
<img class="something_something rounded" src="https://www.gravatar.com/avatar/1aedb8d9dc4751e229a335e371db8058" style="width: 120px; height: 115px; margin-top: 10px; margin-bottom: 25px; margin-left: 55px; background:#ccc;" onerror="this.src='https://www.gravatar.com/avatar/1aedb8d9dc4751e229a335e371db8058'">
Expand All @@ -54,8 +55,9 @@
id: 'password1',
onpaste: 'return false;' }
%>
<small class="invisible">Placeholder</small>
</div>

<div class="form-group col-md-6">
<label for="password_confirmation"><%= t('users._form.confirmation') %></label>
<%= f.password_field :password_confirmation, { placeholder: I18n.t('users._form.confirm_password'),
Expand All @@ -64,6 +66,7 @@
id: 'password-confirmation',
onpaste: 'return false;' }
%>
<small class="invisible">Placeholder</small>
</div>
</div>

Expand Down Expand Up @@ -105,7 +108,7 @@

<div class="col-md-12 form-group form-inline" style="clear:both;padding-top:10px;">
<!-- button for creating new users -->
<button style="margin-right:8px;" class="btn btn-lg btn-primary btn-save" type="submit" tabindex="7"><%= t('users._form.sign_up') %></button>
<button style="margin-right:8px;" class="btn btn-lg btn-primary btn-save sranje" type="submit" tabindex="7"><%= t('users._form.sign_up') %></button>
<span>or <a id="toLogin" href="/login">
<%= t('users._form.log_in') %>
</a>
Expand All @@ -122,7 +125,7 @@
</div>

<% end %>



<script>
Expand Down
Loading