5 - Custom Command (Dokument von URL laden)

Nächstes Tutorial ⯈

In den vorherigen Tutorials haben wir den Viewer eingebunden, ein Dokument zur Anzeige gebracht und einige Standardfunktionalität ergänzt. In Toolbar und Commands haben wir dazu einige der Standard-Commands aus JWT in die Toolbar gehängt.

In diesem Tutorial wollen wir unseren ersten eigenen Command implementieren. Unser Command soll einen Dialog öffnen, in dem die URL für ein Dokument eingetragen werden kann. Das Dokument soll dann in den Viewer geladen werden.

Folgendes wollen wir ergänzen:

  • Neuer Button in der Toolbar mit einem Öffnen-Symbol
  • Anzeige eines Dialogs in dem eine URL eingetragen werden kann.
  • Nach Klick auf den Ok-Button Laden der eingebenen URL in den Viewer.

Das Ergebnis des Tutorials kann in https://github.com/levigo/jwt-getting-started/tree/master/jwt-tutorial-005 überprüft werden.

Projektstruktur

Folgende Klassen und Dateien ergänzen wir in diesem Tutorial:

Verzeichnisstruktur
├── src/main/java/ 
│	└── org/jadice/client/ 
│		├── ApplicationEntryPoint.java			- kleines Refactoring bezüglich loadDocument()
│		├── commands/
│ 		│	├── OpenDocumentCommand.java		- unser neuer Command zum Öffnen von Dokumenten
│ 		│ 	└── CustomActions.java				- neue Factory zum Erstellen unserer Commands
│ 		├── api
│ 		│	└── JadiceApi.java					- neue Klasse zum Kapseln unserer API-Funktionalität
│		└── ui/
│			├── JadiceWidget.java				- ergänzen der Toolbar um das neue Command
│ 			└── resources/
│ 				├── CustomMessages.java			- neues Interface zum Zugriff auf unsere i18n messages
│ 				└── CustomImages.java			- neues Interface zum Zugriff auf unsere Icons
└── src/main/resources/ 
	└── org/jadice/client/ui/resources/
		├── CustomMessages_de.properties		- unsere neuen i18n messages in deutsch
		├── CustomMessages_en.properties		- unsere neuen i18n messages in englisch
		└── open.png							- unser neues Icon für die Toolbar

Load document

Bisher laden wir unser initiales Dokument direkt in der Klasse ApplicationEntryPoint. Zum Kapseln unserer Funktionalität, erstellen wir nun die Klasse JadiceApi. Hierhin verschieben wir die Methode loadDocument():

org.jadice.client.api.JadiceApi
package org.jadice.client.api;

import org.jadice.shared.model.UrlSource;

import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.levigo.jadice.document.Document;
import com.levigo.jadice.web.client.PageView;
import com.levigo.jadice.web.client.reader.Reader;

public class JadiceApi {

	private PageView pageView;

	public JadiceApi(PageView pageView) {
		this.pageView = pageView;
	}

	/**
	 * Loads the document referenced by the passed url.
	 * 
	 * @param url
	 */
	public void loadDocument(final String url) {
		Reader r = new Reader();
		r.append(new UrlSource(url));
		r.complete(new AsyncCallback<Document>() {

			@Override
			public void onSuccess(Document doc) {
				pageView.setDocument(doc);
			}

			@Override
			public void onFailure(Throwable caught) {
				caught.printStackTrace();
				Window.alert("Cant load document from \"" + url + "\".");
			}
		});
	}
}


Damit unser neuer Command Zugriff auf unsere JadiceApi-Instanz erhält, fügen wir sie dem Kontext hinzu. So müssen wir JadiceApi nicht explizit übergeben und können den Injektions-Mechanismus des Action und Command Frameworks nutzen. Das ganze machen wir in ApplicationEntryPoint.onModuleLoad():

org.jwt.client.ApplicationEntryPoint
...

	@Override
	public void onModuleLoad() {
		...
		rootPanel.add(jadiceWidget);
		initJadiceApi();
		GWT.log("jwt tutorial loaded");
	}

...

	private void initJadiceApi() {
		PageView pageView = jadiceWidget.getPageView();
		JadiceApi jadiceApi = new JadiceApi(pageView);
		// add the JadiceApi to the context of the JadiceWidget.
		Context.get(jadiceWidget).add(jadiceApi);
	}

...


In ApplicationEntryPoint selbst laden wir nun initial kein Dokument mehr.

Ressourcen

Für unseren neuen Command benötigen wir folgende Ressourcen:

  • Internationalisierte Texte (i18n)
  • Ein neues Icon

Für beides nutzen wir die Standardmechanismen von GWT. Details zu diesen Mechanismen können unter den folgenden Links nachgelesen werden:

Messages

Wir benötigen verschiedene Texte, wie z.B. den Tooltip des Commands oder die Infotexte des Dialogs. Zum Kapseln und Internationalisieren dieser Texte erstellen wir das Interface CustomMessages, das Messages erweitert. Wir stellen jeden Text über eine separate Methode zur Verfügung:

org.jadice.client.ui.resources.CustomMessages
package org.jadice.client.ui.resources;

import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.Messages;

public interface CustomMessages extends Messages {

	public CustomMessages INSTANCE = GWT.create(CustomMessages.class);

	String openDocument_description();
	String openDocument_dialog_label();
	String openDocument_dialog_ok();
}


Die Texte selbst legen wir als Property-Dateien in den Varianten deutsch und englisch ab:

src/main/resources/org/jadice/client/ui/resources/CustomMessages_de.properties
openDocument_description=Dokument öffnen
openDocument_dialog_label=Dokument-URL eingeben
openDocument_dialog_ok=OK
src/main/resources/org/jadice/client/ui/resources/CustomMessage_en.properties
openDocument_description=Open document
openDocument_dialog_label=Fill in document URL
openDocument_dialog_ok=OK


Bei Verwendung der Messages zieht dann jeweils die Variante, die in der locale-Konfiguration des Moduls vorgegeben wurde (vgl. 0 - GWT Projekt, basierend auf Spring Boot, einrichten)

Icon

Für unseren Command wollen wir folgendes png-Icon verwenden:

Für das Icon legen wir ebenfalls ein neues Interface CustomImages an, dass hier ClientBundle erweitert. Jedes Icon wird über eine separate Methode als ImageResource zur Verfügung gestellt. In der jeweiligen @Source-Annotation definieren wir den Speicherort unserer Icons.

org.jadice.client.ui.resources.CustomImages
package org.jadice.client.ui.resources;

import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;

public interface CustomImages extends ClientBundle {

	public CustomImages INSTANCE = GWT.create(CustomImages.class);

	@Source("org/jwt/client/ui/resources/open.png")
	public ImageResource open();
}


Das Icon legen wir, wie in der @Source-Annotation angegeben, hier ab:

src/main/resources/org/jadice/client/ui/resources/open.png


Alternativ können die Icons auch als Icon Font zur Verfügung gestellt werden. TODO Link zur Doku


OpenDocumentCommand

Für den eigentlichen Command erstellen wir die Klasse OpenDocumentCommand, die AbstractContextCommand erweitert. AbstractContextCommand liefert uns die Möglichkeit den Injektions-Mechanismus des Action und Command Frameworks zu nutzen, der die benötigten Objekte aus dem Kontext holt. In unserem Fall wollen wir die oben erstellte Instanz von JadiceApi injiziert bekommen.


Benötigen wir in unserem Command z.B. die PageView oder das aktuelle Dokument, können wir auch bereits vorbereitete Klassen erweitern:

  • AbstractPageViewCommand - Zugriff auf die PageView
  • AbstractDocumentCommand - Zugriff auf die PageView und das aktuelle Dokument

Weitere Beispiele finden sich in der Klassenhierarchie und in den Showcases


In OpenDocumentCommand erstellen wir einen Dialog, über den eine URL eingegeben werden kann. Nach Klick auf den OK-Button wird die damit referenzierte Resource in den Viewer geladen .

org.jadice.client.commands.OpenDocumentCommand
package org.jadice.client.commands;

import org.jadice.client.api.JadiceApi;
import org.jadice.client.ui.resources.CustomMessages;

import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.levigo.jadice.web.client.commands.AbstractContextCommand;
import com.levigo.jadice.web.client.util.command.Argument;

/**
 * Shows a dialog where the user can input an url. When pressing ok, we try to
 * load this url as a document.
 */
public class OpenDocumentCommand extends AbstractContextCommand {

	private PopupPanel dialog = null;

	private JadiceApi jadiceApi;

	@Override
	protected void execute() {
		if (dialog == null) {
			dialog = createOpenDocumentDialog();
		}

		if (dialog.isShowing()) {
			dialog.hide();
		} else {
			dialog.show();
		}
	}

	private PopupPanel createOpenDocumentDialog() {
		VerticalPanel panel = new VerticalPanel();

		Label label = new Label(CustomMessages.INSTANCE.openDocument_dialog_label());
		panel.add(label);

		TextBox inputBox = new TextBox();
		inputBox.getElement().getStyle().setMarginTop(5, Unit.PX);
		inputBox.getElement().getStyle().setMarginBottom(5, Unit.PX);
		inputBox.setSize("400px", "100%");
		panel.add(inputBox);

		Button okButton = new Button(CustomMessages.INSTANCE.openDocument_dialog_ok());
		okButton.addClickHandler(event -> {
			openUrl(dialog, inputBox);
		});
		panel.add(okButton);

		PopupPanel dialog = new PopupPanel();
		dialog.setPopupPosition(100, 100);
		dialog.setWidget(panel);
		dialog.getElement().getStyle().setZIndex(5);

		return dialog;
	}

	private void openUrl(PopupPanel dialog, TextBox inputBox) {
		String url = inputBox.getValue();
		jadiceApi.loadDocument(url);
		dialog.hide();
	}

	/**
	 * {@link JadiceApi} is injected by the action and command framework from the
	 * context.
	 * 
	 * @param jadiceApi
	 */
	@Argument
	public void setJadiceApi(JadiceApi jadiceApi) {
		this.jadiceApi = jadiceApi;
	}
}


Die einzelnen Methoden in Kürze:

  • execute()
    Bei der Ausführung jedes Commands wird die Methode execute() aufgerufen. Hier ist der Einstieg für die tatsächliche Funktionalität. In unserer Implementierung erstellen wir den Dialog und togglen seine Sichtbarkeit.
  • createOpenDocumentDialog()
    Hier bauen wir mit GWT-Mitteln einen einfachen Dialog. Wir stylen den Dialog nur rudimentär. Der Dialog selbst enthält folgende Elemente:
    • Label - Ein Beschreibungstext. Den Inhalt holen wir aus dem oben erstellten Interface CustomMessages.
    • TextBox - Hier kann vom User die gewünschte URL eingegeben werden.
    • Button - Unserem Ok-Button fügen wir einen ClickHandler hinzu, der die unten stehende openUrl()-Methode aufruft.
  • openUrl()
    Die eingegebene URL wird über JadiceApi geladen.
  • @Argument setJadiceApi()
    Durch Angabe der @Argument-Annotation am Setter setJadiceApi(), injiziert uns das Action und Command Framework die JadiceApi-Instanz automatisch aus dem Kontext.


Commands bieten noch weitere Methoden, die wir überschreiben und anpassen können. Erwähnenswert sind z.B.:

  • canExecute()
    Hier können wir entscheiden, ob der Command aktiviert ist oder nicht
  • isSelected()
    Ist der Command selektiert? Nützlich z.B. bei Funktionen, die an- und ausgeschaltet werden können.

Action

Nun haben wir alle Voraussetzungen geschaffen, um das Command in die Toolbar einzuhängen. Wie in Toolbar und Commands erwähnt, müssen wir unseren Command über eine Action an das GUI-Element binden. In unserem Beispiel ist das der neue Button in der Toolbar.

Um die Generierung der Action zu kapseln, erstellen wir die Klasse CustomActions, die uns als Factory dient.

org.jadice.client.commands.CustomActions
package org.jadice.client.commands;

import org.jadice.client.ui.resources.CustomImages;
import org.jadice.client.ui.resources.CustomMessages;

import com.jadice.web.util.icon.client.EffectIcons;
import com.jadice.web.util.icon.client.StateEffectIcon;
import com.jadice.web.util.icon.client.StateIcon;
import com.levigo.jadice.web.client.util.action.Action;
import com.levigo.jadice.web.client.util.action.KeyStroke;
import com.levigo.jadice.web.client.util.action.RegisteredAction;
import com.levigo.jadice.web.client.util.context.Context;

public class CustomActions {

	public static Action openDocumentAction(KeyStroke keyStroke, Context context) {

		String description = CustomMessages.INSTANCE.openDocument_description();
		StateIcon stateIcon = new StateEffectIcon(EffectIcons.getEffectIcon(CustomImages.INSTANCE.open()));
		OpenDocumentCommand openDocumentCommand = new OpenDocumentCommand();

		return new RegisteredAction(description, keyStroke, stateIcon, openDocumentCommand, context);
	}
}


Die statische Methode openDocumentAction() erzeugt die Action inklusive Icon, Tooltip und Tastaturkürzel. Den Tooltip und das Icon holen wir aus unseren neu angelegten Interfaces CustomMessages und CustomImages. Beim Erstellen der Action nutzen wir den übergebenen KeyStroke und Context.


CustomActions kann natürlich als Factory für weitere eigene Actions ergänzt werden.


Einhängen in die Toolbar

Zu guter Letzt müssen wir die Action noch in unserer Toolbar ergänzen. Wir passen dazu JadiceWidget.createToolbar() an und fügen die Action vorne an die Toolbar. Als Tastaturkürzel nutzen wir Strg + O. Die anderen Commands separieren wir optisch noch durch den Aufruf von toolbar.addSeparator().

org.jadice.client.ui.JadiceWidget
...

import org.jadice.client.commands.CustomActions;

...


	private AbstractBar createToolbar(final Context context) {
		final HorizontalToolbar toolbar = new HorizontalToolbar();

		toolbar.add(new JadiceDefaultButton(CustomActions.openDocumentAction(new KeyStroke(Keys.O, Modifier.CTRL), context)));
		toolbar.addSeparator();
		toolbar.add(new JadiceDefaultButton(DefaultActions.zoomInAction(new KeyStroke(Keys.PLUS), context)));
		...
	}

...

Zusammenfassung

In diesem Tutorial haben wir unseren ersten eigenen Command, den OpenDocumentCommand, erstellt. In unserer Toolbar ist nun ein neuer Button verfügbar, der einen Dialog öffnet. Hier können wir eine URL eingeben, die über Klick auf OK in den Viewer geladen wird.

Wir haben dabei den Injektions-Mechanismus des Action und Command Frameworks mit der @Argument-Annotation kennengelernt. Dazu haben wir unseren bestehenden Code leicht refakturiert und unsere Funktionalität zum Laden von Dokumenten in der Klasse JadiceApi gekapselt. Eine JadiceApi-Instanz fügen wir dem Kontext hinzu. Damit wird sie dem Command automatisch injiziert.

Unsere Message- und Icon-Resourcen verwalten wir über die Standard GWT-Mechanismen Messages und ClientBundle.

Hier sehen Sie das Ergebnis beim Laden der Datei https://levigo.de/fileadmin/download/jadicewebtoolkit.pdf


Nächstes Tutorial ⯈