Search code examples
javascriptjquerymaskingdata-masking

How to get the correct value when a masked input is edited(custom masking)


I have two inputs(masked and unmasked) and a toggle button that serves as a show/hide button. It works similarly as password masking except that it only masks certain characters in a string. Whatever I input in the unmasked text box, createMask function is called and the result is shown in the masked text box.

The problem is, when the masked text box is shown and I edit the value, it should update the value in the unmasked text box.

For example, 12345678 -> 1XXXX678

Then I will edit 1XXXX678 to 1XXXX619

When I click the button to unmask, it should show 12345619

For better understanding, here's the full code: jsfiddle


Solution

  • You can store the real value in a data attribute of the input element, that way you have a single source of truth.

    While you are toggling you can get that real value and display masked/unmasked.

    Also you need to have a state that is is masked/unmasked so when you are updating values you know that unmasked values can directly substitute to value and masked value will be just first letter and rest letters you have to pick the masked area from last known value

    Now when in masked state user changes any of the X values I have given a function if it is 'XXXX' it will take the old value if anything else it tries to update the value. I have kept the position of the cursor where the user is at. If you want to get creative you can work on a logic here.

    $(function() {
      $('#SSN').on('input', function(e) {
        const this$ = $(this);
        const val = $(this).val();
    
        if (this$.data('unmask')) {
          this$.data('val', val);
        } else {
          const oldParams = getMask(this$.data('val'), false);
          const newParams = getMask(val, false);
          const newMaskArea = getUpdatedMaskArea(e.target.selectionStart, oldParams.maskArea, newParams.maskArea);
          const newValue = newParams.first + newMaskArea + newParams.rest;
          this$.data('val', newValue);
        }
      }).on('blur', function() {
        const this$ = $(this);
        this$.val(getMask(this$.data('val'), !this$.data('unmask')).value);
      }).data('val', $(this).val())
    
      $('#toggle').on('click', toggle).trigger('click');
    });
    
    function toggle() {
      $(this).find('i').toggleClass('fa-eye-slash');
      $('#SSN').data('unmask', !$(this).find('i').hasClass('fa-eye-slash')).trigger('blur');
    }
    
    function getUpdatedMaskArea(position, oldMask, newMask){
      if(position>=2 && position<=5){
        if(newMask.indexOf('X')===-1){
          return newMask;// user input new value
        } else if(newMask.length === oldMask.length){
          return newMask.replace(/X/g, (v,i)=>oldMask.charAt(i));//user updates 'X'
        } else {
          //when there are less than desired X ? I am not sure how to handle this
        }
      }
      return oldMask;
    }
    
    function getMask(val, mask) {
      const first = val.substring(0, 1);
      const maskArea = val.substring(1, 5);
      const rest = val.substring(5, val.length);
      const value = first + (mask ? maskArea.replace(/./g, 'X') : maskArea) + rest;
      return {first, maskArea, rest, value};
    }
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div class="div">
      <input id="SSN" maxlength="8">
      <button id="toggle" type="submit">
        <i class="fa fa-eye"></i>
      </button>
    </div>