Overview

Copy to clipboard with vaadin

No Comments

Using only standard javascript techniques it is not possible to copy text from the browser to the users clipboard. The only cross-platform method for web applications utilizes a Flash movie. Therefore a transparent Flash movie floats above a div element. A javascript on the web page can set the text to copy to the Flash movie by Adobes ExternalInterface API. Flash is able to copy the text to the clipboard after the user clicks an the transparent movie.
A widely used framework which bundles this functionality is ZeroClipboard [1]. The ZeroClipboard.js communicates with the provided Flash movie and creates, repositions and removes the corresponding DOM elements on the web page.
For component based web applications like vaadin it would be nice to have the complete javascript and Flash magic bundled into a ready-to-use widget. This blog post is about integrating ZeroClipboard into a vaadin widget. The widget is available in the vaadin addon directory [2], the sources can be found on github [3].

As a foundation for the new widget the com.vaadin.terminal.gwt.client.ui.VButton is used. It already provides everything I would expect from a Button. Only the initialization and communication with the ZeroClipboard lib and the communication with the server side vaadin component must be added. The new widget should then provide the following:

  • the button should behave correctly in any position on the page
  • add/remove buttons during runtime
  • even in popups the button must be applicable (e.g using the Vaadin PopupButton addon [4])
  • multiple buttons on the page
  • provide a listener to register for successful copy actions

The server side API is kept simple:

  • construction like a usual Button
  • #setClipboardText(String t)
  • #addListener(ClipboardListener l)

The client side is responsible for the ZeroClipboard handling:

  • JSNI calls for instance creation, repositioning, callback registration
  • to provide accurate repositioning:
    • show the Flash movie on mouseover
    • hide the Flash movie an mouseout
  • remove the DOM element from the page if the button is removed from the page

The ZeroClipboard client will be created in the #updateFromUIDL method since all javascript objects are available now. A flag limits instantiation to the first call:

if (firstLoad) {
    this.buttonId = ID_PREFIX + paintableId; // create a unique Id for the DOM Element
    Element root = getElement();
    DOM.setElementAttribute(root, "id", buttonId); // add the id. this allows multiple clipboard buttons on the page
    glueCopy(GWT.getModuleBaseURL(), root, buttonId); // glue the js and flash to the component:
 
    firstLoad = false;
}
 
// set the clipboard text to the flash movie
setClipboardText(clipboardText, buttonId);

The JSNI call [5] in #glueCopy connects the DOM element of the button with the new DOM element of the ZeroClipboard client. The client element is directly added to the page body. The placing of the client element inside the Vaadin generated DOM element might leed to simpler object and event handling but leads to missplacement of the whole widget when positioned in a popup. In this case Vaadin gets confused during size calculation of the button.

    private native void glueCopy(String baseUrl, Element buttonElement, String buttonId)
    /*-{
        var path = baseUrl + 'ZeroClipboard10.swf';
        $wnd.ZeroClipboard.setMoviePath(path);
 
        var clip = $wnd.ZeroClipboard.newClient(); // new instance
        clip.zIndex = 20001; // float just over a vaadin popup
        clip.glue(buttonElement); // magic
        clip.hide(); // initially hide the flash movie
 
        var widget = this;
 
        // notify me when the copy action is done:
        var completeListener = function copyComplete(client, text) { 
                widget.@de.codecentric.vaadin.gwt.client.VCopy2ClipboardButton::onComplete()(); 
        }
        clip.addEventListener('complete', completeListener);
 
        // notify me when the mouse leaves the flash movie:
        var mouseoutListener = function mouseoutL() { 
                widget.@de.codecentric.vaadin.gwt.client.VCopy2ClipboardButton::onMouseout()(); 
        }
        clip.addEventListener('mouseout', mouseoutListener);
 
        // store the object on the button
        $doc.getElementById(buttonId).clip = clip;
     }-*/;

Unfortunately Firefox behaves different on JSNI calls from Chrome and IE.
Instead of

var clip = new $wnd.ZeroClipboard.Client();

the construction of the client must be handled in a separate method newClient() inside ZeroClipboard.js. Even the listeners must be assigned to separate variables before registration. They will call back methods an the widget: The listener for the event “complete” will be called for a successful copy to clipboard action. The listener for “mouseout” lets the widget know the mouse left the Flash movie. It will then be hidden outside the viewport of the web page. The widget itself reacts to the “mouseover” event to show the Flash movie above the button.

Basic usage

Using the Copy2Clipboard button is straight forward:

// Create a new instance
Copy2ClipboardButton button = new Copy2ClipboardButton("Copy");
// Set the text to copy
button.setClipboardText("copy this to the clipboard");
 
// The listener will trigger on successful copy action
button.addListener(new ClipboardListener() {
 
    @Override
    public void copiedToClipboard(ClipboardEvent event) {
        mainWindow.showNotification("Copied to the clipboard");
    }
});

Copy2ClipboardButton in a popup

If you want to use the Copy2ClipboardButton inside a popup you have to deal with two things:
Since the server side button will not be destroyed but only set to invisible the #onUnload method must decide whether to destroy or keep the ZeroClipboard DOM objects. You can tell the Copy2ClipboardButton during instantiation to behave the latter way using the retainClipElement flag.
In addition the popup will be closed when clicking on the flash movie because the event triggered outside of the popup. In order to reopen the popup instantly you have to register a ClipboardListener:

Copy2ClipboardButton copyButton = new Copy2ClipboardButton("copy", true); // set retainClipElement to true
final PopupButton popup = new PopupButton("show popup");
popup.addComponent(copyButton);
 
copyButton.addListener(new ClipboardListener() {
 
	@Override
	public void copiedToClipboard(ClipboardEvent event) {
	    popup.setPopupVisible(true);
	}
});

References
[1] ZeroClipboard on github
[2] copy2clipboard in the vaadin addon directory
[3] copy2clipboard on github
[4] Vaadin PopupButton addon
[5] JSNI Basics at google

Comment

Your email address will not be published. Required fields are marked *