'How to access just the changed data of an 'input' event regardless of text deletion or insertion like pasted or typed or restored text for the latter?

I have a textarea and would like to return the most recent change to the text for use in avia JavaScript function. That would include:

  • Text that is input via keyboard, paste, or inserted via widget (oninput, perhaps?)
  • Text that is removed via backspace, delete, ctrl + backspace, etc.

For example:

  • If the user presses "a" that is what I want to return.
  • If they paste "apple" I want that whole pasted phrase.
  • If they hit backspace and delete "h" I want that returned.
  • If they highlight "horse" and press delete,

I'm still new to this and have no real idea how to call that added/removed text for use in a JavaScript function.



Solution 1:[1]

Reading about InputEvent, InputEvent.inputType and InputEvent.data might be a good starting point. In combination with selectionStart / selectionEnd as with e.g. HTMLInputElements and/or HTMLTextAreaElements one pretty much could cover/solve the OP's task.

A possible approach which detects (text) changes within the input data/value might ...

  • utilize a WeakMap instance as textarea and/or input-element based storage for such an element's most recent (text) value.

  • enable the exact detection of data/value-changes by keeping track of such an element's 'input' events, based on the most recent and the current (text) values as well as on selectionEnd and sometimes on the input event's data value.

  • In case of having verified an input-event based text-value change a custom event is created and dispatched. Thus one now can directly listen to and handle textarea and/or input-element related 'input:datachange' events.

On top of the new custom 'input:datachange' event type, with all the additional information one does neither receive by 'input' nor by 'change' events, the OP now will be able to directly access the changed value whether deleted and/or inserted (pasted or typed or restored for the latter).

// the node reference based storage of most recent element values.
const mostRecentValueStorage = new WeakMap;

function getDataChangeFromDeleteOrPaste({ currentTarget, data }) {
  const recentValue = mostRecentValueStorage.get(currentTarget);
  const { value: currentValue, selectionEnd } = currentTarget;

  let deletionStart = selectionEnd;
  let leadingValue = currentValue.slice(0, deletionStart);

  while ((leadingValue !== '') && !recentValue.startsWith(leadingValue)) {
    leadingValue = leadingValue.slice(0, --deletionStart);
  }
  const deletionLength =
    (selectionEnd - deletionStart) + (recentValue.length - currentValue.length);

  const deleted = recentValue.slice(deletionStart, (deletionStart + deletionLength));

  const insertLength = currentValue.length + deleted.length - recentValue.length;
  const insertStart = selectionEnd - insertLength;

  const inserted = (insertStart < selectionEnd)
    && currentValue.slice(insertStart, selectionEnd)
    || null;

  return {
    currentValue,
    recentValue,
    deleted: (deleted === '') ? null : deleted,
    deletionStart: (deleted === '') ? null : deletionStart,
    deletionLength: (deleted === '') ? null : deletionLength,
    inserted,
    insertStart: (inserted === null) ? null : insertStart,
    insertLength: (inserted === null) ? null : insertLength,
  };
}
function getDataChangeFromInsertText({ currentTarget, data }) {
  const recentValue = mostRecentValueStorage.get(currentTarget);
  const { value: currentValue, selectionEnd } = currentTarget;

  const insertLength = data.length;

  const deletionStart = (selectionEnd - insertLength);
  const deletionLength = (recentValue.length - currentValue.length + insertLength);

  const deleted = recentValue.slice(deletionStart, (deletionStart + deletionLength));

  return {
    currentValue,
    recentValue,
    deleted: (deleted === '') ? null : deleted,
    deletionStart: (deleted === '') ? null : deletionStart,
    deletionLength: (deleted === '') ? null : deletionLength,
    inserted: data,
    insertStart: (selectionEnd - insertLength),
    insertLength,
  };
}

function handleCustomInputDataChange(evt) {
  const { currentTarget } = evt;

  if (currentTarget.value !== mostRecentValueStorage.get(currentTarget)) {
    const { data } = evt;

    const dataChange = (
      (typeof data === 'string') && getDataChangeFromInsertText(evt)
    ) || (
      (data === null) && getDataChangeFromDeleteOrPaste(evt)
    ) || null;

    // put the most recent element value into an object based storage.
    mostRecentValueStorage.set(currentTarget, currentTarget.value);

    currentTarget
      .dispatchEvent(
        new CustomEvent('input:datachange', {
          bubbles: true,
          detail: {
            dataChange,
            inputEvent: evt,
          },
        })        
      );
  }
}

function enableCustomInputDataChangeHandling(elmNode) {
  // put the initial element value into an object based storage.
  mostRecentValueStorage.set(elmNode, elmNode.defaultValue);

  elmNode
    .addEventListener('input', handleCustomInputDataChange);
}
function initializeCustomInputDataChangeHandling() {
  document
    // for every element which features a
    // `data-handle-input-data-change` attribute ...
    .querySelectorAll('[data-handle-input-data-change]')

    // ... enable the handling of a custom
    // 'input:datachange' event.
    .forEach(enableCustomInputDataChangeHandling);
}

function main() {
  initializeCustomInputDataChangeHandling();

  document
    // subscribe to the custom 'input:datachange' event wherever it is needed. 
    .addEventListener(
      'input:datachange',
      ({ target, detail: { dataChange } }) => console.log({ /*target, */dataChange })
    );
}
main();
body { margin: 0; }
.as-console-wrapper { left: auto!important; width: 76%; min-height: 100%!important; }
<textarea
  data-handle-input-data-change
  cols="16"
  rows="12"
>The quick brown fox jumps over the lazy dog ... edit text in whichever way.</textarea>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1