From 0c429f7739771c0abab7326f906e4a3e05603c88 Mon Sep 17 00:00:00 2001 From: Erik Lumme Date: Tue, 12 Sep 2017 11:30:37 +0300 Subject: [PATCH] Migrate CreatingAUIExtension --- .../articles/CreatingAUIExtension.asciidoc | 172 ++++++++++++++++++ documentation/articles/contents.asciidoc | 1 + 2 files changed, 173 insertions(+) create mode 100644 documentation/articles/CreatingAUIExtension.asciidoc diff --git a/documentation/articles/CreatingAUIExtension.asciidoc b/documentation/articles/CreatingAUIExtension.asciidoc new file mode 100644 index 0000000000..8c0fcd8fc2 --- /dev/null +++ b/documentation/articles/CreatingAUIExtension.asciidoc @@ -0,0 +1,172 @@ +[[creating-a-ui-extension]] +Creating a UI extension +----------------------- + +An *Extension* is an entity that is not a full-fledged UI component, but +is instead used to enhance or extend the functionality of an existing +component (or connector, more generally.) Unlike components, extensions +cannot be detached and reattached once they are attached to their +target. + +Extensions usually consist of a pair of `Connector`{empty}s like components do. +Hence, they can use the regular shared state and RPC mechanisms to +communicate between the client and the server. Extensions may or may not +have a UI. They can create and display widgets on the client side, but +are not part of the regular layout hierarchy. + +We will rewrite the +https://vaadin.com/directory/component/refresher[Refresher] add-on as an +extension. The Refresher causes the client to "ping" the server at +regular intervals, allowing the server to keep the client up-to-date if +the application state is changed eg. by a background thread (because of +the way Vaadin works, the server cannot itself initiate communication.) + +We start by writing the barebones server-side class for our extension: + +[source,java] +.... +public class Refresher extends AbstractExtension { + public Refresher(UI ui) { + extend(target); + } +} +.... + +Two things to note: + +* If we were writing a component, we would probably want to inherit from +`AbstractComponent`. Here, we inherit from `AbstractExtension` instead. +* The connector that should be extended is passed to the constructor, +which then uses the protected `extend(Connector)` method to attach +itself to the target connector. In this case it does not make much sense +attached to individual components, so the constructor only accepts `UI`. + +Next, the Refresher needs an RPC interface to ping the server and a +shared state to keep track of the interval. These are rather trivial: + +[source,java] +.... +public interface RefresherRpc extends ServerRpc { + public void refresh(); +} +.... + +[source,java] +.... +public class RefresherState extends SharedState { + public int interval; +} +.... + +The client-side connector is just like a component connector except that +we inherit from `AbstractExtensionConnector`, not +`AbstractComponentConnector`. We do not write a client-side widget at +all, because the Refresher does not have a UI. + +We create a `Timer` instance that calls the `refresh` RPC method when +run. In `onStateChange()`, we know that either the interval, enabled +state, or both have changed, so we always cancel a possible +currently-running timer and schedule a new one if we're enabled. We also +remember to cancel the timer when the extension is detached. + +[source,java] +.... +@Connect(Refresher.class) +public class RefresherConnector extends AbstractExtensionConnector { + + private Timer timer = new Timer() { + @Override + public void run() { + getRpcProxy(RefresherRpc.class).refresh(); + } + }; + + @Override + public void onStateChanged(StateChangeEvent event) { + super.onStateChanged(event); + timer.cancel(); + if (isEnabled()) { + timer.scheduleRepeating(getState().interval); + } + } + + @Override + public void onUnregister() { + timer.cancel(); + } + + @Override + protected void extend(ServerConnector target) { + // Nothing for refresher to do here as it does not need to access the + // connector it extends + } + + @Override + public RefresherState getState() { + return (RefresherState) super.getState(); + } +} +.... + +Finally, we add an event listener interface and some accessor methods to +`Refresher`. There is nothing extension-specific in the following code: + +[source,java] +.... +public interface RefreshListener { + static Method METHOD = ReflectTools.findMethod(RefreshListener.class, + "refresh", RefreshEvent.class); + + public void refresh(RefreshEvent refreshEvent); +} + +public class RefreshEvent extends EventObject { + public RefreshEvent(Refresher refresher) { + super(refresher); + } + + public Refresher getRefresher() { + return (Refresher) getSource(); + } +} + +public Refresher(UI ui) { + registerRpc(new RefresherRpc() { + @Override + public void refresh() { + fireEvent(new RefreshEvent(Refresher.this)); + } + }); + extend(ui); +} + +@Override +public RefresherState getState() { + return (RefresherState) super.getState(); +} + +public void setInterval(int millis) { + getState().interval = millis; +} + +public int getInterval() { + return getState().interval; +} + +public void setEnabled(boolean enabled) { + getState().enabled = enabled; +} + +public boolean isEnabled() { + return getState().enabled; +} + +public void addRefreshListener(RefreshListener listener) { + super.addListener(RefreshEvent.class, listener, RefreshListener.METHOD); +} + +public void removeRefreshListener(RefreshListener listener) { + super.removeListener(RefreshEvent.class, listener, + RefreshListener.METHOD); +} +.... diff --git a/documentation/articles/contents.asciidoc b/documentation/articles/contents.asciidoc index 486416cf5b..19f999a992 100644 --- a/documentation/articles/contents.asciidoc +++ b/documentation/articles/contents.asciidoc @@ -46,3 +46,4 @@ are great, too. - link:VaadinOnGrailsMultipleUIs.asciidoc[Vaadin on grails - Multiple UIs] - link:IntegratingAJavaScriptComponent.asciidoc[Integrating a JavaScript component] - link:IntegratingAJavaScriptLibraryAsAnExtension.asciidoc[Integrating a JavaScript library as an extension] +- link:CreatingAUIExtension.asciidoc[Creating a UI extension] -- 2.39.5