softshake 2013 - vaadin componentization
TRANSCRIPT
Vaadin extension points
Componentize your UI
7
Who am I
• Java (EE) Dev/Architect
• Vaadin enthusiast
• General blog• http://blog.frankel.ch
• @nicolas_frankel
• Vaadin specific• http://morevaadin.com
• @learnvaadin
Extension points
1. Compose base components
2. Wrap GWT widgets
3. Extensions
4. Wrap JavaScript
5. Theme
This presentation requires you have a bare minimum knowledge of Vaadin and how it works
Extension points
1. Compose base components
2. Wrap GWT widgets
3. Extensions
4. Wrap JavaScript
5. Theme
Composition
• It can’t be that hard Just compose…
• Let’s create an Address component And see for ourselves
First step
public class Address extends VerticalLayout {
public Address() {
addComponent(new TextField("Ligne 1")); addComponent(new TextField("Ligne 2")); addComponent(new TextField("Ville")); addComponent(new TextField("NPA")); }}
Input vs label
TextField ligne1 = new TextField();TextField ligne2 = new TextField();TextField ville = new TextField();TextField npa = new TextField();
ligne1.setInputPrompt("Ligne 1");ligne2.setInputPrompt("Ligne 2");ville.setInputPrompt("Ville");npa.setInputPrompt("NPA");
addComponent(ligne1);addComponent(ligne2);addComponent(ville);addComponent(npa);
Spacing
setSpacing(true);
TextField ligne1 = new TextField();TextField ligne2 = new TextField();TextField ville = new TextField();TextField npa = new TextField();
ligne1.setInputPrompt("Ligne 1");ligne2.setInputPrompt("Ligne 2");ville.setInputPrompt("Ville");npa.setInputPrompt("NPA");
addComponent(ligne1);addComponent(ligne2);addComponent(ville);addComponent(npa);
Problems?
No choice between labels and input prompts
No choice between spacing or not
i18n
Fixed layout
And so on
No configuration feature!
Configuration design
Expose your configuration features
Wrap the rest As for API
Introduce middle-components for decoupling For layout, use CustomComponent
public class Address extends CustomComponent {
private Layout layout; private TextField ligne1 = new TextField(); // Declare other components
public Address(Layout layout) {
this.layout = layout; setCompositionRoot(layout);
layout.addComponent(ligne1); // Add other components}
public void setSpacing(boolean spacing) {…} public boolean isSpacing() {…} public String getLigne1Caption() {…} // Other getters and setters}
Generic component
Configurable layout
Delegate to layout
Delegate to TextField
UI design vs data design
Address address = new Address();
address.getLigne1().setValue("M. Nicolas Frankel");address.getLigne2().setValue("Route du Simplon 1");address.getVille().setValue("Paudex");address.getNpa().setValue("1094");
Tight coupling No abstraction
No validation as a whole No JSR-303
Simple data design
An address component should display an address bean!
Better data design
Enable data buffering Commit / discard
Even better data design
(Near-)Final data design
Provide a default concrete class Help 80% of the time
Composition summary
Think about UI configuration
Think about the wrapped model
Make it easy for your developers, they are your users This makes it hard for you as the designer!
Extension points
1. Compose base components
2. Wrap GWT widgets
3. Extensions
4. Wrap JavaScript
5. Theme
Connector architectureServer-
side
"Glue" between server and client
Client-side
Connector architecture
This let us use a server-side component MyComponent
Displayed client-side MyWidget
YouTubePlayer widget
Simple widget displaying a YouTube video
For example purpose only Deprecated
YouTubePlayer server-side
public class YouTubePlayer extends AbstractComponent {}
YouTubeConnector client-side
@Connect(YouTubePlayer.class)public class YouTubeConnector extends AbstractComponentConnector {
public YouTubeViewer getWidget() { return (YouTubeViewer) super.getWidget(); }
protected Widget createWidget() { return new YouTubeViewer("GOKX-bGmi0k"); }}
Shared state
Communication between server and client
Shared state
Create API server-side
Create a getter/setter on the shared state
Implement onStateChanged() on the connector Call widget’s methods accordingly
YouTubeState client-side
public class YouTubeState extends AbstractComponentState {
private String movieId;
public String getMovieId() {
return movieId; }
public void setMovieId(String movieId) {
this.movieId = movieId; } }
YouTubePlayer server-side
public class YouTubePlayer extends AbstractComponent {
public YouTubePlayer(String movieId) {
getState().setMovieId(movieId); }
@Override public YouTubeState getState() {
return (YouTubeState ) super.getState(); }}
Doesn’t necessarily mirror state nor client
YouTubeConnector client-side
@Connect(YouTubePlayer.class)public class YouTubeConnector extends AbstractComponentConnector {
// get & create widget as previously public void onStateChanged(StateChangeEvent e) {
super.onStateChanged(e); String movieId = getState().getMovieId(); getWidget().setMovieID(movieId); }}
What happens with more than one possible
state attribute?
Because it’s pays to play on the safe side
hasPropertyChanged
public void onStateChanged(StateChangeEvent e) {
super.onStateChanged(e);
if (e.hasPropertyChanged(“movieId”) {
String movieId = getState().getMovieId(); getWidget().setMovieID(movieId); } }
A constant is in order
Project structure
<root-package>
client
ClientWidget
ClientConnector
SharedState
ServerComponent
gwt.xml
Configuration
Reference widget(s) in gwt.xml
Reference gwt.xml in servlet configuration Either web.xml Or annotation
Client compilation
Only a single GWT compiled package per WAR
Compile once in the final WAR whatever the number of widgets And be done with it!
GWT widget wrap summary
1. Create server-side component API
2. Then develop what is needed Client-side widget (if necessary) Connector State
3. No need to package compiled-client code more than once
Extension points
1. Compose base components
2. Wrap GWT widgets
3. Extensions
4. Wrap JavaScript
5. Theme
Extensions
Extensions are a way to add client-side features to an existing component
Examples: Tooltip on labels Icon on text fields Caps lock warning on password fields Etc.
7
Extension architecture
Extension server-side
public class Tooltip extends AbstractExtension {
public void extend(TextField field) {
super.extend(field);
}
}
Extension client-side
@Connect(Tooltip.class)public class TooltipConnector extends AbstractExtensionConnector { @Override protected void extend(ServerConnector target) { final Widget tf = ((ComponentConnector) target).getWidget(); final VOverlay tooltip = new VOverlay(); tooltip.add(new HTML("<div class='c-tooltip'>Static tooltip</div>"));
Extension client-side continued
tf.addDomHandler(new MouseOverHandler() { @Override public void onMouseOver(MouseOverEvent event) { tooltip.showRelativeTo(tf); } }, MouseOverEvent.getType()); tf.addDomHandler(new MouseOutHandler() { @Override public void onMouseOut(MouseOutEvent event) { tooltip.hide(); } }, MouseOutEvent.getType()); }}
Usage
TextField tf = new TextField("Name");
new Tooltip().extend(tf);
And that’s all! But you need GWT skills...
Noticed the tooltip is static? "<div class='c-tooltip'>Static tooltip</div>"
Reversed order
Client-side state extension
public class TooltipState extends SharedState { private String tooltip; public String getTooltip() { return tooltip; } public void setTooltip (String tooltip) { this.tooltip = tooltip; }}
Dynamic extension server-side
public class Tooltip extends AbstractExtension { @Override public TooltipState getState() { return (TooltipState) super.getState(); } public void extend(TextField tf, String tooltip) { getState().setTooltip(tooltip); super.extend(link); }}
Dynamic Extension client-side
@Connect(TooltipExtension.class)public class TooltipConnector extends AbstractExtensionConnector { @Override public TooltipState getState() { return (TooltipState) super.getState(); }
@Override protected void extend(ServerConnector target) { … String text = getState().getTooltip(); tooltip.add(new HTML("<div class='c-tooltip'>" + text + "</div>")); … }}
Dynamic usage
TextField tf = new TextField("Name");
new Tooltip().extend(tf, "really useful tooltip");
Extension summary
1. For added client-side capacity
2. Simple usage server-side
3. Requires GWT skills
4. State to the rescue For runtime changes
Wrap JavaScript
1. Compose base components
2. Wrap GWT widgets
3. Extensions
4. Wrap JavaScript
5. Theme
7
JavaScript before 7
window.executeJavascript(String script)
“ Executes JavaScript in this window.
This method allows one to inject JavaScript from the server to client. A client implementation is not required to implement this functionality, but currently all web-based clients do implement this.
Use of this method should be avoided and instead it is recommended to create new widgets with GWT.”
JavaScript before 7
Not always feasible to wrap JavaScript in GWT
Not always available GWT skills
Not always possible to use JavaScript directly Nor maintainable when it is
JavaScript in 7
To embed scripts client-side
1. JavaScript extension To improve existing widgets
2. JavaScript component To add
JavaScript extension architecture
@JavaScript usage
On server-side JavaScript extension
Absolute resources https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js
Local resources bootstrap.js bootstrap_connector.js
Calling JS function
callFunction("functionName", arguments) Provides a way to call JavaScript scripts server-side
JavaScript extension server-side
@JavaScript({ "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js", "bootstrap.js", "bootstrap_connector.js" })public class JsTooltipExtension extends AbstractJavaScriptExtension { public void extend(TextField tf, String tooltip) { super.extend(tf); callFunction("attach", tooltip); }}
Binding with JavaScript
There’s no connector per se As there’s no GWT widget But we still need to bind betwen server and client
All local scripts have to be in the same package as the extension
JavaScript extension client-side
window.package_JavascriptTooltipExtension = function() { this.attach = function(options) { var connectorId = this.getParentId(); var element = this.getElement(connectorId); var tf = element.childNodes[0]; tf.rel = "tooltip"; tf.title = options[0]; $(tf).tooltip(); }}
Anonymous function
Namespace as underscore separated package name
Called server-
side
JavaScript and state
State as seen previously applies com.vaadin.shared.ui.JavaScriptComponentState
On client-side
window.package_JavascriptExtension = function() { this.onChange = function() { this.getState() … }}
JavaScript component
Very alike to extension But will create the HTML DIV Hopefully… never played with it
JavaScript component architecture
Wrap JavaScript summary
When JavaScript is available Just need a JS connector script
Pros No need to use GWT Can still be packaged as JARs
Cons Loses all static typing
Themes
1. Compose base components
2. Wrap GWT widgets
3. Extensions
4. Wrap JavaScript
5. Theme
7
Themes
Themes are an easy way to change applications appearance CSS / SASS Images HTML layouts
@Theme
Provided OOTB reindeer (default) runo chameleon
7
Theme structure
VAADIN/themes
mytheme.scss
styles.scss
img
layouts
Themes are undervalued
SASS is extra-powerful
Very easy to use
Packaged in JARs
Front-end and Java developers can work in parallel
Summary
Vaadin is Component-Oriented Create your own Design for reusability
Use it to your advantage!
Thanks for your attention ?