Da der programmatische Zugriff auf die Zwischenablage des Betriebssystems aus dem Browser heraus nicht standardisiert, bzw. mit reinem JavaScript gar nicht möglich ist, weichen viele Webanwendungen auf den Umweg über ein Flash Movie aus. Dabei wird ein transparentes Flash Movie über einen Bereich gelegt und mit dem Text versehen, der aus dem Browser in die Zwischenablage kopiert werden soll. Klickt der Nutzer auf diesen Bereich, kann Flash den Inhalt in die Zwischenablage kopieren. Eine bekannte Bibliothek für diesen Zweck ist ZeroClipboard [1] . Dabei kommuniziert ein JavaScript Objekt mit dem Flash Movie und kümmert sich um die Erzeugung, die korrekte Positionierung und das Entfernen der DOM Elemente auf der Webseite.
Basiert die Webanwendung vollständig auf einem komponentenbasierten Framework wie Vaadin/gwt, ist auch die Einbindung von ZeroClipboard über diesen Weg wünschenswert. Dieser Eintrag beschäftigt sich mit der Integration von ZeroClipboard in ein Vaadin Widget. Das Widget kann im Vaadin Addon Verzeichnis heruntergeladen werden [2] , der Sourcecode ist auf github.com verfügbar [3] .
Die Grundkomponente besteht dabei aus dem com.vaadin.terminal.gwt.client.ui.VButton. Dieses Widget bietet bereits alles, was ich von einem Knopf erwarte. Ergänzt werden muss nur noch die korrekte Initialisierung und Kommunikation mit ZeroClipboard sowie die Kommunikation mit der Serverkomponente. Das Widget soll dann folgende Funktionen bieten:
- Jede Position auf der Webseite in einer Vaadin Anwendung soll möglich sein
- Button neu erzeugen und entfernen, bei laufender Anwendung
- Der Knopf soll auch in Popups anwendbar sein (z.B. über das Vaadin addon PopupButton [4] )
- Mehrere Knöpfe gleichzeitig auf einer Seite darstellbar
- Über einen Listener wird die Anwendung über den Erfolg der Kopieren Aktion informiert
Das serverseitige API bleibt eher schlicht:
- Instanziierung wie ein gewöhnlicher Button
- #setClipboardText(String t)
- #addListener(ClipboardListener l)
Die Clientseite muss sich um die Anbindung an ZeroClipboard kümmern:
- JSNI Aufrufe für die Instanziierung, Positionierung und Registrierung von Callbacks
- für die korrekte Positionierung des Overlays:
- positionieren des Flash Movies bei onMouseOver
- verstecken des Flash Movies bei onMouseOut
- entfernen der DOM Element wenn der Button von der Oberfläche entfernt wird
Clientseitig wird der ZeroClipboard Client in der #updateFromUIDL Methode erzeugt, da erst zu diesem Zeitpunkt alle JavaScript Objekte im DOM verfügbar sind. Über ein Flag wird die Instanziierung auf den ersten Aufruf beschränkt:
1if (firstLoad) { 2 this.buttonId = ID_PREFIX + paintableId; // create a unique Id for the DOM Element 3 Element root = getElement(); 4 DOM.setElementAttribute(root, "id", buttonId); // add the id. this allows multiple clipboard buttons on the page 5 glueCopy(GWT.getModuleBaseURL(), root, buttonId); // glue the js and flash to the component 6 7 firstLoad = false; 8} 9 10// set the clipboard text to the flash movie 11setClipboardText(clipboardText, buttonId);
Der JSNI [5] Aufruf #glueCopy verbindet das DOM Element des Buttons mit einem neuen DOM Element, dem ZeroClipboard Client. Das Client Element wird dabei direkt unter dem Body des html eingehängt. Das Einhängen unter den von Vaadin generierten DOM Elementen des Buttons hat zwar Vorteile bei der Objektverwaltung und dem Eventhandling, führt aber besonders in Verbindung mit dem PopupButton zur falschen Positionierung des Knopfes auf der Oberfläche. Vaadin kommt dann mit der Größenberechnung des Buttons durcheinander.
1private native void glueCopy(String baseUrl, Element buttonElement, String buttonId)
2 /*-{
3 var path = baseUrl + 'ZeroClipboard10.swf';
4 $wnd.ZeroClipboard.setMoviePath(path);
5
6 var clip = $wnd.ZeroClipboard.newClient(); // new instance
7 clip.zIndex = 20001; // float just over a vaadin popup
8 clip.glue(buttonElement); // magic
9 clip.hide(); // initially hide the flash movie
10
11 var widget = this;
12
13 // notify me when the copy action is done:
14 var completeListener = function copyComplete(client, text) {
15 widget.@de.codecentric.vaadin.gwt.client.VCopy2ClipboardButton::onComplete()();
16 }
17 clip.addEventListener('complete', completeListener);
18
19 // notify me when the mouse leaves the flash movie:
20 var mouseoutListener = function mouseoutL() {
21 widget.@de.codecentric.vaadin.gwt.client.VCopy2ClipboardButton::onMouseout()();
22 }
23 clip.addEventListener('mouseout', mouseoutListener);
24
25 // store the object on the button
26 $doc.getElementById(buttonId).clip = clip;
27 }-*/;
Leider verhält sich Firefox bei den JSNI-Aufrufen anders als Chrome oder der IE.
Anstatt
1var clip = new $wnd.ZeroClipboard.Client();
muss im ZeroClipboard.js noch die Methode newClient() hinzugefügt werden, die einfach eine neue Instanz des Clients liefert.
Auch die Listener müssen vor der Registrierung in Variablen erfasst werden. Diese rufen jeweils einen Callback des Widgets auf:
Der Listener für das Event „complete“ wird aufgerufen, wenn die Kopieraktion in die Zwischenablage erfolgreich war. Der Listener für „mouseout“ informiert das Widget wenn die Maus das Flash Movie wieder verlässt. Dieses wird dann außehalb des Anzeigebereichs verschoben. Das Widget selber reagiert auf das „mouseover“ Event und plaziert das Flash Movie wieder über dem Knopf.
Einbindung
Das Widget wird wie ein normaler Button eingebunden:
1// Create a new instance
2Copy2ClipboardButton button = new Copy2ClipboardButton("Copy");
3// Set the text to copy
4button.setClipboardText("copy this to the clipboard");
5
6// The listener will trigger on successful copy action
7button.addListener(new ClipboardListener() {
8
9 @Override
10 public void copiedToClipboard(ClipboardEvent event) {
11 mainWindow.showNotification("Copied to the clipboard");
12 }
13});
Copy2ClipboardButton im Popup
Wird der neue Copy2ClipboardButton in einem Popup benutzt ergeben sich zwei Besonderheiten:
Da der Button serverseitig nicht entfernt sondern nur versteckt wird muss in der #onUnload Methode unterschieden werden, ob die ZeroClipboard Objekte behalten oder entfernt werden. Bei der Instanziierung der Serverkomponente muss in diesem Fall das retainClipElement Flag gesetzt werden.
Außerdem wird das Popup beim Klick auf das Flash Movie direkt geschlossen, da aus Vaadin Sicht ein Event außerhalb des Popup triggert. Um das Popup direkt wieder zu öffnen muss serverseitig ein ClipboardListener definiert werden, der dies übernimmt:
1Copy2ClipboardButton copyButton = new Copy2ClipboardButton("copy", true); // set retainClipElement to true
2final PopupButton popup = new PopupButton("show popup");
3popup.addComponent(copyButton);
4
5copyButton.addListener(new ClipboardListener() {
6
7 @Override
8 public void copiedToClipboard(ClipboardEvent event) {
9 popup.setPopupVisible(true);
10 }
11});
Referenzen
[1] ZeroClipboard bei github
[2] copy2clipboard im Vaadin Addon Verzeichnis
[3] copy2clipboard bei github
[4] Vaadin PopupButton addon
[5] JSNI Basics bei google
Weitere Beiträge
von Henning Treu
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Henning Treu
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.