Search This Blog

Thursday, February 26, 2009

Copy to Clipboard Functionality with JavaScript and Flash 10: Creating a Loop to Instantitate Multiple Copy Buttons


As a security precaution, JavaScript is not allowed to read from or write to the system clipboard. Up until Flash 10 came out with a new security feature that required copying to the clipboard to be instantiated directly from a user clicking on a Flash control, it was a simple thing to download a tiny Flash file, include it invisibly on a web page, and send it information intended for the clipboard via JavaScript. When Flash 10 came out, however, that functionality was broken.

Not any more. There is a project on GoogleCode developed by Joseph Huckaby known as zeroclipboard that is also backwards compatible with Flash 9:
The Zero Clipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie, and a JavaScript interface. The "Zero" signifies that the library is invisible and the user interface is left entirely up to you.

This library is fully compatible with Flash Player 10, which requires that the clipboard copy operation be initiated by a user click event inside the Flash movie. This is achieved by automatically floating the invisible movie on top of a DOM element of your choice. Standard mouse events are even propagated out to your DOM element, so you can still have rollover and mouse down effects.
To date, adding the functionality to multiple controls requires you to call multiple instances, unless the size of the control remains constant. Joe put together a demo version of the latter using JQuery on this page.

I also put together a non-JQuery technique using straight JavaScript below. The code is heavily commented to explain what I am doing in each step and where everything goes.

Step 1: Find the "glue" function and space it down one space to open a blank line. Paste the following "addon" function (including ending comma!) directly above the "glue" function in the space you opened--it recreates only that portion of the "glue" function that we still need to get hover effects to work properly.
addon: function (elem) {
    this.domElement = ZeroClipboard.$(elem);
},
Step 2: Replace the original ZeroClipboard.Client() function with the following, which remembers the unique ID of each copy button (that's what the "divId" is about):
// modified ZeroClipboard.Client() function
Client: function (elem, divId) {
    // constructor for new simple upload client
    this.handlers = {};

    // unique ID
    this.id = ZeroClipboard.nextId++;
    this.movieId = 'ZeroClipboardMovie_' + this.id;

    // register client with singleton to receive flash events
    ZeroClipboard.register(this.id, this);

    // create movie
    if (elem) this.glue(elem);

    // if passed, register the unique div id
    if (divId) this.divId = divId;
}
Step 3: The following init() function is meant to replace the existing one. It loops through all objects of a given type looking for a particular class. Your HTML page will need to assign both a common class and a unique ID to these elements in order for it to work.

Example:
<div id="unique1" class="common">Copy!</div>
<div id="unique2" class="common">Copy!</div>
and so on.

Make sure you change the SOME_CLASS_NAME variable to whatever common class name you are using. As with the original script, you will also need to determine what text to copy and work out how that will be performed.
function init() {
    // set the SOME_CLASS_NAME variable to whatever common class you are
    // using. Our example above uses the class "common"
    var SOME_CLASS_NAME = 'common';
    // Gather together all the elements we're interested in 'divs' variable
    // (could be any element including '*' to grab all elements), and
    // initialize 'node' variable.
    var divs = document.getElementsByTagName('div'),
        node = '';

    // This kind of unorthodox looping has been shown to be faster with
    // benchmarking tests when looping over DOM elements
    // (http://blogs.sun.com/greimer/entry/best_way_to_code_a). Note that
    // 'node' is the unique instance of the container in each loop.
    for (; node = divs[i++];) {

        // If the elements we've collected together have the class
        // 'SOME_CLASS_NAME' set, we want them. We need to
        // ensure that they have a unique id specified as well.
        if (node.className && /SOME_CLASS_NAME/.test(node.className)) {

            // We read the id of the element automatically (the reason a unique ID
            // needs to be set in the first place).
            var buttonId = node.id;
            // We send the newly read ID for registration in the modified
            // ZeroClipboard.Client function shown above.
            clip = new ZeroClipboard.Client('', buttonId);
            // We take advantage of new 'addon' function above.
            clip.addon(buttonId);
            clip.setHandCursor(true);
            clip.addEventListener('mouseOver', function (client) {

                // 'client' is the unique instance of our movie container, so we need it
                // to prefix the setText function. (Our example below uses the instantiated
                // id of the copy button div to grab its value--$(client.divId).value
                // --though it is not likely that this will be the copy element we want.
                // A little ingenuity is probably in order here for the setText text.) 
                client.setText($(client.divId).value);

                // Here we pass the copy button id on mouseover.
                ZeroClipboard.$(buttonId).addClass('hover');
            });
            clip.addEventListener('mouseOut', function (client) {
                // We again pass the copy button id on mouseout.
                ZeroClipboard.$(buttonId).removeClass('hover');
            });

            // We read/set button_width and button_height.
            var button_width = $(buttonId).offsetWidth,
                button_height = $(buttonId).offsetHeight;

            // We create a floating DIV...
            var div = document.createElement('div'),
                zIndex = 99;
            // Check the z-index...
            if ($(buttonId).style && $(buttonId).style.zIndex) {
                zIndex = parseInt($(buttonId).style.zIndex) + 1;
            }
            div.style.position = 'absolute';
            div.style.left = 0;
            div.style.top = 0;
            div.style.width = button_width + 'px';
            div.style.height = button_height + 'px';
            div.style.zIndex = zIndex;
            // We need the button id so that we can append its movie instance.
            $(buttonId).appendChild(div);
            // All finished! :)
            div.innerHTML = clip.getHTML(button_width, button_height);
        }
    }
}

5 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. I'm having clipboard issues with chrome, mainly the position after mouseout. It looks like it's moving the flash element up for some reason so if I hover again over the 'button' it does not set the text. I can't recreate this in IE or FF. Any suggestions? Thanks!

    ReplyDelete
  3. Hi Griff. I don't have any suggestions without being able to look at an example of the problem. If you can, e-mail me a zipped copy of your JS, CSS, and HTML files at "mrrena AT gmail DOT com," and I'll have a look. Just give it the subject "Zero-Clipboard not working on Chrome" so I'll know who you are. Thanks!

    ReplyDelete
  4. Can you show us a working demo of your code, or a downloadable code will be much better.

    ReplyDelete
  5. Hi Danish.

    That's a great idea, though unfortunately I won't have time to get to it for a while at least. My mom was recently killed in a car accident, and I'm now playing a game of catch-up, behind on just about everything. :(

    P.S. Note that ZeroClipboard is now a repo on GitHub: https://github.com/jonrohan/ZeroClipboard

    ReplyDelete