I am working on a bookmark app where i have to store the user's selected keywords or words or content. I am using the createRange() and addRange() javascript methods to create the range and then find out the selected elements/contents by the user. The code i written for this is as follow.
<head>
<script type="text/javascript">
var storedSelections = [];
function StoreSelection () {
if (window.getSelection) {
var currSelection = window.getSelection ();
for (var i = 0; i < currSelection.rangeCount; i++) {
storedSelections.push (currSelection.getRangeAt (i));
}
currSelection.removeAllRanges ();
} else {
alert ("Your browser does not support this example!");
}
}
function ClearStoredSelections () {
storedSelections.splice (0, storedSelections.length);
}
function ShowStoredSelections () {
if (window.getSelection) {
var currSelection = window.getSelection ();
currSelection.removeAllRanges ();
for (var i = 0; i < storedSelections.length; i++) {
currSelection.addRange (storedSelections[i]);
}
} else {
alert ("Your browser does not support this example!");
}
}
</script>
</head>
<body>
Select some content on this page and use the buttons below.<br /> <br />
<button onclick="StoreSelection ();">Store the selection</button>
<button onclick="ClearStoredSelections ();">Clear stored selections
</button>
<button onclick="ShowStoredSelections ();">Show stored selections</button>
</body>
This code is working perfectly on Firefox. I am able to select multiple things one by one and able to show the selected content again but on chrome and chromium i am getting Discontiguous selection is not supported. error when i store more than one elements in range array and click on show stored selections button.
Help will be appreciated. And please suggest me if there is some other alternatives do accomplish this bookmarking task.
Write
window.getSelection().removeAllRanges();
immediately before creating range.
https://bugs.chromium.org/p/chromium/issues/detail?id=399791
Here's the only possible way of doing this that I was able to come up with:
Wrap the selection in <span style="background: Highlight;">...</span>.
But note:
Obviously, you have to remove those spans again as soon as anything else is selected, but that shouldn't be too difficult. However, you should use window.onmousedown for that rather than window.onclick, because onclick is fired after any button is pressed, so when pressing your "Show stored selections" button, a new selection will be created, thus destroying the one that was supposed to be captured.
Removing or replacing any elements in which a stored selection starts or ends will invalidate that selection, so when clicking "Show stored selections", nothing will show up.
If the selection spans over multiple elements, it needs to split up into one selection for each element, otherwise inserting the span will either fail or cut other elements (like buttons) in half.
The following code (fiddle) is the best I was able to do:
var storedSelections = [];
var simulatedSelections = [];
window.onmousedown = clearSimulatedSelections;
function storeSelection()
{
if(window.getSelection)
{
var currSelection = window.getSelection();
for(var i = 0; i < currSelection.rangeCount; i++)
{
storeRecursive(currSelection.getRangeAt(i));
}
currSelection.removeAllRanges();
}
else
{
alert("Your browser does not support this example!");
}
}
function storeRecursive(selection, node, started)
{
node = node || document.body;
started = started || false;
var nodes = node.childNodes;
for(var i = 0; i < nodes.length; i++)
{
if(nodes[i].nodeType == 3)
{
var first = nodes[i] == selection.startContainer;
var last = nodes[i] == selection.endContainer;
if(first)
{
started = true;
}
if(started)
{
var sel = selection.cloneRange();
if(!first)
{
sel.setStartBefore(nodes[i]);
}
if(!last)
{
sel.setEndAfter(nodes[i]);
}
storedSelections.push(sel);
if(last)
{
return false;
}
}
}
else
{
started = storeRecursive(selection, nodes[i], started);
}
}
return started;
}
function clearStoredSelections()
{
storedSelections = [];
}
function showStoredSelections()
{
if(window.getSelection)
{
var currSelection = window.getSelection();
currSelection.removeAllRanges();
for(var i = 0; i < storedSelections.length; i++)
{
var node = document.createElement("span");
node.className = "highlight";
storedSelections[i].surroundContents(node);
simulatedSelections.push(node);
}
}
else
{
alert("Your browser does not support this example!");
}
}
function clearSimulatedSelections()
{
for(var i = 0; i < simulatedSelections.length; i++)
{
var sec = simulatedSelections[i];
var pn = sec.parentNode;
while(sec.firstChild)
{
pn.insertBefore(sec.firstChild, sec);
}
pn.removeChild(sec);
}
simulatedSelections = [];
}
.highlight
{
background: Highlight;
}
Select some content on this page and use the buttons below.<br><br>
<button onclick="storeSelection();">Store the selection</button>
<button onclick="clearStoredSelections();">Clear stored selections</button>
<button onclick="showStoredSelections();">Show stored selections</button>
It works in Firefox, Safari and Chrome, but has the following shortcomings:
Selections over multiple lines don't select the blank area between the end of the line and the border of the parent element, like actual selections do.
Sometimes when starting a selection at a point before the start of a stored selection, displaying them will merge the ranges, so the text in between is selected too. Sorting the array of stored selections doesn't seem to help.
In Safari, the tab crashed multiple times with a segmentation fault when selecting multiple lines and ending/starting a selection in the middle of a button's text.
However, I doubt that anything better is possible in browsers other than Firefox, but even Firefox has a ticket to drop discontiguous selections.
FYI I was getting a similar error when rolling my own "copy to clipboard" feature. I'm not going to address OP's provided code, but I'll tell you how I fixed it in my own code.
Reproduce:
Copy some other text on the page to the clipboard, e.g. "foo".
Paste the text somewhere. It outputs "foo".
Click your "copy to clipboard" button, which copies e.g. "bar".
Paste the text somewhere.
Expected:
"bar" is outputted.
Actual:
"Discontiguous selection is not supported"
Fix:
Call window.getSelection().removeAllRanges() at the start of your "copy to clipboard" event handler. "Discontiguous" means "not connected". So my guess is that the browser copies the first range (the node containing "foo"), and then gets angry when you try to select another range that is not next to the first node.
Related
I'm building a chrome extension where selected text can have different highlighting styles applied to it. I used ranges to get this all to work, and I clone the range, put a span around it, and then delete the range and replace it with the cloned one. Everything seems fine except I've somehow managed to disable right clicking by triggering this behavior through the extension. I've narrowed it down the single line of range.surroundContents(span), but here's the full code section:
// Determines the selected text
document.onmouseup = function() {
var selection = document.getSelection();
selection = getSelectedText(color);
};
// Finds the text selected in the page, spans it, and gives it a class
function getSelectedText(inputColor) {
var span = document.createElement('span');
span.setAttribute('class', inputColor);
if(document.getSelection) {
var selection = document.getSelection();
if(selection.rangeCount == true) {
var range = selection.getRangeAt(0).cloneRange();
range.surroundContents(span);
selection.removeAllRanges();
selection.addRange(range);
}
}
}
Is there a way I can counter this? I've already tried using document.oncontextmenu = false directly following the problem line, but that's not bringing back right click. I also tried replacing it with newNode.appendChild(range.extractContents()); range.insertNode(newNode) as recommended by https://developer.mozilla.org/en-US/docs/Web/API/Range/surroundContents but then instead of highlighting text, it seems to just be removing it from the page.
#wOxxOm answered my question in a comment, but a setTimeout() is what worked. So for anyone else who might have a similar issue in the future:
// Finds the text selected in the page, spans it, and gives it a class
function getSelectedText(inputColor) {
var span = document.createElement('span');
span.setAttribute('class', inputColor);
if(document.getSelection) {
var selection = document.getSelection();
if(selection.rangeCount == true) {
var range = selection.getRangeAt(0).cloneRange();
setTimeout(function(){
range.surroundContents(span);
selection.removeAllRanges();
selection.addRange(range);
}, 100)
}
}
}
I have contenteditable=true div, where user enters his feedback and I want to allow him change the text color of selected text written in div when user clicks on colorpalate.
I have full code to do my task of changing the color of selected text with user desired color.
But...
Everthing else on the page can be selected and changed its colour too, which I want to prevent. I want to allow change the colour only for class="comment".
Just can't understand how to do that with below code.
if (window.getSelection) { // all browsers, except IE before version 9
var selectionRange = window.getSelection ();
if (selectionRange.isCollapsed) {
alert ("Please select some content first!");
}
else {
//do the stuff
}
}
Thanks for help
UPDATED : to include highlight functionality
Here's the jsFiddle : DEMO
$("#highlight").click(function(){
var flag = 0;
sel = window.getSelection();
for (var i = 0; i < sel.rangeCount; i++) {
var $sNode = $(sel.getRangeAt(i).startContainer.parentNode);
var $eNode = $(sel.getRangeAt(i).endContainer.parentNode);
if ($sNode.prop("class") == "comment" && $eNode.prop("class") == "comment"){
$sNode.html($sNode.html().replace(sel,"<span class='selectedText'>"+ sel + "</span>"));
}
}
});
Will only highlight if text is selected from comment class div only
I have found a code snippet (can't remember where), and it's working fine - almost :-)
The problem is, that it copies the selection no matter where the selection is made on the entire website, and it must only copy the selection if it is in a specific div - but how is that done?
function getHTMLOfSelection () {
var range;
if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
return range.htmlText;
}
else if (window.getSelection) {
var selection = window.getSelection();
if (selection.rangeCount > 0) {
range = selection.getRangeAt(0);
var clonedSelection = range.cloneContents();
var div = document.createElement('div');
div.appendChild(clonedSelection);
return div.innerHTML;
} else {
return '';
}
} else {
return '';
}
}
$(document).ready(function() {
$("#test").click(function() {
var kopitekst = document.getElementById("replytekst");
var kopitjek=getHTMLOfSelection(kopitekst);
if (kopitjek=='')
{
alert("Please select some content");
}
else
{
alert(kopitjek);
}
});
});
I have made a Jsfiddle
This is my first post here. Hopefully I done it right :-)
That's because it checks the entire document with:
if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
return range.htmlText;
}
Not a specific section. If you want to check specific sections for selected text, you need to identify that you are searching for them in the search selection, something that nails your range down to a particular div:
range = $('#replytekst');
Specify a particular DOM element instead of using document object.
var oDiv = document.getElementById( 'selDiv' );
then use
if ( oDiv.selection && oDiv.selection.createRange ) {
range = oDiv.selection.createRange();
return range.htmlText;
}
You need to check if the section contains the selection. This is separate from getting the selection. There is a method for doing this in this answer: How to know if selected text is inside a specific div
I've updated your fiddle
Basically you need to check the id of the parent/ascendant of the selected text node.
selection.baseNode.parentElement.id or selection.baseNode.parentElement.parentElement.id will give you that.
Edit: I've thought of another, somewhat hack-y, way of doing it.
If
kopitekst.innerHTML.indexOf(kopitjek) !== -1
gives true, you've selected the right text.
DEMO1
DEMO2
(these work in Chrome and Firefox, but you might want to restructure the getHTMLOfSelection function a little)
If it possible for you I recommend to use rangy framework. Then your code might look like this:
// get the selection
var sel = rangy.getSelection();
var ranges = sel.getAllRanges();
if (!sel.toString() || !sel.toString().length)
return;
// create range for element, where selection is allowed
var cutRange = rangy.createRange();
cutRange.selectNode(document.getElementById("replytekst"));
// make an array of intersections of current selection ranges and the cutRange
var goodRanges = [];
$.each(ranges, function(j, tr) {
var rr = cutRange.intersection(tr);
if (rr)
goodRanges.push(rr);
});
sel.setRanges(goodRanges);
// do what you want with corrected selection
alert(sel.toString());
// release
sel.detach();
In this code if text was selected in your specific div then it will be kept, if there was selection where other elements take part too, these selection ranges will be cut off.
My JS code:
function getSelectedText(){
if(window.getSelection){
select = window.getSelection().getRangeAt(0);
var st_span = select.startContainer.parentNode.getAttribute("id").split("_")[1];
var end_span = select.endContainer.parentNode.getAttribute("id").split("_")[1];
console.log(select.endContainer);
var ret_urn=[st_span,end_span];
return ret_urn
}
else if(document.getSelection){
return document.getSelection();
}
}
$(document).ready(function() {
$("div#check_button button").click(function () {
var loc = getSelectedText();
console.log(loc);
});
});
Here is my whole html file: http://pastebin.com/acdiU623
It is hard to explain it, so I prepared short movie: http://www.youtube.com/watch?v=tVk4K70JO80
In a few words: when I press left mouse button and hold it to select text/numbers and start selection from the half of letter/number, although this letter/number is not highlighted, it is added to selection. I have to start selection precisely. It is ok with wide letters, but hard with letters like i,j or l.
This is second example of my movie. I pressed left button on 3/4 of length of number 5, although 5 is not highlighted, it is selected.
Tested on FF and Opera.
Ok just tried this demo. and it works flawlessly. it even works on firefox. Just tested opera and safari and it works on both of them as well. Even if i select half a letter or number, it just returns the highlighted text which is what is expected when you make a selection.
try it out on a fresh webpage though just for testing purposes. then when it works and you are satisfied with the results then start making changes to your existing page.
Its a lot more simpler than your code. This is a cross-browser script to get text selected by the user
<script language=javascript>
function getSelText()
{
var txt = '';
if (window.getSelection)
{
txt = window.getSelection();
}
else if (document.getSelection)
{
txt = document.getSelection();
}
else if (document.selection)
{
txt = document.selection.createRange().text;
}
else return;
document.aform.selectedtext.value = txt;
}
</script>
<input type="button" value="Get selection" onmousedown="getSelText()">
<form name=aform >
<textarea name="selectedtext" rows="5" cols="20"></textarea>
</form>
http://www.codetoad.com/javascript_get_selected_text.asp
Hope this helps.
PK
There are multiple different boundary points for a selection that will look the same to the user. What you're seeing is probably the difference between the following, where | is a selection boundary:
<span>5</span><span>|6</span><span>7|</span><span>8</span>
and
<span>5|</span><span>6</span><span>7</span><span>|8</span>
In both cases, calling toString() on the selection will give you the same result ("67").
Let's say I highlight some text on the page using my mouse. How can I remove all highlighted text using JavaScript?
Thank you.
I've understood the question a bit differently. I believe you want to know how to delete the selected text from the document, in which case you could use:
function deleteSelection() {
if (window.getSelection) {
// Mozilla
var selection = window.getSelection();
if (selection.rangeCount > 0) {
window.getSelection().deleteFromDocument();
window.getSelection().removeAllRanges();
}
} else if (document.selection) {
// Internet Explorer
var ranges = document.selection.createRangeCollection();
for (var i = 0; i < ranges.length; i++) {
ranges[i].text = "";
}
}
}
If you just want to clear the highlight itself, and not remove the text being highlighted, the following should do the trick:
function clearSelection() {
if (window.getSelection) {
window.getSelection().removeAllRanges();
} else if (document.selection) {
document.selection.empty();
}
}
IE 4 and old Netscape used to have a method to do just this... It's not longer proper (nor supported).
Your best guess would be to use Javascript to focus() on an object, and then blur() as well -- effectively like clicking away from the object.
document.getElementById("someObject").focus();
document.getElementById("someObject").blur();