504

I would like to have users click a link, then it selects the HTML text in another element (not an input).

By "select" I mean the same way you would select text by dragging your mouse over it. This has been a bear to research because everyone talks about "select" or "highlight" in other terms.

Is this possible? My code so far:

HTML:

<a href="javascript:" onclick="SelectText('xhtml-code')">Select Code</a> <code id="xhtml-code">Some Code here </code> 

JS:

function SelectText(element) { $("#" + element).select(); } 

Am I missing something blatantly obvious?

1

18 Answers 18

690

Plain Javascript

function selectText(nodeId) { const node = document.getElementById(nodeId); if (document.body.createTextRange) { const range = document.body.createTextRange(); range.moveToElementText(node); range.select(); } else if (window.getSelection) { const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(node); selection.removeAllRanges(); selection.addRange(range); } else { console.warn("Could not select text in node: Unsupported browser."); } } const clickable = document.querySelector('.click-me'); clickable.addEventListener('click', () => selectText('target'));
<div id="target"><p>Some text goes here!</p><p>Moar text!</p></div> <p class="click-me">Click me!</p>

Here is a working demo. For those of you looking for a jQuery plugin, I made one of those too.


jQuery (original answer)

I have found a solution for this in this thread. I was able to modify the info given and mix it with a bit of jQuery to create a totally awesome function to select the text in any element, regardless of browser:

function SelectText(element) { var text = document.getElementById(element); if ($.browser.msie) { var range = document.body.createTextRange(); range.moveToElementText(text); range.select(); } else if ($.browser.mozilla || $.browser.opera) { var selection = window.getSelection(); var range = document.createRange(); range.selectNodeContents(text); selection.removeAllRanges(); selection.addRange(range); } else if ($.browser.safari) { var selection = window.getSelection(); selection.setBaseAndExtent(text, 0, text, 1); } } 
Sign up to request clarification or add additional context in comments.

4 Comments

jQuery solution gives me Uncaught TypeError: Cannot read property 'msie' of undefined
@egmfrs yea since this answer was posted, jquery has removed their browser sniffer.
The jquery solution originated from the answer of VillageIdiot below. The wonderful thread is now a dead link. Bit rot :-(
I'm selecting a table, but when i press CTRL + C to copy the selected area it always copies line breaks on the bigining and at the end of the selected area, any sugestion to don't copy them?
136

Here's a version with no browser sniffing and no reliance on jQuery:

function selectElementText(el, win) { win = win || window; var doc = win.document, sel, range; if (win.getSelection && doc.createRange) { sel = win.getSelection(); range = doc.createRange(); range.selectNodeContents(el); sel.removeAllRanges(); sel.addRange(range); } else if (doc.body.createTextRange) { range = doc.body.createTextRange(); range.moveToElementText(el); range.select(); } } selectElementText(document.getElementById("someElement")); selectElementText(elementInIframe, iframe.contentWindow); 

2 Comments

is there any way to select the text of a div contentEditable='true'?
@PrimitiveNom: This code will work on a contenteditable element but you'll have to ensure it has focus first.
24

This thread contains really wonderful stuff. But I'm not able to do it right on this page using FF 3.5b99 + FireBug due to a "Security Error".

Yipee!! I was able to select the whole right-hand sidebar with this code:

var r = document.createRange(); var w=document.getElementById("sidebar"); r.selectNodeContents(w); var sel=window.getSelection(); sel.removeAllRanges(); sel.addRange(r); 

PS:- I was not able to use objects returned by jquery selectors like

var w=$("div.welovestackoverflow",$("div.sidebar")); //this throws **security exception** r.selectNodeContents(w); 

2 Comments

You need to get the element from jQuery, as you're trying to select a jQuery object: var w=$("div.welovestackoverflow",$("div.sidebar")).get(0);
doesn't work... i get an error "object does not support this method" and it highlights the first line. i did some digging and found that there's a "document.body.createTextRange()" but then "selectNodeContents" doesn't work.... and this is in IE
18

Jason's code can not be used for elements inside an iframe (as the scope differs from window and document). I fixed that problem and I modified it in order to be used as any other jQuery plugin (chainable):

Example 1: Selection of all text inside < code > tags with single click and add class "selected":

$(function() { $("code").click(function() { $(this).selText().addClass("selected"); }); }); 

Example 2: On button click, select an element inside an Iframe:

$(function() { $("button").click(function() { $("iframe").contents().find("#selectme").selText(); }); }); 

Note: remember that the iframe source should reside in the same domain to prevent security errors.

jQuery Plugin:

jQuery.fn.selText = function() { var obj = this[0]; if ($.browser.msie) { var range = obj.offsetParent.createTextRange(); range.moveToElementText(obj); range.select(); } else if ($.browser.mozilla || $.browser.opera) { var selection = obj.ownerDocument.defaultView.getSelection(); var range = obj.ownerDocument.createRange(); range.selectNodeContents(obj); selection.removeAllRanges(); selection.addRange(range); } else if ($.browser.safari) { var selection = obj.ownerDocument.defaultView.getSelection(); selection.setBaseAndExtent(obj, 0, obj, 1); } return this; } 

I tested it in IE8, Firefox, Opera, Safari, Chrome (current versions). I'm not sure if it works in older IE versions (sincerely I don't care).

2 Comments

$.browser is now deprecated / removed - this needs a rewrite
@JamesMcCormack: yes. I'm not sure if rewriting it will be worth it as there are other solutions posted here which does not involve $.browser.
9

You can use the following function to select content of any element:

jQuery.fn.selectText = function(){ this.find('input').each(function() { if($(this).prev().length == 0 || !$(this).prev().hasClass('p_copy')) { $('<p class="p_copy" style="position: absolute; z-index: -1;"></p>').insertBefore($(this)); } $(this).prev().html($(this).val()); }); var doc = document; var element = this[0]; console.log(this, element); if (doc.body.createTextRange) { var range = document.body.createTextRange(); range.moveToElementText(element); range.select(); } else if (window.getSelection) { var selection = window.getSelection(); var range = document.createRange(); range.selectNodeContents(element); selection.removeAllRanges(); selection.addRange(range); } }; 

This function can be called as follows:

$('#selectme').selectText(); 

Comments

5

I liked lepe's answer except for a few things:

  1. Browser-sniffing, jQuery or no isn't optimal
  2. DRY
  3. Doesn't work in IE8 if obj's parent doesn't support createTextRange
  4. Chrome's ability to use setBaseAndExtent should be leveraged (IMO)
  5. Will not select text spanning across multiple DOM elements (elements within the "selected" element). In other words if you call selText on a div containing multiple span elements, it will not select the text of each of those elements. That was a deal-breaker for me, YMMV.

Here's what I came up with, with a nod to lepe's answer for inspiration. I'm sure I'll be ridiculed as this is perhaps a bit heavy-handed (and actually could be moreso but I digress). But it works and avoids browser-sniffing and that's the point.

selectText:function(){ var range, selection, obj = this[0], type = { func:'function', obj:'object' }, // Convenience is = function(type, o){ return typeof o === type; }; if(is(type.obj, obj.ownerDocument) && is(type.obj, obj.ownerDocument.defaultView) && is(type.func, obj.ownerDocument.defaultView.getSelection)){ selection = obj.ownerDocument.defaultView.getSelection(); if(is(type.func, selection.setBaseAndExtent)){ // Chrome, Safari - nice and easy selection.setBaseAndExtent(obj, 0, obj, $(obj).contents().size()); } else if(is(type.func, obj.ownerDocument.createRange)){ range = obj.ownerDocument.createRange(); if(is(type.func, range.selectNodeContents) && is(type.func, selection.removeAllRanges) && is(type.func, selection.addRange)){ // Mozilla range.selectNodeContents(obj); selection.removeAllRanges(); selection.addRange(range); } } } else if(is(type.obj, document.body) && is(type.obj, document.body.createTextRange)) { range = document.body.createTextRange(); if(is(type.obj, range.moveToElementText) && is(type.obj, range.select)){ // IE most likely range.moveToElementText(obj); range.select(); } } // Chainable return this; } 

That's it. Some of what you see is the for readability and/or convenience. Tested on Mac in latest versions of Opera, Safari, Chrome, Firefox and IE. Also tested in IE8. Also I typically only declare variables if/when needed inside code blocks but jslint suggested they all be declared up top. Ok jslint.

Edit I forgot to include how to tie this in to the op's code:

function SelectText(element) { $("#" + element).selectText(); } 

Cheers

3 Comments

Yeah, that looks heavy-handed to me, although it appears correct. My main gripe is that using the non-standard setBaseAndExtent() just because it exists seems pointless to me when you can simply remove that branch and everything works just as well and in a standards-based way. The feature detection is nice but I'd get tired of testing everything that thoroughly pretty quickly.
Well @TimDown the point of leveraging setBaseAndExtent is that's it's significantly more efficient, and even with the added if statemnent is still far more so than if you "remove that branch". I don't really understand the comment of "I'd get tired.."? Write it and forget it, the only thing you have to do is call the function, not write it. :)
@Madbreaks I'd be surprised if setBaseAndExtent was significantly more performant than addRange. Why would it be? My other comment was related to your feature testing ethos extended to all DOM interaction: it's an awful lot of code to test every single DOM method and property before using it. I don't disapprove; I'm just happy to draw the line and make a few more assumptions in my code.
5

An Updated version that works in chrome:

function SelectText(element) { 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) { var selection = window.getSelection(); var range = doc.createRange(); range.selectNodeContents(text); selection.removeAllRanges(); selection.addRange(range); } } $(function() { $('p').click(function() { SelectText("selectme"); }); }); 

http://jsfiddle.net/KcX6A/326/

Comments

5

For any tag one can select all text inside that tag by this short and simple code. It will highlight the entire tag area with yellow colour and select text inside it on single click.

document.onclick = function(event) { var range, selection; event.target.style.backgroundColor = 'yellow'; selection = window.getSelection(); range = document.createRange(); range.selectNodeContents(event.target); selection.removeAllRanges(); selection.addRange(range); }; 

Comments

4

I was searching for the same thing, my solution was this:

$('#el-id').focus().select(); 

3 Comments

you can't use focus() on a non-input, which is what this question is about.
but you can use it on a textarea element - which was the problem I googled to arrive here. My fault for not reading the question all the way through.
@Jason is right, this does not answer OP, but it does solve my problem on an input element. Google sent me to this question, and Google does not adhere to SO rules but it does know what I want. +1
2

lepe - That works great for me thanks! I put your code in a plugin file, then used it in conjunction with an each statement so you can have multiple pre tags and multiple "Select all" links on one page and it picks out the correct pre to highlight:

<script type="text/javascript" src="../js/jquery.selecttext.js"></script> <script type="text/javascript"> $(document).ready(function() { $(".selectText").each(function(indx) { $(this).click(function() { $('pre').eq(indx).selText().addClass("selected"); return false; }); }); }); 

Comments

1

Tim's method works perfectly for my case - selecting the text in a div for both IE and FF after I replaced the following statement:

range.moveToElementText(text); 

with the following:

range.moveToElementText(el); 

The text in the div is selected by clicking it with the following jQuery function:

$(function () { $("#divFoo").click(function () { selectElementText(document.getElementById("divFoo")); }) }); 

Comments

1

here is another simple solution to get the selected the text in the form of string, you can use this string easily to append a div element child into your code:

var text = ''; if (window.getSelection) { text = window.getSelection(); } else if (document.getSelection) { text = document.getSelection(); } else if (document.selection) { text = document.selection.createRange().text; } text = text.toString(); 

Comments

1

My particular use-case was selecting a text range inside an editable span element, which, as far as I could see, is not described in any of the answers here.

The main difference is that you have to pass a node of type Text to the Range object, as described in the documentation of Range.setStart():

If the startNode is a Node of type Text, Comment, or CDATASection, then startOffset is the number of characters from the start of startNode. For other Node types, startOffset is the number of child nodes between the start of the startNode.

The Text node is the first child node of a span element, so to get it, access childNodes[0] of the span element. The rest is the same as in most other answers.

Here a code example:

var startIndex = 1; var endIndex = 5; var element = document.getElementById("spanId"); var textNode = element.childNodes[0]; var range = document.createRange(); range.setStart(textNode, startIndex); range.setEnd(textNode, endIndex); var selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); 

Other relevant documentation:
Range
Selection
Document.createRange()
Window.getSelection()

Comments

0

Have a look at the Selection object (Gecko engine) and the TextRange object (Trident engine.) I don't know about any JavaScript frameworks that have cross-browser support for this implemented, but I've never looked for it either, so it's possible that even jQuery has it.

Comments

0

Added jQuery.browser.webkit to the "else if" for Chrome. Could not get this working in Chrome 23.

Made this script below for selecting the content in a <pre> tag that has the class="code".

jQuery( document ).ready(function() { jQuery('pre.code').attr('title', 'Click to select all'); jQuery( '#divFoo' ).click( function() { var refNode = jQuery( this )[0]; if ( jQuery.browser.msie ) { var range = document.body.createTextRange(); range.moveToElementText( refNode ); range.select(); } else if ( jQuery.browser.mozilla || jQuery.browser.opera || jQuery.browser.webkit ) { var selection = refNode.ownerDocument.defaultView.getSelection(); console.log(selection); var range = refNode.ownerDocument.createRange(); range.selectNodeContents( refNode ); selection.removeAllRanges(); selection.addRange( range ); } else if ( jQuery.browser.safari ) { var selection = refNode.ownerDocument.defaultView.getSelection(); selection.setBaseAndExtent( refNode, 0, refNode, 1 ); } } ); } ); 

Comments

0

Just use selectAllChildren

function select(obj) { window.getSelection().selectAllChildren(obj) }
<button onclick="select(codebox)">Select Code</button> <code id="codebox">Some Code here</code>

(Or window.getSelection().setBaseAndExtent(obj, 0, obj, 1), see setBaseAndExtent)

Comments

0

CSS Custom Highlight API

Using this API allows to highlight text without any DOM mutation.

Below is the official example from MDN which I've slightly modified:

const query = document.getElementById("query"); const article = document.querySelector("article"); const output = document.querySelector("output"); // Find all text nodes in the article. We'll search within // these text nodes. const treeWalker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT); const allTextNodes = []; let currentNode = treeWalker.nextNode(); while (currentNode) { allTextNodes.push(currentNode); currentNode = treeWalker.nextNode(); } // Listen to the input event to run the search. function highlightText(value) { // If the CSS Custom Highlight API is not supported, // display a message and bail-out. if (!CSS.highlights) { article.textContent = "CSS Custom Highlight API not supported."; return; } // Clear the HighlightRegistry to remove the // previous search results. CSS.highlights.clear(); // Clean-up const str = value.trim().toLowerCase(); if (!str) { return; } // Iterate over all text nodes and find matches. const ranges = allTextNodes .map((el) => ({ el, text: el.textContent.toLowerCase() })) .map(({ text, el }) => { const indices = []; let startPos = 0; while (startPos < text.length) { const index = text.indexOf(str, startPos); if (index === -1) break; indices.push(index); startPos = index + str.length; } // Create a range object for each instance of // str we found in the text node. return indices.map((index) => { const range = new Range(); range.setStart(el, index); range.setEnd(el, index + str.length); return range; }); }); // Create a Highlight object for the ranges. const flatRanges = ranges.flat(); const searchResultsHighlight = new Highlight(...flatRanges); // Register the Highlight object in the registry. CSS.highlights.set("search-results", searchResultsHighlight); return flatRanges.length; }; // Usage: function onQueryChange() { const count = highlightText(query.value) output.innerHTML = count + " Matches found"; } query.addEventListener("input", onQueryChange) onQueryChange()
::highlight(search-results) { background: PaleGreen; }
<input id="query" type="text" value="qu" placeholder="Search text" /> <output></output> <article> <p> Maxime debitis hic, delectus perspiciatis laborum molestiae labore, deleniti, quam consequatur iure veniam alias voluptas nisi quo. Dolorem eaque alias, quo vel quas repudiandae architecto deserunt quidem, sapiente laudantium nulla. </p> <p> Maiores odit molestias, necessitatibus doloremque dolor illum reprehenderit provident nostrum laboriosam iste, tempore perferendis! Ab porro neque esse voluptas libero necessitatibus fugiat, ex, minus atque deserunt veniam molestiae tempora? Vitae. </p> <p> Dolorum facilis voluptate eaque eius similique ducimus dignissimos assumenda quos architecto. Doloremque deleniti non exercitationem rerum quam alias harum, nisi obcaecati corporis temporibus vero sapiente voluptatum est quibusdam id ipsa. </p> </article>

Comments

-1

According to the jQuery documentation of select():

Trigger the select event of each matched element. This causes all of the functions that have been bound to that select event to be executed, and calls the browser's default select action on the matching element(s).

There is your explanation why the jQuery select() won't work in this case.

1 Comment

i'm not trying to highlight the text with a css style. i want the text to be selected.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.