howto designa ui classlibraryfiles.meetup.com/4939632/20151125_how_to_design_a_ui_library.pdfhowto...
TRANSCRIPT
How to design a
UI class library
Lightweight Java Meetup, 25.11.2015
Johannes Döbler
Developer virtues
CraftsmanshipWrite clean and efficient code
AlgorithmicFind and implement solutions for problems
ArchitecturalAssemble components to complex systems
UI design Make it easy for end users to use an application
API designMake it easy for developers to use a library
Language designInvent expressive and concise languages, syntax
Agenda
(1) The story behind the talk
(2) Library and API design
(3) Code examples
Project: Port a huge desktop-based information
system to the web (modern SPA, RIA)
500+ database tables, 1000+ forms and dialogs
The story behind the talk
Which web framework?
Found two candidates:
Liked development model and architecture…
The story behind the talk
panel.add(new Button("OK")).addClickListener(
event -> log("Clicked"));
{create:"button", id:"f451", text:"OK",...}
<button id="f451">OK</button>
{event:"click", id:"f451"}
panel.add(new Button("OK")).addClickListener(
event -> log("Clicked"));
Programming model:
The story behind the talk
… then we built proof-of-concept prototypes ̶
and were shaken, not stirred…
Framework Friction Alert!
… and decided to write
an own framework
for the candidate
architecture (yes!)
The story behind the talk
UI
Servlet Container
Databinding I10N
↓ Protocol ↑
Protocol Interpreter
Event Handler Up/Download Utilities
API
JSON
JS
Java
Servlet Layer
Protocol Interpreter
thisthisthisthis talktalktalktalk
JQuery
Agenda
(1) The story behind the talk
(2) Library and API design
(3) Code examples
API qualities
Allows good client code:simple, short, elegant, fast, fun
Implied library qualities:expressive, intuitive, coherent, extensible
Interface designers must take responsibility.
Clients — the people using your interfaces — are trying to
do a good job. They want things to behave correctly.
That being the case, if clients make a mistake
when using your interface, it's your fault.
Scott Myershttp://www.aristeia.com/Papers/IEEE_Software_JulAug_2004_revised.htm
Simply start
FADE IN...
public class Button {...
public class TextField {...
public class Table {...
How to be come a better screenwriter?
Get some paper, put it in a typewriter,
type FADE IN and keep typing.
Peter »Columbo« Falk
Have a guiding idea
UI objects render to HTML element(s)
TextField field
�
<input type=" " pattern=" ">
public class TextField {
public enum Type { TEXT,EMAIL,URL,... };
public void setType(Type type);
public void setPattern(String pattern);
...
}
Build on prior art
copy , improve , discard , innovate
Java: AWT, Swing, SWT, JavaFX
Web: GWT, Vaadin
Cross-PF: QT
Windows: MVC, Windows Forms (.net)
Linux: KDE, Gnome
JS: JQueryUI, ExtJS, KendoUI
Mobile: Android, iOS
https://en.wikipedia.org/wiki/List_of_widget_toolkits
Know the design patterns
Composite
Observer
Builder, Factory
Decorator, Command
Model-View-Controller
Design Patterns:Elements of Reusable Object-Oriented Software1994
Gamma E., Helm R., Johnson R., Vlissides, J.
Build minimal and complete interfaces*
Javadoc - Methods inherited from class java.awt.Componentaction, add, addComponentListener, addFocusListener, addHierarchyBoundsListener, addHierarchyListener, addInputMethodListener, addKeyListener, addMouseListener, addMouseMotionListener, addMouseWheelListener, addPropertyChangeListener, addPropertyChangeListener, applyComponentOrientation, areFocusTraversalKeysSet, bounds, checkImage, checkImage, coalesceEvents, contains, contains, createImage, createImage, createVolatileImage, createVolatileImage, deliverEvent, disable, disableEvents, dispatchEvent, doLayout, enable, enable, enableEvents, enableInputMethods, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, getAlignmentX, getAlignmentY, getBackground, getBaseline, getBaselineResizeBehavior, getBounds, getBounds, getColorModel, getComponentAt, getComponentAt, getComponentListeners, getComponentOrientation, getCursor, getDropTarget, getFocusCycleRootAncestor, getFocusListeners, getFocusTraversalKeys, getFocusTraversalKeysEnabled, getFont, getFontMetrics, getForeground, getGraphics, getGraphicsConfiguration, getHeight, getHierarchyBoundsListeners, getHierarchyListeners, getIgnoreRepaint, getInputContext, getInputMethodListeners, getInputMethodRequests, getKeyListeners, getLocale, getLocation, getLocation, getLocationOnScreen, getMaximumSize, getMinimumSize, getMouseListeners, getMouseMotionListeners, getMousePosition, getMouseWheelListeners, getName, getParent, getPeer, getPreferredSize, getPropertyChangeListeners, getPropertyChangeListeners, getSize, getSize, getToolkit, getTreeLock, getWidth, getX, getY, gotFocus, handleEvent, hasFocus, hide, imageUpdate, inside, invalidate, isBackgroundSet, isCursorSet, isDisplayable, isDoubleBuffered, isEnabled, isFocusable, isFocusCycleRoot, isFocusOwner, isFocusTraversable, isFontSet, isForegroundSet, isLightweight, isMaximumSizeSet, isMinimumSizeSet, isOpaque, isPreferredSizeSet, isShowing, isValid, isVisible, keyDown, keyUp, layout, list, list, list, list, list, locate, location, lostFocus, minimumSize, mouseDown, mouseDrag, mouseEnter, mouseExit, mouseMove, mouseUp, move, nextFocus, paint, paintAll, postEvent, preferredSize, prepareImage, prepareImage, print, printAll, processComponentEvent, processFocusEvent, processHierarchyBoundsEvent, processHierarchyEvent, processInputMethodEvent, processKeyEvent, processMouseEvent, processMouseMotionEvent, processMouseWheelEvent, remove, removeComponentListener, removeFocusListener, removeHierarchyBoundsListener, removeHierarchyListener, removeInputMethodListener, removeKeyListener, removeMouseListener, removeMouseMotionListener, removeMouseWheelListener, removeNotify, removePropertyChangeListener, removePropertyChangeListener, repaint, repaint, repaint, repaint, requestFocus, requestFocus, requestFocusInWindow, requestFocusInWindow, reshape, resize, resize, revalidate, setBackground, setBounds, setBounds, setComponentOrientation, setCursor, setDropTarget, setEnabled, setFocusable, setFocusTraversalKeys, setFocusTraversalKeysEnabled, setFont, setForeground, setIgnoreRepaint, setLocale, setLocation, setLocation, setMaximumSize, setMinimumSize, setName, setPreferredSize, setSize, setSize, setVisible, show, show, size, toString, transferFocus, transferFocusBackward, transferFocusUpCycle, update, validate
No further questions your honor, harharhar, … *Scott Myers
A camel is a horse designed by committee
View your design through the eyes of a user.
Eat your own dog food. Write a real-life, non trivial application using the library.
And then adjust the design. Rinse and repeat.
Refactor without mercy
public class Div^H^H^H Panel {
}
Create class. Delete. Restore. Rename. Delete.
Revert. Can‘t sleep. Rename again.
(…to be continued)
���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ���� ����
Agenda
(1) The story behind the talk
(2) Library and API design
(3) Code examples
Composite pattern
UI components build a tree.
Composite pattern: Allows to treat a group (tree) of
objects in the same way as a single instance of an object.
Common operations, tree traversal, content
Variant 1: Variant 2:
Composite pattern
Widget base class: Tree traversal, content
public abstract class Widget {
public Widget getParent()
public int getChildCount()
public Widget getChild(int index)
public void add(Widget child)
public void empty()
public void delete()
...
Composite pattern
Widget base class: Common operations
...
public boolean isVisible()
public void setVisible(boolean)
public boolean isDisabled()
public void setDisabled(boolean)
public void setEnabled(boolean)
// more common operations
}
public class Button extends Widget {...
How to set a button label?
Button ok = new Button();
ok.setText("OK");
Button ok = new Button("OK");
Create a simple OK button
How to set a button label?
javax.swing.JButton:
JButton(String)
JButton(Icon)
JButton(String, Icon)
setText(String)
setIcon(Icon)
setRolloverIcon(Icon)
setSelectedIcon(Icon)
setRolloverSelectedIcon(Icon)
setIconTextGap(int)
Combinatorial
EXPLOSION
ALERT
!
Create a button with an icon
Button ok = new Button(?);
button.setText(?);
How to set a button label?
<button>OK</button>
<button>
<span class="glyphicon glyphicon-download"></span>
Download
</button>
Button ok = new Button();
ok.setText("OK");
Button dl = new Button();
dl.add(Glyphicon.download);
dl.addText("Download");
Create buttons like
declared indeclared indeclared indeclared in
widget base classwidget base classwidget base classwidget base class
HTML mixed contentHTML mixed contentHTML mixed contentHTML mixed content
How to set a … label?
Create widgets like etc.
<input type="submit" name="OK" value="OK" src="...">
<select>
<option value="1">Uno</option
<option value="2">Due</option
<option value="3">Tre</option
</select>
HTML is not perfect:
How to set a button label?
Why AWT/Swing /SWT/Javafx/etc fail...
awt.Button
swt.Button
awt.Component
swt.Widget
javafx.Node
awt.Container
swt.Composite
swing.JComponent
swing.JButton
javafx.Parent
javafx.Button
Styles and layout
positionpositionpositionposition widthwidthwidthwidth
heightheightheightheightcolorcolorcolorcolor
fontfontfontfont
borderborderborderborder
marginmarginmarginmargin
backgroundbackgroundbackgroundbackgroundpaddingpaddingpaddingpaddingspacingspacingspacingspacing
minminminmin----maxmaxmaxmax----prefsizeprefsizeprefsizeprefsize
orientationorientationorientationorientation
animationanimationanimationanimation
hoverhoverhoverhover
cursorcursorcursorcursor alignmentalignmentalignmentalignment
Styles and layout
org.eclipse.swt.widgets.Control
Style
getStyle()
getBorderWidth()
setBackground(Color*)
setBackgroundImage(Image*)
setCursor(Cursor*)
setFont(Font*)
setForeground(Color*)
setOrientation(int)
setTextDirection(int)
* extends
org.eclipse.swt.graphics.Resource
Position and size
setBounds(int,int,int,int)
setBounds(Rectangle)
setLayoutData(Object)
setLocation(int,int)
setLocation(Point)
setSize(int,int)
setSize(Point)
Layout
computeSize(int,int,boolean)
pack()
setRedraw()
update()
LayoutManagerFAT API ALERT!
Styles and layout
CSS to the rescue (HTML, JavaFX)
<button font="..."></button>
<button class="btn btn-default">Default</button><button class="btn btn-primary">Primary</button>
...
Widget widget = ...
widget.css("btn", "btn-primary"); // use constants
widget.css().toggle("required"); // use constants
widget.style("valign", "top"); // use constants
widget.style().width().percent(85);
125
Fluent API
Keystrokes matter...
Button button = new Button();
button.addClickListener(...);
button.add(Gylphicon.OK);
button.addText("OK");
button.css(Css.BTN);
Requires consistency
Problems with hierarchies:
Method cascading:
(Smalltalk, Dart)
Widget Widget.css(…)
Button Button.addClickListener(…)
void Widget.css(…)
void Button.addClickListener(…)
Button button = new Button()
..addClickListener(...)
..add(Gylphicon.OK)
..addText("OK")..css(Css.BTN);
Method chaining:
97Button button = new Button()
.addClickListener(...)
.add(Gylphicon.OK)
.addText("OK").css(Css.BTN);
Builder pattern
again save same some keystrokes
... and improve readability
Table table = new Table();
table.addColumn();
table.addColumn().width(10); // more columns
TableRow row = table.addRow();
row.addCell("Street").setAlign(align.right);
row.addCell();
row.addCell().add(new TextField());
// 8 more cells to go
TableBuilder tb = new TableBuilder("[right]10[50]3[]");
tb.cell("Street").cell(2).textField();
tb.cell("ZIP / City")
tb.cell().textField();
tb.cell().textField();
tb.cell("Country").cell(2).select();
definedefinedefinedefine
columnscolumnscolumnscolumns
just push cellsjust push cellsjust push cellsjust push cellsinspired by http://miglayout.com/
Edit Widgets: API
javax.swing
JTextField, void setText(String)JTextArea String getText()
JCheckBox, void setSelected(boolean)
JRadioButton boolean isSelected()
JComboBox<E> void setSelectedItem(Object)
Object getSelectedItem()
JList<E> void setSelectedValue(Object)
E getSelectedValue()
Naming Confusion ALERT!
Edit Widgets: API
Control := common base class for edit widgets
public abstract class Control<VALUE> extends Widget {
public void setValue(VALUE);
public VALUE getValue();
public boolean isChanged();
public void setChanged(boolean);
public boolean isRequired();
public void setRequired(boolean);
public boolean isInvalid();
public void setInvalid(boolean, String);
public void addValueListener(ValueListener);
public void removeValueListener(ValueListener);
}
Edit Widgets: Type support
from the having-a-hard-time-department
How to enter number/date/time values?
import com.vaadin.ui.TextField;
TextField field = new TextField();
field.setConverter(Integer.class);
String s = field.getValue();
try {
Integer n = (Integer)field.getConvertedValue();
} catch (ConversionException e) {
Notification.show("Could not convert value");
}
https://vaadin.com/wiki/-/wiki/Main/Creating+a+TextField+for+Integer+only+input+when+not+using+a+data+source
Edit Widgets: Type support
Control implementations for common simple types
class TextField extends Control<String>
class DoubleField extends Control<Double>
class IntField extends Control<Integer>
class DateField extends Control<Date>
class Select<VALUE> extends Control<VALUE>
class List<VALUE> extends Control<VALUE>
class Checkbox<VALUE> extends Control<VALUE>
locale dependentlocale dependentlocale dependentlocale dependent
jqueryjqueryjqueryjquery pluginspluginspluginsplugins::::
autonumericautonumericautonumericautonumeric,,,,
datepickerdatepickerdatepickerdatepicker,,,,
timentrytimentrytimentrytimentry,,,,
chosenchosenchosenchosen, , , , …
Edit Widgets: Extensions
More fun with controls...
InlineEditor<Address>
SearchLookup<Company>
DateTimeField
Class hierarchy
Building classifications is hard…
From an ancient chinese encyclopedia:
Tiere unterteilen sich in a) Tiere, die dem Kaiser
gehören, b) einbalsamierte Tiere, c) gezähmte, d)
Milchschweine, e) Sirenen, f) Fabeltiere, g) herrenlose
Hunde, h) in diese Gruppierung gehörige, i) die sich
wie Tolle gebärden, k) die mit einem ganz feinen Pinsel
aus Kamelhaar gezeichnet sind, l) et cetera,
m) die den Wasserkrug zerbrochen haben, n) die von
weitem wie Fliegen aussehen.
Jorge Luis Borges, Die analytische Sprache des John Wilkins
Class hierarchy
Inherit API or implementation? How deep?
javafx.scene.Node base component classbase component classbase component classbase component class
javafx.scene.Parent base composite classbase composite classbase composite classbase composite class
javafx.scene.layout.Region base class for UI controlsbase class for UI controlsbase class for UI controlsbase class for UI controls
javafx.scene.control.Control base class for UI controlsbase class for UI controlsbase class for UI controlsbase class for UI controls
javafx.scene.control.Labeled has textual contenthas textual contenthas textual contenthas textual content
javafx.scene.control.ButtonBase buttonbuttonbuttonbutton----like controlslike controlslike controlslike controls
javafx.scene.control.Button
javafx.scene.control.Checkbox
javafx.scene.control.Hyperlink
javafx.scene.control.MenuButton
javafx.scene.control.ToggleButton
javafx.scene.control.RadioButton
Class hierarchy
Inherit API or implementation? How deep?
web.ui.Widget base widgetbase widgetbase widgetbase widget////composite classcomposite classcomposite classcomposite class
web.ui.Button
web.ui.Link
web.ui.Radio
web.ui.Control<V> base class for input widgetsbase class for input widgetsbase class for input widgetsbase class for input widgets
web.ui.Checkbox<V>
web.ui.RadioGroup<V>
Class hierarchy
com.vaadin.ui.Component.Focusable
All Known Implementing Classes:
AbstractField, AbstractSelect, AbstractTextField, Accordion, Button, CheckBox, ColorPickerPopup, ComboBox, CustomField, DateField, Form, InlineDateField, LegacyWindow, ListSelect, MenuBar, NativeButton, NativeSelect, OptionGroup, Panel, PasswordField, PopupDateField, ProgressBar, ProgressIndicator, RichTextArea, Select, Slider, Table, TabSheet, TextArea, TextField, Tree,TreeTable, TwinColSelect, UI, Upload, Window
if (component instanceof Focusable)
((Focusable)component).focus();
public class Widget {
public boolean focus()
}
widget.focus();
Nobody is perfect
Osgood Fielding III., Gerald ‚Jerry‘ (Daphne)
Events
Observer or Listener pattern
Decouple event handling from event source
ClickListener
ClickListener
addClickListener
Event processEvent(Event)
ButtononClick(Event)
onClick(Event)
API:
Event class(es), Listener interfaces, register and process methods
public class Event
public class ActionEvent extends Event {
public int getModifiers()
}
Events
Swing: Explicit event class per event type
...API EXPLOSION ALERT!
ActionEvent, AdjustmentEvent, AncestorEvent, CaretEvent, ChangeEvent, ComponentEvent, ContainerEvent, FocusEvent, HierarchyEvent, HyperlinkEvent, InputEvent, InputMethodEvent, InternalFrameEvent, InvocationEvent, ItemEvent, KeyEvent, ListDataEvent, ListSelectionEvent, MenuDragMouseEvent, MenuEvent, MenuKeyEvent, MouseEvent, MouseWheelEvent, PaintEvent, PopupMenuEvent, RowSorterEvent, TableColumnModelEvent, TableModelEvent, TextEvent, TreeExpansionEvent, TreeModelEvent, TreeSelectionEvent, UndoableEditEvent, WindowEvent,
Events
Simplify: Only use one event class with a type field, and a
generic param map, discard layout related event types
public class Event {
public EventType getType()
public Widget getTarget()
public String getParam(String key)
public int getIntParam(String key)
}
action, click, close, closing, drop, dblclick,
focusgained, focuslost, hide, hiding,
input, keydown, keypressed, keyup,
mousedown, mousemove, mouseout, mouseover, mouseup,
open, opening, scrolled, show, showing,
valuechanged, valueset, <userdefined>
Events
AWT/Swing EventListeners
public interface ActionListener extends EventListener {
actionPerformed(ActionEvent event);
}
public interface FocusListener extends EventListener {
focusGained(FocusEvent event);
focusLost(FocusEvent event);
}
SOME CanNOt use lambdas and method handles:
TextField f = ...
f.addFocusListener(e –> System.out.println(e));
f.addFocusListener(this::onFocus);
Events
Simplify: Only use functional interfaces
public interface FocusListener extends EventListener {
public void onFocus(Event event);
}
public class MyListener implements FocusListener {
public void onFocus(Event event) {
if (event.getType() == FocusListener.GAINED)
...
else
...
}
}
public class MyListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
try {
showResult(db.query());
} catch (SQLException e) {
log.error(e);
showMessage("sorry, can't fix");
}
}
}
Exceptions
A Listener in AWT/Swing:
public class MyListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
showResult(db.query());
}
}
Must catch any checked exception.
What happens to uncatched runtime exceptions?
Exceptions
Solution: Allow any exception during event processing:
public interface ClickListener {
public void onClick(Event event) throws Exception;
}
Uncatched exceptions bubble up the widget tree.
Allows to handle the exception in the right context.
listener.onClick(Event)
widget.onEventError(Event, Exception)
widget.getParent().onEventError(Event, Exception)
...
window.onEventError(Event, Exception)
Property binding
Bind properties to the result of an expression.
Example:
OldschoolCheckbox cb = ...; TextField tf = ...;
cb.addValueListener(event ->
tf.setEnabled(cb.isChecked());
New hotnesstf.setEnabledWhen(cb);
Property binding
Bindable properties: disabled / visible / required / invalid /
css / value / text content / …
Controls are expressions, expressions are composable
IntField n = ...
TextField t = ...
t.setInvalid(not(equal(length(t),n)));
Digest cycle, listener handling, etc. done by the library
DEMO
Can also define additional dynamic variablesForm form = ...
Button save = ...
save.setEnabledWhen(form.getEditStatus().is(DIRTY));
Summary
Presented simple design examples
UI libraries: very old topic – still room for
improvements. Same ideas (and errors) are
reproduced in most libraries
No library is perfect, we are all learning
Metrics: Lines of code, API size, easy coding
We are not only assemblers of other libraries,
but also designers, authors, artists, creators
Images
API qualities
http://www.artima.com/cppsource/top_cpp_aha_moments.html
Simply start
https://en.wikipedia.org/wiki/File:Peter_Falk_-_1973.JPG
Derive API from context/constraints
https://en.wikipedia.org/wiki/File:Crystal_Clear_app_ktip.svg
Know the design patterns
http://info.tatcenter.ru/article/107688/
https://de.wikipedia.org/wiki/Datei:FANTA_4.jpg
https://de.wikipedia.org/wiki/Datei:Stadtmusikanten_Bremen.png
https://en.wikipedia.org/wiki/File:Dean_Franklin_-_06.04.03_Mount_Rushmore_Monument_%28by-sa%29-3_new.jpg
A camel is a horse designed by committee
http://projectcartoon.com/cartoon/3
Refactor without mercy
https://en.wikipedia.org/wiki/File:Clint_Eastwood_-_1960s.JPG
https://en.wikipedia.org/wiki/File:BatmanRoguesGallery.jpg
https://en.wikipedia.org/wiki/File:Gladiator_ver1.jpg
Composite pattern
https://en.wikipedia.org/wiki/File:Composite_UML_class_diagram_%28fixed%29.svg
Edit Widgets: Type support
https://commons.wikimedia.org/wiki/File:Waaah!.jpg
Class Hierarchy
https://de.wikipedia.org/wiki/Datei:DPAG_2010_40_Frankfurter_Buchmesse_Argentinien.jpg
https://de.wikipedia.org/wiki/Datei:Max_und_Moritz_%28Busch%29_040.png
http://girls-can-play.blogspot.de/2011/06/some-like-it-hot.html