2

EDIT: I'm getting this error, any idea?

aura_prod.js:24 Uncaught (in promise) TypeError: Failed to execute 'selectNodeContents' on 'Range': parameter 1 is not of type 'Node'.
    at Proxy.Tt.l (aura_prod.js:24)
    at T (clipboard:1)
    at _ (clipboard:1)
    at Object.eval (clipboard:1)
    at eval (clipboard:1)
    at Object.eval [as next] (clipboard:1)
    at eval (clipboard:1)
    at new Promise (<anonymous>)
    at e (clipboard:1)
    at Object.p [as write] (clipboard:1)

EDIT END

Is there a way to take out html tag, when copying?

I have an output and next to it there is a copy button, when the user clicks on copy I want to preserve the formatting when paste.
How would I do that?
I have looked many different ways but none of them works, here is what I have tried.

Here is how it output on my page:

public class TestAccountTrigger { 
@testSetup
    static void initData(){
        Account a0 = new Account(Name = 'Account Zero');
        insert a0; 
}

Here is how it looks like when I inspect it in google chrome:

<p>public class TestAccountTrigger { </p><p></p>
<p>@testSetup</p><p>static void initData(){</p><p>Account a0 = new
Account(Name = 'Account Zero');</p><p>insert a0; </p><p> }</p>

My JS code:

  var doc = new DOMParser().parseFromString(returnText, 'text/html');
     // Create an hidden input
    var hiddenInput = document.createElement("input");
    // passed text into the input
    hiddenInput.setAttribute("value", doc.body.textContent);
    // Append the hiddenInput input to the body
    document.body.appendChild(hiddenInput);
    // select the content
    hiddenInput.select();
    // Execute the copy command
    document.execCommand("copy");
    // Remove the input from the body after copy text
    document.body.removeChild(hiddenInput);
    // store target button label value
    var orignalLabel = event.getSource().get("v.label");
    // change button icon after copy text
    event.getSource().set("v.iconName" , 'utility:check');
    // change button label with 'copied' after copy text
    event.getSource().set("v.label" , 'Copied'); 

After I copied and paste this is how it looks like:

public class TestAccountTrigger { @testSetup static void initData(){ Account a0 = new Account(Name = 'Account Zero');insert a0;

My question: after I copy, how do I preserve the formatting?

Adriano
  • 3,788
  • 5
  • 32
  • 53
Nick Kahn
  • 19,652
  • 91
  • 275
  • 406
  • You could also look into converting your HTML to RTF. Please see more from this answer https://stackoverflow.com/a/50376106/11854340 – Chif Sep 16 '19 at 18:55
  • i don't have to include any external .js for this very simple requirement, if its a native JS code then let me know. – Nick Kahn Sep 16 '19 at 19:12
  • 1
    Taking out the HTML and preserving the formatting are somewhat contradictory goals. The thing which gives a HTML document its layout is the combination of HTML and CSS. – ADyson Sep 16 '19 at 22:34
  • 1
    Where are you hoping to paste to? If you paste to most modern applications the formatting will be preserved from html. – Lee Taylor Sep 16 '19 at 22:37
  • @ADyson: the idea here is the copy what you see the code in formatted – Nick Kahn Sep 16 '19 at 22:54
  • @LeeTaylor: paste anywhere like in textpad or texteditor or past anywhere. – Nick Kahn Sep 16 '19 at 22:55
  • Try copying from your browser to those apps manually and note what happens. I doubt you'll achieve better results than that. – Lee Taylor Sep 16 '19 at 23:01
  • if i copy manually like selecting the text and pasting in different app, i do get correct formatted text. – Nick Kahn Sep 16 '19 at 23:02
  • 1
    @Nick. If you inspect your clipboard you will find that the format is still in html. – Lee Taylor Sep 16 '19 at 23:03
  • my question is how to preserves the formatting after you copy? and if I copy manually like selecting the text and ctrl+c and pasting will paste correct format but when I copy from the code (shown in my question) then I do get one long string without formatted! hope you understand what I'm trying to achieve. – Nick Kahn Sep 16 '19 at 23:11
  • @NickKahn See https://github.com/lgarron/clipboard-polyfill Specifically, `dt.setData("text/html", "Markup text.");` I have used and it works as per your wish. – Lee Taylor Sep 16 '19 at 23:27
  • looks like I need to add some .js file in my page? I'm looking to have this achieved in native javascript, unfortunately I can not add any external .js file – Nick Kahn Sep 16 '19 at 23:30
  • 1
    @NickKahn You have three options. 1) Use the library above. 2) Copy the salient code from the library above and paste it in to your page. 3) Use https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipboard but run the risk of limiting the number of users your page will work with. – Lee Taylor Sep 16 '19 at 23:35
  • Or 4) Roll your own. – Lee Taylor Sep 16 '19 at 23:35
  • the github lib has so many files which file should I be using? can you pinpoint or paste that as answer perhaps? – Nick Kahn Sep 16 '19 at 23:39
  • @NickKahn Done. – Lee Taylor Sep 16 '19 at 23:46

1 Answers1

2

From https://github.com/lgarron/clipboard-polyfill:

Either download this version:

This version is smaller, but does not work in Internet Explorer unless you add your own Promise polyfill (see below).

https://github.com/lgarron/clipboard-polyfill/releases/latest/download/clipboard-polyfill.js

or

This version works "out of the box" in all browsers that support copying to the clipboard, but is larger.

https://github.com/lgarron/clipboard-polyfill/releases/latest/download/clipboard-polyfill.promise.js

Once you've decided on either of the above files, you can either link to it or directly copy and paste the code into your own page. It's already minified.

Sample usage:

var dt = new clipboard.DT();
dt.setData("text/plain", "Fallback markup text.");
dt.setData("text/html", "<i>Markup</i> <b>text</b>.");
clipboard.write(dt);

I think the following still stands... You can only perform a copy during a human-initiated action (e.g. the clicking of a button, or a keyboard event). This is a security measure.

The beauty of using a polyfill is that when browsers are modified to follow accepted guidelines your code will still work and you can drop the dependency on this code.

Sample:

$("#copy").on("click", function(e)
{
  var dt = new clipboard.DT();
  
  dt.setData("text/plain", $("#table")[0].outerText);
  dt.setData("text/html", $("#table")[0].outerHTML);
  clipboard.write(dt);
});

$("#copy2").on("click", function(e)
{

  var dt = new clipboard.DT(); 
  dt.setData("text/plain", $("#content")[0].innerText); 
  dt.setData("text/html", $("#content")[0].innerHTML); 
  clipboard.write(dt);
});



// ---------------------------------------------------


!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).clipboard={})}(this,function(t){"use strict";function e(t,e,n,r){return new(n||(n=Promise))(function(o,i){function a(t){try{c(r.next(t))}catch(t){i(t)}}function u(t){try{c(r.throw(t))}catch(t){i(t)}}function c(t){t.done?o(t.value):new n(function(e){e(t.value)}).then(a,u)}c((r=r.apply(t,e||[])).next())})}function n(t,e){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(i){return function(u){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]<o[3])){a.label=i[1];break}if(6===i[0]&&a.label<o[1]){a.label=o[1],o=i;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(i);break}o[2]&&a.ops.pop(),a.trys.pop();continue}i=e.call(t,a)}catch(t){i=[6,t],r=0}finally{n=o=0}if(5&i[0])throw i[1];return{value:i[0]?i[1]:void 0,done:!0}}([i,u])}}}var r=["text/plain","text/html"];var o=function(){(console.warn||console.log).call(arguments)}.bind(console,"[clipboard-polyfill]"),i=!0;var a=function(){function t(){this.m={}}return t.prototype.setData=function(t,e){i&&-1===r.indexOf(t)&&o("Unknown data type: "+t,"Call clipboard.suppressWarnings() to suppress this warning."),this.m[t]=e},t.prototype.getData=function(t){return this.m[t]},t.prototype.forEach=function(t){for(var e in this.m)t(this.m[e],e)},t}(),u=function(t){},c=!0;var s=function(){(console.warn||console.log).apply(console,arguments)}.bind("[clipboard-polyfill]"),d="text/plain";function l(t){u=t}function f(){c=!1,i=!1}function p(t){return e(this,void 0,void 0,function(){var e;return n(this,function(n){if(c&&!t.getData(d)&&s("clipboard.write() was called without a `text/plain` data type. On some platforms, this may result in an empty clipboard. Call clipboard.suppressWarnings() to suppress this warning."),k()){if(function(t){var e=t.getData(d);if(void 0!==e)return window.clipboardData.setData("Text",e);throw new Error("No `text/plain` value was specified.")}(t))return[2];throw new Error("Copying failed, possibly because the user rejected it.")}if(x(t))return u("regular execCopy worked"),[2];if(navigator.userAgent.indexOf("Edge")>-1)return u('UA "Edge" => assuming success'),[2];if(D(document.body,t))return u("copyUsingTempSelection worked"),[2];if(function(t){var e=document.createElement("div");e.setAttribute("style","-webkit-user-select: text !important"),e.textContent="temporary element",document.body.appendChild(e);var n=D(e,t);return document.body.removeChild(e),n}(t))return u("copyUsingTempElem worked"),[2];if(void 0!==(e=t.getData(d))&&function(t){u("copyTextUsingDOM");var e=document.createElement("div");e.setAttribute("style","-webkit-user-select: text !important");var n=e;e.attachShadow&&(u("Using shadow DOM."),n=e.attachShadow({mode:"open"}));var r=document.createElement("span");r.innerText=t,n.appendChild(r),document.body.appendChild(e),T(r);var o=document.execCommand("copy");return E(),document.body.removeChild(e),o}(e))return u("copyTextUsingDOM worked"),[2];throw new Error("Copy command failed.")})})}function v(t){return e(this,void 0,void 0,function(){return n(this,function(e){return navigator.clipboard&&navigator.clipboard.writeText?(u("Using `navigator.clipboard.writeText()`."),[2,navigator.clipboard.writeText(t)]):[2,p(C(t))]})})}function h(){return e(this,void 0,void 0,function(){var t;return n(this,function(e){switch(e.label){case 0:return t=C,[4,b()];case 1:return[2,t.apply(void 0,[e.sent()])]}})})}function b(){return e(this,void 0,void 0,function(){return n(this,function(t){if(navigator.clipboard&&navigator.clipboard.readText)return u("Using `navigator.clipboard.readText()`."),[2,navigator.clipboard.readText()];if(k())return u("Reading text using IE strategy."),[2,U()];throw new Error("Read is not supported in your browser.")})})}var m=!1;function w(){m||(c&&s('The deprecated default object of `clipboard-polyfill` was called. Please switch to `import * as clipboard from "clipboard-polyfill"` and see https://github.com/lgarron/clipboard-polyfill/issues/101 for more info.'),m=!0)}var y={DT:a,setDebugLog:function(t){return w(),l(t)},suppressWarnings:function(){return w(),f()},write:function(t){return e(this,void 0,void 0,function(){return n(this,function(e){return w(),[2,p(t)]})})},writeText:function(t){return e(this,void 0,void 0,function(){return n(this,function(e){return w(),[2,v(t)]})})},read:function(){return e(this,void 0,void 0,function(){return n(this,function(t){return w(),[2,h()]})})},readText:function(){return e(this,void 0,void 0,function(){return n(this,function(t){return w(),[2,b()]})})}},g=function(){this.success=!1};function x(t){var e=new g,n=function(t,e,n){u("listener called"),t.success=!0,e.forEach(function(e,r){var o=n.clipboardData;o.setData(r,e),r===d&&o.getData(r)!==e&&(u("setting text/plain failed"),t.success=!1)}),n.preventDefault()}.bind(this,e,t);document.addEventListener("copy",n);try{document.execCommand("copy")}finally{document.removeEventListener("copy",n)}return e.success}function D(t,e){T(t);var n=x(e);return E(),n}function T(t){var e=document.getSelection();if(e){var n=document.createRange();n.selectNodeContents(t),e.removeAllRanges(),e.addRange(n)}}function E(){var t=document.getSelection();t&&t.removeAllRanges()}function C(t){var e=new a;return e.setData(d,t),e}function k(){return"undefined"==typeof ClipboardEvent&&void 0!==window.clipboardData&&void 0!==window.clipboardData.setData}function U(){return e(this,void 0,void 0,function(){var t;return n(this,function(e){if(""===(t=window.clipboardData.getData("Text")))throw new Error("Empty clipboard or could not read plain text from clipboard");return[2,t]})})}t.DT=a,t.default=y,t.read=h,t.readText=b,t.setDebugLog=l,t.suppressWarnings=f,t.write=p,t.writeText=v,Object.defineProperty(t,"__esModule",{value:!0})});
//# sourceMappingURL=clipboard-polyfill.js.map
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="copy">Copy</button> <br />
<button id="copy2">Copy 2</button> <br />

<table id="table" style="width:100%">
<tr>
  <td style="font-size:20pt;">1</td>
  <td style="background-color:pink;">2</td>
  <td style="color:red">3</td>
</tr>
</table>

<div id="content">
<p>public class TestAccountTrigger { </p><p></p><p>@testSetup</p><p>static void initData(){</p><p>Account a0 = new Account(Name = 'Account Zero');</p><p>insert a0; </p><p> }</p>

</div>
Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
  • did not work, I used as you suggested above and I have a button that fire the event to copy, have you tested? – Nick Kahn Sep 17 '19 at 16:22
  • @NickKahn Not only have I tested, I have it as part of my system that I've developed. What browser(s) are you using? Any errors? – Lee Taylor Sep 17 '19 at 16:41
  • using chrome and i don't see error and here is my code `var dt = new clipboard.DT(); dt.setData("text/plain", "Fallback markup text."); dt.setData("text/html", "

    public class TestAccountTrigger {

    @testSetup

    static void initData(){

    Account a0 = new Account(Name = 'Account Zero');

    insert a0;

    }

    "); clipboard.write(dt);` after i click the button and when i try to paste i'm getting this text `Fallback markup text` i'm not sure why its getting that? or am I using incorrect?
    – Nick Kahn Sep 17 '19 at 16:46
  • @NickKahn I've updated my answer with a working demo. After clicking the button I can paste directly in to Word. – Lee Taylor Sep 17 '19 at 16:57
  • I have tested your sample and it works great but why its not working when I paste my html code? here is the jsfiddle I have created you can see here https://jsfiddle.net/abuhamzah/n5qw2dm0/3/ – Nick Kahn Sep 17 '19 at 17:32
  • @NickKahn I've changed the snippet above to work with your sample... Your jsfiddle doesn't work because you're html is invalid you can't put

    tags directly in a table, for example.

    – Lee Taylor Sep 17 '19 at 17:40
  • I implement in my code and I'm getting the error related with the clipboard .js file, I edited my question and paste the error message in my question. – Nick Kahn Sep 17 '19 at 23:04
  • Which application are you pasting to? What if you paste to Word? – Lee Taylor Sep 17 '19 at 23:05
  • when I get the error it does not copy to clipboard hence, I can not paste – Nick Kahn Sep 17 '19 at 23:07
  • pasting the error `aura_prod.js:24 Uncaught (in promise) TypeError: Failed to execute 'selectNodeContents' on 'Range': parameter 1 is not of type 'Node'. at Proxy.Tt.l (aura_prod.js:24) at T (clipboard:1) at _ (clipboard:1) at Object.eval (clipboard:1) at eval (clipboard:1) at Object.eval [as next] (clipboard:1) at eval (clipboard:1) at new Promise () at e (clipboard:1) at Object.p [as write] (clipboard:1)` – Nick Kahn Sep 17 '19 at 23:10
  • Which code is causing that error? Do you have a jsfiddle? – Lee Taylor Sep 17 '19 at 23:10
  • it may be because my code run under the for-each loop? so I have loop and inside the loop I have a div id so depends on the how many records it pull from the database its assigning the same id... i'm thinking maybe loop is causing the issue? if I have only one div it works perfectly fine, do you have any idea? – Nick Kahn Sep 17 '19 at 23:15
  • i'm trying to create jsfiddle but hard to bring the logic to jsfiddle :( – Nick Kahn Sep 17 '19 at 23:16
  • @NickKahn I would really need to see a rough idea of what you are trying to do. Pulling in data shouldn't make a difference apart from potentially breaking the notion that it's a human-instigated process. – Lee Taylor Sep 17 '19 at 23:18
  • I spent lot of my time debuggin but I could not able to reach the solution, here is what is going on with your .js file, i have `for-each loop` within that loop i have div that renders some sample code (formatted) and plus a button for copy that code, the very first copy button works but when i click on the second div within that loop then it does not copy and i get his message when i looked in the chrome `clipboard.write() was called without a `text/plain` data type. On some platforms, this may result in an empty clipboard. Call clipboard.suppressWarnings() to suppress this warning.` – Nick Kahn Sep 19 '19 at 02:33
  • i have no idea what is going on here – Nick Kahn Sep 19 '19 at 02:34
  • I think the only way forward is to create a codepen or similar to show your issue. Without seeing what you're faced with I can't offer much help. – Lee Taylor Sep 19 '19 at 14:32