Search code examples
javascriptkeypress

Javascript Next Sibling


I have an example fiddle of what I'm trying to accomplish: https://jsfiddle.net/qscL4gjh/1/

The input box must be a "text" input (not "number") and only allow either a blank value or 8 numeric digits. I have that part working.

But I want to show an error block (underneath the input) if the user enters a key value that does not meet the requirements - and for some reason I cannot get javascript to recognize the error block. I want the error block to appear for 3 seconds and fade out.

function validateObj(obj, evt) {
   var key = (evt.which) ? evt.which : evt.keyCode
   var b = (!(key > 31 && (key < 48 || key > 57)) && obj.value.length < 9);
   if (!b) {
    var a = obj.getAttribute("data-errorid");
    console.log("Error Block Id: " + a);
    
    
     var $err = document.querySelector(a) || obj.nextElementSibling;
     
     if ($err.length) {
       console.log("Found error note");
       $err.style.visibility = "visible";
       $err.style.opacity = "1";
       $err.style.transition = "3s";
       $err.style.opacity = "0";
     }
     else {
       console.error("Could not find error block");
     }
   }
   return b;
 }
<label>Test 1</label>
<input type="text" id="num" value="" onkeypress="return validateObj(this, event)" data-errorid="#error_note1" value=""/>
<div id="error_note1" class="error_note">
  Must be empty or 8 numeric digits
</div>

<br><br>

<label>Test2</label>
<input type="text" id="num" value="" onkeypress="return validateObj(this, event)" data-errorid="#error_note2" value=""/>
<div id="error_note2" class="error_note">
  Must be empty or 8 numeric digits
</div>


Solution

  • In the example below:

    • Everything is wrapped in <form id='UI'> and is referenced via .forms property.

      const UI = document.forms.UI;
      
    • Do not use inline attribute events, inline event handlers are garbage. Use onevent properties or .addEventListener(). If you have multiple <input>s, register events on an ancestor tag like <form>, <body>, document, or window. Read about events and event delegation

      onkeypress="return validateObj(this, event)" //👎
      

      .addEventListener(event, event handler, capture)

      UI.addEventListener('keydown', resetVal); //👍
      UI.addEventListener('keyup', valData, true); //👍 
      /* ☝ certain events require the capture phase instead of the bubbling phase
      (see previous link to "event") */
      

      OR onevent property

      UI.onkeydown = resetVal; // 👍
      UI.onkeyup = valData; // 🤔 
      /* "keyup" events actually fire on the capture phase that's why 
      .addEventListener() is recommended */
      
    • To always refer to the <input> the user is currently typing into, use the Event.target property.

      const inp = e.target; // Refers >inp< as the <input> the user is typing into
      if (inp.matches('input')) {... // Delegate event to >inp< only
      
    • .value of the <input> which is a String is being validated vs. the [pattern] attribute of the <input> which is RegExp.

      <input id='test1' pattern='\d{8}'>
      
      const data = inp.value;
      const error = inp.nextElementSibling;
      const pattern = inp.pattern;
      const rgx = new RegExp(pattern, 'gm');
      const match = rgx.test(data);
      
    • All styles are by class via .classList property.

      if (!match) {
        inp.classList.add('red');
        error.classList.add('flash');
      } else {
        inp.classList.remove('red');
        error.classList.remove('flash');
      }
      

    This example will accommodate virtually an unlimited number of <input> (static and dynamic) and with minor modifications can also accept from <textarea> as well or contenteditable tags ([data-pattern] attribute and .textContent and .dataset properties). Note: Flashing error messages isn't really good UX so I added the red text as a persistent reminder to the user that the text is still invalid.

    const UI = document.forms.UI;
    
    UI.addEventListener('keydown', resetVal);
    UI.addEventListener('keyup', valData, true);
    
    function resetVal(e) {
      const inp = e.target;
      if (inp.matches('input')) {
        inp.nextElementSibling.classList.remove('flash');
        if (inp.value.length < 1) {
          inp.classList.remove('red');
        }
      }
    };
    
    function valData(e) {
      const inp = e.target;
      if (inp.matches('input') && inp.value.length > 0) {
        const data = inp.value;
        const error = inp.nextElementSibling;
        const pattern = inp.pattern;
        const rgx = new RegExp(pattern, 'gm');
        const match = rgx.test(data);
        if (!match) {
          inp.classList.add('red');
          error.classList.add('flash');
        } else {
          inp.classList.remove('red');
          error.classList.remove('flash');
        }
      }
    };
    label {
      display: block;
    }
    
    .error {
      display: block;
      width: 30ch;
      margin: 3px 0 6px 0;
      visibility: hidden;
      text-align: center;
    }
    
    .flash {
      visibility: visible;
      opacity: 1;
      animation: fade 3s forwards;
    }
    
    @keyframes fade {
      0%,
      100% {
        opacity: 0
      }
      50% {
        opacity: 1
      }
    }
    
    .red {
      color: red;
    }
    <form id='UI'>
      <label>Test 1 <input id="test1" pattern='\d{8}'>
    <output class="error">
      Must have 8 numeric digits
    </output></label>
    
      <label>Test 2 <input id="test2" pattern='^[a-zA-Z]+$'>
    <output class="error">
      Must only have letters 
    </output></label>
    
      <label>Test 3 <input id="test3" pattern='^[^\-\^\\\]()@#$%&*_+=~;:]+$'>
    <output class="error">
      Cannot have special characters
    </output></label>
    </form>