17

I am trying to build a custom text input for phone numbers that accepts only numbers and the plus (+) symbol; all other characters need to be discarded and not shown on the field.

I am trying to do this using an event handler (onkeydown/onkeypress) and discarding inputs corresponding to other keys. However, I can't figure out a cross-browser way to do it. Here are the approaches that I have tried and don't work:

  • Using the onkeypress event and looking at event.key to figure out which key was pressed: doesn't work on Chrome (see http://caniuse.com/keyboardevent-key). Is there any cross-browser workaround?

  • Using the onkeycode event and looking at event.keyCode: does not work when we need to press multiple keys to print a character (for instance, an English keyboard layout requires pressing Shift and = to give +). Furthermore, it allows characters such as !@#$%ˆ&*() to appear, as these appear when pressing Shift and a number. (This is the approach followed in JavaScript keycode allow number and plus symbol only, but it does not help me much ;))

  • Using the HTML pattern attribute: this does not really seem to prevent people from writing whatever they feel like.

Thanks!

unpollito
  • 937
  • 1
  • 11
  • 30
  • 1
    You could use the onkeyup event, get the entire input and filter out any unwanted characters using regexp – RobinvdA May 23 '16 at 12:20
  • 5
    Possible duplicate of [JavaScript keycode allow number and plus symbol only](http://stackoverflow.com/questions/5535449/javascript-keycode-allow-number-and-plus-symbol-only) – evolutionxbox May 23 '16 at 12:26
  • 2
    @evolutionxbox no, it's not a duplicate. The approach followed in that link is exactly one of the approaches that I tried and does not work. The reason why it doesn't work is explained in the first post, second bullet point. – unpollito May 23 '16 at 12:30
  • The approach may not work, but the question is essentially the same. Which makes it a duplicate. Regardless, @RobinvdA suggested approach is likely to be what you want. – evolutionxbox May 23 '16 at 12:32
  • @RobinvdA If you are regexp'ing the value of an input and sanitizing it then the cursor will be forced to the end of the textbox unless you know of a cross-browser way to maintain cursor position. I have come across plenty of infuriating interfaces which perform that operation unnecessarily and prevent me from fixing something in the middle of the text. – MonkeyZeus May 23 '16 at 16:12

7 Answers7

17

There is another solution, you can use Array.prototype.filter() to remove the bad characters, and Array.prototype.join() to recreate the string before insert it into the input.

You can use oninput event. It execute a JavaScript when a user writes something in an <input> field.


See example below

var inputEl = document.getElementById('tel');
var goodKey = '0123456789+ ';

var checkInputTel = function(e) {
  var key = (typeof e.which == "number") ? e.which : e.keyCode;
  var start = this.selectionStart,
    end = this.selectionEnd;

  var filtered = this.value.split('').filter(filterInput);
  this.value = filtered.join("");

  /* Prevents moving the pointer for a bad character */
  var move = (filterInput(String.fromCharCode(key)) || (key == 0 || key == 8)) ? 0 : 1;
  this.setSelectionRange(start - move, end - move);
}

var filterInput = function(val) {
  return (goodKey.indexOf(val) > -1);
}

inputEl.addEventListener('input', checkInputTel);
<input type='tel' id='tel' />

Note : I use input type tel to show default number pad in a smartphone or a tablet.

tel: A control for entering a telephone number; line-breaks are automatically removed from the input value, but no other syntax is enforced. You can use attributes such as pattern and maxlength to restrict values entered in the control. The :valid and :invalid CSS pseudo-classes are applied as appropriate.

Reference : MDN <input>

Community
  • 1
  • 1
R3tep
  • 12,512
  • 10
  • 48
  • 75
  • Essentially, it works, but has the problem that the input flashes. It might be cleaner to do the same with onkeypress instead. – unpollito May 23 '16 at 12:45
  • This breaks selecting the value of the field with ctrl-a. – lesderid May 23 '16 at 15:53
  • @lesderid The ctrl-a works great for me. What is your browser ? – R3tep May 23 '16 at 16:06
  • @R3tep Latest Chrome on Linux – lesderid May 23 '16 at 17:04
  • 1
    You may wish to listen to `input` in addition to `change`; it'll work much nicer in Chrome (and Firefox?). – Schism May 23 '16 at 20:36
  • 1
    In my old chrome it works nice, except for that it jumps to the end on any invalid input. – maaartinus May 23 '16 at 22:49
  • 1
    This behaves in a wonky way if you try typing somewhere besides the end of the input so I wouldn't use this as-is. It needs code to restore the caret position when the value is changed. – Matti Virkkunen May 24 '16 at 01:33
  • @MattiVirkkunen As I say in my presedent revision. If you don't want this behaviour, use the event onchange. I change my answer because I think it's not very clear. – R3tep May 24 '16 at 08:08
  • It's entirely possible to have it work in real time by either restoring the caret position or using `preventDefault` to prevent the event from being processed at all (but YMMV with the latter) – Matti Virkkunen May 24 '16 at 11:51
  • @MattiVirkkunen Indeed, I found the feature [`setSelectionRange`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange). Thank you for your contribution. – R3tep May 24 '16 at 12:26
  • That's better, but still very robust, slow ( not that it matters here ), but still HTML5 only - and no backward compatibility support at all. – Bekim Bacaj May 24 '16 at 12:50
  • Minor bug: "Prevents moving the pointer for a bad character" - does not prevent the cursor from moving forward when pasting more than one invalid character. – le_m May 24 '16 at 15:43
9

Always try to make server-side validation, this is meant to 'help' the user, not to validate the data, (You can always open up your console and change the value of the input value however you want)

Now, the method I would use is:

Check the value of the whole input on change and make that value go trough a regex to clean unwanted chars, while keeping track of where the "text cursor" was

const tel = document.getElementById('tel');

tel.addEventListener('input', function() {
  let start = this.selectionStart;
  let end = this.selectionEnd;
  
  const current = this.value
  const corrected = current.replace(/([^+0-9]+)/gi, '');
  this.value = corrected;
  
  if (corrected.length < current.length) --end;
  this.setSelectionRange(start, end);
});
<input type="tel" id="tel">

You could make a much more 'accurate' regex according to what you want, here is a nice tool for that: RegExr

Aramil Rey
  • 3,387
  • 1
  • 19
  • 30
  • Why do you have a lookahead in your phoneRegExp? – le_m May 24 '16 at 18:54
  • "Notice that this regex will allow any value with at least one number" - the relevant regex is the one you use in val.replace(...) which does not guarantee at least one number. – le_m May 24 '16 at 19:07
8

I suggest you look at this project. https://github.com/igorescobar/jQuery-Mask-Plugin/

You could use it as is: $('.phone').mask('0000-0000'); Example: https://igorescobar.github.io/jQuery-Mask-Plugin/

Or you could read the source code and check out how they solve it.

yeouuu
  • 1,863
  • 3
  • 15
  • 32
  • Their approach seems to be interesting, but they're cheating. ;-) Apparently, the masks don't care about which key you press. If you're expected to type a (, you might as well press a number or the + key; it'll always print a (. It's not quite what I want, but it's an interesting option if we can't figure anything else. Thank you! – unpollito May 23 '16 at 12:21
8

This robust generic approach is inspired by @R3tep's answer. It allows to define regexp patterns via the data-filter attribute similar to the pattern attribute:

// Apply filter to all inputs with data-filter:
let inputs = document.querySelectorAll('input[data-filter]');

for (let input of inputs) {
  let state = {
    value: input.value,
    start: input.selectionStart,
    end: input.selectionEnd,
    pattern: RegExp('^' + input.dataset.filter + '$')
  };
  
  input.addEventListener('input', event => {
    if (state.pattern.test(input.value)) {
      state.value = input.value;
    } else {
      input.value = state.value;
      input.setSelectionRange(state.start, state.end);
    }
  });

  input.addEventListener('keydown', event => {
    state.start = input.selectionStart;
    state.end = input.selectionEnd;
  });
}
<input type='tel' data-filter='[0-9|+]*' placeholder='123+456'>
<input type='tel' data-filter='(\+|(\+[1-9])?[0-9]*)' placeholder='+10123'>
<input type='text' data-filter='([A-Z]?|[A-Z][a-z]*)' placeholder='Abcdefg'>
<input type='text' data-filter='([A-Z]{0,3}|[A-Z]{3}[0-9]*)' placeholder='ABC123'>
le_m
  • 19,302
  • 9
  • 64
  • 74
  • 1
    Nice way, it has the distinction of being totally generic. – R3tep May 25 '16 at 08:20
  • @R3tep, Yes, I love it. I'll give my thumbs up and my vote up, for its tentative of implicit route. But to bad it only works on most recent browsers. In that case I think I'd rather go with instead, and ask my clients to switch to Safari and upgrade to version 8 if they want to use my web app. Its easier that way and cleaner too. – Bekim Bacaj May 25 '16 at 08:51
6

use input

type="tel" or "phone"

It Shows default number pad in mobiles.

Hope this will help :)

bhagya
  • 103
  • 1
  • 6
  • 1
    This is not a cross-browser solution. – MacMac May 23 '16 at 14:06
  • 4
    @Kevin Apologises for not being clear. While OP is looking to restrict certain characters being entered. bhagya has provided an answer to show a number pad in mobiles, while this does not occur in desktop browsers, therefore allowing any character being entered. This does not directly answer OP's question. – MacMac May 23 '16 at 21:22
2

Using a combination of a keyup event listener, some RegEx/String comparison methods, and an intermediate variable, I was able to come up with the following:

var ele = document.getElementById('phone');
var curr = "";
var regexPatt = /^(0|[1-9][0-9]*)$/;
ele.addEventListener('keyup',function(e){
  var code = e.keyCode || e.which;
  if(this.value.match(regexPatt) || this.value.indexOf('+') > -1 || code == 8){
    curr = this.value;
    this.value = curr;
  } else {
    this.value = curr;
  }
});

Take a look at the fiddle: https://jsfiddle.net/Lg31pomp/2/

This seems to work with digits, the + character as well as if the user backspaces the input element.

A.Sharma
  • 2,771
  • 1
  • 11
  • 24
  • Does not prevent invalid characters from being added after a space, prevents user to navigate the cursor using the arrow keys or selecting with [ctrl]+[a]. – le_m May 24 '16 at 15:50
-1

Your input should look something like this:

<input type="text" onkeypress='return isNumberKey(event);'>

and this is the script:

function isNumberKey(evt) {
    var charCode = (evt.which) ? evt.which : event.keyCode;
    console.log(charCode);
    if (charCode != 43 &&  charCode > 31
        && (charCode < 48 || charCode > 57))
        return false;

    return true;
}

This is one of the many possible solutions.

Ivan Gajic
  • 456
  • 4
  • 12
  • This does not work when we need to press multiple keys to print a character (for instance, an English keyboard layout requires pressing Shift and = to give +). – unpollito May 23 '16 at 12:36
  • 1
    @BekinBacaj, I'd tested this solution and made certain that the onkeydown event does not help when we need holding keys to type in a character. It would be useful it we were using onkeypress instead, but this does not work on Chrome. Considering that and the fact that this had already been explained in the opening question, I believe my downvote was more than warranted. – unpollito May 24 '16 at 08:19
  • 1
    @unpollito, - he's using the onkeypress event. you've been wrong for using the onkeydown intead. My snippet is using onkeypress and it doesn't mind you using ( shift & = ) sign, or ( alt & 4 & 3 ) to write the plus (+) sign at all. It simply allows for any key combination needed to input the allowed symbol. To bad his code is not his own to begin with - therefore erroneous, i.e. doesn't work at all - and that's 'the' reason that warrants your vote. – Bekim Bacaj May 24 '16 at 10:38
  • Fair enough, you're right - I misread. I've tested your approach and it works. Sorry, and thank you! – unpollito May 24 '16 at 13:35