Can't get document.execCommand("copy") to work as expected - javascript

I'm trying to scrape some data off a page and can't quite get document.execCommand("copy") to work as expected.
As a test, I am injecting some HTML into the page and then try to copy it to the clipboard
$('#topcard h1').append('<a id="getData" type="text">This is some data</a>')
I can then see This is some data on the page appear
Then:
var copyText = document.querySelector("#getData")
copyText
Shows in the console:
<a id=​"getData" type=​"text">​This is some data​</a>​
It seems that copyText is exactly what I want it to be.
However:
copyText.select()
returns
VM2319:1 Uncaught TypeError: copyText.select is not a function
at <anonymous>:1:10
What am I doing wrong?

.select() will let you set the selection range for an <input> or <textarea>:
document.getElementById("foo").select()
<input id="foo" value="12345">
...but will not work for other DOM nodes (including contentEditable nodes). To select anything other than a form field's contents, you need to use the Selection API:
var r = document.createRange();
r.selectNode(document.getElementById("foo"));
var s = window.getSelection();
s.addRange(r);
<div id="foo">12345</div>
In either case, once the selection is made you can then use document.execCommand("copy") to capture the selected text to the clipboard -- but with one very important caveat: this must be done within a user-initiated event handler. (This is to prevent malicious websites from hijacking the user's clipboard without their knowledge.)
var captureSelection = function() {
var r = document.createRange();
r.selectNode(document.getElementById("foo"));
var s = window.getSelection();
s.addRange(r);
document.execCommand("copy");
}
// this will select the DOM node, but will not copy it to
// the clipboard (because it's not user-initiated):
captureSelection();
// this _will_ copy it to the clipboard:
document.getElementById("bar").onclick = captureSelection;
<div id="foo">12345</div>
<button id="bar">Go</button>
<br><br>
<textarea placeholder="paste text here"></textarea>

Related

selectionStart ignoring new line/line break in textarea in Chrome

I am trying to insert text into a textarea at the cursor position or to replace a selected piece of text. The code works in that the text is inserted and if a selection of text is made it is replaced.
The problem is if the user inserts several line breaks first then tries to insert the text. Then instead of inserting the text at the point of the cursor it inserts it at the second line. So for example if the user inserts line breaks and ends on line 5 then clicks the input to insert the text, the text is inserted at line 2.
var holdFocus;
function updateFocus(x) {
holdFocus = x;
}
Triggered by:
<textarea onFocus="updateFocus(this)" cols="53" rows="10" name="entry" id="report" style="width: 98%;"></textarea>
Then the insertion of the text:
function InsertCodeInTextArea(text){
var tav = $(holdFocus).val(),
strPos = $(holdFocus)[0].selectionStart;
var endPos = $(holdFocus)[0].selectionEnd;
front = (tav).substring(0,strPos),
back = (tav).substring(endPos,tav.length);
var textValue = text.split("%lb%").join("\r\n");
$(holdFocus).val(front+ textValue + back);
}
Triggered by clicking on:
<input class="input" type="button" name="LODCTRRAPPA" value ="LODCTRRAPPA" onClick="InsertCodeInTextArea('Location: %lb%Onset: %lb%Duration: %lb%Course: %lb%Type of pain/symptom: %lb%')"/>
This problem seems to only be occurring in Chrome. Safari and FF are okay. Not tested IE. I am sure that this also wasn't happening until a few weeks ago.
Use vanilla JavaScript
From jQuery docs on val():
Note: At present, using .val() on elements strips carriage return characters from the browser-reported value. When this value is sent to the server via XHR, however, carriage returns are preserved (or added by browsers which do not include them in the raw value). A workaround for this issue can be achieved using a valHook as follows:$.valHooks.textarea = {
get: function( elem ) {
return elem.value.replace( /\r?\n/g, "\r\n" );
}
};
Something like the following should suffice:
var holdFocus;
function updateFocus(x) {
holdFocus = x;
}
function InsertCodeInTextArea(text){
var tav = holdFocus.value;
holdFocus.value = tav.substring(0, holdFocus.selectionStart) +
text.split("%lb%").join("\r\n") +
tav.substring(holdFocus.selectionEnd, tav.length);
}
<textarea autofocus onFocus="updateFocus(this)" cols="53" rows="10" name="entry" id="report" style="width: 98%;">foo
bar
baz</textarea>
<input class="input" type="button" name="LODCTRRAPPA" value ="LODCTRRAPPA" onClick="InsertCodeInTextArea('Location: %lb%Onset: %lb%Duration: %lb%Course: %lb%Type of pain/symptom: %lb%')"/>
Update
It's a bug! See productforums.google.com
... if you click out of the textarea and then click back in, and then hit the button the error is now gone and it works.
Tested my snippet, and it did indeed work after clicking out (losing focus) then clicked back in (regaining focus).
This would obviously cause loss of any selections made, and is impractical to ask users to do.
If I find a programmatic workaround, I'll update this answer.

How can I prevent Range.selectNode() selecting too much of the DOM when attempting to select a node injected with appendChild()?

I'm facing an issue with the combination of using appendChild() and Range.selectNode() in JavaScript.
When attempting to use a range to select the newly-appended <textarea> node, it selects too much of the DOM. Copying and pasting the selection seems to just contain a space.
However, if I put the <textarea> node into the DOM from the start (i.e. don't add it with appendChild()) then it works perfectly well and I can copy and paste the selected text as expected.
Note that the CSS isn't really necessary here, but it highlights the fact that the selection contains more than just the <textarea> (or at least it does in Chrome).
HTML:
<div>
<a class="hoverTrigger">Click to trigger textarea element with selected text</a>
</div>
CSS:
.floating {
position: absolute;
}
JavaScript/jQuery (run on DOM ready):
$(".hoverTrigger").click(createAndSelectStuff);
function createAndSelectStuff() {
var textArea = document.createElement("textarea");
textArea.className = "floating";
textArea.value = "Some dynamic text to select";
this.parentNode.appendChild(textArea);
selectObjectText(textArea);
return false;
}
function selectObjectText(container) {
var range = document.createRange();
range.selectNode(container);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
}
Here's a jsFiddle.
This is what the resulting selection looks like in Chrome:
How can I stop this happening, and just select the desired text?
Replace your call to selectObjectText with:
container.setSelectionRange(0, container.value.length);
The problem with textarea elements is that they do not hold their contents in DOM nodes. The text value is a property of the element. When you call range.selectNode, what happens is that the range is set so as to encompass the node you pass to the function and the children node of this node, but since a textarea does not store its text in children nodes, then you select only the textarea.
setSelectionRange works with the value of an input element so it does not suffer from this problem. You might want to check the compatibility matrix here to check which browsers support it.

execCommand not working in javascript jQuery to copy text to clipboard

i'm trying to copy a text to the clipboard. But've already shown the text as selected in the modal window where it appears after an ajax call.The code is the following:
jQuery.fn.selectText = function(){
var doc = document
, element = this[0]
, range, selection
;
if (doc.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(element);
range.select();
} else if (window.getSelection) {
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
}
so after range = document.createRange(); i tryied inserting range.execCommand('copy'); cause i've read this tutorial about it but it doesn't mention any problem with this command. The error i'm getting is the following:
TypeError: range.execCommand is not a function
This is a mozilla tutorial about execCommand.
A range doesn't have an execCommand function, the execCommand function belongs to the document object.
Taken from the same tutorial:
When an HTML document has been switched to designMode, the document
object exposes the execCommand method which allows one to run commands
to manipulate the contents of the editable region. Most commands
affect the document's selection (bold, italics, etc), while others
insert new elements (adding a link) or affect an entire line
(indenting). When using contentEditable, calling execCommand will
affect the currently active editable element.

execCommand bold fails in Firefox when all text is selected

I'm setting up to do some simple WYSIWYG editing using JavaScript, and I've run into an issue in Firefox that I don't get in Chrome or IE (recent versions of all). When all the text in my contentEditable span is selected, if I attempt to make it bold using document.execCommand('bold',false,null), I receive a rather nondescript error message: "NS_ERROR_FAILURE: Failure"
Here's some simple example code to easily reproduce the issue:
<html>
<head>
<script>
function start(){
var edit = document.getElementById('edit');
edit.contentEditable = true;
var button = document.getElementById('button');
button.onclick = function(){
// Get the editable span
var edit = document.getElementById('edit');
// Select the contents of the span
var range = document.createRange();
range.selectNodeContents(edit);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
// Make the text bold
document.execCommand('bold',false,null);
}
}
</script>
</head>
<body onload="start();">
<span id='edit'>Click on the button</span>
<button id='button'>Bold It All!</button>
</body>
</html>
So, what am I doing wrong here? Have I just run into a bug? If so, can anyone suggest a work-around solution?
This is a bug. Consider filing it. In short:
The editor will attempt to wrap the <span> with a <b> (or another <span> when useCSS).
This would remove the <span>.
Therefore the code checks that the parent of the <span> is editable, which it isn't.
Boom!
Work-around: contenteditable="true" a real block element like <div>.

Obtaining a DOM Range by clicking anywhere within an Element

Given the following HTML...
<p>Today is <span data-token="DateTime.DayOfWeek">$$DayOfWeek$$</span>,
</p>
<p>Tomorrow is the next day, etc, etc....</p>
Clicking on $$DayOfWeek$$ returns a DOM Range object (via a component, which is a WYSIWIG editor bundled with KendoUI).
I can then access the entire Element like so...
var element = range.startContainer.parentElement;
console.log(element);
which outputs...
<span data-token="DateTime.DayOfWeek">$$DayOfWeek$$</span>
What i am trying to figure out is how to construct a Range object that consists of the entire Element, as a Range.
The desired 'high level' behaviour is to single click a piece of text, and have the browser select all the text within that element, returning a Range object.
Happy to accept a jQuery solution.
HTML
<p>Today is <span data-token="DateTime.DayOfWeek">$$DayOfWeek$$</span>,</p>
<p>Tomorrow is the next day, etc, etc....</p>
JS
var span = document.querySelector('[data-token]');
span.addEventListener('click', function() {
var sel = window.getSelection();
var range = document.createRange();
sel.removeAllRanges();
range.setStart(span.childNodes[0], 0);
range.setEnd(span.childNodes[0], span.innerText.length);
sel.addRange(range);
});
Here's a fiddle for you:
http://jsfiddle.net/V66zH/2/
It' might not be super cross browser, but works in chrome. See JavaScript Set Window selection for some additional optimizations elsewhere.
Also assumes only one childNode as in your example html
Some additional reference for Ranges (https://developer.mozilla.org/en-US/docs/Web/API/range) and Selections (https://developer.mozilla.org/en-US/docs/Web/API/Selection)
here is a way i came up with that seems to work if i understand you correctly, that you want the element surrounding a click to produce a range containing everything in that element.
without the onclick code, which i assume you can handle, here is the DOM range code you describe:
var sel=document.getSelection(); //find the node that was clicked
var rng=sel.getRangeAt(); //get a range on that node
//now, extend the start and end range to the whole element:
rng.setStart(rng.startContainer.parentNode.firstChild);
rng.setEndAfter(rng.endContainer.parentNode.lastChild);
//DEMO: verify the correct range using a temp div/alert:
var t=document.createElement("div");
t.appendChild(rng.cloneContents());
alert(t.innerHTML);

Categories