Why does a variable with .substring() not reflect css styling?

Viewed 56

Using pure javascript, I am trying to create a price-validating function that when used, validates prices entered into a form. There are 4 requirements to my validation:

  1. Input must be a number and cannot be empty
  2. If a decimal is entered, there must at least be 1 pre- and post-decimal character
  3. Price entered must be a number that is between $1 and $99.99 (both inclusive)
  4. Price entered must not have whitespaces in between

and here is my code:

function formCheck() {
  var success = true; //default, assumes client enters all fields correctly so "process.html" will load
  var msgBox = document.getElementById('divMessage'); //feedback div

  //Price Variables
  var movPrice = document.getElementById('txtPrice');
  var priceFdBk1 = '<ul><li> Please enter <b>numbers</b> only. </li></ul>';
  var priceFdBk2 =
    '<ul><li> You entered a decimal point. Please enter a <b>number</b> both before and after the decimal place. </li></ul>';
  var priceFdBk3 = '<ul><li> Please enter a movie price between $1.00 to $99.99 (up to 2 decimal places). </li></ul>';
  var priceFdBk4 = '<ul><li> Please do not leave a space when entering the movie price. </li></ul>';

  //Price Validation
  function priceCheck(price, fdBk1, fdBk2, fdBk3, fdBk4) {
    //arguments = price and feedbacks if errors are made
    var price_clean = price.value.trim(); //price entered by client without whitespace
    var price_len = price_clean.length; //number of characters in price entered

    //If there is a $ sign, remove it first
    var dollarSensor = price_clean.charAt(0);
    if (dollarSensor == '$') {
      price_clean = price_clean.substring(1);
    }

    //If there is a decimal point, obtain pre- and post-decimal characters
    if (price_clean.indexOf('.') > -1) {
      var deciSensor = 1; //remember that input has a decimal
      var intValue = price_clean.split('.')[0]; //obtain pre-decimal characters)
      var decimalValue = price_clean.split('.')[1]; //obtain post-decimal characters
      var postCounter = 0;
      for (var j = 0; j < decimalValue.length; j++) {
        //count number of decimal places
        postCounter += 1;
      }
    }

    //Filter 1: Input must be a number and cannot be empty
    if (isNaN(price_clean) || price_clean == '') {
      msgBox.innerHTML = fdBk1;
      price.className = 'yellow';
      success = false;
    }

    //Filter 2: If a decimal is entered, there must at least be 1 pre- and post-decimal character
    else if ((deciSensor == 1 && intValue == '') || (deciSensor == 1 && decimalValue == '')) {
      //if client did not enter a number before and after the decimal point
      msgBox.innerHTML = fdBk2;
      price.className = 'yellow';
      success = false;
    }

    //Filter 3: Price entered must be a number that is between $1 and $99.99 (both inclusive)
    else if (price_clean < 1 || price_clean > 99.99 || postCounter > 2) {
      msgBox.innerHTML = fdBk3; //message in feedback div
      price.className = 'yellow';
      success = false; //prevent loading of "process.html" since selected movie is invalid
    } else {
      price.className = 'transparent';
    }

    //Filter 4: Price entered must not have whitespaces in between
    for (var i = 0; i < price_len; i++) {
      oneDigit = price_clean.charAt(i);
      if (oneDigit == ' ') {
        //if the price float character has a whitespace
        msgBox.innerHTML = fdBk4; //message in feedback div
        price.className = 'yellow'; //highlight error in client's input
        success = false; //prevent loading of "process.html" since selected movie is invalid
      } else if (oneDigit == '') {
        //if the price float character has no whitespace
        price.className = 'transparent'; //remove highlight from client's input
      }
    }
  }
  priceCheck(movPrice, priceFdBk1, priceFdBk2, priceFdBk3, priceFdBk4);

  return success;
}
.yellow {
  background-color: yellow;
}

.transparent {
  background-color: transparent;
}

h1 {
  color: #7157ff;
}

hr {
  display: block;
  border: 0;
  border-top: 3px solid #f90;
  padding: 0;
}

textarea {
  width: 70%;
}

#div_main {
  font-family: Sans-serif;
  margin: auto;
  margin-top: 30px;
  width: 500px;
}

#div_left {
  width: 150px;
  display: inline-block;
  float: left;
}

#div_left p {
  margin-bottom: 19px;
}

#div_right {
  width: 350px;
  display: inline-block;
  float: right;
}

.clear {
  clear: both;
}
<div id="div_main">
  <h1>
    Add Movie
  </h1>
  <hr>
  <form action="process.html" method="POST">
    <div id="div_left">
      <p>Price* ($):</p>
    </div>
    <div id="div_right">
      <p><input type="text" id="txtPrice" name="txtPrice"></p>
    </div>
    <input type="submit" id="btnSubmit" onclick="return formCheck()">
  </form>
  <div id="divMessage">
    *denotes compulsary fields.
  </div>
</div>

The code works fine but has 1 issue, which I suspect comes from this line:

price_clean = price_clean.substring(1)

Specifically, whenever I enter a "$" sign into the form, my code will remove the "$" sign and validate the input as usual. However, when the input is invalid, it no longer highlights the input box in yellow.

May I know what is going on and how can this be fixed using vanilla javascript? Thank you

2 Answers

A small fix to your code, you've set price_len before removing leading $

   const price_len = price_clean.length;
   for(var i=0; i<price_len; i++){
     ...
   }

<html>
    <head>
    <style> 
        .yellow{
            background-color:yellow
        }

    .transparent{
        background-color: transparent;
    }

    h1 {
        color: #7157ff;
    }

    hr {
        display: block;
        border: 0;
        border-top: 3px solid #f90;
        padding: 0; 
    }
    
    textarea {
        width: 70%;
    }
    
    #div_main {
        font-family: Sans-serif;
        margin: auto;
        margin-top: 30px;
        width: 500px;
    }
    
    #div_left {
        width: 150px;
        display: inline-block;
        float: left;
    }
    
    #div_left p {
        margin-bottom: 19px;
    }
    
    #div_right {
        width: 350px;
        display: inline-block;
        float: right;
    }
    
    .clear {
        clear: both;
    }
    </style>

    <script language="javascript">
        function formCheck(){
        var success=true; //default, assumes client enters all fields correctly so "process.html" will load
        var msgBox = document.getElementById("divMessage"); //feedback div
    
    //Price Variables
        var movPrice = document.getElementById("txtPrice");
        var priceFdBk1 = "<ul><li> Please enter <b>numbers</b> only. </li></ul>"
        var priceFdBk2 = "<ul><li> You entered a decimal point. Please enter a <b>number</b> both before and after the decimal place. </li></ul>"
        var priceFdBk3 = "<ul><li> Please enter a movie price between $1.00 to $99.99 (up to 2 decimal places). </li></ul>"
        var priceFdBk4 = "<ul><li> Please do not leave a space when entering the movie price. </li></ul>"

    //Price Validation
    function priceCheck(price,fdBk1,fdBk2,fdBk3,fdBk4){ //arguments = price and feedbacks if errors are made
        var price_clean = price.value.trim(); //price entered by client without whitespace
        //var price_len = price_clean.length; //number of characters in price entered

        //If there is a $ sign, remove it first
        var dollarSensor = price_clean.charAt(0);
        if (dollarSensor=="$"){
            price_clean = price_clean.substring(1);
        }            
        
        //If there is a decimal point, obtain pre- and post-decimal characters
        if (price_clean.indexOf('.')>-1){
            var deciSensor=1 //remember that input has a decimal          
            var intValue = price_clean.split(".")[0]; //obtain pre-decimal characters)
            var decimalValue = price_clean.split(".")[1]; //obtain post-decimal characters
            var postCounter=0
            for (var j=0;j<decimalValue.length;j++){ //count number of decimal places
                postCounter+=1;
            }
        }
    
        //Filter 1: Input must be a number and cannot be empty      
        if (isNaN(price_clean)||price_clean==""){
            msgBox.innerHTML = fdBk1;
            price.className="yellow";
            success=false;
        }

        //Filter 2: If a decimal is entered, there must at least be 1 pre- and post-decimal character          
        else if (deciSensor==1 && intValue==""||deciSensor==1 && decimalValue==""){ //if client did not enter a number before and after the decimal point
            msgBox.innerHTML = fdBk2;
            price.className="yellow";
            success=false;
        }

        //Filter 3: Price entered must be a number that is between $1 and $99.99 (both inclusive)
        else if (price_clean<1||price_clean>99.99||postCounter>2){
                msgBox.innerHTML = fdBk3; //message in feedback div
                price.className="yellow";
                success=false; //prevent loading of "process.html" since selected movie is invalid
        }

        else{
            price.className="transparent";
        }

        //Filter 4: Price entered must not have whitespaces in between
        const price_len = price_clean.length;
        for(var i=0; i<price_len; i++){
            oneDigit = price_clean.charAt(i);
            if (oneDigit==" "){ //if the price float character has a whitespace
                msgBox.innerHTML= fdBk4; //message in feedback div
                price.className="yellow"; //highlight error in client's input
                success=false; //prevent loading of "process.html" since selected movie is invalid
            }
            else if (oneDigit==""){ //if the price float character has no whitespace
                price.className="transparent"; //remove highlight from client's input
            }
        }
    }
    priceCheck(movPrice,priceFdBk1,priceFdBk2,priceFdBk3,priceFdBk4)
    return success;
}
</script>
</head>
<body>
<div id="div_main">
    <h1>
        Add Movie
    </h1>
    <hr>
    <form action="process.html" method="POST">
    <div id="div_left">
        <p>Price* ($):</p>
    </div>
    <div id="div_right">
        <p><input type="text" id="txtPrice" name="txtPrice"></p>
    </div>
    <input type="submit" id="btnSubmit" onclick="return formCheck()">
    </form>
    <div id="divMessage">
        *denotes compulsary fields.
    </div>
</div>
</body>
</html>

Your issue is that you're adding the yellow highlight, but then removing it in your final for loop, this is due to your final for loop using price_len, which is calculated before you remove the $. As a result, your for loop is doing one additional iteration, and so .charAt(i) tries to access an index not in your string giving you an empty string. To fix this, you can calculate the length after you've removed the additional $:

function formCheck() {
  var success = true; //default, assumes client enters all fields correctly so "process.html" will load
  var msgBox = document.getElementById('divMessage'); //feedback div

  //Price Variables
  var movPrice = document.getElementById('txtPrice');
  var priceFdBk1 = '<ul><li> Please enter <b>numbers</b> only. </li></ul>';
  var priceFdBk2 =
    '<ul><li> You entered a decimal point. Please enter a <b>number</b> both before and after the decimal place. </li></ul>';
  var priceFdBk3 = '<ul><li> Please enter a movie price between $1.00 to $99.99 (up to 2 decimal places). </li></ul>';
  var priceFdBk4 = '<ul><li> Please do not leave a space when entering the movie price. </li></ul>';

  //Price Validation
  function priceCheck(price, fdBk1, fdBk2, fdBk3, fdBk4) {
    //arguments = price and feedbacks if errors are made
    var price_clean = price.value.trim(); //price entered by client without whitespace
    

    //If there is a $ sign, remove it first
    var dollarSensor = price_clean.charAt(0);
    if (dollarSensor == '$') {
      price_clean = price_clean.substring(1);
    }

    var price_len = price_clean.length; //number of characters in price entered
    //If there is a decimal point, obtain pre- and post-decimal characters
    if (price_clean.indexOf('.') > -1) {
      var deciSensor = 1; //remember that input has a decimal
      var intValue = price_clean.split('.')[0]; //obtain pre-decimal characters)
      var decimalValue = price_clean.split('.')[1]; //obtain post-decimal characters
      var postCounter = 0;
      for (var j = 0; j < decimalValue.length; j++) {
        //count number of decimal places
        postCounter += 1;
      }
    }

    //Filter 1: Input must be a number and cannot be empty
    if (isNaN(price_clean) || price_clean == '') {
      msgBox.innerHTML = fdBk1;
      price.className = 'yellow';
      success = false;
    }

    //Filter 2: If a decimal is entered, there must at least be 1 pre- and post-decimal character
    else if ((deciSensor == 1 && intValue == '') || (deciSensor == 1 && decimalValue == '')) {
      //if client did not enter a number before and after the decimal point
      msgBox.innerHTML = fdBk2;
      price.className = 'yellow';
      success = false;
    }

    //Filter 3: Price entered must be a number that is between $1 and $99.99 (both inclusive)
    else if (price_clean < 1 || price_clean > 99.99 || postCounter > 2) {
      msgBox.innerHTML = fdBk3; //message in feedback div
      price.className = 'yellow';
      success = false; //prevent loading of "process.html" since selected movie is invalid
    } else {
      price.className = 'transparent';
    }

    //Filter 4: Price entered must not have whitespaces in between
    for (var i = 0; i < price_len; i++) {
      oneDigit = price_clean.charAt(i);
      if (oneDigit == ' ') {
        //if the price float character has a whitespace
        msgBox.innerHTML = fdBk4; //message in feedback div
        price.className = 'yellow'; //highlight error in client's input
        success = false; //prevent loading of "process.html" since selected movie is invalid
      } else if (oneDigit == '') {
        //if the price float character has no whitespace
        price.className = 'transparent'; //remove highlight from client's input
      }
    }
  }
  priceCheck(movPrice, priceFdBk1, priceFdBk2, priceFdBk3, priceFdBk4);

  return success;
}
.yellow {
  background-color: yellow;
}

.transparent {
  background-color: transparent;
}

h1 {
  color: #7157ff;
}

hr {
  display: block;
  border: 0;
  border-top: 3px solid #f90;
  padding: 0;
}

textarea {
  width: 70%;
}

#div_main {
  font-family: Sans-serif;
  margin: auto;
  margin-top: 30px;
  width: 500px;
}

#div_left {
  width: 150px;
  display: inline-block;
  float: left;
}

#div_left p {
  margin-bottom: 19px;
}

#div_right {
  width: 350px;
  display: inline-block;
  float: right;
}

.clear {
  clear: both;
}
<div id="div_main">
  <h1>
    Add Movie
  </h1>
  <hr>
  <form action="process.html" method="POST">
    <div id="div_left">
      <p>Price* ($):</p>
    </div>
    <div id="div_right">
      <p><input type="text" id="txtPrice" name="txtPrice"></p>
    </div>
    <input type="submit" id="btnSubmit" onclick="return formCheck()">
  </form>
  <div id="divMessage">
    *denotes compulsary fields.
  </div>
</div>

There are however a few improvements you can consider making to your code though:

  1. Use const or let instead of var (to help with minimizing scoping issues)
  2. Update your code to use classList.toggle() to remove/add your highlight class
  3. Consider using regular expressions or HTML5 validation with custom validation messages using setCustomValidity()
  4. Use DOM2 event hanlders by using .addEventListener() with e.preventDefault() instead of DOM0 (onxyz) HTML attributes
Related