Wednesday, May 05, 2010
Text, Text on the Range
Blog Entry #178
In the Don't take my stuff (please...) subsection of the previous post, we demonstrated that, for a JavaScript-enabled browser, a
return false;
statement will prevent the mousedown-based selection and copying of text on a Web page. Today we consider the opposite situation: what if you wanted to facilitate the copying of such content?At the MSDN Library, Microsoft prefaces its code samples with Copy hyperlinks (see here*, for example) that when clicked trigger a function that copies those samples to the operating system's clipboard IF you're an MSIE for Windows user. Actually, I can't rule out that there might be other Windows browsers that will run the Copy function, but the Copy links don't work with any of the browsers on my iMac.
(*October 2016 Update: Microsoft has evidently discontinued the Copy facility.)
We may go through Microsoft's code-copying code later, but for now let's turn to our HTML Goodies tutorial du jour, "Click... It's Copied!", which relatedly presents a script that copies a text string to the clipboard upon clicking a push button. In its original form, the "Click... It's Copied!" script is also Mac-incompatible, but all is not lost: after some experimentation I came up with a revamped script that would work with Safari and Chrome.
In this entry, we'll deconstruct the "Click... It's Copied!" script in detail; depending on how long that takes, I'll discuss my changes to the script in this or the next post.
Script overview and HTML
In brief, here's what the "Click... It's Copied!" script does:
(1) The script scoops up the text content of a span element and loads it into a textarea field.
(2) The textarea text is converted into a corresponding "TextRange object".
(3) The TextRange object text is written to the clipboard by the object's execCommand( ) method,
a command available only in IE 4.0 or betterat the time the tutorial was written.
The script's HTML is given below:
<span id="copytext" style="height:150;width:162;background-color:pink">This text will be copied onto the clipboard when you click the button below. Try it!</span>
<br>
<textarea id="holdtext" style="display:none;"></textarea>
<button onclick="ClipBoard( );">Copy to Clipboard</button>
Joe put the This text will be copied onto the clipboard when you click the button below. Try it! string in an id="copytext" span element, but he could have used almost any renderable non-empty element (p, div, a, etc.) for this purpose, as explained later.
Joe attempts to presentationally highlight the span element text by mounting it on a 162px-by-150px pink 'square' via a height:150;width:162;background-color:pink set of style declarations. OK, class, who can spot the problems in this run of CSS?
"You can't use width and height with spans."
Quite. Neither the CSS width property nor its height counterpart applies to "non-replaced inline elements", which would include our good friend the span element, would it not? As a result, the width:150; and height:162; declarations are ignored by pretty much all browsers except MSIE, which will indeed apply CSS widths and heights to a span element. Now, what else?
"It's illegal for CSS lengths to not have any units!"
Right you are, although in practice most if not all browsers will respectively interpret 150 and 162 to be 150px and 162px, and then apply these 'actual' values to a relevant element. Another hand...
"The pink color name isn't recognized by the W3C."
Not currently, true, but it's on track to be.
For those of you not surfing with MSIE, here's what Joe's CSS looks like when applied to a corresponding div element:
This text will be copied onto the clipboard when you click the button below. Try it!
We will in due course copy the span element text to the id="holdtext" textarea element that follows the span element. In the tutorial's "The Text To Be Copied" section, Joe notes that the textarea field has
been made invisiblevia a display:none; style declaration. Actually, in CSS an "invisible" element still occupies its normal area on the page, you just can't see it, whereas display:none; goes beyond this and shrinks an element's area to 0. As a practical matter, it's not necessary to 'invisibilize' the textarea field, which can if desired be used to hold the This text will be copied... string in the first place.
Speaking of textarea dimensions, Joe did not equip the textarea element with cols and rows attributes, which are both #REQUIRED. It might seem silly to specify these attributes for a display:none; textarea field, but you gotta have 'em if you were to ever run this code through a validator.
Finally, the textarea element is followed by a button element that is meant to code a push button for triggering the script's ClipBoard( ) function and thus should be given a type="button" attribute. Without specifying its type, the button is in fact a submit button, not that we'll be submitting anything when we click the button given that the textarea and button elements do not have a form element parent.
Script element deconstruction
We begin by clicking the button, which calls the ClipBoard( ) function composing the script's script element. ClipBoard( )'s first statement assigns the span element's innerText to the textarea element's innerText:
function ClipBoard( ) {
holdtext.innerText = copytext.innerText;
The innerText property was implemented by Microsoft in MSIE 4: innerText [s]ets or retrieves the text between the start and end tags of the object.Of the OS X GUI browsers on my computer, innerText is supported by MSIE, Opera, Safari, and Chrome, but not by Firefox and Camino.
Much like the DOM textContent property, innerText only picks up the #PCDATA textual content of an object; for example, for a
<span id="span1">Some like it <strong>hot</strong></span>
span object,
document.getElementById("span1").innerText
returns the Some like it hot string but leaves the strong element markup behind.Joe reports that the script didn't work when he tried to use an
<input type="hidden">
element instead of the textarea element: this is because innerText must be paired with a non-empty element. Moreover, the Remarks section of the MSDN Library's innerText page states, The innerText property is valid for block elements only,suggesting that innerText cannot be used with inline elements, but the Applies To box at the bottom of the page makes it clear that this is not the case. Indeed, all content-holding elements are fair game for use with innerText except for applet, colgroup, dl, dt, frameset, head, noframes, noscript, object, optgroup, and style.
Now, how 'bout them hierarchy expressions, huh? You wouldn't think (or at least I didn't think) that holdtext.innerText and copytext.innerText would be sufficient to access the textarea element and the span element, respectively, and that an absolute
document.getElementById("holdtext").innerText = document.getElementById("copytext").innerText;
statement would be needed to send the span element text to the textarea element. To sort this matter out, I tested the
holdtext.innerText = copytext.innerText;
command with a visible textarea box. The verdict: yep, they're sufficient - the text was successfully copied and no errors were thrown.Relatedly, Joe notes,
In case you're wondering, I tried the script changing out name for id and the JavaScript wouldn't recognize it.It's true that both the W3C and Microsoft nix the use of the name attribute with the span element. But Joe definitely could have given the textarea element a name, put it in a form, and then referenced it with a
document.formObject.textareaName
-type expression; for MSIE/Opera users, document.all("textareaName")
could be used to reference a named textarea object even in the absence of a form.Let's move on to the next ClipBoard( ) statement:
Copied = holdtext.createTextRange( );
The createTextRange( ) method, and the TextRange object it returns, were implemented by Microsoft in MSIE 4 for Windows. The createTextRange( ) method
[c]reates a TextRange object for the element; in turn, the TextRange object
[r]epresents text in an HTML element.Notwithstanding its definition, which superficially doesn't seem to be any different than that for the textContent property - substitute String for TextRange and they're dead ringers - createTextRange( ) can only be applied to a handful of elements: body, button, input, and textarea. On my computer, createTextRange( ) is only supported by Opera, and Opera only supports it for the input and textarea elements. With MSIE 5.2.3 for Mac OS X, createTextRange( ) throws an Object doesn't support this property or method runtime error for all of the aforelisted elements.
So at least with Opera, we now have a This text will be copied onto the clipboard when you click the button below. Try it! TextRange object having a Copied identifier. I should point out, however, that there is a standard version of the above createTextRange( ) operation. The DOM Level 2 Range Specification defines a Range object representing a contiguous run of content in a document and also a document.createRange( ) method that returns such an object. We can easily create a Range containing the textarea element text with:
Copied = document.createRange( );
Copied.selectNodeContents(document.getElementById("holdtext"));
All of the non-MSIE OS X GUI browsers on my computer support the Range object and the document.createRange( ) method. Up to this point, then, there's no reason why the ClipBoard( ) function couldn't be formulated in a cross-browser manner given that the initial
holdtext.innerText = copytext.innerText;
assignment could also be achieved via either the textContent or innerHTML property. Unfortunately, ClipBoard( )'s final command thrusts us into 'irredeemably proprietary' territory:Copied.execCommand("Copy"); }
Also implemented by Microsoft in MSIE 4 for Windows, the execCommand( ) method
[e]xecutes a command on the current document, current selection, or the given range.execCommand( ) brings us into the realm of rich-text editing, more specifically, it can be used to create the rich-text sections of Web pages: inter alia, execCommand( ) can bold/italicize/underline text, set font sizes and typefaces, and create lists and tables, at least for those browsers that support it.
Apropos the "Click... It's Copied" tutorial, execCommand( ) can also carry out Cut, Copy, and Paste operations à la a word processor. In the event, running the
Copied.execCommand("Copy");
command with Opera threw a Type mismatch error, i.e., Opera evidently does not support the execCommand( ) method for the TextRange object. Ever the optimist, I tried with non-MSIE browsers to run execCommand( ) commands on a standard Range object, but those attempts were similarly unsuccessful.On the other hand, subsequent experimentation revealed that all of the OS X GUI browsers on my computer, including MSIE, support execCommand( ) for the document object, and on that note, we will continue our execCommand( ) conversation in the following entry.
reptile7
Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)