Select all DIV text with single mouse click - javascript

How to highlight/select the contents of a DIV tag when the user clicks on the DIV...the idea is that all of the text is highlighted/selected so the user doesn't need to manually highlight the text with the mouse and potentially miss a bit of the text?
For example, say we've got a DIV as below:
<div id="selectable">http://example.com/page.htm</div>
...and when the user clicks on any of that URL the whole URL text is highlighted so they can easily drag the selected text around in the browser, or copy the complete URL with a right click.
Thanks!

function selectText(containerid) {
if (document.selection) { // IE
var range = document.body.createTextRange();
range.moveToElementText(document.getElementById(containerid));
range.select();
} else if (window.getSelection) {
var range = document.createRange();
range.selectNode(document.getElementById(containerid));
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
}
}
<div id="selectable" onclick="selectText('selectable')">http://example.com/page.htm</div>
Now you have to pass the ID as an argument, which in this case is "selectable", but it's more global, allowing you to use it anywhere multiple times without using, as chiborg mentioned, jQuery.

UPDATE 2017:
To select the node's contents call:
window.getSelection().selectAllChildren(
document.getElementById(id)
);
This works on all modern browsers including IE9+ (in standards mode).
Runnable Example:
function select(id) {
window.getSelection()
.selectAllChildren(
document.getElementById("target-div")
);
}
#outer-div { padding: 1rem; background-color: #fff0f0; }
#target-div { padding: 1rem; background-color: #f0fff0; }
button { margin: 1rem; }
<div id="outer-div">
<div id="target-div">
Some content for the
<br>Target DIV
</div>
</div>
<button onclick="select(id);">Click to SELECT Contents of #target-div</button>
The original answer below is obsolete since window.getSelection().addRange(range); has been deprecated
Original Answer:
All of the examples above use:
var range = document.createRange();
range.selectNode( ... );
but the problem with that is that it selects the Node itself including the DIV tag etc.
To select the Node's text as per the OP question you need to call instead:
range.selectNodeContents( ... )
So the full snippet would be:
function selectText( containerid ) {
var node = document.getElementById( containerid );
if ( document.selection ) {
var range = document.body.createTextRange();
range.moveToElementText( node );
range.select();
} else if ( window.getSelection ) {
var range = document.createRange();
range.selectNodeContents( node );
window.getSelection().removeAllRanges();
window.getSelection().addRange( range );
}
}

There is pure CSS4 solution:
.selectable{
-webkit-touch-callout: all; /* iOS Safari */
-webkit-user-select: all; /* Safari */
-khtml-user-select: all; /* Konqueror HTML */
-moz-user-select: all; /* Firefox */
-ms-user-select: all; /* Internet Explorer/Edge */
user-select: all; /* Chrome and Opera */
}
user-select is a CSS Module Level 4 specification, that is currently a draft and non-standard CSS property, but browsers support it well — see #search=user-select.
.selectable{
-webkit-touch-callout: all; /* iOS Safari */
-webkit-user-select: all; /* Safari */
-khtml-user-select: all; /* Konqueror HTML */
-moz-user-select: all; /* Firefox */
-ms-user-select: all; /* Internet Explorer/Edge */
user-select: all; /* Chrome and Opera */
}
<div class="selectable">
click and all this will be selected
</div>
Read more on user-select here on MDN and play with it here in w3scools

The answer of Neuroxik was really helpful. I had only a trouble with Chrome, because when I clicked on an external div, It did not work. I could solve it removing the old ranges before add the new range:
function selectText(containerid) {
if (document.selection) {
var range = document.body.createTextRange();
range.moveToElementText(document.getElementById(containerid));
range.select();
} else if (window.getSelection()) {
var range = document.createRange();
range.selectNode(document.getElementById(containerid));
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
}
}
<div id="selectable" onclick="selectText('selectable')">http://example.com/page.htm</div>

For content editable stuff (not regular inputs, you need to use selectNodeContents (rather than just selectNode).
NOTE: All the references to "document.selection" and "createTextRange()" are for IE 8 and lower... You'll not likely need to support that monster if you're attempting to do tricky stuff like this.
function selectElemText(elem) {
//Create a range (a range is a like the selection but invisible)
var range = document.createRange();
// Select the entire contents of the element
range.selectNodeContents(elem);
// Don't select, just positioning caret:
// In front
// range.collapse();
// Behind:
// range.collapse(false);
// Get the selection object
var selection = window.getSelection();
// Remove any current selections
selection.removeAllRanges();
// Make the range you have just created the visible selection
selection.addRange(range);
}

Using a text area field, you could use this: (Via Google)
<form name="select_all">
<textarea name="text_area" rows="10" cols="80"
onClick="javascript:this.form.text_area.focus();this.form.text_area.select();">
Text Goes Here
</textarea>
</form>
This is how I see most websites do it. They just style it with CSS so it doesn't look like a textarea.

This snippet provides the functionality you require. What you need to do is add an event to that div that which activates fnSelect in it. A quick hack that you totally shouldn't do and possibly might not work, would look like this:
document.getElementById("selectable").onclick(function(){
fnSelect("selectable");
});
Obviously assuming that the linked to snippet had been included.

I found it useful to wrap this function as a jQuery plugin:
$.fn.selectText = function () {
return $(this).each(function (index, el) {
if (document.selection) {
var range = document.body.createTextRange();
range.moveToElementText(el);
range.select();
} else if (window.getSelection) {
var range = document.createRange();
range.selectNode(el);
window.getSelection().addRange(range);
}
});
}
So, it becomes a reusable solution. Then you can do this:
<div onclick="$(this).selectText()">http://example.com/page.htm</div>
And it will selected test in the div.

How about this simple solution? :)
<input style="background-color:white; border:1px white solid;" onclick="this.select();" id="selectable" value="http://example.com/page.htm">
Sure it is not div-construction, like you mentioned, but still it is worked for me.

Niko Lay:
How about this simple solution? :)
`<input style="background-color:white; border:1px white solid;" onclick="this.select();" id="selectable" value="http://example.com/page.htm">`
.....
Code before:
<textarea rows="20" class="codearea" style="padding:5px;" readonly="readonly">
Code after:
<textarea rows="20" class="codearea" style="padding:5px;" readonly="readonly" onclick="this.select();" id="selectable">
Just this part onclick="this.select();" id="selectable" in my code worked fine.
Selects all in my code box with one mouse click.
Thanks for help Niko Lay!

Easily achieved with the css property user-select set to all. Like this:
div.anyClass {
user-select: all;
}

try this, altn adding to oncontextmenu will gice quick access to extentions allowing input this way.
<div onclick='window.getSelection().selectAllChildren(this)' id="selectable">http://example.com/page.htm</div>

A solution, working also in case your element is part of an iframe
element.ownerDocument?.getSelection()?.selectAllChildren(element);

$.fn.selectText = function () {
return $(this).each(function (index, el) {
if (document.selection) {
var range = document.body.createTextRange();
range.moveToElementText(el);
range.select();
} else if (window.getSelection) {
var range = document.createRange();
range.selectNode(el);
window.getSelection().addRange(range);
}
});
}
Above answer is not working in Chrome because addRange remove previous added range. I didnt find any solution for this beside fake selection with css.

export const selectText = (containerId) => {
let selection = window.getSelection()
let myElement = document.getElementById(containerId)
if (selection.rangeCount > 0) {
selection.removeAllRanges()
}
let range = document.createRange()
range.selectNode(myElement)
selection.addRange(range)
}**The most simplest answer works in all browsers**

Related

How do i copy text to clipboard - cross browser

Ok so feels like i have gone down a rabbit hole of how to copy text to clipboard on here and tried a lot of suggestions
seems easy to do it for chrome but that option doesn't work in other browsers
I have a few requirements
I would like to copy text to clipboard
to be able to copy a section of html with multiple elements
To work in safari and chrome at least
Vanilla Javascript
I have found this solution and it works except that it copies the html tags as well?
i tried changing the .innerHTML to .value on the button, but that comes back undefined
<div id="something">
<div>first name: <span class="name">name</span></div>
<div>Job title: <span class="job">job</span></div>
<div>Phone number: 0123456789</div>
<img class="companylogo" src="./img/example.jpg">
</div>
<button onclick="copyToClipboard(document.getElementById('something').innerHTML)">
Copy the stuff
</button>
<script>
/* copy function */
function copyToClipboard(textToCopy) {
var textArea;
function isOS() {
//can use a better detection logic here
return navigator.userAgent.match(/ipad|iphone/i);
}
function createTextArea(text) {
textArea = document.createElement('textArea');
textArea.readOnly = true;
textArea.contentEditable = true;
textArea.value = text;
document.body.appendChild(textArea);
}
function selectText() {
var range, selection;
if (isOS()) {
range = document.createRange();
range.selectNodeContents(textArea);
selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
textArea.setSelectionRange(0, 999999);
} else {
textArea.select();
}
}
function copyTo() {
document.execCommand('copy');
document.body.removeChild(textArea);
}
createTextArea(textToCopy);
selectText();
copyTo();
}
</script>
document.getElementById('something').innerHTML
sends html code inside #something to copyToClipboard and this behavior you mentiond is expected . what do you want to be copied to clipboard if not html ?
thanks Hosseind600 for trying to help
but i managed to find some code that does exactly what i would like it to do. whilst ticking off the requirements i had set
its currently the second answer but highest votes
How to copy text from a div to clipboard
<html>
<body>
<div id="a" onclick="copyDivToClipboard()"> Click to copy </div>
<script>
function copyDivToClipboard() {
var range = document.createRange();
range.selectNode(document.getElementById("a"));
window.getSelection().removeAllRanges(); // clear current selection
window.getSelection().addRange(range); // to select text
document.execCommand("copy");
window.getSelection().removeAllRanges();// to deselect
}
</script>
</body>
</html>

How to disable selecting some of the text in div?

Let's say I have a text with mark, such as:
And I would like to disable selecting such that start or end of selection is not in the mark. Therefore, all of these selections should be possible:
On the other hand, these selections should be disabled:
So far, I tried only using some simple css,
mark {
-khtml-user-select: all;
-webkit-user-select: all;
-o-user-select: all;
user-select: all;
}
This is <mark>marked text</mark>. I want to disable selecting only part of <mark>marked text</mark>.
jsfiddle link
Is there any way to do this? Any answer would be appreciated!
I achieved what I wanted to do, so I share it here.
function checkSelection () {
var sel = window.getSelection();
var marks = document.getElementsByTagName('mark');
for(var i = 1; i < sel.rangeCount; i++) {
sel.removeRange(sel.getRangeAt(i));
}
var range = sel.getRangeAt(0);
var startnode = range.startContainer;
var endnode = range.endContainer;
for (var i = 0; i < marks.length; i++) {
if (marks[i].contains(startnode)) {
range.setStartBefore(startnode);
}
if (marks[i].contains(endnode)) {
range.setEndAfter(endnode);
}
}
}
document.addEventListener('mouseup', checkSelection)
document.addEventListener('touchend', checkSelection)
This is <mark>marked text</mark>. I want to disable selecting only part of <mark>marked text</mark>.
jsfiddle link
I don't think it would be possible to do with CSS alone, you're most likely going to need JavaScript to detect which elements the user began selecting on.
What I did was I used the containsNode method from the Selection API (a Working Draft, may not work with all browsers yet, and may be deprecated) to detect whether the selected range either contains the whole marked element or not at all. If the selection only contains part of a marked element, it would clear the selection using the removeAllRanges method.
function checkSelection () {
var sel = window.getSelection()
var marks = document.getElementsByTagName('mark')
for (var i = 0; i < marks.length; i++) {
if (sel.containsNode(marks[i], false) !== sel.containsNode(marks[i], true))
sel.removeAllRanges() // clear selection
}
}
document.addEventListener('mouseup', checkSelection)
document.addEventListener('touchend', checkSelection)
mark {
background: yellow;
}
This is <mark>marked text</mark>. I want to disable selecting only part of <mark>marked text</mark>.
Note that you would need to capture all methods of selection that you want, whether by mouse events and touch events (all that I included), keyboard caret selection, or programmatic selection.
This can be extended and tuned to your liking, but this is my best shot at replicating the behavior you want from your specs. The above snippet doesn't work correctly all the time if one of the selection's bounds begins or ends at the edge of a marked region, but the snippet should demonstrate the basic concept behind what should work, just with a little fine-tuning for edge cases.
user-select CSS property is not yet official and it is only supported by the latest browsers.
Anyways you can use user-select: none; and enclose all text that can not be selected inside a span tag
https://developer.mozilla.org/en-US/docs/Web/CSS/user-select
But you can try something like this
.unselectable {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
<p>This text can be selected, But <span class="unselectable">this</span> can only be <span class="unselectable">partially se</span>lected</p>
https://developer.mozilla.org/en-US/docs/Web/CSS/user-select

detect underline and strike through

Is there any way to detect with JavaScript/jQuery if an element is strike-through and underlined.
So let's say we have:
<u><s>text here.</s>other text here.</u>
Is it possible to detect if the text within <s> is also underlined?
*Ideally, it would not be allowed to look if <u> has any <s> children.
I've been toying around with it and it seems peculiar that both styles use the same CSS property, which in turn makes me wonder how it even works in the first place.
To make my problem clearer:
I'm playing around with a self-made wysiwyg editor, for usability reasons I'm trying to implement a listener on the text which alters (lights up) editing buttons. e.g. when a part of text is bold the "B" button changes to an active state. I'm currently handling this by getting the element at the cursor and checking if the element is bold or inherits it.
The problem with underline and striketrough is that they are neither overwriting the text-decoration attribute of each other, and are not visible in css
when I put the cursor on a underlined text-fragment, the text-decoration property only shows as underline, while the text is both underline and line-through. In such situations I cannot know what the exact relation is between the <u> element and the <s> element; the <s> element could be 100 parents back as far as I could know.
A lot of text, but I hope it kinda clears up my situation.
Here is the robust way of doing it. #Cheery answer works well but it fails if italic or underline or any other font-style provided through CSS. Credit is given to Tim Down for his numerous answers for these kind of questions.
function checkState(element, check) {
var doc = document;
var text = doc.getElementById(element);
if (doc.body.createTextRange) { // ms
var range = doc.body.createTextRange();
range.moveToElementText(text);
range.select();
} else if (window.getSelection) { // moz, opera, webkit
var selection = window.getSelection();
var range = doc.createRange();
range.selectNodeContents(text);
selection.removeAllRanges();
selection.addRange(range);
}
var range, checked = false;
if (window.getSelection) {
var sel = window.getSelection();
if (sel && sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
document.designMode = "on";
sel.removeAllRanges();
sel.addRange(range);
}
}
if (document.queryCommandState) {
checked = document.queryCommandState(check);
}
if (document.designMode == "on") {
document.designMode = "off";
}
if (window.getSelection) {
if (window.getSelection().empty) { // Chrome
window.getSelection().empty();
} else if (window.getSelection().removeAllRanges) { // Firefox
window.getSelection().removeAllRanges();
}
} else if (document.selection) { // IE?
document.selection.empty();
}
return checked;
}
alert(checkState('c', 'underline')); // italic, bold etc..
var str = '<u><s>text here.</s>other text here.</u>';
var el = $('<div>').html(str);
alert($('u s', el).length);
what if the combination is or even something like
so what, check inverse too..
var str = '<s><div><u></u></div><p><u></u></p></s>';
var el = $('<div>').html(str);
alert($('u s', el).length || $('s u', el).length);
if the initial string is not a valid html then you do not know how some browsers will behave at its output.
ps: made some simple example, by click..
$(function(){
$('.wrapper').on('click', '*', function() {
var styles = ['line-through', 'underline'], counter = [0, 0], tags = ['S', 'U'];
$(this).parentsUntil('.wrapper').andSelf().each(function() {
var current = $(this).css('text-decoration'), $tag = $(this)[0];
$.each(styles, function(index, style) {
if (current.indexOf(style) > -1 || $tag.tagName == tags[index]) counter[index] += 1;
});
});
var results = [];
if (counter[0] > 0) results.push('striked');
if (counter[1] > 0) results.push('underlined');
alert(results.join(' and '));
return false;
});
});
.strike {
text-decoration: line-through;
}
.underline {
text-decoration: underline;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<div class='wrapper'>
<div class='strike'>
striked <u>underlined</u>
</div>
<div class='underline'>
underlined <s>striked</s>
</div>
</div>
A somewhat horrible approach, but the only way I could see that doesn't involve explicitly checking nesting, or relying on default CSS surviving any theming (<s> won't always, necessarily, have text-decoration: line-through;, similarly <u> won't always, necessarily, have text-decoration: underline;):
// the text-decoration styles you want to find:
var styles = ['line-through', 'underline'];
// finding all elements within the <body>, and
// filtering them:
var underlinedAndLineThrough = $('body *').filter(function() {
// caching because of re-use:
var self = $(this),
decor = self.css('text-decoration');
// if the 'text-decoration' style is found in the array of styles we're
// looking for:
if (styles.indexOf(decor) > -1) {
// we add that style as a class-name to the current element, and all
// descendants:
self.find('*').add(self).addClass(decor);
// we return the current element (to keep it in the collection):
return self;
}
// filtering again:
}).filter(function(){
// we keep the current element of the collection if it has all the css styles
// we're looking for:
return $(this).is('.' + styles.join('.'));
});
console.log(underlinedAndLineThrough);
JS Fiddle demo.
References:
JavaScript:
Array.prototype.indexOf().
Array.prototype.join().
jQuery:
add().
addClass().
filter().
find().
is().

Working with contenteditable

Okay, so I am able to edit the content of my DIV just fine. However, there are a couple things I'm trying to work around.
In my editable div, I have a div containing a site address (such as twitter.com), I don't want that to be deletable from the editable DIV; the reason it's in there is so a user could copy all of the text including the domain.
If you remove all of the text except the domain, then the caret doesn't show up - or it does for a second all the way at the right side of the DIV.
And if a user deletes the domain name, it reappears but the caret is now to the left of it which I also don't want.
Any solutions on making the caret always visible and keeping it to the right of the domain?
Oh, and for some reason I can't use onKeyDown on the editable DIV to activate the anylyeText function (at least not in JSfiddle) so I had to do a setTimeout loop.
Here is my fiddle: jsfiddle
CSS:
.url {
display: block;
border: 1px solid rgb(140,140,140);
padding: 5px;
box-sizing: border-box;
color: rgb(35,155,215);
cursor: text;
outline: none;
}
.url:focus {
border-color: rgb(35,155,215);
}
.noedit {
display: inline;
color: rgb(140,140,140);
}
HTML:
<div class="url" contenteditable="true" id="textbox"><div class="noedit" contenteditable="false">http://twitter.com/</div>username</div>
JS:
var analyzeText = function() {
var textbox = document.getElementById('textbox');
var textVal = textbox.innerHTML;
var urlString = '<div class="noedit" contenteditable="false">http://twitter.com/</div>';
if (textVal.length < urlString.length) {
textbox.innerHTML = urlString;
textbox.focus();
}
setTimeout(function() { analyzeText(); }, 100);
};
(function(){analyzeText();})();
Here's a method using Selection and Range.
MDN Selection
MDN Range
(If you check out MDN, you'll notice that IE9 support isn't there - prior to IE9, you had to use proprietary IE script. Google can help you out there!)
First thing I do is add these two variables:
var range = document.createRange();
var sel = window.getSelection();
Then, I modified your script like so:
if (textVal.length < urlString.length) {
textbox.innerHTML = urlString;
// We create a text node and append to the textbox to select it
textbox.appendChild(document.createTextNode(''));
// Text node is selected
range.setStart(textbox.childNodes[1], 0);
// Collapse our range to single point (we're not selecting a word, after all!)
range.collapse(true);
// Let's clear any selections
sel.removeAllRanges();
// Alright, time to position our cursor caret!
sel.addRange(range);
textbox.focus();
}
And that's it! Here's an updated Fiddle to demonstrate:
Edit - Updated to include logic to prevent user from inserting text to the left of the domain
http://jsfiddle.net/Qycw2/6/

Change CSS of selected text using Javascript

I'm trying to make a JavaScript bookmarklet that will act as a highlighter, changing the background of selected text on a webpage to yellow when the bookmarklet is pressed.
I'm using the following code to get the selected text, and it works fine, returning the correct string
function getSelText() {
var SelText = '';
if (window.getSelection) {
SelText = window.getSelection();
} else if (document.getSelection) {
SelText = document.getSelection();
} else if (document.selection) {
SelText = document.selection.createRange().text;
}
return SelText;
}
However, when I created a similar function to change the CSS of the selected text using jQuery, it isn't working:
function highlightSelText() {
var SelText;
if (window.getSelection) {
SelText = window.getSelection();
} else if (document.getSelection) {
SelText = document.getSelection();
} else if (document.selection) {
SelText = document.selection.createRange().text;
}
$(SelText).css({'background-color' : 'yellow', 'font-weight' : 'bolder'});
}
Any ideas?
The easiest way to do this is to use execCommand(), which has a command to change the background colour in all modern browsers.
The following should do what you want on any selection, including ones spanning multiple elements. In non-IE browsers it turns on designMode, applies a background colour and then switches designMode off again.
UPDATE
Fixed in IE 9.
function makeEditableAndHighlight(colour) {
var range, sel = window.getSelection();
if (sel.rangeCount && sel.getRangeAt) {
range = sel.getRangeAt(0);
}
document.designMode = "on";
if (range) {
sel.removeAllRanges();
sel.addRange(range);
}
// Use HiliteColor since some browsers apply BackColor to the whole block
if (!document.execCommand("HiliteColor", false, colour)) {
document.execCommand("BackColor", false, colour);
}
document.designMode = "off";
}
function highlight(colour) {
var range, sel;
if (window.getSelection) {
// IE9 and non-IE
try {
if (!document.execCommand("BackColor", false, colour)) {
makeEditableAndHighlight(colour);
}
} catch (ex) {
makeEditableAndHighlight(colour)
}
} else if (document.selection && document.selection.createRange) {
// IE <= 8 case
range = document.selection.createRange();
range.execCommand("BackColor", false, colour);
}
}
Here is a crude example of how it could work. As Zack points out you'll need to be aware of cases where the selection spans multiple elements. This isn't intended to be used as-is, just something to help get ideas flowing. Tested in Chrome.
var selection = window.getSelection();
var text = selection.toString();
var parent = $(selection.focusNode.parentElement);
var oldHtml = parent.html();
var newHtml = oldHtml.replace(text, "<span class='highlight'>"+text+"</span>");
parent.html( newHtml );
To make the highlight stick permanently, I believe you are going to have to wrap the selection in a new DOM element (span should do), to which you can then attach style properties. I don't know if jQuery can do that for you. Keep in mind that selections can span element boundaries, so in the general case you're going to have to inject a whole bunch of new elements
Have a look at a little example i made at http://www.jsfiddle.net/hbwEE/3/
It does not take into account selections that span multiple elements..
(IE will do but will mess the html a bit ..)
In Firefox, you can use the ::-moz-selection psuedo-class.
In Webkit, you can use the ::selection pseudo-class.
I like Tim's answer, it's clean and fast. But it also shuts down the doors to doing any interactions with the highlights.
Inserting inline elements directly around the texts is a bad choice, as they broke the text flow and mess things up in complex situations,
So I suggest a dirty hack that
calculates the absolute layout of each line of selected text (no matter where they are),
then insert colored, semi-transparent inline-block elements in the end of the document body.
This chrome extension is an example of how this can be done.
It uses API from this library to get the absolute layout of each selected line.

Categories