Develop New Components
Reference
(Version 7) https://vaadin.com/docs/-/part/framework/clientside/clientside-widget.html
https://vaadin.com/docs/-/part/framework/clientside/clientside-module.html#clientside.module
https://vaadin.com/docs/-/part/framework/gwt/gwt-overview.html#gwt.overview (everything is here)
Project Setup
Eclipse "File->New->Other->Vaadin 7 Project (Maven) -> Add-on Project (vaadin-archetype-widget)"
3 Maven projects are created:
- myaddon-root
- contains a root pom.xml and two children projects
- myaddon
- the custom component
- myaddon-demo
- the demo application to utilize the client side component
- test "mvn jetty:run"
File / Class structure
Custom Shared State
Example shared state.
package com.example.myaddon.client;
public class MyComponentState extends com.vaadin.shared.AbstractComponentState {
// State can have both public variable and bean properties
public String text = "MyComponent";
}
Custom shared state usually extends com.vaadin.shared.AbstractComponentState, which:
- further extends com.vaadin.shared.communication.SharedState;
- defines certain common component states including:https://sites.google.com/site/bingsite/web-development/vaadin/book-of-vaadin-notes/develop-new-components#TOC-Custom-ClientPRC
- String height
- String width
- boolean readOnly
- boolean immediate
- String description
- String caption
- List<String> styles
- String id
- String primaryStyleNamre
- String errorMessage
- boolean captionAsHtml
- SharedState, root of shared state, implements Serializable and defines automatically managed resources used by the connector:
- public Map<String, URLReference> resources = new HashMap<String, URLReference>();
- public boolean enabled = true;
- public Set<String> registeredEventListeners = null;
Read the document of the root SharedState class:
/**
* Interface to be implemented by all shared state classes used to communicate
* basic information about a {@link Connector} from server to client.
*
* Shared state classes have to be declared in shared package to be accessible
* both for server and client code.
*
* Shared state objects are only sent from the server to the client, and any
* modifications from the client should be performed via an RPC call that
* modifies the authoritative state on the server.
*
* A shared state class should be a bean with getters and setters for each
* field. Supported data types are simple Java types, other beans and maps and
* arrays of these.
*
* On the client side the connector should override
* {@link com.vaadin.client.ui.AbstractConnector#getState()} to return the
* correct state type. This automatically causes a correct state object to be
* created.
*
* Subclasses of a {@link Connector} using shared state should also provide a
* subclass of the shared state class of the parent class to extend the state. A
* single {@link Connector} can only have one shared state object.
*
* @since 7.0
*/
Client Side
Start with existing GWT widget or design new one. Usually GWT widget should be enough. Very rare needs to design new one with Javascript.
Custom GWT widget refer to here: http://www.gwtproject.org/doc/latest/DevGuideUiCustomWidgets.html
Read this for style GWT widget http://www.gwtproject.org/javadoc/latest/com/google/gwt/user/client/ui/UIObject.html
Client side source code must be under "client" package under the package of descriptor file
Client-side Module Descriptor
Client-side modules are defined in a module descriptor gwt.xml file. Normally need to inherit DefaultWidgetSet (created from archetype).
- Stylesheet is included here <stylesheet src="mywidget/styles.css"/> (path is relative to the public folder under the folder of descriptor
- Limiting compile targets during development : (list see here)
- <set-property name="user.agent" value="gecko1_8"/>
Custom ClientPRC
package com.example.myaddon.client;
import com.vaadin.shared.communication.ClientRpc;
// ClientRpc is used to pass events from server to client
// For sending information about the changes to component state, use State instead
public interface MyComponentClientRpc extends ClientRpc {
// Example API: Fire up alert box in client
public void addStyleName(int index, String styleName);
public void removeStyleName(int index, String styleName);
}
ClientRPC is interface that server uses to call client. ClientRpc interface is just a marker and extends Serializable.
On server side
com.vaadin.ui.AbstractComponent (which extends AbstractClientConnector) can obtain a proxy instance of the interface by calling
protected <T extends ClientRpc> T getRpcProxy(final Class<T> rpcInterface)
This will obtain the right proxy.
On client side
The AbstractComponentConnector needs to obtain an instance of the custom ClientRpc (implementation of, that is), and register it with method below of com.vaadin.client.ui.AbstractComponentConnector (which inherits it from AbstractConnector)
protected <T extends ClientRpc> void registerRpc(Class<T> rpcInterface,
T implementation)
Custom ServerRpc
ServerRpc is also just a marker interface and extends Serializable. It's interface that client call server.
package com.example.myaddon.client;
...
// ServerRpc is used to pass events from client to server
public interface MyComponentServerRpc extends ServerRpc {
public void msgServer(String message);
}
On client side
Uses com.vaadin.client.communication.RpcProxy's method below to obtain an instance.
public static <T extends ServerRpc> T create(Class<T> rpcInterface,
ServerConnector connector)
It's best to just call from within the body of an AbstractComponentConnector's definition:
MyComponentServerRpc rpc = RpcProxy.create(MyComponentServerRpc.class, this);
On Server Side
Server side needs to provide an implementation and register it.
// To process events from the client, we implement ServerRpc
private MyComponentServerRpc rpc = new MyComponentServerRpc() {
@Override
public void msgServer(String message) {
System.err.println("Clicked: "+message);
}
};
......
registerRpc(rpc);
Register with AbstractComponent's method below (inherited from AbstractClientConnector):
protected <T extends ServerRpc> void registerRpc(T implementation) {
Custom AbstractComponentConnector
Connector binds client side widget class to server side component class. It runs in client and uses @Connect annotation to specifies corresponding server-side component
package com.example.myaddon.client;
....
@Connect(MyComponent.class)
public class MyComponentConnector extends AbstractComponentConnector {
// ServerRpc is used to send events to server. Communication implementation
// is automatically created here
MyComponentServerRpc rpc = RpcProxy.create(MyComponentServerRpc.class, this);
public MyComponentConnector() {
// To receive RPC events from server, we register ClientRpc implementation
registerRpc(MyComponentClientRpc.class, new MyComponentClientRpc() {
@Override
public void addStyleName(int index, String styleName) {
...
}
... // implementations //.....
});
}
// We must implement getWidget() to cast to correct type
// (this will automatically create the correct widget type)
@Override
public MyComponentWidget getWidget() {
return (MyComponentWidget) super.getWidget();
}
// We must implement getState() to cast to correct type
@Override
public MyComponentState getState() {
return (MyComponentState) super.getState();
}
// Whenever the state changes in the server-side, this method is called
@Override
public void onStateChanged(StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);
// State is directly readable in the client after it is set in server
getWidget().updateSharedState(getState());
}
}
Custom Widget Set
Widget class can extend any GWT Widget. Good practice to specify a distinct style. GWT Composite is usually a good choice, see GWT custom widget.
.....
public class MyComponentWidget extends ScrollPanel {
...
public MyComponentWidget() {
// CSS class-name should not be v- prefixed
setStyleName("myaddon");
... initialize ui ...
}
......
}
Defines a GWT widge set, in ./myaddon-addon/src/main/resources/com/example/myaddon/WidgetSet.gwt.xml (generated from archetype and generally not needed to modify)
<module>
<!-- WS Compiler: manually edited -->
<!-- Inherit DefaultWidgetSet -->
<inherits name="com.vaadin.DefaultWidgetSet" />
<!-- Widget styles in public -directory -->
<stylesheet src="myaddon/styles.css"/>
</module>
Server Side
package com.example.myaddon;
...
// This is the server-side UI component that provides public API
// for MyComponent
public class MyComponent extends com.vaadin.ui.AbstractComponent {
......
// To process events from the client, we implement ServerRpc
// Bing: this is how server component response to event sent from client
// Bing: implement a custom server PRC and register
private MyComponentServerRpc rpc = new MyComponentServerRpc() {
// Event received from client - user clicked our widget
public void clicked(MouseEventDetails mouseDetails) {
// Bing: look like here sends back response to client through client PRC
// Send nag message every 5:th click with ClientRpc
if (++clickCount % 5 == 0) {
getRpcProxy(MyComponentClientRpc.class)
.alert("Ok, that's enough!");
}
// Update shared state. This state update is automatically
// sent to the client.
getState().text = "You have clicked " + clickCount + " times";
}
};
public MyComponent() {
// To receive events from the client, we register ServerRpc
registerRpc(rpc);
}
// We must override getState() to cast the state to MyComponentState
@Override
protected MyComponentState getState() {
return (MyComponentState) super.getState();
}
}
Component and UI Extensions
This method add features to existing components without writing it entirely and without writing the client-side widget. See https://vaadin.com/docs/-/part/framework/gwt/gwt-extension.html
An example is to extend PasswordField widget to give warning with caps locked.
Styling
References
https://vaadin.com/docs/-/part/framework/gwt/gwt-styling.html
https://vaadin.com/wiki/-/wiki/Main/Widget+styling+using+only+CSS
Basics
Default style should be defined in the widget sources
Different themes can then modify the style
Styling in widget classes
Style the composite widget with overall style and with separate styles for sub-widgets as well. Use setStyleName().
public class MyPickerWidget extends ComplexPanel { public static final String CLASSNAME = "mypicker"; ...... public MyPickerWidget() {
setElement(Document.get().createDivElement()); setStylePrimaryName(CLASSNAME); textBox.setStylePrimaryName(CLASSNAME + "-field"); button.setStylePrimaryName(CLASSNAME + "-button"); add(textBox, getElement()); add(button, getElement()); ...... } }
In addition, all Vaadin components get the v-widget class. If it extends an existing Vaadin or GWT widget, it inhgerits CSS classes as well. v-widget sets box-sizing to border-box, causes borders and paddlings to be considered.
Default stylesheet
Stylesheets must be placed under "public" folder under folder of widget set. Example:
.mypicker { white-space: nowrap; } .mypicker-button { display: inline-block; border: 1px solid black; padding: 3px; width: 15px; text-align: center; }
Vaadin 7 adds a v-has-width/v-has-height class to the component's main element when width/height of the widget is set (other than undefined). Addition CSS can make use of this to make further adjustment.
.mypicker.v-has-width > .mypicker-field {
width: 100%;
}
(CSS: E>F means an F elememnt child of an E element)
My observation
To style my widget like a Vaadin widget, style with an "official" style. For example, style a text area with "v-textarea" give it the style just like the official textarea: when on focus a decoration border is shown.
Use browser developer kit to observe generated DOM.
Use "?debug" debug mode. Sometimes local widget throws an uncatched exception, this will show only in debug mode.
Official style sheet can be found in "Maven Dependencies -> vaadin-themes-x.x.x.jar"