]> source.dussan.org Git - vaadin-framework.git/commitdiff
Migrate CreatingAUIExtension pr9959/r20
authorErik Lumme <erik@vaadin.com>
Tue, 12 Sep 2017 08:30:37 +0000 (11:30 +0300)
committerErik Lumme <erik@vaadin.com>
Tue, 12 Sep 2017 08:30:37 +0000 (11:30 +0300)
documentation/articles/CreatingAUIExtension.asciidoc [new file with mode: 0644]
documentation/articles/contents.asciidoc

diff --git a/documentation/articles/CreatingAUIExtension.asciidoc b/documentation/articles/CreatingAUIExtension.asciidoc
new file mode 100644 (file)
index 0000000..8c0fcd8
--- /dev/null
@@ -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);
+}
+....
index 486416cf5beba3ba08e25ab6d62c4a036eeb06c5..19f999a9928ae337a78515a27ba56ad28476449d 100644 (file)
@@ -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]