Souames
Souames

Reputation: 1155

Allow only alphanumeric, special letters and symbols in input

I need to allow user to enter English and French characters (so I need to take in consideration chars like é,à,ç,è...etc). I also need to allow user to enter symbols and numbers.

In other words I need to prevent the user from entering non latin characters like arabic, chinese, japanese, russian ...etc

$("input:text,textarea").keydown(function(event) {
  var allowed = /[A-Za-z0-9àâçéèêëîïôûùüÿñæœ!@#\$%\^&\*\(\)_|}{~">:<\?/\]\[\\=\- ; ]/g;

  var key = String.fromCharCode(event.which);
  if (event.keyCode == 8 || event.keyCode == 37 || event.keyCode == 39 || allowed.test(key)) {
    return true;
  } else {
    return event.preventDefault()
  }

});

This works well except for some characters like ; ? / Ù even though I aded them

Upvotes: 2

Views: 2439

Answers (3)

manou.MHD
manou.MHD

Reputation: 38

I've seen that you're currently using jquery and the answer given was using pure Js, so here's a solution using Jquery:

 $("input:text,textarea").on("input",function(event) {
const notAllowed = /[^a-zàâçéèêëîïôûùüÿñæœ0-9 \\?!@#$%^&*":;~=|\-(){}<>[\]]/i;
 return (function(event){
 const val = event.target.value;
 if(notAllowed.test(val)){
 //Show error message
 // Your code here ...
 // Getting old value
 let old = $(this).data('val')? $(this).data('val'):'';
 event.target.value=old
 }
 else{
 //Remove error message
 // Your code here ...
 //Setting current value in order to use in case a non permitted character is entered
 $(this).data('val',event.target.value);
      }
 })(event)
 }); 

you can then implement an error message

Upvotes: 1

raina77ow
raina77ow

Reputation: 106483

There's a couple of issues with the pattern you've showed. For a start, you don't have to escape all the metacharacters inside [] definition, but only those that might lead to its incorrect parsing. So quite a bit of those \ are redundant.

Also, be aware that patterns designed just for .test purposes should in general avoid /g flag, as this leads to some surprising results:

const patt = /[ab]/g;
console.log( patt.test('a') ); // true
console.log( patt.test('b') ); // false

Finally, as you didn't make your pattern case-insensitive, all the capital letters with diacritics will be missed by that. You can fix this by adding /i flag, which should work in your case, though, but... that's not the true reason your code fails.

See, the far bigger problem than all the previous combined is using event.which in combination with keydown event. Again, there are some minor issues with the approach overall (for example, how can you prevent user from pasting incorrect values inside the form - with or without a mouse?), but the particular thing that bites you is inconsistency of event.which results:

The which read-only property of the KeyboardEvent interface returns the numeric keyCode of the key pressed, or the character code (charCode) for an alphanumeric key pressed.

Simply said, String.fromCharCode() line works fine only for Latin characters and digits keys on the keyboard - but not for all the rest of the symbols you want to allow in the input:

// `0` pressed:
event.which - 48
String.fromCharCode - '0'

// '-' (neighbor of 0) pressed:
event.which - 189
String.fromCharCode - '½'

Now I suppose the real question is 'well, how to avoid this?'. One possible approach is checking event key property instead, which is now universally supported:

The KeyboardEvent interface's key read-only property returns the value of the key pressed by the user, taking into consideration the state of modifier keys such as Shift as well as the keyboard locale and layout.

In this case, however, you still need to handle copy-paste too, as well as some edge cases on keyboard input itself.

What I'd suggest as alternative is using input event to test the whole value of the input after the change, and not just individual letters. While input event is not cancellable, it's possible to revert the change it introduces, like this:

document.querySelector('input').oninput = (function() {
  let oldValue = '';
  const smallEl = document.querySelector('small');
  const notAllowed = /[^a-zàâçéèêëîïôûùüÿñæœ0-9 \\?!@#$%^&*":;~=|\-(){}<>[\]]/i;
  return function(event) {
    const val = event.target.value;
    if (notAllowed.test(val)) {
      event.target.value = oldValue;
      smallEl.removeAttribute('hidden');
    }
    else {
      oldValue = event.target.value;
      smallEl.setAttribute('hidden', '');
    }
  }
})();
small {
  color: red;
}
[hidden] {
  display: none !important;
}
<label>Let's put some text here: <input /><br />
<small hidden>Non-Latin alpha characters are not allowed</small></label>

This works, but has two caveats. First, the validator now has to store the previous value of input (which raises its complexity a bit). Second, when incorrect chars are thrown into a mix and the value of input is rewritten, cursor always jumps into the end of the line. Not great, but definitely not terrible to me.

All of this won't be necessary when beforeinput event will be properly implemented in all the browsers. At this moment (Nov 2020) Firefox has it hidden under experimental flag (here's the ticket), and Chrome seems to not handle its cancellation correctly:

document.querySelector('input').addEventListener('beforeinput', (function() {
  const smallEl = document.querySelector('small');
  const notAllowed = /[^a-zàâçéèêëîïôûùüÿñæœ0-9 \\?!@#$%^&*":;~=|\-(){}<>[\]]/i;
  return function(event) {
    const val = event.target.value;
    if (notAllowed.test(val)) {
      event.preventDefault();
      smallEl.removeAttribute('hidden');
    }
    else {
      smallEl.setAttribute('hidden', '');
    }
  }
})());
small {
  color: red;
}


[hidden] {
  display: none !important;
}
<label>Let's put some text here: <input /><br />
<small hidden>Non-Latin alpha characters are not allowed</small></label>

When I test it locally in Chrome 87, the input gets stuck immediately after processing the first 'incorrect' character, disallowing any further action on that input. There are other issues with beforeinput in Chrome, some of them stay opened for ~2 years already, so I wouldn't place my bets on this event's usability in the nearest future.


As a final sidenote, one should strongly consider checking this UX Experience thread to validate the whole approach from UX perspective. While Prevention is the only way in some cases, in others it might just lead to user frustration ("why doesn't this stupid app let me put my proper name in it???" is the typical reaction), so choose your options wisely.

Upvotes: 2

RRP
RRP

Reputation: 328

Filtering keypresses can work to eliminate the characters you don't want to allow, although you'll also need to handle copy/paste too.

Filter on Unicode character ranges is the most concise method I think. Pick the ranges / chars you want to allow from here: https://en.wikipedia.org/wiki/Latin_script_in_Unicode

// Allow backspace, left and right arrows
if ([8, 37, 39].includes(event.keyCode)) {
    return true;
}

// Check for disallowed characters
const re = /[^\u0020-\u00ff\u0152\u0153]/;
var key = String.fromCharCode(event.which);
if (!re.test(stringValue)) {
    return true
}

Upvotes: 0

Related Questions