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
};
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
})
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;
}
add the focus inside a timeout function, that should fix your issue.
setTimeout(function(){
document.getElementById("textToInsert").focus();
}, 1);
jsfiddle: http://jsfiddle.net/mody5/L5hx9h3k/1/
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.
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
Here is a very old snippet I've posted as an answer on some other list a long time ago. It might help you rethink your current strategy and completely avoid the need for hacking the naturally expected behavior of focus.
function createLink(e){
if(e.target){
var a = window.getSelection().getRangeAt(0);
var b = a.toString();
var z = document.createElement("span");
var l2 = prompt("Enter URL:", "http://");
b = b.link(l2);
z.innerHTML=b;
a.deleteContents();
a.insertNode(z) }
else{
document.execCommand("CreateLink") }
}
<!DOCTYPE html>
<html>
<head>
<title>Text to Hyperlink</title>
</head>
<body>
<h1>Create a link</h1>
Select some text and click the button. On the presented toolbox provide the url and confirm. The selected text will become a hyperlink<br>
My Homepage<br>
My Favorite<br>
My Search Page<br><br>
<button onclick="createLink(event)">Make it a link</button>
<script>
</script>
</body>
</html>
I'll give you a hint and let you figure it out yourself. You will need to detect if chrome is being used. In your jsfiddle, add console.log(sel);
after sel = window.getSelection();
. Notice in the log that the selections are different on the different browsers. To be honest, I'm not sure why, but this may help you find out what the problem is.
Also notice the same issue if you comment out sel.removeAllRanges();
you will get an error telling you that they are different, as above.
I guess it will not answer your question but still, for your purpose I highly recommend you to use Angular.js or React.js.
If you didn't wotk with them yet you'll have a learning curve but in the long run it will be extremely worth it!
(You'll also find it easier to "select" cosmetically)
Hope that helps... :)