Copy plain text with no rich styles to clipboard without losing focus?
Asked Answered
E

1

7

I looked at this question where it is asked for a way to simply copy text as plain text. I want to do exactly that but with one additional thing - not lose focus on the current element.

I need this for a Chrome extension, so I'm not bothered with cross-browser support. When the user types in an input (or contenteditable), a dropdown with choices appears. If he chooses one of them, it is copied to his clipboard. I don't want the element to lose focus because some sites might have implemented logic to run on the element's blur event.

Here's what I've tried:

Solution 1

Create an <input> element and use its select() method:

function clipWithInput(text) {
    var input = document.createElement("input");
    document.body.appendChild(input);

    input.addEventListener("focus", function (e) {
        e.preventDefault();
        e.stopPropagation();
    });

    input.value = text;
    input.select();

    document.execCommand("copy");
    document.body.removeChild(input);
}

document.getElementById("choice").onmousedown = function (e) {
  e.preventDefault(); // prevents loss of focus when clicked
  clipWithInput("Hello");
};
#main {background: #eee;}
#choice {background: #fac;}
<div id="main" contenteditable="true">Focus this, click the div below and then paste here.</div>
<div id="choice">Click to add "Hello" to clipboard</div>

As you can see, this works. The text is copied. However, when you focus the contenteditable and click on the "choice", the focus is lost. The choice element has preventDefault() on its mousedown event which causes it to not break focus. The dummy <input> element is the problem here, even though it has preventDefault() on its focus event. I guess the problem here is that it's too late - the initial element has already fired its blur, so my dummy input's focus is irrelevant.

Solution 2

Use a dummy text node and the Selection API:

function clipWithSelection(text) {
    var node = document.createTextNode(text),
        selection = window.getSelection(),
        range = document.createRange(),
        clone = null;

    if (selection.rangeCount > 0) {
        clone = selection.getRangeAt(selection.rangeCount - 1).cloneRange();
    }

    document.body.appendChild(node);
    selection.removeAllRanges();
    range.selectNodeContents(node);
    selection.addRange(range);
    document.execCommand("copy");

    selection.removeAllRanges();
    document.body.removeChild(node);

    if (clone !== null) {
        selection.addRange(clone);
    }
}

document.getElementById("choice").onmousedown = function (e) {
  e.preventDefault(); // prevents loss of focus when clicked
  clipWithSelection("Hello");
};
#main {background: #eee;}
#choice {background: #fac;}
<div id="main" contenteditable="true">Focus this, click the div below and then paste here.</div>
<div id="choice">Click to add "Hello" to clipboard</div>

This works perfectly at first glance. The text is copied, no focus is lost, the caret stays at the same position. No drama. However, when you paste the text in a contenteditable (like Gmail's email composer), this is the result:

<span style="color: rgb(0, 0, 0); font-family: "Times New Roman"; font-size: medium;">Hello</span>

Not plain text.

  • I tried appending the element in the <head> where there are no styles - nope. Text isn't selected and nothing is copied.
  • I tried appending the text node in a <span> and set stuff like style.fontFamily to inherit, as well as fontSize and color. Still doesn't work. I logged the dummy element and it correctly had my inherit styles. However, the pasted text didn't.

Recap

I want to programmatically copy plain text with no styles while preserving focus on the currently active element.

Embolectomy answered 18/9, 2017 at 9:28 Comment(0)
O
0

Your solution (especially 2) was okay. When you paste in a contenteditable, it needs to be expected that there are span codes inserted, many use that in insertHTML. You are not to expect plain text programmatically. Some would suggest not using a contenteditable at all (though I understand you're talking about some extension). But your solution is more compatible with mobiles than MDN or such.

So, you programmatically copy plain with no style added (if no contenteditable) while preserving focus on the current element.

Opt answered 3/10, 2022 at 7:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.