Search code examples
javascripthtmlcssrefactoring

I am attempting to refactor my JS, for CSS/JS form validation


I am struggling to figure out how to refactor my JS for form validation. I figured out how to target each element using there ID, I just feel there has to be a better way to clean up this code using an array though. Any help would be appreciated. The commented out JS was my attempt at beginning to refactor.

"use strict"

//clunky code for error-img:
const fNameInput = document.getElementById("first-input");
const lNameInput = document.getElementById("last-input");
const emailInput = document.getElementById("email");
const passwordInput = document.getElementById("password");

fNameInput.addEventListener("blur", function(){
    const errorImg1 = document.getElementById("error-img1");
    
    if(!fNameInput.value){
        errorImg1.classList.add("visible");
    } else{
        errorImg1.classList.remove("visible");
    }
});

lNameInput.addEventListener("blur", function(){
    const errorImg2 = document.getElementById("error-img2");

    if(!lNameInput.value){
        errorImg2.classList.add("visible");
    } else{
        errorImg2.classList.remove("visible");
    }
});

emailInput.addEventListener("blur", function(){
    const errorImg3 = document.getElementById("error-img3");

    if(!emailInput.value){
        errorImg3.classList.add("visible");
    } else{
        errorImg3.classList.remove("visible");
    }
});

passwordInput.addEventListener("blur", function(){
    const errorImg4 = document.getElementById("error-img4");

    if(!passwordInput.value){
        errorImg4.classList.add("visible");
    } else{
        errorImg4.classList.remove("visible");
    }
});


// attempt at making clean code that applies to all inputs:

// const inputs = document.getElementsByTagName("input");
// const errorImgs = document.getElementsByClassName("error-img");

// //learn forEach loop:

// inputs[i].addEventListener("blur", () => {
//     inputs.forEach(element => {
//         if(!element.value){

//         }
//     });
// });
@import url(https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap);

body{
    margin: 0;
    background-image: url(images/bg-intro-desktop.png);
    background-color: rgba(255, 0, 0, .5);
    font-family: 'Poppins', sans-serif;
}

.container{
    margin: 6rem auto;
    height: auto;
    width: 70%;
    
    display: grid;
    grid-template-areas: 
    "left right"
    "left right";
    grid-auto-columns: minmax(0, 32rem);
}

.left-container{
    grid-area: left;
}

.left-heading{
    color: white;
    font-size: 2.7rem;
    line-height: 3rem;
    font-weight: 700;

    position: relative;
    top: 30%;
}

.left-content{
    color: white;
    font-size: .9rem;
    font-weight: 400;

    position: relative;
    top: 29%;
}

.right-container{
    grid-area: right;
}

.right-top-container{
    background-color: hsl(248, 32%, 49%);
    border-radius: .5rem;
    height: 11.5%;
    margin: auto auto 1.5rem auto;
    box-shadow: 0px 6px 1px rgba(0, 0, 0, .2);
}

.top-text-special{
    color: white;
    font-weight: 600;
    font-size: .85rem;

    position: relative;
    top: 50%;
    left: 72%;
    transform: translate(-50%, -50%);
}

.top-text{
    color: white;
    font-weight: 400;
}

.right-form{
    background-color: white;
    height: 23rem;
    border-radius: .5rem;
    padding: 1.5rem;
    padding-top: 2.5rem;
    box-shadow: 0 7px 1px rgba(0, 0, 0, .15);

    display: grid;
    grid-template-areas: 
    "fname"
    "lname"
    "email"
    "password"
    "submit-btn"
    "bottom-text";
}

input{
    font-family: 'Poppins', sans-serif;
    font-weight: 500;
    font-size: .8rem;

    position: relative;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    padding: 0 0 0 1.5rem;

    height: 3rem;
    width: 90%;

    border: solid;
    border-width: .09rem;
    border-radius: .4rem;
    border-color: hsl(246, 25%, 77%);
}

.first-name-div{
    grid-area: fname;
    position: relative;
}

.last-name-div{
    grid-area: lname;
    position: relative;
}

.email-div{
    grid-area: email;
    position: relative;
}

.password-div{
    grid-area: password;
    position: relative;
}

.submit-btn-div{
    grid-area: submit-btn;
}

#submit-btn{
    position: relative;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 97.1%;
    height: 3rem;
    border-radius: .4rem;

    background-color: hsl(154, 59%, 51%);
    border: none;
    color: white;
    box-shadow: 0 2px .5px rgba(0, 0, 0, .4);

    cursor: pointer;
}

.right-footer-text{
    grid-area: bottom-text;
}

.footer-text{
    position: relative;
    top: 1%;
    left: 58%;
    transform: translate(-50%, -50%);

    color: grey;
    font-size: .7rem;
    font-weight: 600;
}

.footer-span{
    color: hsl(0, 100%, 74%);
}

.error-img{
    display: none;
    position: absolute;
    bottom: 75%;
    right: 5%;
}

.visible{
    display: block;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FrontEndMentor#3</title>
    <link rel="stylesheet" href="styles.css">
    <link rel="shortcut icon" href="images/favicon-32x32.png" type="image/x-icon">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">

</head>
<body>
    <div class="container">
        <div class="left-container">
            <h1 class="left-heading">Learn to code by <br>
                 watching others</h1>
            <p class="left-content">See how experienced developers solve problems in real-time. <br>
                Watching scripted tutorials is great, but understanding how <br>
                developers think is invaluable.</p>
        </div>

        <div class="right-container">
            <div class="right-top-container">
                <p class="top-text-special">Try it free 7 days <span class="top-text">then $20/mo. thereafter</span></p>
            </div>


            <div class="right-form">
            <form action="#" class="form">
                <div class="first-name-div">
                    <input type="text" name="fname" id="first-input" placeholder="First Name">
                    <span><img class="error-img" id="error-img1" src="images/icon-error.svg" alt=""></span>
                </div>
                <div class="last-name-div">
                    <input type="text" name="lname" id="last-input" placeholder="Last Name">
                    <span><img class="error-img" id="error-img2" src="images/icon-error.svg" alt=""></span>
                </div>
                <div class="email-div">
                    <input type="email" name="email-address" id="email" placeholder="example@email.com">
                    <span><img class="error-img" id="error-img3" src="images/icon-error.svg" alt=""></span>
                </div>
                <div class="password-div">
                    <input type="password" name="password" id="password" placeholder="Password">
                    <span><img class="error-img" id="error-img4" src="images/icon-error.svg" alt=""></span>
                </div>
                <div class="submit-btn-div">
                    <button type="submit" id="submit-btn">CLAIM YOUR FREE TRIAL</button>
                </div>
                </form>
            </div>


            <div class="right-footer-text">
                <p class="footer-text">By clicking the button, you are agreeing to our <span class="footer-span">Terms and Services</span></p>
            </div>
        </div>
    </div>
    <script src="index.js"></script>
</body>
</html>

(Not all styling is complete, will do when JS is cleaned up)


Solution

  • To improve your JS logic you can apply the 'Don't Repeat Yourself' principle, or DRY. This means that you should avoid code repetition by genericising the logic as much as possible. The corollary effect of this is that the code can be made to work for an infinite number of form fields, so long as the HTML structure around them is consistent.

    Here's an example of this for your use case. Note the use of a single class applied to all the fields to be validated, and also how DOM traversal is used to find the error img related to the current field only.

    "use strict"
    
    const fields = document.querySelectorAll('.to-validate').forEach(el => {
      el.addEventListener('blur', e => {
        let field = e.target;
        field.closest('div').querySelector('.error-img').classList.toggle('visible', !field.value.trim().length);
      });
    });
    @import url(https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap);
    body {
      margin: 0;
      background-image: url(images/bg-intro-desktop.png);
      background-color: rgba(255, 0, 0, .5);
      font-family: 'Poppins', sans-serif;
    }
    
    .container {
      margin: 6rem auto;
      height: auto;
      width: 70%;
      display: grid;
      grid-template-areas: "left right" "left right";
      grid-auto-columns: minmax(0, 32rem);
    }
    
    .left-container {
      grid-area: left;
    }
    
    .left-heading {
      color: white;
      font-size: 2.7rem;
      line-height: 3rem;
      font-weight: 700;
      position: relative;
      top: 30%;
    }
    
    .left-content {
      color: white;
      font-size: .9rem;
      font-weight: 400;
      position: relative;
      top: 29%;
    }
    
    .right-container {
      grid-area: right;
    }
    
    .right-top-container {
      background-color: hsl(248, 32%, 49%);
      border-radius: .5rem;
      height: 11.5%;
      margin: auto auto 1.5rem auto;
      box-shadow: 0px 6px 1px rgba(0, 0, 0, .2);
    }
    
    .top-text-special {
      color: white;
      font-weight: 600;
      font-size: .85rem;
      position: relative;
      top: 50%;
      left: 72%;
      transform: translate(-50%, -50%);
    }
    
    .top-text {
      color: white;
      font-weight: 400;
    }
    
    .right-form {
      background-color: white;
      height: 23rem;
      border-radius: .5rem;
      padding: 1.5rem;
      padding-top: 2.5rem;
      box-shadow: 0 7px 1px rgba(0, 0, 0, .15);
      display: grid;
      grid-template-areas: "fname" "lname" "email" "password" "submit-btn" "bottom-text";
    }
    
    input {
      font-family: 'Poppins', sans-serif;
      font-weight: 500;
      font-size: .8rem;
      position: relative;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      padding: 0 0 0 1.5rem;
      height: 3rem;
      width: 90%;
      border: solid;
      border-width: .09rem;
      border-radius: .4rem;
      border-color: hsl(246, 25%, 77%);
    }
    
    .first-name-div {
      grid-area: fname;
      position: relative;
    }
    
    .last-name-div {
      grid-area: lname;
      position: relative;
    }
    
    .email-div {
      grid-area: email;
      position: relative;
    }
    
    .password-div {
      grid-area: password;
      position: relative;
    }
    
    .submit-btn-div {
      grid-area: submit-btn;
    }
    
    #submit-btn {
      position: relative;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 97.1%;
      height: 3rem;
      border-radius: .4rem;
      background-color: hsl(154, 59%, 51%);
      border: none;
      color: white;
      box-shadow: 0 2px .5px rgba(0, 0, 0, .4);
      cursor: pointer;
    }
    
    .right-footer-text {
      grid-area: bottom-text;
    }
    
    .footer-text {
      position: relative;
      top: 1%;
      left: 58%;
      transform: translate(-50%, -50%);
      color: grey;
      font-size: .7rem;
      font-weight: 600;
    }
    
    .footer-span {
      color: hsl(0, 100%, 74%);
    }
    
    .error-img {
      display: none;
      position: absolute;
      bottom: 75%;
      right: 5%;
      /* following rules only for this demo */
      width: 20px;
      height: 20px;
      background-color: #C00;
    }
    
    .visible {
      display: block;
    }
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
    
    <div class="container">
      <div class="left-container">
        <h1 class="left-heading">Learn to code by <br> watching others</h1>
        <p class="left-content">See how experienced developers solve problems in real-time. <br> Watching scripted tutorials is great, but understanding how <br> developers think is invaluable.</p>
      </div>
      <div class="right-container">
        <div class="right-top-container">
          <p class="top-text-special">Try it free 7 days <span class="top-text">then $20/mo. thereafter</span></p>
        </div>
        <div class="right-form">
          <form action="#" class="form">
            <div class="first-name-div">
              <input type="text" name="fname" id="first-input" placeholder="First Name" class="to-validate" />
              <span><img class="error-img" src="images/icon-error.svg" alt=""></span>
            </div>
            <div class="last-name-div">
              <input type="text" name="lname" id="last-input" placeholder="Last Name" class="to-validate" />
              <span><img class="error-img" src="images/icon-error.svg" alt=""></span>
            </div>
            <div class="email-div">
              <input type="email" name="email-address" id="email" placeholder="example@email.com" class="to-validate" />
              <span><img class="error-img" src="images/icon-error.svg" alt=""></span>
            </div>
            <div class="password-div">
              <input type="password" name="password" id="password" placeholder="Password" class="to-validate" />
              <span><img class="error-img" src="images/icon-error.svg" alt=""></span>
            </div>
            <div class="submit-btn-div">
              <button type="submit" id="submit-btn">CLAIM YOUR FREE TRIAL</button>
            </div>
          </form>
        </div>
        <div class="right-footer-text">
          <p class="footer-text">By clicking the button, you are agreeing to our <span class="footer-span">Terms and Services</span></p>
        </div>
      </div>
    </div>

    It's worth noting, though, that what you're doing can be achieved simply by adding the required attribute to your input elements. Then there's no JS required at all.