Search code examples
javascripthtmlcss

How to make an Text-Input with uneditable E-Mail appendix @mail.de


I had the following Problem. The objective was to make an text-input for an E-Mail thats is always showing the the same appendix.

For example Max.Mustermann@mail.de.

Requirements:

  1. The appendix @mail.de has to be in correct position behind the user input
  2. The appendix needs to be visually contained in the input field
  3. The apendix is uneditable
  4. Shouldn't look janky

The input field:

<input type="text" class="form-control" id="mailAddress" placeholder="Max.Mustermann@mail.de" maxlength="64">

So how is this possible?


Solution

  • Adding a div and position it over the text input will do the trick. Another hidden div is used to measure the content length and position the appendix accordingly.

    The following example migth not be perfect but will do the job:

    (feel free to improve the solution)

    const mailAddress = document.getElementById("mailAddress")
    const appendix = document.getElementById("appendix")
    const hiddenDiv = document.getElementById("hiddenDiv")
    
    window.addEventListener('resize', () => {
      hideOverflow(mailAddress.value)
    })
    
    mailAddress.addEventListener('keydown', styleAppendixDown)
    mailAddress.addEventListener('keyup', styleAppendixUp)
    
    mailAddress.addEventListener("paste", (event) => {
      let paste = (event.clipboardData || window.clipboardData).getData("text");
      let selection = getSelectedText(mailAddress)
      styleAppendixPaste(paste, selection)
    });
    
    function getSelectedText(elem) { // only allow input[type=text]/textarea
      if (elem.tagName === "TEXTAREA" ||
        (elem.tagName === "INPUT" && elem.type === "text")) {
        return elem.value.substring(elem.selectionStart,
          elem.selectionEnd);
      }
      return null;
    }
    
    function styleAppendixPaste(paste, selection) {
      // measure the width
      hiddenDiv.textContent = paste
      let pasteWidth = hiddenDiv.clientWidth
    
      // measure the width
      hiddenDiv.textContent = selection
      let selectionWidth = hiddenDiv.clientWidth
    
      // measure the width
      hiddenDiv.textContent = mailAddress.value
      let textWidth = hiddenDiv.clientWidth
    
      if (mailAddress.value.length === 0) {
        appendix.style.left = (textWidth + pasteWidth + 13 - selectionWidth) + 'px'
      } else {
        appendix.style.left = (textWidth + pasteWidth + 13 - selectionWidth) + 'px'
        appendix.style.display = 'block'
        appendix.style.color = '#495057'
    
        // Delete Selected Text
        let pre = mailAddress.value.substring(0, mailAddress.selectionStart)
        let post = mailAddress.value.substring(mailAddress.selectionEnd, mailAddress.length)
        let finalText = pre + paste + post;
    
        hideOverflow(finalText)
      }
    }
    
    function styleAppendixDown(e) {
      let keyCode = e.keyCode
    
      let keyString = ''
      if (e.key) {
        if (e.key.length <= 1) {
          keyString = e.key
        }
        if (keyCode === 32) {
          keyString = ''
        }
      }
    
      if (mailAddress.value.length === 0) {
        appendix.style.display = 'none'
      }
    
      let cursorPosition = mailAddress.selectionStart
    
      if (e.key == 'Backspace' || keyCode === 8) {
        if (cursorPosition >= 1) {
          let prevKey = mailAddress.value.substring(cursorPosition - 1, cursorPosition)
    
          hiddenDiv.textContent = prevKey
          let keyWidth = hiddenDiv.clientWidth
    
          // measure the width
          hiddenDiv.textContent = mailAddress.value
          let textWidth = hiddenDiv.clientWidth
    
          appendix.style.left = (textWidth - keyWidth + 13) + 'px'
          appendix.style.display = 'block'
          appendix.style.color = '#495057'
        }
        hideOverflow(mailAddress.value)
      }
    
      // Remove Spaces so Spaces don't break the Styling
      if (keyCode === 32) {
        let value = mailAddress.value
        mailAddress.value = value.toString().replace(" ", "")
      }
    
      // Move the div
      moveDivDown(keyCode, keyString)
    }
    
    // Move the @mail.de div Based on Input
    function moveDivDown(keyCode, keyString) {
      let keyWidth
    
      // measure the width
      hiddenDiv.textContent = keyString
      keyWidth = hiddenDiv.clientWidth
    
      // measure the width
      hiddenDiv.textContent = mailAddress.value
      let textWidth = hiddenDiv.clientWidth
    
      if (mailAddress.value.length === 0) {
        appendix.style.left = (textWidth + keyWidth + 13) + 'px'
      } else {
        appendix.style.left = (textWidth + keyWidth + 13) + 'px'
        appendix.style.display = 'block'
        appendix.style.color = '#495057'
    
        hideOverflow(mailAddress.value + keyString)
      }
    }
    
    
    function styleAppendixUp(e) {
      let keyCode = e.keyCode
    
      if (mailAddress.value.length !== 0) {
        // show Appendix
        appendix.style.display = 'block'
        appendix.style.color = '#495057'
      }
    
      if (mailAddress.value.length === 0) {
        appendix.style.display = 'none'
      }
    
      // Check Backspace
      if (keyCode === 8) {
        backspaceUp()
      }
    
      // Remove Spaces again so Spaces don't break the Styling 
      if (keyCode === 32) {
        let value = mailAddress.value
        mailAddress.value = value.toString().replace(" ", "")
      }
    
      if (mailAddress.value.length === 0) {
        appendix.style.display = 'none'
      }
    
      // Move the div again
      hiddenDiv.textContent = mailAddress.value
      let textWidth = hiddenDiv.clientWidth
      appendix.style.left = (textWidth + 13) + 'px'
    
      hideOverflow(mailAddress.value)
    }
    
    
    function backspaceUp() {
      let cursorPosition = mailAddress.selectionStart
      if (cursorPosition >= 1) {
        // measure the width
        hiddenDiv.textContent = mailAddress.value
        let textWidth = hiddenDiv.clientWidth
    
        appendix.style.left = (textWidth + 13) + 'px'
        appendix.style.display = 'block'
        appendix.style.color = '#495057'
      }
      hideOverflow(mailAddress.value)
    }
    
    function hideOverflow(text) {
      hiddenDiv.textContent = text;
      let textWidth = hiddenDiv.clientWidth
    
      hiddenDiv.textContent = appendix.textContent
      let appendixWidth = hiddenDiv.clientWidth
      let maxWidth = mailAddress.clientWidth
    
      // hide Overflow
      if (textWidth + appendixWidth >= (maxWidth - 8)) {
        let calcWidth = maxWidth - textWidth
        if (calcWidth < 20) {
          appendix.style.width = '0'
        } else {
          calcWidth = calcWidth - 24
          appendix.style.width = calcWidth + 'px'
        }
      } else {
        appendix.style.width = 'fit-content'
      }
    }
    
    function focusMailAddress() {
      mailAddress.focus()
    }
    #mailAddress {
      z-index: 1;
    }
    
    #appendix {
      position: absolute;
      margin-top: 14px;
      top: 0;
      left: 159px;
      z-index: 9;
      height: fit-content;
      width: fit-content;
      color: grey;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: fade;
      display: none;
    }
    
    #hiddenDiv {
      width: auto;
      display: inline-block;
      visibility: hidden;
      position: fixed;
      overflow: auto;
    }
    
    #parent {
        position: relative;
        width: 100%;
    }
    
    body {
      margin: 0;
      font-family: OpenSansRegular, Arial, Helvetica, sans-serif;
      font-size: 1rem;
      font-weight: 400;
      line-height: 1.5;
      color: #58595b;
      text-align: left;
      background-color: #fff;
    }
    
    .form-control {
      display: block;
      width: 100%;
      height: calc(1.5em + 0.75rem + 2px);
      padding: 0.375rem 0.75rem;
      font-size: 1rem;
      font-weight: 400;
      line-height: 1.5;
      color: #495057;
      background-color: #fff;
      background-clip: padding-box;
      border: 1px solid #ced4da;
      border-radius: 0.25rem;
      transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
    }
    <div id="parent">
      <input type="text" class="form-control" id="mailAddress"
                           placeholder="Max.Mustermann@mail.de" maxlength="64">
    
      <div id='appendix' unselectable="on" contenteditable="false" onclick="focusMailAddress()"> @mail.de </div>
    
      <div id="hiddenDiv"></div>
    </div>