Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keep text selected when focus input

This question has already been asked but until now there is no working answer so I am tempting to open it again hopefully we can find a hack to it.

I have a contentEditable paragraph and a text input, when I select some text and click the input, the selection is gone.

So I've tried to save the selection on input mousedown and to restore it back on mouseup and yeah it works ( as expected in firefox) But... in chrome the input lose focus :(

See it in action (use chrome) : https://jsfiddle.net/mody5/noygdhdu/

this is the code I've used :

HTML

<p contenteditable="true">
    Select something up here and click the input below
    <br> on firefox the input get the focus and the text still selected.
    <br> on chrome the text still selected but the input lose focus
</p>

    <input type="text" id="special" style="border: solid blue 1px">

javascript

function saveSelection() {
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
            return sel.getRangeAt(0);
        }
    } else if (document.selection && document.selection.createRange) {
        return document.selection.createRange();
    }
    return null;
}

function restoreSelection(range) {
    if (range) {
        if (window.getSelection) {
            sel = window.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        } else if (document.selection && range.select) {
            range.select();
        }
    }
}


var specialDiv = document.getElementById("special");
var savedSel = null;

specialDiv.onmousedown = function() {

    savedSel = saveSelection(); // save the selection

};

specialDiv.onmouseup = function() {

    restoreSelection(savedSel); // restore the selection

};
like image 257
medBouzid Avatar asked May 05 '16 15:05

medBouzid


People also ask

What does text input focus do?

Input Text focus() Method The focus() method is used to give focus to a text field.

How do you know if input is focused or not?

To detect if the element has the focus in JavaScript, you can use the read-only property activeElement of the document object. const elem = document. activeElement; The activeElement returns the currently focused element in the document.


4 Answers

As I can't comment on maioman (some reputation needed :)), here a little addition to his aswer:

The reason it doesn't work in firefox is that putting the focus on the input field removes the selection.

It all works fine if you put a mouseup event on the p instead of a focus event on the inputfield:


    p.addEventListener('mouseup', () => {
      highlight(select()); // save the selection
    })

like image 102
Erik Avatar answered Sep 27 '22 18:09

Erik


Replacing selection with a <span> is probably the simplest way. You can also use an <iframe>, which is what google uses in Google Docs to maintain selection of text inside document while clicking UI elements.

With <span>, the solution might be like this (this solution build on your original code and the ideas of the other people here, especially @Bekim Bacaj).

!function(doc, win) {
  var input = doc.getElementById('special')
  	, editable = doc.getElementById('editable')
    , button = doc.getElementById('button')
    , fragment = null
    , range = null;

	function saveSelection() {  
    if (win.getSelection) {
      sel = win.getSelection();
      if (sel.getRangeAt && sel.rangeCount) {
        return sel.getRangeAt(0);
      }
    } else if (doc.selection && doc.selection.createRange) {
      return doc.selection.createRange();
    }
    return null;
  }
  
  /* Not needed, unless you want also restore selection
  function restoreSelection() {
    if (range) {
      if (win.getSelection) {
        sel = win.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
      } else if (doc.selection && range.select) {
        range.select();
      }
    }
  }
  */
    
  function saveRangeEvent(event) {
    range = saveSelection();
    if (range && !range.collapsed) {
    	fragment = range.cloneContents();
      toggleButton();
    }
  } 
   
  function toggleButton() {
      button.disabled = !fragment || !input.value.match(/^https?:.*/);
  }
  toggleButton();
  
  editable.addEventListener('mouseup', saveRangeEvent);
  editable.addEventListener('keyup', saveRangeEvent);
  button.addEventListener('click', function(event) {
    // insert link
  	var link = doc.createElement('a');
    link.href = input.value;
    input.value = '';
    range.surroundContents(link);
    toggleButton();
  });
  input.addEventListener('keyup', toggleButton);
  input.addEventListener('change', toggleButton);
  input.addEventListener('mousedown', function(event) {
    // create fake selection
    if (fragment) {
      var span = doc.createElement('span');
      span.className = 'selected';
      range.surroundContents(span);
    }
  });
  input.addEventListener('blur', function(event) {
    // remove fake selection
  	if (fragment) {
      range.deleteContents();
      range.insertNode(fragment);  
      //restoreSelection();
    }
    fragment = null;
  }, true);
  
}(document, window)
    
    
.selected {
  background-color: dodgerblue;
  color: white;
}
<p id="editable" contenteditable="true">
  Select something up here and click the input below
  <br>on firefox the input get the focus and the text still selected.
  <br>on chrome the text still selected but the input lose focus
</p>

<table>
  <tr>
    <td>
      <input type="text" id="special" style="border: solid blue 1px" placeholder="insert valid link incl. http://">
    </td>
    <td>
      <button id="button">Add link</button>
    </td>
  </tr>
</table>

Link to jsFiddle

like image 35
Rudolf Gröhling Avatar answered Sep 27 '22 20:09

Rudolf Gröhling


I worked a little on this one... It was a really fun and instructive exercise.
I mainly started from Maioman's answer.

I made it so that selected text would end up in an anchor with the href provided in the input field... And the selected text remains selected while inputing the link. That is my understanding of your question.

See my working Fiddle : https://jsfiddle.net/Bes7weB/rLmfb043/
Tested to be working on FF 46, Chrome 50, Safari 5.1 and Explorer 11.

Notice that classList is only supported in IE10 and later.
Also, the links are not "clickable" because of the mouseup event.
But you can see the title attribute on mouseover.
I assume you'll save the paragraph's innerHTML to output it somewhere else.
;)


CSS:

a.highlighted {
  background: blue;
  color:white;
}

HTML:

<h1>Select some text below and click GO!</h1>
<br>
<p contenteditable="true" tabindex="0">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris nec risus turpis. Donec nisi urna, semper nec ex ac, mollis egestas risus. Donec congue metus massa, nec lacinia tortor ornare ac. Nulla porttitor feugiat lectus ut iaculis. In sagittis tortor et diam feugiat fermentum. Nunc justo ligula, feugiat dignissim consectetur non, tristique vitae enim. Curabitur et cursus velit. Etiam et aliquam urna. Duis pharetra fermentum lectus et fermentum. Phasellus eget nunc ultricies, ornare libero quis, porta justo. Sed euismod, arcu sed tempor venenatis, urna ipsum lacinia eros, ac iaculis leo risus ac est. In hac habitasse platea dictumst. Sed tincidunt rutrum elit, ornare posuere lorem tempor quis. Proin tincidunt, lorem ac luctus dictum, dui mi molestie neque, a sagittis purus leo a nunc.
</p><br>
<br>
<b>Add a link to selected text:</b> <input type="text" id="hrefInput" style="border: solid blue 1px" value="http://www.test.com"> <input type="button" id="gobutton" value="GO!"><br>
<span id="errorMsg" style="display:none;">No selected text!</span><br>
<input type="button" id="undoButton" value="Undo">

JavaScript:

var p = document.querySelector('p');
var old = p.innerHTML;
var HrefInput = document.getElementById("hrefInput");
var GoButton = document.getElementById("gobutton");
var UndoButton = document.getElementById("undoButton");
var errorMsg = document.getElementById("errorMsg");
var idCounter=0;
var textSelected=false;

UndoButton.addEventListener('focus', function() {
    console.log("Undo button clicked. Default text reloaded.");
    restore();
})

GoButton.addEventListener('click', function() {
    if(!textSelected){
        errorMsg.style.display="inline";
        errorMsg.style.color="rgb(166, 0, 0)";
        errorMsg.style.fontWeight="bold";
        return;
    }
    console.log("GO button clicked: Link id=a-"+idCounter+" created.");
    targetId="a-"+idCounter;
    document.getElementById(targetId).setAttribute("href",HrefInput.value);
    document.getElementById(targetId).classList.add("createdlink");
    document.getElementById(targetId).setAttribute("title",HrefInput.value);
    document.getElementById(targetId).classList.remove("highlighted");
    textSelected=false;
    idCounter++
})

p.addEventListener('focus', function() {
    errorMsg.style.display="none";
});

p.addEventListener('mouseup', function() {
    textSelected=true;
    console.log("Mouseup event in p : Text selected.");
    appendanchor(selectText()); // extract the selection
    HrefInput.focus();  // FireFox 
    HrefInput.blur();   // Needs it. Try without, you'll see.
})

function appendanchor(r) {  // onmouseup
    if (!r) return;
    extracted = r.extractContents();
    el = document.createElement('a');
    el.setAttribute("id", "a-"+idCounter);
    el.setAttribute("class", "highlighted");
    el.appendChild(extracted);
    r.insertNode(el)
}

function selectText() { // onmouseup
    if (window.getSelection) {
        console.log("window.getSelection");
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) { // Chrome, FF
            console.log(sel.getRangeAt(0));
            return sel.getRangeAt(0);
        }
        else{console.log(sel);}
    } else if (document.selection && document.selection.createRange) {
        console.log("elseif");
        return document.selection.createRange();
    }
    return null;
}

function restore() {
    p.innerHTML = old;
    textSelected=false;
}
like image 43
Louys Patrice Bessette Avatar answered Sep 27 '22 19:09

Louys Patrice Bessette


Substituting your selected region with a span element (and coloring that) could be a workaround:

var p = document.querySelector('p');
var old = p.innerHTML;
var input = document.querySelector('input');

p.addEventListener('blur', () => {
   highlight(select()); // save the selection
})
p.addEventListener('focus', () => {
  restore(); // restore the selection
})

function highlight(r) {
  if (!r) return;

  var extracted = r.extractContents();
  el = document.createElement('span');
  el.appendChild(extracted);
  r.insertNode(el)
}

function select() {
  if (window.getSelection) {
    sel = window.getSelection();
    if (sel.getRangeAt && sel.rangeCount) {
      return sel.getRangeAt(0);
    }
  } else if (document.selection && document.selection.createRange) {
    return document.selection.createRange();
  }
  return null;
}

function restore() {
  p.innerHTML = old;
}
span {
  background: tomato;
  color:white;
}
<p contenteditable="true" tabindex="0">
  Select something up here and click the input below
  <br> on firefox the input get the focus and the text still selected.
  <br> on chrome the text still selected but the input lose focus
</p>


<input type="text" id="special" style="border: solid blue 1px">

works on chrome, but not on FF

as suggested by Eric using mouseup event (I actually used blur) on p to call highlight(select()) will fix the issue on FF.

like image 39
maioman Avatar answered Sep 27 '22 20:09

maioman