7

I would like to select all text on a page programmatically with the exact same result as if I would press key combo Ctrl+A.

The problem with using document.getSelection().selectAllChildren(body) is that the selection will also include text nodes that are not selectable by the user, i.e. <script> </script> or nodes for which there is user-select:none defined in CSS:

<div style="-moz-user-select:none">Will be selected</div>

There is the method modify on selection objects that could be used like this: selection.modify("extend", "forward", "documentboundary"); to extend a selection from the beginning of a document to its end which will ignore any script or style element contents and elements with -moz-user-select:none - unfortunately Firefox does not allow documentboundary as 3. argument and word does not help much.

Is there a fast way to accomplish this? Only needs to work in Firefox.

EDIT (not-so-good-solution): Select first text node, then use selection.modify('extend', 'forward', 'line') repeatedly while selection.focusNode is not equal to the last text node - but depending on the length of the document this takes up to several seconds!

EDIT: selection.selectAllChildren will work as intended in Chrome where text elements with user-select:none won't be selected - unfortunately there is a different behavior in FF.

EDIT: This is not a duplicate of this post since I'm neither addressing contenteditable elements nor am I concerned about them;)

Community
  • 1
  • 1
user1521685
  • 210
  • 2
  • 13
  • Possible duplicate of [Programmatically select text in a contenteditable HTML element?](http://stackoverflow.com/questions/6139107/programmatically-select-text-in-a-contenteditable-html-element) – juvian May 17 '17 at 15:20
  • i dont have ff to test, thats why this is just a comment, not an answer: basically try this: hide-select-show. in detail: 1. use a class for unselectable elements 2. $('unselectable').hide() 3. document.getSelection().selectAllChildren($('body').get(0) 4. $('unselectable').show() – ZPiDER May 24 '17 at 12:15
  • @ZPiDER Thank's, but I'm not the author of the HTML+CSS code (it's for an FF extension). – user1521685 May 24 '17 at 14:08
  • 1
    if you dont have the option to use classes, use filtering: $('*').filter(function() { return $(this).css('user-select') == 'none' }).hide() - of course you will need to store which of those elements were hidden before, since you will not want to show them. – ZPiDER May 24 '17 at 14:34
  • 1
    good idea, but I work without jQuery and to check each text node if one of its parents has `-moz-user-select:none` using `window.getComputedStyle(el).getPropertyValue('-moz-user-select')` (see also my comment under Jake's answer) would probably take more time than selecting all user-selectable text manually with `selection.modify('extend', 'forward', 'line')` in a loop from first to last text node – user1521685 May 24 '17 at 15:35
  • working without jQuery is just self-torture. but as you are doing this for an extension, i can see why it might be beneficial in some aspects. however you can do anything that jQ does without it and also at least as speedily. if selection.modify works well enough for you, by all means, use it! – ZPiDER May 29 '17 at 06:37

3 Answers3

1

It seems to me the most efficient way is to move what you want selected into its own selectable div, and selectAllChildren of that. I tried this on a google search, several stack overflow questions, and a few random sites. In every case the results were instantaneous and exactly the same has a ctrl+A.

function selectAll() {
  var sel = window.getSelection();
  var body = document.querySelector("body");
  // Place the children in an array so that we can use the filter method
  var children = Array.prototype.slice.call(body.children);

  // Create the selectable div
  var selectable = document.createElement("div");

  // Style the selectable div so that it doesn't break the flow of a website.

  selectable.style.width = '100%';
  selectable.style.height = '100%';
  selectable.margin = 0;
  selectable.padding = 0;
  selectable.position = 'absolute';

  // Add the selectable element to the body
  body.appendChild(selectable);

  // Filter the children so that we only move what we want to select.
  children = children.filter(function(e) {
    var s = getComputedStyle(e);
    return s.getPropertyValue('user-select') != 'none' && e.tagName != 'SCRIPT'
  });
  // Add each child to the selectable div
  for (var i = 0; i < children.length; i++) {
        selectable.appendChild(children[i]);
  }

  // Select the children of the selectable div
  sel.selectAllChildren(selectable);

}

selectAll();
richbai90
  • 4,994
  • 4
  • 50
  • 85
  • I'm not the author of the HTML+CSS code (it's for an FF extension). – user1521685 May 26 '17 at 11:26
  • I've updated the answer and it seems effective to me – richbai90 May 26 '17 at 18:56
  • In my case this doesn't help much, because I think my "not-so-good-solution" in my post will be much faster than traversing over each element, then put the user-selectable ones into a div and so on. But I will choose your answer as the winner, because it answers the question adequately. Thank you – user1521685 May 26 '17 at 23:48
0

Just to note I used jquery here although it was asked with Javascript - I'm not sure if this is exactly what you were looking for, but maybe it can point you in the right direction? Let me know if you need an explanation.

function slct(el) {
      var d = document;
      var t = d.getElementById(el);
        var selection;
      var range;
    
      if(d.body.createTextRange) {
          range = d.body.createTextRange();
          range.moveToElementText(t);
          range.select();
      } else if(window.getSelection) {
          selection = window.getSelection();        
          range = d.createRange();
          range.selectNodeContents(t);
          selection.removeAllRanges();
          selection.addRange(range);
      }
  }

  $(function() {
      $('#myButton').click(function() {
          slct('content');
      });
  });
<script
  src="https://code.jquery.com/jquery-3.2.1.min.js"
  integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
  crossorigin="anonymous">
 </script>

<div id="content">
  <h4>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quam  necessitatibus assumenda nam obcaecati unde libero aspernatur neque ad vel enim tempora, qui consectetur corporis reiciendis, eum dolorum voluptas soluta voluptatibus!</h1>
  <h5>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eveniet asperiores, iste distinctio sint quidem ea aut voluptatem earum error similique, repudiandae consectetur labore esse. Aut quas repudiandae accusamus non iusto.</h2>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. In rem blanditiis delectus placeat officia, iusto qui! Dicta laboriosam ea exercitationem, molestiae officiis! Asperiores quibusdam laborum in optio eum, similique vitae.</p>
</div>

<button id="myButton">select stuff</button>
Jake Flynn
  • 148
  • 7
  • Thank's for the answer but unfortunately it doesn't help: What you do there cound be accomplished easier with `selection.selectAllChildren(t)` but either way the text content of elements with `user-select:none` (CSS) will be selected too in Firefox (not so in Chrome, but need it to wok in FF though) – user1521685 May 19 '17 at 16:24
  • @user1521685 just spitballing here but what if you gave the elements you wanted to give user-select:none to a class name and then wrapped the if statement inside if(!$(t).hasClass('no-select')){ ... } as a sort of FF fallback? – Jake Flynn May 22 '17 at 08:30
  • I'm not the author of the HTML code (it's for an FF extension). And the argument `t` of your example is the body element in my case and `-moz-user-select:none` is really hard to catch with JS, since with CSS you can reverse it for child elements with a bunch of values like `-moz-text`, `element` etc. and therefore I would not only have to check each element of the page but also use `window.getComputedStyle(el).getPropertyValue(x)` where x can be one of these reversing values – user1521685 May 22 '17 at 09:33
0

I think you can use document.execCommand('selectAll', false, null) for this.

You can find more documentation on this here.

emptyjayy
  • 494
  • 4
  • 10
  • for this to work you have to switch the document's `designMode` first and also it will select all text content - including text not selectable by the user (see: `-moz-user-select:none`). Therefore this approach has no advantage over `selection.selectAllChildren(document.body)` – user1521685 May 25 '17 at 15:32
  • Sorry I was sleepy and haven´t seen your post. I don´t need Point´s..... I am writing here only for fun... – Frank Wisniewski May 26 '17 at 14:00
  • @Frank: hey sorry, did not want to be harsh - all good - have a nice weekend and thank's for trying to help! (was also sleepy while writing that last comment;) – user1521685 May 26 '17 at 15:59