javascripthtmldomtext

Back and Forward buttons not behaving as expected. the buttons cycle correctly but when using only buttons places text at the end. using keyboard ok


This codes behavior is unexpected when you use only the buttons to place chars from the carrot location. It always places the char at the end of the string. If i use the buttons and place the char with the keyboard it does work as expected. Why does this happen and any help with a solution is greatly appreciated.

function getInputSelection(el) {
  var start = 0,
    end = 0,
    normalizedValue, range,
    textInputRange, len, endRange;
  if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
    start = el.selectionStart;
    end = el.selectionEnd;
  } else {
    range = document.selection.createRange();
    if (range && range.parentElement() == el) {
      len = el.value.length;
      normalizedValue = el.value.replace(/\r\n/g, "\n");
      textInputRange = el.createTextRange();
      textInputRange.moveToBookmark(range.getBookmark());
      endRange = el.createTextRange();
      endRange.collapse(false);
      if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
        start = end = len;
      } else {
        start = -textInputRange.moveStart("character", -len);
        start += normalizedValue.slice(0, start).split("\n").length - 1;
        if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
          end = len;
        } else {
          end = -textInputRange.moveEnd("character", -len);
          end += normalizedValue.slice(0, end).split("\n").length - 1;
        }
      }
    }
  }
  return {
    start: start,
    end: end
  };
}

function setBack() {
  document.getElementById('user').value =
    document.getElementById('user').value.substring(0,
      document.getElementById('user').value.length - 1);
}


function moveBack(elId) {
  var elem = document.getElementById(elId);
  elem.setSelectionRange(elem.value.length,
    getInputSelection(elem).start - 1);
  elem.focus();
}

function moveForward(elId) {
  var elem = document.getElementById(elId);
  elem.setSelectionRange(elem.value.length,
    getInputSelection(elem).start + 1);
  elem.focus();
}
function focusElem(){
var foo =document.getElementById('user');
foo.focus();
}

function setA(){ document.getElementById('user').value += "A"; focusElem();}
function setB(){ document.getElementById('user').value += "B"; focusElem();}
function setC(){ document.getElementById('user').value += "C"; focusElem();}
function setD(){ document.getElementById('user').value += "D"; focusElem();}
<input id="user" type="text">
<button type="button" onclick="setBack();">backspace</button>
<button type="button" onclick="moveBack('user')">back</button>
<button type="button" onclick="moveForward('user')">forward</button>


<div>
<button type="button" onclick="setA()">A</button>
<button type="button" onclick="setB()">B</button>
<button type="button" onclick="setC()">C</button>
<button type="button" onclick="setD()">D</button>
<div>
Thanks for your time in advance!


Solution

  • Your helper functions do not respect the caret position

    Here is a version that uses delegation and data-attributes to not have inline event handlers

    window.addEventListener('load', () => {
      const elem = document.getElementById("user");
    
      function insertCharAtCaret(char) {
        var selection = getInputSelection(elem);
        var startPos = selection.start;
        var endPos = selection.end;
    
        elem.value = elem.value.substring(0, startPos) + char + elem.value.substring(endPos);
    
        // Set the caret position after the inserted character
        elem.setSelectionRange(startPos + 1, startPos + 1);
        elem.focus();
      }
    
      function setBack() {
        var selection = getInputSelection(elem);
        var startPos = selection.start;
        var endPos = selection.end;
    
        // Only proceed if there's something to delete
        if (startPos > 0) {
          // Remove the character before the caret or the selected text
          elem.value = elem.value.substring(0, startPos - 1) + elem.value.substring(endPos);
    
          // Set the caret position after the deletion
          elem.setSelectionRange(startPos - 1, startPos - 1);
          elem.focus();
        }
      }
    
      function moveBack() {
        var start = getInputSelection(elem).start - 1;
        elem.setSelectionRange(start, start);
        elem.focus();
      }
    
      function moveForward() {
        var start = getInputSelection(elem).start + 1;
        elem.setSelectionRange(start, start);
        elem.focus();
      }
    
      // Delegation for character buttons
      document.getElementById("buttonContainer").addEventListener("click", function(event) {
        const tgt = event.target.closest('button');
        if (!tgt || !tgt.dataset.char) return; // not a button or no char specified
        insertCharAtCaret(tgt.dataset.char);
      });
    
      const actions = {
        setBack,
        moveBack,
        moveForward,
      };
    
      document.getElementById("controlContainer").addEventListener("click", function(event) {
        const tgt = event.target.closest('button');
        if (!tgt || !tgt.dataset.action) return; // not a button or no action specified
    
        const actionFunc = actions[tgt.dataset.action];
        if (actionFunc) {
          actionFunc();
        }
      });
    
      function getInputSelection(el) {
        var start = 0,
          end = 0,
          normalizedValue, range, textInputRange, len, endRange;
        if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
          start = el.selectionStart;
          end = el.selectionEnd;
        } else {
          range = document.selection.createRange();
          if (range && range.parentElement() == el) {
            len = el.value.length;
            normalizedValue = el.value.replace(/\r\n/g, "\n");
            textInputRange = el.createTextRange();
            textInputRange.moveToBookmark(range.getBookmark());
            endRange = el.createTextRange();
            endRange.collapse(false);
            if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
              start = end = len;
            } else {
              start = -textInputRange.moveStart("character", -len);
              start += normalizedValue.slice(0, start).split("\n").length - 1;
              if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
                end = len;
              } else {
                end = -textInputRange.moveEnd("character", -len);
                end += normalizedValue.slice(0, end).split("\n").length - 1;
              }
            }
          }
        }
        return {
          start: start,
          end: end
        };
      }
    });
    <input id="user" type="text">
    
    <div id="controlContainer">
      <button type="button" data-action="setBack">backspace</button>
      <button type="button" data-action="moveBack">back</button>
      <button type="button" data-action="moveForward">forward</button>
    </div>
    
    <div id="buttonContainer">
      <button type="button" data-char="A">A</button>
      <button type="button" data-char="B">B</button>
      <button type="button" data-char="C">C</button>
      <button type="button" data-char="D">D</button>
    </div>