skip to Main Content

I have a text box field in my simple HTML/JavaScript-based form where users can insert variables, as shown in the example below:

The name of the product is ${productName}

When the user submits the form, a JavaScript function replaces the variable with its actual product value.

I’d like to implement a feature where, if the user places their cursor to the right of the variable ${productName} and presses the backspace key, the entire variable is deleted in one go, instead of deleting each character one at a time. Placing the cursor in the middle of variable text should not do anything. User shouldnt be allowed to delete individual character of the variable and/or add characters. Essentially, I want the variable to behave as a single, indivisible entity in the text box.

I’m unsure what the technical term for this feature is or how to start researching it. What’s the most efficient way to implement this behaviour?

2

Answers


  1. Simple example, you should judge for yourself whether it’s efficient enough.

    On keydown of backspace (as long as no text is selected), we get the part of the text before the cursor and see if it ends in ${myvar}. If so we figure out the length of ${myvar} and remove that much from the text. Then we set the cursor to its new position.

    const tb = document.querySelector("input");
    const re = /.*(${w+})$/;
    
    tb.addEventListener("keydown", (e) => {
      if (e.code === "Backspace" && tb.selectionStart === tb.selectionEnd) {
        let textBeforeCursor = tb.value.substring(0, tb.selectionStart);
        let match = textBeforeCursor.match(re);
        if (match && match.length > 1) {
          e.preventDefault();
          let newPos = textBeforeCursor.length - match[1].length;
          tb.value = textBeforeCursor.substring(0, newPos) + tb.value.substring(tb.selectionStart);
          tb.selectionStart = newPos;
          tb.selectionEnd = newPos;
        }
      }
    });
    input {
      width: 100%;
    }
    <input type='text' value='The name of the product is ${productName} and it is ${productColor} in color'>
    Login or Signup to reply.
  2. Here is an attempt to implement the rules:

    • If text is selected, and it overlaps a token, then no modification can be made.
    • If no text is selected, but the caret sits inside a token, then no modification can be made.
    • If no text is selected, and the caret is right after a token, then a backspace will delete that token.
    • If no text is selected, and the caret is right before a token, then a delete key will delete that token.
    • In all other cases, keys will act normally.

    This code does not itself mutate the value. It merely changes the selection of the text upon which the key will act. That way the undo/redo mechanism will continue to work as expected.

    const input = document.querySelector("input");
    
    input.addEventListener("keydown", function (e) {
        // No special treatment is needed for keys that don't alter content
        if (e.key.length > 1 && !["Backspace","Delete","Space"].includes(e.key)) return;
        const {selectionStart: i, selectionEnd: j, value} = input;
        // Get the offsets of nearby ${...} occurences
        let startLeft = value.lastIndexOf("${", i + 1);
        let endLeft = startLeft >= 0 ? value.indexOf("}", startLeft) + 1 : 0;
        if (!endLeft) startLeft = endLeft = -1;
        let endRight = value.indexOf("}", j) + 1;
        let startRight = endRight > 0 ? value.lastIndexOf("${", endRight) : -1;
        if (startRight < 0) endRight = -1;
        // Check if the current cursor (selection) overlaps any of these 
        let inside = 0 <= startLeft && startLeft < i && i < endLeft 
                  || 0 <= startRight && startRight < j && j < endRight;
        // If so: disallow this key, as it would alter a ${...} token
        if (inside) return e.preventDefault();
        // If there is a non-empty selection, act normally on that selection
        if (i !== j) return;
        // If there is no selection, and a deletion would damage a ${...} occurrence,
        //   then first select that whole token, so the deletion will affect all of it
        if (e.key === "Backspace" && endLeft === i) {
            input.selectionStart = startLeft;
        } else if (e.key === "Delete" && startRight === j) {
            input.selectionEnd = endRight;
        }
    });
    <input size="100%" value="Hello ${beautiful} world! The population is ${population}.">
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search