summaryrefslogtreecommitdiffstats
path: root/documentation/articles/CreatingAUIExtension.asciidoc
blob: 56e209ede7dca55d08202bcb8d00e27feacc21e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
---
title: Creating A UI Extension
order: 73
layout: page
---

[[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);
}
....