Search code examples
javascriptinputdragundo

Drag text from one INPUT to another INPUT without text being removed from source INPUT


I have two text INPUT elements A and B.

I want the user to be able to select part or all of the text from A and drag to B but for the text not to disappear from A.

Say 'A' contains 'quick brown fox', the user highlights the word 'fox' and drags it to 'B'. By default box 'A' now contains 'quick brown ', but I want it to still say 'quick brown fox'.

I don't want 'A' to be readonly.


Solution

  • function preventDragNDropChangesOn(inputElement) {
      let previousValue = inputElement.value
      let isDragged = false
      let wasDropped = false
    
      /*
      * event order:
      *   dragstart (prepare for possible text cutting)
      *   drop (input text was not cut out yet at this point, save it to revert it later)
      *   change (input text is already cut out at this point - revert it to the previous value)
      *   dragend (set variables to initial state)
      */
    
      inputElement.addEventListener('dragstart', () => isDragged = true)
      document.body.addEventListener('drop', () =>
        (wasDropped = isDragged) && (previousValue = inputElement.value))
      inputElement.addEventListener('change', (e) =>
        isDragged && wasDropped && (e.target.value = previousValue))
      inputElement.addEventListener('dragend', () => isDragged = wasDropped = false)
    }
    

    const inputElement = document.querySelector(".js-controlled-input")
    
    preventDragNDropChangesOn(inputElement)
    
    function preventDragNDropChangesOn(inputElement) {
      let previousValue = inputElement.value
      let isDragged = false
      let wasDropped = false
      let shouldRevertChanges = () => isDragged && wasDropped
      
      document.body.addEventListener('drop', () => wasDropped = isDragged)
      inputElement.addEventListener('dragstart', () => isDragged = true)
      inputElement.addEventListener('dragend', () => (isDragged = false) || (wasDropped = false))
      inputElement.addEventListener('input', (e) =>
        shouldRevertChanges()
        ? (e.target.value = previousValue)
        : (previousValue = e.target.value) 
      )
    }
    <input class="js-controlled-input" value="quick brown fox"/>
    <input class="js-target"/>

    https://jsfiddle.net/91o9mpaz/4/


    IE11 compatible solution:

    function preventDragNDropChangesOn(inputElement) {
      let previousValue = inputElement.value
      inputElement.addEventListener('dragstart', function () { previousValue = inputElement.value })
      inputElement.addEventListener('dragend', function () { setTimeout(function() {inputElement.value = previousValue }, 1) })
    }