Enabling 4-digit pincode autofill using cookies

Viewed 354

I have a simple form where one needs to enter a 4-digit PIN code. However, I would also like to autofill that PIN code when the user comes back to the website again, using JS cookies.

JS:

function loginCheck() {
    var pinCode = document.getElementById("pinCode").value;
        if (pinCode.match(/^[0-9]+$/) != null) {
            if (pinCode.length == 4) {
                function setCookie(cname, cvalue) {
                    document.cookie = cname + "=" + cvalue + ";"
                }
                function getCookie(cname) {
                    var name = cname + "=";
                    var ca = document.cookie.split(';');
                    for(var i = 0; i < ca.length; i++) {
                        var c = ca[i];
                        while (c.charAt(0) == ' ') {
                            c = c.substring(1);
                        }
                        if (c.indexOf(name) == 0) {
                            return c.substring(name.length, c.length);
                        }
                    }
                    return "";
                }
                function checkCookie() {
                    var pinCode = document.getElementById("pinCode").value;
                    var userPIN = getCookie("userPIN");
                    if (userPIN != "") {
                        pinCode.value = userPIN;
                    } else {
                        setCookie("username", userPIN);
                    }
                }
                checkCookie();
            } else {
                document.getElementById("rightorwrong").innerHTML = "Not 4 digits!";
            }
        } else {
            document.getElementById("rightorwrong").innerHTML = "Not a number!";
        }
}

HTML:

<div id = "validation">
            <form id = "validationForm" target = "frame">
                <fieldset>
                    <label for = "pass">Password:</label><br  />
                    <input type = "text" id = "pass" name = "pass"  /><br  />
                    <label for = "pinCode">4-digit PIN:</label><br  />
                    <input type = "text" id = "pinCode" name = "pinCode"  /><br  />
                    <input type = "submit" value="Log In" onclick = "loginCheck()"  />
                </fieldset>
            </form>
        </div>
        <p id = "rightorwrong"></p>

I'm aware of a few things that are wrong about this code.

  • in the checkCookie() function, if the user has a cookie stored, then I'm not entirely sure how to retrieve the PIN they first inputted.
  • Defining functions inside functions, and calling them by simply doing checkCookie(); and nothing else, is generally bad practice.
  • When I run checkCookie(); it only does the first part of the if statement and not the second part. I'm not sure why and I couldn't figure this out.
  • The code in general may have some errors. I modified a cookies script from here but it doesn't seem to work.

I'm new to the idea of cookies, and am still trying to learn them. A step-by-step explanation would be more helpful.

Help would be greatly appreciated, TIA.

2 Answers

So I'm trying to break this up in some simple steps, the whole process could be (imo) a lot cleaner if you're not using js directly to handle the cookies but e.g. ajax calls and php

First is to set a cookie by js and set a timer for the browser (how long to keep the cookie in GMT-timeformat):

//you could add a third parameter like "timer" to this function if you want it more dynamically
function setCookie(key, value) {
  var timeNow = new Date();
  //ms * sec * min * h * day this could be your third param
  var thirtyDays = 1000 * 60 * 60 * 24 * 30;
  var expiration = new Date(timeNow.getTime() + thirtyDays);
  document.cookie = key + "=" + value + "; expires=" + expiration.toGMTString() + ";"
}

Now check if a cookie is set and retreive it

function getCookieValue() {
  var cookieValue = "";
  //check if any cookie is set
  //you would have to build some loop around this if you would set multiple cookies
  if(document.cookie) {
    //starting value of the cookie from the first occasion of "=" + 1 letter to first occasion of ";"
    var cookieStart = document.cookie.indexOf("=") + 1;
    var cookieEnd = docuemnt.cookie.indexOf(";");
    cookieValue = document.cookie.substring(cookieStart, cookieEnd);
  }
  
  return cookieValue;

}  

Now you could pass the cookie value to your pinCode field by

document.getElementById('pinCode').value = getCookieValue();

make sure the js part loads after your html element is existing ofc

so on loading of the page you would check for existing cookie values by calling the getCookieValue function and set the value to your pinCode input field, and on your loginCheck function you would validate the input and call e.g. setCookie('pin', pinValue) and retreive the pinValue with document.getElementById("pinCode").value first

For cookies I use my "simpleCookie" object with the set / getVal methods to read or save a cookie.

ex:

simpleCookie.setVal( 'my cookie', 'ab/kjf;c', 3 )

let valueX = simpleCookie.getVal('my cookie'))  // return 'ab/kjf;c'
 
simpleCookie.setVal( 'my cookie', '', -1)  remove the cookie

this object is achieved via an IIEF function, and I strongly advise you to use the mozilla documentation

Since automatic form validation exists, I no longer use a text box to indicate an input error, but I have diverted its "normal" use a bit because I find it very restrictive, as you will see in my code.

When at the base of your question, you just have to find a match between the name entered and a possible cookie with the same name, then save this cookie if the form is valid.

Oh, and I also put some css to simplify writing html (no more need for <br>)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>login form</title>
  <style>
    fieldset { margin: 1em; width: 15em; }
    fieldset * { display: block; float: left; clear: both; margin-top: 0.2em; }
    fieldset label { margin-top: 0.7em; }
    fieldset button { margin-top: 2em; }
    fieldset button:last-of-type { clear: none; float: right; }
   </style>
</head>
<body>
  <form id="login-form" action="">
    <fieldset>
      <label>Name:</label> 
      <input type="text" name="name" autocomplete="off"  pattern="[A-Za-z0-9]{1,20}"> 
      <label>Password:</label> 
      <input type="password" name="pass" pattern="[A-Za-z0-9]{1,20}"> 
      <label>4-digit PIN:</label> 
      <input type="text" name="pinCode" autocomplete="off" pattern="[0-9]{4}"> 
      <button type="reset">clear</button>
      <button type="submit">Log In</button>
    </fieldset>
  </form>

  <script src="simpleCoolie.js"></script> <!-- the cookie object (IIFE) -->
  <script src="login_form.js"></script>
</body>
</html>

simpleCoolie.js :

// the cookie object (IIFE)
const simpleCookie = (function()
  {
  const OneDay_ms = 24 *60 *60 *1000  // one Day in milliseconds 
  return { 
    setVal:(cName, cValue='', exDays=10)=>  // 10 days is default cookie recovery, 
      {                                    // negative value remove the cookie
      cName  = encodeURIComponent(cName.trim())
      cValue = encodeURIComponent(cValue.trim())
      if (cName)
        {
        let dte = new Date()
        dte.setTime(dte.getTime() + (exDays *OneDay_ms))          
        document.cookie = `${cName}=${cValue};expires=${dte.toUTCString()};SameSite=Strict;path=/`
      } }
  , getVal:cName=>
      {
      cName = encodeURIComponent(cName.trim())
      let xCookie = document.cookie.split('; ').find(x=>x.startsWith(`${cName}=`))
      return xCookie ? decodeURIComponent(xCookie.split('=')[1]) : ''
    } }
  })()

login_form.js :

const formLogin        = document.getElementById('login-form')
  ,   msgErrorDuration = 5000
  ,   checkInputs =
        [...formLogin.querySelectorAll('input[pattern]')]
          .map(el=>
            {
            let pattern = el.pattern
            el.removeAttribute('pattern')
            return { name:el.name, pattern } 
            });

// generic set checking for report validyty
const getCheckingValidity=(formElement, patternValue)=>
  {
  formElement.pattern  = patternValue
  formElement.required = true
  return formElement.reportValidity()  
  }
// generic checking remove after delay 
const unCheckElement=(formElement,isOK)=>
  {
  formElement.removeAttribute('pattern')
  formElement.required = false

  if(!isOK)
    {
    formElement.setCustomValidity('')
    if(document.activeElement === formElement )  // bugg fix: Firefox doesnt remove error message after delay 
      {                                         // (other browser do)
      formElement.blur();                      // double flip focus
      formElement.focus();                    // --> will remove message bubble
      }
    }
  }

// client-side form validation mecanism to get error message for each input
formLogin.name.oninvalid=_=>
  {
  formLogin.name.setCustomValidity('Please enter a name')
  setTimeout(unCheckElement, msgErrorDuration, formLogin.name, false)
  }
formLogin.pass.oninvalid=_=>
  {
  formLogin.pass.setCustomValidity("can't do anything without password !")
  setTimeout(unCheckElement, msgErrorDuration, formLogin.pass, false)
  }
formLogin.pinCode.oninvalid=_=>
  {
  if (formLogin.pinCode.value==='')
    { formLogin.pinCode.setCustomValidity("PIN code can't be empty !") }
  else
    { formLogin.pinCode.setCustomValidity('PIN code must be 4 digits') }
  setTimeout(unCheckElement, msgErrorDuration, formLogin.pinCode, false)
  }

formLogin.onsubmit=e=>
  {
  let validForm = true 

  for (let Elm of checkInputs)  
    {
    validForm = validForm && getCheckingValidity(formLogin[Elm.name], Elm.pattern )
    if (validForm)
      { unCheckElement(formLogin[Elm.name], true) }
    else break 
    } 
  if (validForm)
    { simpleCookie.setVal( formLogin.name.value, formLogin.pinCode.value ) }
  else
    { e.preventDefault() } // disable form submiting
  }

formLogin.name.oninput=()=>  // check for cookie pin code on name
  {
  formLogin.pinCode.value = simpleCookie.getVal(formLogin.name.value)
  }

In 2009, session/localStorage arrived, which can replace cookies, especially for this kind of use.

To not have to redo all the previous logic, I created here a module called pseudoCookie which actually uses localStorage

here is the complete code to test with it:

// the pseudo cookie object (IIFE)
const pseudoCookie = (function()  // use localStorage 
  {
  return {
    setVal:(cName, cValue='', exDays=10)=> // negative value remove the value in localstorage
      {                          // the values are kept until your browser or your system crashes
      cName  = encodeURIComponent(cName.trim())
      cValue = encodeURIComponent(cValue.trim())
      if (cName) {
        if   (exDays < 0)  localStorage.removeItem(cName)
        else               localStorage.setItem(cName, cValue)
      } }
  , getVal:cName=>
      {
      cName = encodeURIComponent(cName.trim())
      let xCookie = localStorage.getItem(cName)
      return xCookie ? decodeURIComponent(xCookie) : ''
    } }
  })()

and the part to change in JS:

formLogin.onsubmit=e=>
  {
  let validForm = true 

  for (let Elm of checkInputs)  
    {
    validForm = validForm && getCheckingValidity(formLogin[Elm.name], Elm.pattern )
    if (validForm)
      { unCheckElement(formLogin[Elm.name], true) }
    else break 
    } 
  if (validForm)
    { pseudoCookie.setVal( formLogin.name.value, formLogin.pinCode.value ) }
  else
    { e.preventDefault() } // disable form submiting
  }

formLogin.name.oninput=()=>  // check for cookie pin code on name
  {
  formLogin.pinCode.value = pseudoCookie.getVal(formLogin.name.value)
  }
Related