publiclab/plots2

View on GitHub
app/assets/javascripts/validation.js

Summary

Maintainability
A
3 hrs
Test Coverage
$(document).ready(function() {
  $("#edit-form").validate({
    rules: {
      'user[email]': {
        required: true,
        email: true
      },
      'user[password]': {
        minlength: 8
      },
      'user[password_confirmation]': {
        equalTo: "#new_password"
      }
    },
    messages: {
      'user[email]': {
        required: "Please enter email",
        email: "Invalid email address"
      },
      'user[password]': {
        minlength: "Password should be minimum 8 characters long"
      },
      'user[password_confirmation]': {
        minlength: "Password should be minimum 8 characters long",
        equalTo: "Passwords doesn't match"
      }
    },
    submitHandler: function(form) {
      form.submit();
    }
  });
});

$(document).ready(function() {
  // The two forms have the same ID
  var signUpForms = document.querySelectorAll("#create-form");
  var signUpErrorMessages = document.querySelector("#error-message");

  // Sign up modal form
  signUpForms[0].classList.add("signup-modal-form");
  var signUpModalForm = new SignUpFormValidator(".signup-modal-form");
  // publiclab.org/register form
  if (signUpForms[1]) {
    signUpForms[1].classList.add("signup-register-form");
    var signUpRegisterForm = new SignUpFormValidator(".signup-register-form");

    if (signUpErrorMessages.innerHTML.includes("error")) {
      signUpRegisterForm.updateUI(signUpRegisterForm.emailElement, true);
      signUpRegisterForm.updateUI(signUpRegisterForm.usernameElement, true);
      if (signUpErrorMessages.innerHTML.includes("Email")) {
        signUpRegisterForm.updateUI(signUpRegisterForm.emailElement, false, "Email already exists");
      }
      if (signUpErrorMessages.innerHTML.includes("Username")) {
        signUpRegisterForm.updateUI(signUpRegisterForm.usernameElement, false, "Username already exists");
      }
    }
  }

  // The same goes for login forms
  var loginForms = document.querySelectorAll(".user-sessions-form");

  loginForms[0].classList.add("login-modal-form");
  LoginFormValidator(".login-modal-form");

  // publiclab.org/login form
  if (loginForms[1]) {
    loginForms[1].classList.add("login-page-form");
    LoginFormValidator(".login-page-form");
  }
});

// The main login form validation function
function LoginFormValidator(formSelector) {
  var loginForm = document.querySelector(formSelector);

  loginForm.addEventListener("submit", handleLoginFormValidation);
}

function handleLoginFormValidation(e) {
  var formSelector = this.classList.value.split(" ").join(".");
  e.preventDefault();

  var usernameElement = document.querySelector(
    formSelector + " #username-login"
  );
  var passwordElement = document.querySelector(
    formSelector + " #password-signup"
  );

  var username = usernameElement.value.trim();
  var password = passwordElement.value.trim();

  var isUsernameValid = username.length >= 3;

  if (isUsernameValid && isPasswordValid(password)) {
    removeLoginFormError(formSelector);
    renderSubmitBtnSpinner(formSelector);
    this.submit();
  } else {
    renderLoginFormError(formSelector);
  }
}

function renderSubmitBtnSpinner(formSelector) {
  var submitFormBtn = document.querySelector(formSelector + " #login-button");

  submitFormBtn.classList.add("disabled");
  submitFormBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i>';
}

function renderLoginFormError(formSelector) {
  removeLoginFormError(formSelector);

  var loginFormWrapper = document.querySelector(formSelector + " .login-form");

  var errorMessageHTML =
    '<div class="alert alert-danger error-msg-container" style="margin: 0 18px;">\
      <button type="button" class="close" data-dismiss="alert">\
        ×\
      </button>\
      Invalid username or password\
      </div>';

  loginFormWrapper.insertAdjacentHTML("beforeBegin", errorMessageHTML);
}

function removeLoginFormError(formSelector) {
  var loginFormErrorElement = document.querySelector(
    formSelector + " .error-msg-container"
  );

  if (loginFormErrorElement) {
    loginFormErrorElement.remove();
  }
}

// Sign Up 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();

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

  // Every time user types something, corresponding event listener are triggered
  this.usernameElement.addEventListener(
    "input",
    validateUsername.bind(this.usernameElement, this)
  );
  this.emailElement.addEventListener(
    "input",
    validateEmail.bind(this.emailElement, this)
  );
  this.passwordElement.addEventListener(
    "input",
    validatePassword.bind(this.passwordElement, this.confirmPasswordElement, this)
  );
  this.confirmPasswordElement.addEventListener(
    "input",
    validateConfirmPassword.bind(this.confirmPasswordElement, this.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.validateForm = function() {
  validateUsername.call(this.usernameElement, this)
  validateEmail.call(this.emailElement, this)
  validatePassword.call(this.passwordElement, this.confirmPasswordElement, this)
  validateConfirmPassword.call(this.confirmPasswordElement, this.passwordElement, this)
};

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.trim();
  var self = this;

  if (username.length < 3) {
    obj.updateUI(this, false, "Username has to be at least 3 characters long");
  } 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);
      }
    });
  }
}

function validateEmail(obj) {
  var email = this.value.trim();

  if (email.length === 0) {
    obj.updateUI(this, false, "The email cannot be empty.");
    return;
  }

  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.trim();

  if (password.length === 0) {
    obj.updateUI(this, false, "The password cannot be empty.");
    return;
  }

  if (!isPasswordValid(password)) {
    obj.updateUI(
      this,
      false,
      "Please make sure password is at least 8 characters long"
    );
    return;
  }

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

  obj.updateUI(this, true);
}

function validateConfirmPassword(passwordElement, obj) {
  var confirmPassword = this.value.trim();
  var password = passwordElement.value;

  if (confirmPassword.length === 0) {
    obj.updateUI(this, false, "The password confirmation cannot be empty");
    return;
  }

  if (confirmPassword !== password || !isPasswordValid(password)) {
    obj.updateUI(
      this,
      false,
      "Password and Password Confirmation should be the same"
    );
    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 isValidPassword = password.length >= 8;

  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);
}