Copy plain text with no rich styles to clipboard without losing focus? - javascript

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.

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.

Related

Copy text from <span> to clipboard

I've been trying to copy the innerContent of a <span> to my clipboard without success:
HTML
<span id="pwd_spn" class="password-span"></span>
JavaScript
Function Call
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('copy').addEventListener('click', copy_password);
});
Function
function copy_password() {
var copyText = document.getElementById("pwd_spn").select();
document.execCommand("Copy");
}
I've also tried:
function copy_password() {
var copyText = document.getElementById("pwd_spn").textContent;
copyText.select();
document.execCommand("Copy");
}
It seems like .select() doesn't work on a <span> element since I get the following error on both:
You could do this: create a temporary text area and append it to the page, then add the content of the span element to the text area, copy the value from the text area and remove the text area.
Because of some security restrictions you can only execute the Copy command if the user interacted with the page, so you have to add a button and copy the text after the user clicks on the button.
document.getElementById("cp_btn").addEventListener("click", copy_password);
function copy_password() {
var copyText = document.getElementById("pwd_spn");
var textArea = document.createElement("textarea");
textArea.value = copyText.textContent;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("Copy");
textArea.remove();
}
<span id="pwd_spn" class="password-span">Test</span>
<button id="cp_btn">Copy</button>
See https://stackoverflow.com/a/48020189/2240670 there is a snippet of code for that gives you an example for a div, that also applies to a span, I did not copy it here to avoid duplication.
Basically, when you are copying to clipboard you need to create a selection of text, <textarea> and <input> elements make this easy because they have a select() method, but if you are trying to copy contents from any other type of element like a <div> or <span>, you'll need to:
Create/get a Range object(some browsers do not provide a constructor, or a decent way to do this). Calling document.getSelection().getRangeAt(0), I found works on most browsers except edge(ie11 works though).
Add the element you want to copy from to that range's selection.
Add that range to the window or document Selection.
Call document.execCommand("copy") to copy the selected text.
I also recommend checking the API of Selection and Range, that will give you a better grasp of this.
simple method
1)create a input
2)give style z-index -1 and it will be hide
var code = $("#copy-to-clipboard-input");
var btnCopy = $("#btn-copy");
btnCopy.on("click", function () {
code.select();
document.execCommand("copy");
});
<input type="input" style="width:10px; position:absolute; z-index: -100 !important;" value="hello" id="copy-to-clipboard-input">
<button class="btn btn-success" id="btn-copy">Copy</button>

setting caret (cursor) inside bold element present inside contenteditable paragraph

I have been trying to build a web based text editor. And as part of the process, I am trying to dynamically create and modify elements based and keystroke events for font editing. In this particular jsfiddle example I'm trying to create a strong element upon hitting CTRL+b and setting the focus/caret inside the strong element so that subsequent text entered will be part of the bold element and hence will have bold text. But my code is just creating a strong element but not transferring the focus hence no text is getting bolder.
In the below code I'm creating event listener to capture keystroke events
p=document.getElementsByTagName("p")[0];
//console.log(p)
// adding eventlistener for keydown
p.addEventListener("keydown",listener);
// eventlistenerr callback function
function listener(){
e=window.event;
if(e.ctrlKey && e.keyCode==66)
{
console.log("CTRL+B");
// creating bold element
belm=document.createElement("strong");
belm.setAttribute("contenteditable","true")
p.appendChild(belm);
//bug below
// setting focus inside bold element
setfocus(belm,0);
e.preventDefault();
}
}
Here is the function for setting the focus.
function setfocus(context, position){
var range = document.createRange();
position =position || 0;
var sel = window.getSelection();
range.setStart(context, position);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
context.focus();
}
However, I have not doubt that the function which sets focus is faulty, because in the fiddle if you observe, I have created a separate setup just to test this
out. Click on the button "Click Here" and the focus dynamically shifts to paragraph element without any hassle. I am unable to figure out what is going wrong.
It's pretty much impossible to move the cursor into an empty element in a contenteditable div. However, as shay levi suggested in another post, you can insert the zero-width character &#200B into your empty element to give it an index value.
Here's an example*:
function insertNode(nodeName) {
var sel = window.getSelection(),
range;
range = sel.getRangeAt(0);
range.deleteContents();
var child = document.createElement(nodeName);
child.innerHTML = '​';
range.insertNode(child);
}
var div = document.querySelector('div'),
btn = document.querySelector('button');
btn.addEventListener('click', function() {
insertNode('strong');
div.focus();
});
div.focus();
<div contenteditable></div><button><strong>B</strong></button>
*For the sake of simplicity, this script doesn't toggle bold text, it only sets it.

contenteditable - Selecting 2 child paragraphs and writing text over (Firefox Issue)

I have been searching on Google for over a week now, I've been trying to implement different solutions, but with no success, and it's bugging the hell out of me.
So you have a contenteditable div with several paragraphs(or other child elements of the same kind). Obviously this is the kind of layout you wanna keep. If the user selects two or more paragraphs and types text over it, it removes the paragraphs and sets the caret focus inside the parent div:
body {
font-family: georgia;
}
.editable {
color: red;
}
.editable p {
color: #333;
}
.editable span {
color: limegreen !important;
}
<div class="editable" contenteditable><p>paragraph one</p><p>paragraph two</p></div>
<hr>
<p>How to reproduce the bug:</p>
<ul>
<li>Focus the contenteditable above by placing the cursor somewhere in one of the two paragraphs.</li>
<li>press ctrl-a (in windows or linux) or cmd-a (in osx) to select-all</li>
<li>type some text</li>
<li>red text means that the text went directly inside the contenteditable div, black text means it went inside a paragraph</li>
</ul>
<p>The correct behaviour should be that that select-all and delete (or "type-over") in a contenteditable with only block tags should leave the cursor inside the first block tag.</p>
<p>Webkit gets this right, Firefox gets it wrong.</p>
I did try something like this in Jquery:
$(document).on('blur keyup','div[contenteditable="true"]',function(event){
var sel = window.getSelection();
var activeElement = sel.anchorNode.parentNode;
var tagName = activeElement.tagName.toLowerCase();
if (!(tagName == "p" || tagName == "span")) {
console.log('not on editable area');
event.preventDefault();
//remove window selection
var doselect = window.getSelection();
doselect.removeAllRanges();
}
});
So after blur or keyup event on contenteditable, detect where the caret position and if it's outside accepted editable areas stop the event or something?
I've tried changing the selection range, and a bunch of other stuff but maybe I'm just not seeing it. I'm sure a lot of people have had the same problem, but the only answers I found on Google or here is "content editable sucks", "why don't you just use an open source editor" and that kind of stuff.
Firefox weird behaviour: Inserting BR tags on break line
I have also tried to remove Firefox'es weird behaviour with a function to remove all the <BR> tags firefox automatically inserts. Like this:
function removeBr(txteditor) {
var brs = txteditor.getElementsByTagName("br");
for (var i = 0; i < brs.length; i++) { brs[i].parentNode.removeChild(brs[i]); }
}
So I attached this to a keydown event, and it does exactly what it's expected, however that causes more weird behaviour (like preventing you adding spaces on selected paragraph).
Please vote up the question so we can raise more awareness.
I'm sure a lot of other people have bumped into the same problem, I think it would be good to know if there's a workaround or any "right" way to do it. I think it should really be discussed...
So Firefox injects this abomination - <br></br> - into the contenteditable div when removing the paragraphs.
With a little bit of jQuery we can remove it and replace it with paragraph tags.
Current Limitation - The break tag seems to be injected only when removed with delete or backspace, not when typed over... consider this a concept :-)
Open this example in Firefox to test:
$("button").click(function() {
$("br:not(p br)").replaceWith('<p>Write Me!</p>'); // dont replace when inside p
});
body {
font-family: georgia;
}
.editable {
color: red;
}
.editable p {
color: #333;
}
.editable span {
color: limegreen !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="editable" contenteditable>
<p>paragraph one</p>
<p>paragraph two</p>
</div>
<hr>
<p>Remove all text to trigger bug in Firefox and hit "fix problem!"</p>
</ul>
<p>Webkit gets this right, Firefox gets it wrong.</p>
<button class="fix">Fix Problem</button>
Whats happening is this, when you select both groups of text and delete them you are also deleting all tags within the editable element. So your actually deleting the <p> tags from the div and then the only thing to write to is the div itself.
Why do you need two seperate paragraph tags? It would be easier to have the div by itself...
Rather than setting the div as editable, have you tried setting the <p> tags as <p contenteditable="true">

Insert data attribute in P tag in ContentEditable change event

I would like to insert a data attribute in the <p data-attribute="blah">...</p> tag when the user hits Enter (key 13) inside a content editable. I have gotten to the point of being notified when the user hits Enter but I am not sure how I can insert the attribute in the element the browser creates as default behaviour.
Any hints?
Thanks!
The best thing you can probably do is keeping track of the paragraphs. Once the contenteditable element has been created, you get a list of the paragraphs.
<div id="textArea" contenteditable></div>
Javascript (with jQuery, since you used its tag):
var textArea = $("#textArea"),
pars = textArea.find("p");
textArea.on("input", function() {
var curPars = $("p", this);
curPars.each(function() {
if ($.inArray(this, pars) === -1) // this is a new paragraph
this.setAttribute("data-attribute", someIndex);
});
pars = curPars;
});
Deleted paragraphs will be discarded - you decide what to do with them.
I used the input event because it's the most reliable event to keep track of changes on the content, including cutting and pasting from the clipboard using just the mouse. Too bad it's not available on IE8 and lower, and in IE9 it doesn't fire when deleting content (!), and IIRC not even on contenteditable elements. Uuuugh.
You may want to add the propertychange event too, or you can rely on just keypress and mouseup events.

How to copy a div's content to clipboard without flash

That's it :) I have a div with the id #toCopy, and a button with the id #copy.
What's the best way to copy #toCopy content to clipboard when pressing #copy?
You can copy to clipboard almost in any browser from input elements only (elements that has .value property), but you can't from elements like <div>, <p>, <span>... (elements that has .innerHTML property).
But I use this trick to do so:
Create a temporary input element, say <textarea>
Copy innerHTML from <div> to the newly created <textarea>
Copy .value of <textarea> to clipboard
Remove the temporary <textarea> element we just created
function CopyToClipboard (containerid) {
// Create a new textarea element and give it id='temp_element'
const textarea = document.createElement('textarea')
textarea.id = 'temp_element'
// Optional step to make less noise on the page, if any!
textarea.style.height = 0
// Now append it to your page somewhere, I chose <body>
document.body.appendChild(textarea)
// Give our textarea a value of whatever inside the div of id=containerid
textarea.value = document.getElementById(containerid).innerText
// Now copy whatever inside the textarea to clipboard
const selector = document.querySelector('#temp_element')
selector.select()
document.execCommand('copy')
// Remove the textarea
document.body.removeChild(textarea)
}
<div id="to-copy">
This text will be copied to your clipboard when you click the button!
</div>
<button onClick="CopyToClipboard('to-copy')">Copy</button>
The same without id:
function copyClipboard(el, win){
var textarea,
parent;
if(!win || (win !== win.self) || (win !== win.window))
win = window;
textarea = document.createElement('textarea');
textarea.style.height = 0;
if(el.parentElement)
parent = el.parentElement;
else
parent = win.document;
parent.appendChild(textarea);
textarea.value = el.innerText;
textarea.select();
win.document.execCommand('copy');
parent.removeChild(textarea);
}
I didn't tested for different windows (iframes) though!
UPDATED ANSWER
Javascript was restricted from using the clipboard, early on.
but nowadays it supports copy/paste commands.
See documentation of mozilla and caniuse.com.
document.execCommand('paste')
make sure that you support browsers that don't.
https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
http://caniuse.com/#search=command
Javascript is not allowed to use the clipboard, but other plugins like flash do have access.
How do I copy to the clipboard in JavaScript?

Categories

Resources