4

I've used this method to copy rich text from an html element. The problem is that if styles are not inlined in the html, but come from css, this method doesn't work. The existing code breaks the formatting and doesn't take the styles into account. Here's the code.

HTML

<button onclick="copyToClip(document.getElementById('foo').innerHTML)">
  Copy the stuff
  </button>

<div id=foo>
  You can write some JS to generate this data. 
  It can contain rich stuff.
  <b> test </b> me <i> also </i>
  <div class="green">Hello world</div> You can use setData to put TWO COPIES into the same clipboard, one that is plain and one that is rich. That way your users can paste into either a
  <ul>
    <li>plain text editor</li>
    <li>or into a rich text editor</li>
  </ul>
</div>

CSS

.green {
  display: inline;
  color: green;
}

JavaScript

function copyToClip(str) {
  function listener(e) {
    e.clipboardData.setData("text/html", str);
    e.clipboardData.setData("text/plain", str);
    e.preventDefault();
  }
  document.addEventListener("copy", listener);
  document.execCommand("copy");
  document.removeEventListener("copy", listener);
};

The example in Codepen.

Eugene Barsky
  • 5,780
  • 3
  • 17
  • 40

1 Answers1

2

The line break added to copied text happen because the "block level elements" even if you add "display : inline" in css sheet

See W3 HTML Block and Inline Elements

To avoid this we need to change any block level elements that has display inline and restore all style include default style to new tag

Finally the code will be like :

Codepen

Note : explanation in code comment

function CopyToClipboard(element) {
 // array off all block level elements
 var block_level_elements = ['P','H1', 'H2', 'H3', 'H4', 'H5', 'H6','OL', 'UL','DIV','FORM','HR','TABLE']; 

 //create new Element so we can change elments like we need
 var newelment = document.createElement("div");

 //copy target Element to the new Element
 newelment.innerHTML = document.getElementById(element).innerHTML;

 //hide new Element to body
 newelment.style.opacity  = 0;
 // add new Element to body
 document.body.appendChild(newelment);

 //get all element childs
 var descendents = newelment.getElementsByTagName('*');

 //loop in childs
 for (var i = 0; i < descendents.length; ++i) {
  //get defult Style
     var style = window.getComputedStyle(descendents[i]);
     var dis = style.getPropertyValue('display');
     //get defult tag name
     var tagname = descendents[i].tagName;
  
  //---------------------------
  //this part is little tricky
  //---------------------------
  //true : Element is a block level elements and css display is inline
     if(dis.includes("inline") && block_level_elements.includes(tagname)){
      //get all Element style include default style
   var defultcss = document.defaultView.getComputedStyle(descendents[i], "").cssText;
   //chang Element tag from block level elements to inline level elements (span)
   descendents[i].outerHTML = descendents[i].outerHTML.replace(new RegExp(tagname, "ig"),"span");   //todo: need to change RegExp to tag name only not inner text 
   //add all Element style include default style to new tag
   descendents[i].style.cssText = defultcss;
  }
 }
 //-----------------copy new Element--------------
 var doc = document;
 var range, selection;
    
 if (doc.body.createTextRange)
    {
  range = doc.body.createTextRange();
  range.moveToElementText(newelment);
  range.select();
 } 
    
    else if (window.getSelection)
    {
  selection = window.getSelection();        
  range = doc.createRange();
  range.selectNodeContents(newelment);
  selection.removeAllRanges();
  selection.addRange(range);
  }
 document.execCommand('copy');
 window.getSelection().removeAllRanges();
 
 // remove new Element from document
 document.body.removeChild(newelment);  
 
 document.getElementById("copybtn").innerHTML="Copied";
 
}
.green {
  display: inline;
  color: green;
  white-space: nowrap;
}
  <button id='copybtn' onclick="CopyToClipboard('foo')">
  Copy the stuff
  </button>
  
<div id='foo'>
  You can write some JS to generate this data. 
  It can contain rich stuff.
  <b> test </b> me <i> also </i>
  <div class="green" style="color: green;">Hello world</div> , <h3 class="green" style="color: green;">header3</h3> You can use setData to put TWO COPIES into the same clipboard, one that is plain and one that is rich. That way your users can paste into either a
  <ul>
    <li>plain text editor</li>
    <li>or into a rich text editor</li>
  </ul>
</div>
Mohamed Sa'ed
  • 781
  • 4
  • 13