DEV Community

Andrei Luca
Andrei Luca

Posted on • Updated on

♻️ Override DOM input typed character

Problem trying to solve.

Map every keyboard (input source / layout) back to EN-US.
Why would I want to do this, you ask?

At work we have a QRcode scanner that can be connected to any machine (via USB), and it will simulate some keyboard input when you scan a QRcode.

Scanner works with US Standard 101 layout (EN-US), and it knows for example that character Y has scancode 0x1c and Z will have scancode 0x1d, and it will simulate a scancode for a specific key on keyboard (USB keyboard scancodes).

For example we have a QRcode that represent this URl.

https://amayzyng.com/iamandrewluca
Enter fullscreen mode Exit fullscreen mode

This will type QRcode scanner for 5 keyboard (input source / layout)

// English (ABC)
https://amayzyng.com/iamandrewluca

// German (ABC - QWERTZ)
httpsÖ--amazyzng.com-iamandrewluca

// Dvorak
dyyloSzzamaf;fbivjrmzcamabep.,ngja

// Russian
реезыЖ//фьфнянипюсщь/шфьфтвкуцдгсф

// Romanian - Standard
httpsȘ//amayzyng.com/iamandrewluca
Enter fullscreen mode Exit fullscreen mode

As you see the results for each one are the same 🙃
This is why we need to map US Standard 101 layout scancodes to EN-US

Requirements

Solution is based on KeyboardEvent.code that represents physical code of the key. Also KeyboardEvent.code (status: Working Draft) is not supported in all browsers.

KeyboardEvent.code browser support

Here is a polyfill if you want to support more browsers

Solution for the problem. Let's get started!

This is the only piece of HTML you'll see in this post 🙂

The rest will be mighty JavaScript

<input type="text" />
Enter fullscreen mode Exit fullscreen mode

First of all let see what are most events used on an input, and in what order they are trigerred.

const input = document.querySelector('input')

input.addEventListener('focus', info)
input.addEventListener('keydown', info)
input.addEventListener('keypress', info)
input.addEventListener('input', info)
input.addEventListener('keyup', info)
input.addEventListener('change', info)
input.addEventListener('blur', info)

function info(event) {
    console.log(event.type, event.target.value)
}
Enter fullscreen mode Exit fullscreen mode

The only way to catch typed character into input is to watch for keypress event.

At this phase character does not appear in input.value

function onFocus  (event) { info(event) }
function keyDown  (event) { info(event) }
function keyPress (event) {
  info(event)
  // this 2 calls will stop `input` and `change` events
  event.preventDefault();
  event.stopPropagation();

  // get current props
  const target = event.target
  const start = target.selectionStart;
  const end = target.selectionEnd;
  const val = target.value;

  // get some char based on event
  const char = getChar(event);

  // create new value
  const value = val.slice(0, start) + char + val.slice(end);

  // first attemp to set value
  // (doesn't work in react because value setter is overrided)
  // target.value = value

  // second attemp to set value, get native setter
  const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
    window.HTMLInputElement.prototype,
    "value"
  ).set;
  nativeInputValueSetter.call(target, value);

  // change cursor position
  target.selectionStart = target.selectionEnd = start + 1;

  // dispatch `input` again
  const newEvent = new InputEvent('input', {
    bubbles: true,
    inputType: 'insertText',
    data: char
  })
  event.target.dispatchEvent(newEvent);
}
function keyUp    (event) { info(event) }
function onInput  (event) { info(event) }
function onChange (event) { info(event) }
function onBlur   (event) {
  // dispatch `change` again
  const newEvent = new Event('change', { bubbles: true })
  event.target.dispatchEvent(newEvent);
  info(event)
}

function info     (event) { console.log(event.type) }

function getChar(event) {
  // will show X if letter, will show Y if Digit, otherwise Z
  return event.code.startsWith('Key')
    ? 'X'
    : event.code.startsWith('Digit')
      ? 'Y'
      : 'Z'
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)