Component and UI Extensions

Adding features to existing components by extending them by inheritance creates a problem when you want to combine such features. For example, one add-on could add spell-check to a TextField, while another could add client-side validation. Combining such add-on features would be difficult if not impossible. You might also want to add a feature to several or even to all components, but extending all of them by inheritance is not really an option. Vaadin includes a component plug-in mechanism for these purposes. Such plug-ins are simply called extensions.

Also a UI can be extended in a similar fashion. In fact, some Vaadin features such as the JavaScript execution are UI extensions.

Implementing an extension requires defining a server-side extension class and a client-side connector. An extension can have a shared state with the connector and use RPC, just like a component could.

Server-Side Extension API

The server-side API for an extension consists of class that extends (in the Java sense) the AbstractExtension class. It typically has an extend() method, a constructor, or a static helper method that takes the extended component or UI as a parameter and passes it to super.extend().

For example, let us have a trivial example with an extension that takes no special parameters, and illustrates the three alternative APIs:

Java

  1. public class CapsLockWarning extends AbstractExtension {
  2. // You could pass it in the constructor
  3. public CapsLockWarning(PasswordField field) {
  4. super.extend(field);
  5. }
  6. // Or in an extend() method
  7. public void extend(PasswordField field) {
  8. super.extend(field);
  9. }
  10. // Or with a static helper
  11. public static void addTo(PasswordField field) {
  12. new CapsLockWarning().extend(field);
  13. }
  14. // NOTE: you only need one of the above, not all of them
  15. }

The extension could then be added to a component as follows:

Java

  1. PasswordField password = new PasswordField("Give it");
  2. // Use the constructor
  3. new CapsLockWarning(password);
  4. // ... or with the extend() method
  5. new CapsLockWarning().extend(password);
  6. // ... or with the static helper
  7. CapsLockWarning.addTo(password);
  8. layout.addComponent(password);

Adding a feature in such a “reverse” way is a bit unusual in the Vaadin API, but allows type safety for extensions, as the method can limit the target type to which the extension can be applied, and whether it is a regular component or a UI.

Extension Connectors

An extension does not have a corresponding widget on the client-side, but only an extension connector that extends the AbstractExtensionConnector class. The server-side extension class is specified with a @Connect annotation, just like in component connectors.

An extension connector needs to implement the extend() method, which allows hooking to the extended component. The normal extension mechanism is to modify the extended component as needed and add event handlers to it to handle user interaction. An extension connector can share a state with the server-side extension as well as make RPC calls, just like with components.

In the following example, we implement a “Caps Lock warning” extension. It listens for changes in Caps Lock state and displays a floating warning element over the extended component if the Caps Lock is on.

Java

  1. @Connect(CapsLockWarning.class)
  2. public class CapsLockWarningConnector
  3. extends AbstractExtensionConnector {
  4. @Override
  5. protected void extend(ServerConnector target) {
  6. // Get the extended widget
  7. final Widget pw =
  8. ((ComponentConnector) target).getWidget();
  9. // Preparations for the added feature
  10. final VOverlay warning = new VOverlay();
  11. warning.setOwner(pw);
  12. warning.add(new HTML("Caps Lock is enabled!"));
  13. // Add an event handler
  14. pw.addDomHandler(new KeyPressHandler() {
  15. public void onKeyPress(KeyPressEvent event) {
  16. if (isEnabled() && isCapsLockOn(event)) {
  17. warning.showRelativeTo(passwordWidget);
  18. } else {
  19. warning.hide();
  20. }
  21. }
  22. }, KeyPressEvent.getType());
  23. }
  24. private boolean isCapsLockOn(KeyPressEvent e) {
  25. return e.isShiftKeyDown() ^
  26. Character.isUpperCase(e.getCharCode());
  27. }
  28. }

The extend() method gets the connector of the extended component as the parameter, in the above example a PasswordFieldConnector. It can access the widget with the getWidget().

An extension connector needs to be included in a widget set. The class must therefore be defined under the client package of a widget set, just like with component connectors.