Implement an input with a mask

2019-01-01 05:37发布

问题:

I would like to implement a mask for a text input field which accepts a date. The masked value should display directly inside of the input.

Something like this:

<input type=\'text\' value=\'____/__/__\'>

I wrote the mask as a value in that example, but my intent is to allow people to write a date without typing / or - to separate months, years and days. The user should be able to enter numbers into the displayed field, while the mask enforces the format automatically as the user types.

I have seen this behavior on other sites, but I have no idea how it works or how to implement it myself.

回答1:

Input masks can be implemented using a combination of the keyup event, and the HTMLInputElement value, selectionStart, and selectionEnd properties. Here\'s a very simple implementation which does some of what you want. It\'s certainly not perfect, but works well enough to demonstrate the principle:

Array.prototype.forEach.call(document.body.querySelectorAll(\"*[data-mask]\"), applyDataMask);

function applyDataMask(field) {
    var mask = field.dataset.mask.split(\'\');
    
    // For now, this just strips everything that\'s not a number
    function stripMask(maskedData) {
        function isDigit(char) {
            return /\\d/.test(char);
        }
        return maskedData.split(\'\').filter(isDigit);
    }
    
    // Replace `_` characters with characters from `data`
    function applyMask(data) {
        return mask.map(function(char) {
            if (char != \'_\') return char;
            if (data.length == 0) return char;
            return data.shift();
        }).join(\'\')
    }
    
    function reapplyMask(data) {
        return applyMask(stripMask(data));
    }
    
    function changed() {   
        var oldStart = field.selectionStart;
        var oldEnd = field.selectionEnd;
        
        field.value = reapplyMask(field.value);
        
        field.selectionStart = oldStart;
        field.selectionEnd = oldEnd;
    }
    
    field.addEventListener(\'click\', changed)
    field.addEventListener(\'keyup\', changed)
}
ISO Date: <input type=\"text\" value=\"____-__-__\" data-mask=\"____-__-__\"/><br/>
Telephone: <input type=\"text\" value=\"(___) ___-____\" data-mask=\"(___) ___-____\"/><br/>

(View in JSFiddle)

There are also a number of libraries out there which perform this function. Some examples include:

  • jquery.inputmask
  • MASKED INPUT PLUGIN
  • Politespace (presents an alternative to input masks)


回答2:

After reading all post, I did my own implementation, I hope to help to someone:

The idea is,

  1. allow entry only numbers. (keypress event)
  2. get all numbers in a array
  3. replace every \"_\" character of mask by a number from array in a loop

Improvements are welcome.

/**
 * charCode [48,57] 	Numbers 0 to 9
 * keyCode 46  			\"delete\"
 * keyCode 9  			\"tab\"
 * keyCode 13  			\"enter\"
 * keyCode 116 			\"F5\"
 * keyCode 8  			\"backscape\"
 * keyCode 37,38,39,40	Arrows
 * keyCode 10			(LF)
 */
function validate_int(myEvento) {
  if ((myEvento.charCode >= 48 && myEvento.charCode <= 57) || myEvento.keyCode == 9 || myEvento.keyCode == 10 || myEvento.keyCode == 13 || myEvento.keyCode == 8 || myEvento.keyCode == 116 || myEvento.keyCode == 46 || (myEvento.keyCode <= 40 && myEvento.keyCode >= 37)) {
    dato = true;
  } else {
    dato = false;
  }
  return dato;
}

function phone_number_mask() {
  var myMask = \"(___) ___-____\";
  var myCaja = document.getElementById(\"phone\");
  var myText = \"\";
  var myNumbers = [];
  var myOutPut = \"\"
  var theLastPos = 1;
  myText = myCaja.value;
  //get numbers
  for (var i = 0; i < myText.length; i++) {
    if (!isNaN(myText.charAt(i)) && myText.charAt(i) != \" \") {
      myNumbers.push(myText.charAt(i));
    }
  }
  //write over mask
  for (var j = 0; j < myMask.length; j++) {
    if (myMask.charAt(j) == \"_\") { //replace \"_\" by a number 
      if (myNumbers.length == 0)
        myOutPut = myOutPut + myMask.charAt(j);
      else {
        myOutPut = myOutPut + myNumbers.shift();
        theLastPos = j + 1; //set caret position
      }
    } else {
      myOutPut = myOutPut + myMask.charAt(j);
    }
  }
  document.getElementById(\"phone\").value = myOutPut;
  document.getElementById(\"phone\").setSelectionRange(theLastPos, theLastPos);
}

document.getElementById(\"phone\").onkeypress = validate_int;
document.getElementById(\"phone\").onkeyup = phone_number_mask;
<input type=\"text\" name=\"phone\" id=\"phone\" placeholder=\"(123) 456-7890\" required=\"required\" title=\"e.g (123) 456-7890\" pattern=\"^\\([0-9]{3}\\)\\s[0-9]{3}-[0-9]{4}$\">



回答3:

You can achieve this also by using JavaScripts\'s native method. Its pretty simple and doesn\'t require any extra library to import.

<input type=\"text\" name=\"date\" placeholder=\"yyyy-mm-dd\" onkeyup=\"
  var date = this.value;
  if (date.match(/^\\d{4}$/) !== null) {
     this.value = date + \'-\';
  } else if (date.match(/^\\d{4}\\-\\d{2}$/) !== null) {
     this.value = date + \'-\';
  }\" maxlength=\"10\">


回答4:

You can also try my implementation, which doesn\'t have delay after each key press when typing the contents, and has full support for backspace and delete.

You can try it online: https://jsfiddle.net/qmyo6a1h/1/

    <html>
    <style>
    input{
      font-family:\'monospace\';
    }
    </style>
    <body>
      <input type=\"text\" id=\"phone\" placeholder=\"123-5678-1234\" title=\"123-5678-1234\" input-mask=\"___-____-____\">
      <input type=\"button\" onClick=\"showValue_phone()\" value=\"Show Value\" />
      <input type=\"text\" id=\"console_phone\" />
      <script>
        function InputMask(element) {
          var self = this;

          self.element = element;

          self.mask = element.attributes[\"input-mask\"].nodeValue;

          self.inputBuffer = \"\";

          self.cursorPosition = 0;

          self.bufferCursorPosition = 0;

          self.dataLength = getDataLength();

          function getDataLength() {
            var ret = 0;

            for (var i = 0; i < self.mask.length; i++) {
              if (self.mask.charAt(i) == \"_\") {
                ret++;
              }
            }

            return ret;
          }

          self.keyEventHandler = function (obj) {
            obj.preventDefault();

            self.updateBuffer(obj);
            self.manageCursor(obj);
            self.render();
            self.moveCursor();
          }

          self.updateBufferPosition = function () {
            var selectionStart = self.element.selectionStart;
            self.bufferCursorPosition = self.displayPosToBufferPos(selectionStart);
            console.log(\"self.bufferCursorPosition==\" + self.bufferCursorPosition);
          }

          self.onClick = function () {
            self.updateBufferPosition();
          }

          self.updateBuffer = function (obj) {
            if (obj.keyCode == 8) {
              self.inputBuffer = self.inputBuffer.substring(0, self.bufferCursorPosition - 1) + self.inputBuffer.substring(self.bufferCursorPosition);
            }
            else if (obj.keyCode == 46) {
              self.inputBuffer = self.inputBuffer.substring(0, self.bufferCursorPosition) + self.inputBuffer.substring(self.bufferCursorPosition + 1);
            }
            else if (obj.keyCode >= 37 && obj.keyCode <= 40) {
              //do nothing on cursor keys.
            }
            else {
              var selectionStart = self.element.selectionStart;
              var bufferCursorPosition = self.displayPosToBufferPos(selectionStart);
              self.inputBuffer = self.inputBuffer.substring(0, bufferCursorPosition) + String.fromCharCode(obj.which) + self.inputBuffer.substring(bufferCursorPosition);
              if (self.inputBuffer.length > self.dataLength) {
                self.inputBuffer = self.inputBuffer.substring(0, self.dataLength);
              }
            }
          }

          self.manageCursor = function (obj) {
            console.log(obj.keyCode);
            if (obj.keyCode == 8) {
              self.bufferCursorPosition--;
            }
            else if (obj.keyCode == 46) {
              //do nothing on delete key.
            }
            else if (obj.keyCode >= 37 && obj.keyCode <= 40) {
              if (obj.keyCode == 37) {
                self.bufferCursorPosition--;
              }
              else if (obj.keyCode == 39) {
                self.bufferCursorPosition++;
              }
            }
            else {
              var bufferCursorPosition = self.displayPosToBufferPos(self.element.selectionStart);
              self.bufferCursorPosition = bufferCursorPosition + 1;
            }
          }

          self.setCursorByBuffer = function (bufferCursorPosition) {
            var displayCursorPos = self.bufferPosToDisplayPos(bufferCursorPosition);
            self.element.setSelectionRange(displayCursorPos, displayCursorPos);
          }

          self.moveCursor = function () {
            self.setCursorByBuffer(self.bufferCursorPosition);
          }

          self.render = function () {
            var bufferCopy = self.inputBuffer;
            var ret = {
              muskifiedValue: \"\"
            };

            var lastChar = 0;

            for (var i = 0; i < self.mask.length; i++) {
              if (self.mask.charAt(i) == \"_\" &&
                bufferCopy) {
                ret.muskifiedValue += bufferCopy.charAt(0);
                bufferCopy = bufferCopy.substr(1);
                lastChar = i;
              }
              else {
                ret.muskifiedValue += self.mask.charAt(i);
              }
            }

            self.element.value = ret.muskifiedValue;

          }

          self.preceedingMaskCharCount = function (displayCursorPos) {
            var lastCharIndex = 0;
            var ret = 0;

            for (var i = 0; i < self.element.value.length; i++) {
              if (self.element.value.charAt(i) == \"_\"
                || i > displayCursorPos - 1) {
                lastCharIndex = i;
                break;
              }
            }

            if (self.mask.charAt(lastCharIndex - 1) != \"_\") {
              var i = lastCharIndex - 1;
              while (self.mask.charAt(i) != \"_\") {
                i--;
                if (i < 0) break;
                ret++;
              }
            }

            return ret;
          }

          self.leadingMaskCharCount = function (displayIndex) {
            var ret = 0;

            for (var i = displayIndex; i >= 0; i--) {
              if (i >= self.mask.length) {
                continue;
              }
              if (self.mask.charAt(i) != \"_\") {
                ret++;
              }
            }

            return ret;
          }

          self.bufferPosToDisplayPos = function (bufferIndex) {
            var offset = 0;
            var indexInBuffer = 0;

            for (var i = 0; i < self.mask.length; i++) {
              if (indexInBuffer > bufferIndex) {
                break;
              }

              if (self.mask.charAt(i) != \"_\") {
                offset++;
                continue;
              }

              indexInBuffer++;
            }
            var ret = bufferIndex + offset;

            return ret;
          }

          self.displayPosToBufferPos = function (displayIndex) {
            var offset = 0;
            var indexInBuffer = 0;

            for (var i = 0; i < self.mask.length && i <= displayIndex; i++) {
              if (indexInBuffer >= self.inputBuffer.length) {
                break;
              }

              if (self.mask.charAt(i) != \"_\") {
                offset++;
                continue;
              }

              indexInBuffer++;
            }

            return displayIndex - offset;
          }

          self.getValue = function () {
            return this.inputBuffer;
          }
          self.element.onkeypress = self.keyEventHandler;
          self.element.onclick = self.onClick;
        }

        function InputMaskManager() {
          var self = this;

          self.instances = {};

          self.add = function (id) {
            var elem = document.getElementById(id);
            var maskInstance = new InputMask(elem);
            self.instances[id] = maskInstance;
          }

          self.getValue = function (id) {
            return self.instances[id].getValue();
          }

          document.onkeydown = function (obj) {
            if (obj.target.attributes[\"input-mask\"]) {
              if (obj.keyCode == 8 ||
                obj.keyCode == 46 ||
                (obj.keyCode >= 37 && obj.keyCode <= 40)) {

                if (obj.keyCode == 8 || obj.keyCode == 46) {
                  obj.preventDefault();
                }

                //needs to broadcast to all instances here:
                var keys = Object.keys(self.instances);
                for (var i = 0; i < keys.length; i++) {
                  if (self.instances[keys[i]].element.id == obj.target.id) {
                    self.instances[keys[i]].keyEventHandler(obj);
                  }
                }
              }
            }
          }
        }

        //Initialize an instance of InputMaskManager and
        //add masker instances by passing in the DOM ids
        //of each HTML counterpart.
        var maskMgr = new InputMaskManager();
        maskMgr.add(\"phone\");

        function showValue_phone() {
          //-------------------------------------------------------__Value_Here_____
          document.getElementById(\"console_phone\").value = maskMgr.getValue(\"phone\");
        }
      </script>
    </body>

    </html>


回答5:

Array.prototype.forEach.call(document.body.querySelectorAll(\"*[data-mask]\"), applyDataMask);

function applyDataMask(field) {
    var mask = field.dataset.mask.split(\'\');

    // For now, this just strips everything that\'s not a number
    function stripMask(maskedData) {
        function isDigit(char) {
            return /\\d/.test(char);
        }
        return maskedData.split(\'\').filter(isDigit);
    }

    // Replace `_` characters with characters from `data`
    function applyMask(data) {
        return mask.map(function(char) {
            if (char != \'_\') return char;
            if (data.length == 0) return char;
            return data.shift();
        }).join(\'\')
    }

    function reapplyMask(data) {
        return applyMask(stripMask(data));
    }

    function changed() {   
        var oldStart = field.selectionStart;
        var oldEnd = field.selectionEnd;

        field.value = reapplyMask(field.value);

        field.selectionStart = oldStart;
        field.selectionEnd = oldEnd;
    }

    field.addEventListener(\'click\', changed)
    field.addEventListener(\'keyup\', changed)
}
Date: <input type=\"text\" value=\"__-__-____\" data-mask=\"__-__-____\"/><br/>
Telephone: <input type=\"text\" value=\"(___) ___-____\" data-mask=\"(___) ___-____\"/><br/>


回答6:

Below i describe my method. I set event on input in input, to call Masking() method, which will return an formatted string of that we insert in input.

Html:

<input name=\"phone\" pattern=\"+373 __ ___ ___\" class=\"masked\" required>

JQ: Here we set event on input:

$(\'.masked\').on(\'input\', function () {
    var input = $(this);
    input.val(Masking(input.val(), input.attr(\'pattern\')));
});

JS: Function, which will format string by pattern;

function Masking (value, pattern) {
var out = \'\';
var space = \' \';
var any = \'_\';

for (var i = 0, j = 0; j < value.length; i++, j++) {
    if (value[j] === pattern[i]) {
        out += value[j];
    }
    else if(pattern[i] === any && value[j] !== space) {
        out += value[j];
    }
    else if(pattern[i] === space && value[j] !== space) {
        out += space;
        j--;
    }
    else if(pattern[i] !== any && pattern[i] !== space) {
        out += pattern[i];
        j--;
    }
}

return out;
}


回答7:

Use this code:-

<input type=\"text\" placeholder=\"\" data-mask=\"9999/99/99\">