reptile7's JavaScript blog
Sunday, May 30, 2010
The Unideal Copy
Blog Entry #180

In today's post we will discuss the adaptation of the MSIE-for-Windows-specific script of HTML Goodies' "Click... It's Copied!" tutorial to other browsers. To refresh your collective memories, here's the JavaScript code that we would like to 'translate' if at all possible:
function ClipBoard( ) {
	holdtext.innerText = copytext.innerText;
	Copied = holdtext.createTextRange( );
	Copied.execCommand("Copy"); }
We noted two entries ago that non-MSIE browsers do not support the execCommand( ) method for the standard Range object, but they will allow us to pair execCommand( ) with the document object, so that's what we'll need to do. But there's actually a second and equally important support issue we need to consider vis-à-vis the Copied.execCommand("Copy"); command: What kind of browser support do we have out there for the copy execCommand( ) command?

Query it

The copy command appears on both Microsoft's list and Mozilla's list of supported execCommand( ) commands, but that doesn't mean we should take its support for granted.

Alongside the execCommand( ) method Microsoft implemented a queryCommandSupported( ) method that can be used to test support for a given execCommand( ) command independent of whether a document or any of its elements is user-editable or not. The queryCommandSupported( ) method acts on a single parameter, a case-insensitive string specifying the execCommand( ) command to be tested, and then [r]eturns a Boolean value that indicates whether the current command is supported on the current range. Like execCommand( ), queryCommandSupported( ) is applied by Microsoft to the document and TextRange objects and also to the mysterious controlRange collection.

Of the OS X GUI browsers on my computer, the queryCommandSupported( ) method is supported by Opera, Safari, and Chrome. With Safari and Chrome document.queryCommandSupported("copy"); returns true, but with Opera it returns false. In corroboration, at Mozilla's "Rich-Text Editing Demo" I am indeed able to copy text in the iframe field to the clipboard when using Safari and Chrome but not when using Opera.

The queryCommandSupported( ) method appears in Mozilla's list of document object methods but there's no separate page for it and Mozilla now maintains a separate page for it here. With Firefox and Camino*, document.queryCommandSupported returns function queryCommandSupported() { [native code] }** but document.queryCommandSupported("bold"); - bold being an execCommand( ) command that is definitely supported by these browsers - throws an NS_ERROR_NOT_IMPLEMENTED error. (The Dottoro Web Reference describes this situation as 'support but not implementation' - make of that what you will.)

*Camino does not have a native Error Console - download one here and follow the Terminal instructions on this page if necessary.

**MSIE 5.x for the Mac also returns function queryCommandSupported() { [native code] } for document.queryCommandSupported but returns undefined for any document.queryCommandSupported("commandID"); command.

Back at Mozilla's rich-text editing demo, my attempts to copy text in the iframe field to the clipboard when using Firefox and Camino met with failure, perhaps not so surprisingly given that the copy entry in Mozilla's list of execCommand( ) commands reads:

Copies the current selection to the clipboard. Clipboard capability must be enabled in the user.js preference file. See [1]

The [1] hyperlink leads to a "Setting Prefs for the Mozilla Rich Text Editing Demo" page specifying a set of user preferences that, for local experimentation purposes, can be added to Firefox in order to enable Firefox's support for the cut, copy, and paste execCommand( ) commands. I followed the page's instructions as they pertain to the Firefox/ and Camino/ folders in my ~/Library/Application Support/ directory, setting the capability.policy.allowclipboard.sites preference to Didn't help.

The MozillaZine Knowledge Base contains a "Granting JavaScript access to the clipboard" article that addresses why Mozilla-based browsers do not normally execute the cut/copy/paste commands:
By default, JavaScript is not allowed to read or set your clipboard data for security and privacy reasons. This is because Web site scripts can erase and replace what you currently have in your clipboard (data loss issue) and they can read whatever you have in your clipboard (security and privacy issue); as such, you should grant access with caution.
Interestingly, "Granting JavaScript access to the clipboard" links to a "Midas" page that in turn links to a "cuneAform HTML Editor" at which with Firefox and Camino the following alert( ) box rolls down from the browser window's title bar upon trying to invoke the editor's Copy selection, Cut selection, and Paste from clipboard operations:

['No programmatic access to cut/copy/paste' error]

Lastly, "Granting JavaScript access to the clipboard" says that Mozilla's browsers can use signed scripts to access the user's clipboard. We previously discussed signed scripts and their limitations in Blog Entry #158. IMO, the signed script business is much more trouble than it's worth, and as a Mac user I am unable to use Netscape's script-signing signtool program anyway, so perhaps we should just move on and concentrate on getting the "Click... It's Copied!" script to work with Safari and Chrome, huh?

Select and copy

According to Microsoft, the copy execCommand( ) command doesn't have an associated user interface and doesn't take a value, required or optional. We noted last time that Safari and Chrome follow the Microsoft execCommand( ) syntax model and not the Mozilla execCommand( ) syntax model; as a result, we'll be using document.execCommand("copy"); and not document.execCommand("copy", false, null); to copy a test string to the clipboard.

(Upon trying out the Mozilla rich-text editing demo and the cuneAform HTML editor at my local library today, I learned that the copy command causes MSIE 8 to pop up a confirm( )-like box asking whether or not to grant access to the clipboard - a "Community Content" commenter at Microsoft's copy page reports that MSIE 7 also does this - so it seems that copy does now have a user interface of sorts, even if it only involves making a true/false-type choice. Upon choosing true, I was able to copy text and subsequently paste it into a Microsoft Word window without any problems. Safari and Chrome do not dialogue the user in any way for cut, copy, and paste - maybe they should.)

But we'll actually need one more command to pull this off. In order to copy a string with document.execCommand("copy");, we'll first have to select that string, something Joe didn't have to do with his TextRange object. And to mimic the effect of Joe's code, we would like to select that string programmatically if at all possible, as opposed to selecting it mousically, which, after all, we could do, and then copy the string via the browser's Edit menu, without hassling with the execCommand( ) method in the first place.

As far as I know, there is no cross-browser way to programmatically select normal text (in, say, a p element) on a normal Web page. Microsoft equips its TextRange object with a select( ) method that can do this sort of thing, but the standard Range object unfortunately does not have such a method. However, classical JavaScript did give the client-side text, textarea, password, and fileUpload objects a select( ) method for selecting their values - see here, for example. It follows that retooling the "Click... It's Copied!" script for Safari and Chrome is a simple matter of
(a) putting the to-be-copied string in a visible and sized-as-desired textarea field (or text field if it's a short string) in the first place, and then
(b) recasting the ClipBoard( ) function as:
function ClipBoard( ) {
	document.getElementById("textareaID").select( );
	document.execCommand("copy"); }
Try it out below - upon clicking the button, your browser should select and copy to the clipboard the <p>This is a paragraph.</p> string, which can then be pasted wherever a Paste operation is applicable, IF you're using Safari (download it for free here) or Chrome (download it for free here), and I would think that it would work for MSIE 5.5+ as well (but I'd have to make a return trip to the library to confirm that).

There are a couple of drawbacks to the above select-and-copy method:
(1) Although CSS declarations can be used to format entire values of textarea/text objects, substrings of those values cannot be formatted individually.
(2) By default, textarea and text fields are user-editable, whereas you might want to prevent the user from editing your to-be-copied text. (Microsoft's contentEditable page claims that contentEditable applies to textarea/text fields. It should therefore be possible to use a contentEditable="false" assignment to choke off the editability of these fields, but my attempts to do so with the various non-MSIE OS X GUI browsers on my computer were unsuccessful.)

These disadvantages can be circumvented by a more laborious but more flexible select-and-copy method:
(1) Put the to-be-copied text in a separate document, where it can be marked up per your preference.
(2) Load the document into an iframe.
(3) Give the iframe element an id value and use document.getElementById("iframeID").contentDocument to access its document from the iframe's host document.
(4) To ensure the uneditability of your text, leave the designMode property of the iframe document off (I find that contentEditable="false" assignments have no effect at all on a designMode="on" document).
(5) The text can be selected with execCommand( )'s selectAll command prior to copying it.
function ClipBoard( ) {
	Copied = document.getElementById("iframeID").contentDocument;
	Copied.execCommand("copy"); }
Neither the copy command nor the selectAll command changes a document in any way, so you wouldn't think it'd be necessary to turn on a document's/element's editability for these commands, and this is indeed the case with Safari and Chrome (and also with Opera for selectAll); this would not be the case with Firefox and Camino, which do not 'expose' execCommand( ) as a 'member' of the document object in the absence of editability, but then again, we (well, I at least) can't copy with those guys anyway.

Markup subtraction

In the tutorial's "What If There Is Code?" section Joe briefly discusses the removeFormat execCommand( ) command, which [r]emoves the formatting tags from the current selection. The word "formatting" is too vague for my tastes so I decided to see what removeFormat would and would not subtract. Upon applying removeFormat to various elements, I can report that removeFormat will reliably remove the markup for inline text-related elements, specifically the font style elements (b, big, etc.) and the phrase elements (abbr, acronym, etc.) plus a few others (span, q, sup/sub, even font), but beyond that, what happens is highly browser-dependent: for example, Opera, Safari, and Chrome will remove the markup for an <a href="">I am a link</a> hyperlink, and thus convert I am a link to non-link text, but Firefox and Camino will not. The removeFormat command will not subtract the markup for a center element, which definitely counts as "formatting" in my book.

Joe seems to suggest that removeFormat removes <br> line breaks: not true.

Regarding the original "Click... It's Copied!" script, the use of removeFormat to remove markup would in fact be redundant because the to-be-copied string is accessed via the innerText property, which, as noted two entries ago, leaves behind all of the markup in the content of its 'calling object', including that for block-level elements, which is typically not removed by removeFormat.

In the next post, we'll wrap up our discussion of the execCommand( ) method by going through the Example on Microsoft's execCommand( ) page.


Due to Chrome dev's fix:

it seems that Chrome 6.0 dev does not set the text onto the clipboard any more.
I just rechecked the Copy function at Mozilla's rich-text editing demo with Safari 5 - that doesn't seem to work either. Ahhhh, I'm obsolete already... :-(
Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)