This document discusses principles of object-oriented design and clear separation of responsibilities in Wicket components. It provides examples of how to structure Wicket panels, forms, validators, renderers, and behaviors to have single, well-defined responsibilities. Behaviors can be written by extending an abstract base class and implementing relevant callback methods. The document emphasizes designing reusable and maintainable components.
4. I think most programmers spend
the first 5 years of their career
mastering complexity, and the rest
of their lives learning simplicity.
— BUZZ ANDERSEN
5.
6. Architecture Reusability and
overview maintainability
Layout / logic
Old to new
separation
BluePrint Resources
12. Do not use SpringBean in generic UI components.
(try a constructor argument instead)
13. Do not use SpringBean in generic UI components.
(try a constructor argument instead)
(If you do, the @SpringBean will be a hidden
dependency when you add a generic component)
14. Only use the ‘name’ argument when there
are multiple beans that implement the same
interface.
@SpringBean (name = “selector”)
private GenericService m_genericService;
15. public class SomePanel extends Panel {
@SpringBean
private UserService m_userService;
@SpringBean
private TranslationService m_translationService;
27. Principles of Object Oriented Design
The first five principles are principles of class design:
SRP
The Single Responsibility Principle
A class should have one, and only one, reason to change.
OCP
The Open Closed Principle
You should be able to extend a classes behavior, without modifying it.
LSP
The Liskov Substitution Principle
Derived classes must be substitutable for their base classes.
DIP
The Dependency Inversion Principle
Depend on abstractions, not on concretions.
ISP
The Interface Segregation Principle
Make fine grained interfaces that are client specific.
29. Tools for clear responsibilities
Page
Panel
Form
Validator
Renderer
DataProvider
Behavior
30. Tools for clear responsibilities
Page Provides high level layout
only reusable by extending, so
do not implement reusable logic in Page!
31. Tools for clear responsibilities
Page Provides high level layout
Panel Implement logic of a clearly separated part of a page
Panel is very good for reuse!
Just make sure your interface is clear, and
your Panel does not contain too much
knowledge.
32. Tools for clear responsibilities
Page Provides high level layout
Panel Implement logic of a clearly separated part of a page
Panel is very good for reuse!
The interface of a Panel is the
combination of all the public and
protected methods.
Think about reuse!
33. Panel
“a class should do one thing”
Single Responsibility Principle
In the context of the Single Responsibility Principle,
responsibility is “a reason for change.” If you can think of
more than one motive for changing a class, then that class
has more than one responsibility. Another way to put it:
“a class should do one thing (and be good at it)”
https://wiki.cordys.com/display/edv/How+to+write+a+good+Wicket+component
35. Tools for clear responsibilities
Page Provides high level layout
Panel Implement logic of a clearly separated part of a page
Form, put your FeedbackPanel in here and handle
Form logic of submitting and refreshing FeedbackPanel.
Think: should your Form really
implement the Save of an object?
36. Tools for clear responsibilities
Page Provides high level layout
Panel Implement logic of a clearly separated part of a page
Form, put your FeedbackPanel in here and handle
Form logic of submitting and refreshing FeedbackPanel.
Think: should your Form really
implement the Save of an object?
Is reuse possible if you want the same Form for:
creating new object / edit existing object?
37. Tools for clear responsibilities
Page Provides high level layout
Panel Implement logic of a clearly separated part of a page
Form, put your FeedbackPanel in here and handle
Form logic of submitting and refreshing FeedbackPanel
Validator Handle validation of a FormComponent or a Form
38. Tools for clear responsibilities
Page Provides high level layout
Panel Implement logic of a clearly separated part of a page
Form, put your FeedbackPanel in here and handle
Form logic of submitting and refreshing FeedbackPanel
Validator Handle validation of a FormComponent or a Form
Validations should only do Validation.
39. Tools for clear responsibilities
Page Provides high level layout
Panel Implement logic of a clearly separated part of a page
Form, put your FeedbackPanel in here and handle
Form logic of submitting and refreshing FeedbackPanel
Validator Handle validation of a FormComponent or a Form
Validations should only do Validation.
Have a clear separation between
Validation and Behavior!
40. Tools for clear responsibilities
Page Provides high level layout
Panel Implement logic of a clearly separated part of a page
Form, put your FeedbackPanel in here and handle
Form logic of submitting and refreshing FeedbackPanel
Validator Handle validation of a FormComponent or a Form
Renderer Render a given Object to a string representation
41. Tools for clear responsibilities
Page Provides high level layout
Panel Implement logic of a clearly separated part of a page
Form, put your FeedbackPanel in here and handle
Form logic of submitting and refreshing FeedbackPanel
Validator Handle validation of a FormComponent or a Form
Renderer Render a given Object to a string representation
Renderer: do rendering only! No logic,
no filtering, nothing. Just render.
42. Tools for clear responsibilities
Page Provides high level layout
Panel Implement logic of a clearly separated part of a page
Form, put your FeedbackPanel in here and handle
Form logic of submitting and refreshing FeedbackPanel
Validator Handle validation of a FormComponent or a Form
Renderer Render a given Object to a string representation
DataProvider Provide the right data based on given input.
43. Tools for clear responsibilities
Page Provides high level layout
Panel Implement logic of a clearly separated part of a page
Form, put your FeedbackPanel in here and handle
Form logic of submitting and refreshing FeedbackPanel
Validator Handle validation of a FormComponent or a Form
Renderer Render a given Object to a string representation
DataProvider Provide the right data based on given input.
No rendering, no updating of
FeedbackPanels or Toolbar.
44. Tools for clear responsibilities
Page Provides high level layout
Panel Implement logic of a clearly separated part of a page
Form, put your FeedbackPanel in here and handle
Form logic of submitting and refreshing FeedbackPanel
Validator Handle validation of a FormComponent or a Form
Renderer Render a given Object to a string representation
DataProvider Provide the right data based on given input.
Behavior Behaviors are plugins for components. They can
provide custom markup, custom pieces of logic.
Think of them as decorators.
46. Tools for clear responsibilities: Behaviors
Learn to write your own in 4 steps!
1. Choose the most appropriate base class
All Known Implementing Classes:
AbstractAjaxBehavior, AbstractAjaxTimerBehavior, AbstractAutoCompleteBehavior, AbstractBehavior,
AbstractDefaultAjaxBehavior, AbstractHeaderContributor, AbstractTransformerBehavior,
AjaxEditableLabel.EditorAjaxBehavior, AjaxEditableLabel.LabelAjaxBehavior, AjaxEventBehavior,
AjaxFormChoiceComponentUpdatingBehavior, AjaxFormComponentUpdatingBehavior, AjaxFormSubmitBehavior,
AjaxFormValidatingBehavior, AjaxIndicatorAppender, AjaxPagingNavigationBehavior,
AjaxSelfUpdatingTimerBehavior, AttributeAppender, AttributeModifier, AutoCompleteBehavior,
BodyTagAttributeModifier, ContainerWithAssociatedMarkupHelper, ContextPathGenerator, DatePicker,
HeaderContributor, OnChangeAjaxBehavior, OrderByLink.CssModifier, SimpleAttributeModifier,
StringHeaderContributor, TextTemplateHeaderContributor, VelocityContributor, VelocityHeaderContributor,
VelocityJavascriptContributor, WicketAjaxIndicatorAppender, WicketMessageTagHandler.AttributeLocalizer,
XsltTransformerBehavior
47. Tools for clear responsibilities: Behaviors
Learn to write your own in 4 steps!
2. Extend from your chosen base class
public class MyBehavior extends AbstractBehavior {
..
}
48. Tools for clear responsibilities: Behaviors
Learn to write your own in 4 steps!
3. Decide which methods to implement
49. Tools for clear responsibilities: Behaviors
Learn to write your own in 4 steps!
3. Decide which methods to implement
afterRender(Component component)
Called when a component that has this behavior
coupled was rendered.
50. Tools for clear responsibilities: Behaviors
Learn to write your own in 4 steps!
3. Decide which methods to implement
afterRender(Component component)
Called when a component that has this behavior
coupled was rendered.
bind(Component component)
Bind this handler to the given component.
51. Tools for clear responsibilities: Behaviors
Learn to write your own in 4 steps!
3. Decide which methods to implement
afterRender(Component component)
Called when a component that has this behavior
coupled was rendered.
bind(Component component)
Bind this handler to the given component.
beforeRender(Component component)
Called when a component is about to render.
52. Tools for clear responsibilities: Behaviors
Learn to write your own in 4 steps!
3. Decide which methods to implement
afterRender(Component component)
Called when a component that has this behavior
coupled was rendered.
bind(Component component)
Bind this handler to the given component.
beforeRender(Component component)
Called when a component is about to render.
cleanup()
This method is called either by onRendered
(Component) or onException(Component,
RuntimeException) AFTER they called their respective
template methods.
53. Tools for clear responsibilities: Behaviors
Learn to write your own in 4 steps!
3. Decide which methods to implement
afterRender(Component component) onRendered(Component component)
Called when a component that has this behavior Called when a component that has this behavior
coupled was rendered. coupled was rendered.
bind(Component component)
Bind this handler to the given component.
beforeRender(Component component)
Called when a component is about to render.
cleanup()
This method is called either by onRendered
(Component) or onException(Component,
RuntimeException) AFTER they called their respective
template methods.
54. Tools for clear responsibilities: Behaviors
Learn to write your own in 4 steps!
3. Decide which methods to implement
afterRender(Component component) onRendered(Component component)
Called when a component that has this behavior Called when a component that has this behavior
coupled was rendered. coupled was rendered.
bind(Component component) isTemporary()
Bind this handler to the given component. Specifies whether or not this behavior is temporary.
beforeRender(Component component)
Called when a component is about to render.
cleanup()
This method is called either by onRendered
(Component) or onException(Component,
RuntimeException) AFTER they called their respective
template methods.
55. Tools for clear responsibilities: Behaviors
Learn to write your own in 4 steps!
3. Decide which methods to implement
afterRender(Component component) onRendered(Component component)
Called when a component that has this behavior Called when a component that has this behavior
coupled was rendered. coupled was rendered.
bind(Component component) isTemporary()
Bind this handler to the given component. Specifies whether or not this behavior is temporary.
onComponentTag(Component component,
beforeRender(Component component) ComponentTag tag)
Called when a component is about to render. Called any time a component that has this behavior
registered is rendering the component tag.
cleanup()
This method is called either by onRendered
(Component) or onException(Component,
RuntimeException) AFTER they called their respective
template methods.
56. Tools for clear responsibilities: Behaviors
Learn to write your own in 4 steps!
3. Decide which methods to implement
afterRender(Component component) onRendered(Component component)
Called when a component that has this behavior Called when a component that has this behavior
coupled was rendered. coupled was rendered.
bind(Component component) isTemporary()
Bind this handler to the given component. Specifies whether or not this behavior is temporary.
onComponentTag(Component component,
beforeRender(Component component) ComponentTag tag)
Called when a component is about to render. Called any time a component that has this behavior
registered is rendering the component tag.
cleanup()
detach(Component component)
This method is called either by onRendered
(Component) or onException(Component, Allows the behavior to detach any state it has
RuntimeException) AFTER they called their respective attached during request processing.
template methods.
57. Tools for clear responsibilities: Behaviors
Learn to write your own in 4 steps!
4. Implement your behavior!
public class AjaxFocusBehavior extends AbstractBehavior {
private Component m_component;
@Override
public void bind(Component component) {
this.m_component = component;
component.setOutputMarkupId(true);
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.renderOnLoadJavascript("document.getElementById('" + m_component.getMarkupId() + "').focus()");
}
/**
* {@inheritDoc}
*/
@Override
public boolean isTemporary() {
return true;
}
60. Composing an AutoCompleteField
AutoCompleteChoicesProvider<Long, Pizza> choicesProvider = new PizzaAutoCompleteChoicesProvider
(m_pizzaService);
AutoCompleteField<Long, Pizza> autocomplete = new AutoCompleteField<Long, Pizza>(id, model, choicesProvider,
renderer, settings);
61. Composing an AutoCompleteField
AutoCompleteChoicesProvider<Long, Pizza> choicesProvider = new PizzaAutoCompleteChoicesProvider
(m_pizzaService);
PizzaAutoSuggestRenderer renderer = new PizzaAutoSuggestRenderer();
AutoCompleteField<Long, Pizza> autocomplete = new AutoCompleteField<Long, Pizza>(id, model, choicesProvider,
renderer, settings);
62. Composing an AutoCompleteField
AutoCompleteChoicesProvider<Long, Pizza> choicesProvider = new PizzaAutoCompleteChoicesProvider
(m_pizzaService);
PizzaAutoSuggestRenderer renderer = new PizzaAutoSuggestRenderer();
AutoCompleteField<Long, Pizza> autocomplete = new AutoCompleteField<Long, Pizza>(id, model, choicesProvider,
renderer, settings);
renderer.setOnSelectJavascriptProvider(autocomplete);
63. Composing an AutoCompleteField
AutoCompleteChoicesProvider<Long, Pizza> choicesProvider = new PizzaAutoCompleteChoicesProvider
(m_pizzaService);
PizzaAutoSuggestRenderer renderer = new PizzaAutoSuggestRenderer();
AutoCompleteField<Long, Pizza> autocomplete = new AutoCompleteField<Long, Pizza>(id, model, choicesProvider,
renderer, settings);
renderer.setOnSelectJavascriptProvider(autocomplete);
autocomplete.add(new PlaceHolderTextBehavior(Model.of("Search for pizza...")));
64. Composing an AutoCompleteField
AutoCompleteChoicesProvider<Long, Pizza> choicesProvider = new PizzaAutoCompleteChoicesProvider
(m_pizzaService);
PizzaAutoSuggestRenderer renderer = new PizzaAutoSuggestRenderer();
AutoCompleteField<Long, Pizza> autocomplete = new AutoCompleteField<Long, Pizza>(id, model, choicesProvider,
renderer, settings);
renderer.setOnSelectJavascriptProvider(autocomplete);
autocomplete.add(new PlaceHolderTextBehavior(Model.of("Search for pizza...")));
autocomplete.add(new AjaxSelectTextOnClickBehavior());
65. Composing an AutoCompleteField
AutoCompleteChoicesProvider<Long, Pizza> choicesProvider = new PizzaAutoCompleteChoicesProvider
(m_pizzaService);
PizzaAutoSuggestRenderer renderer = new PizzaAutoSuggestRenderer();
AutoCompleteField<Long, Pizza> autocomplete = new AutoCompleteField<Long, Pizza>(id, model, choicesProvider,
renderer, settings);
renderer.setOnSelectJavascriptProvider(autocomplete);
autocomplete.add(new PlaceHolderTextBehavior(Model.of("Search for pizza...")));
autocomplete.add(new AjaxSelectTextOnClickBehavior());
autocomplete.add(new RequiredValidator<Pizza>());
66. Give your classes clear names.
If it’s a page, call it Page
(example: LoginPage instead of Login)
If it’s a panel, call it Panel
(example: LoginPanel instead of Login)
The same goes for Form,
Validator, Renderer, etcetera.
68. Use wicket:message instead of Label
(for simple translations)
<wicket:message key="SELECT_LANGUAGE_LABEL"/>
69. Use wicket:message instead of Label
(for simple translations)
<wicket:message key="SELECT_LANGUAGE_LABEL"/>
Put this in your HTML and it will be replaced by
Wicket for the translation in your property file.
70. Use wicket:message instead of Label
(for simple translations)
<input type="button" wicket:id="someid" wicket:message="value:CHANGE" />
71. Use wicket:message instead of Label
(for simple translations)
<input type="button" wicket:id="someid" wicket:message="value:CHANGE" />
Wicket will add a ‘value’ attribute and
set it to the localized value!
76. Smell 1
Class names containing:
*With*
*Without*
public class CheckBoxWithoutLabel extends CheckBox
public class CheckBox extends LabeledComponent<CheckBoxWithoutLabel>
Duplicated code hierarchies are ugly...
78. Yes, we also have something like a
LabelWithoutLabel:
position = LabelPosition.NONE;
LabeledComponent<Label> lbl = new
LabeledComponent<Label>(property,
dopmd.getTranslationKey(), position, label);
80. Smell 3
many constructors
public class CheckBox extends LabeledComponent<CheckBoxWithoutLabel> {
private static final long serialVersionUID = -8246992163993958051L;
81. Smell 3
many constructors
public class CheckBox extends LabeledComponent<CheckBoxWithoutLabel> {
private static final long serialVersionUID = -8246992163993958051L;
public CheckBox(String translationKey, IModel<Boolean> model) {
this(null, translationKey, model);
}
82. Smell 3
many constructors
public class CheckBox extends LabeledComponent<CheckBoxWithoutLabel> {
private static final long serialVersionUID = -8246992163993958051L;
public CheckBox(String id, String translationKey, IModel<Boolean> model) {
this(id, translationKey, LabelPosition.RIGHT, model);
}
83. Smell 3
many constructors
public class CheckBox extends LabeledComponent<CheckBoxWithoutLabel> {
private static final long serialVersionUID = -8246992163993958051L;
public CheckBox(String translationKey, LabelPosition position, IModel<Boolean> model) {
this(null, translationKey, position, model);
}
84. Smell 3
many constructors
public class CheckBox extends LabeledComponent<CheckBoxWithoutLabel> {
private static final long serialVersionUID = -8246992163993958051L;
public CheckBox(String id, String translationKey, LabelPosition position, IModel<Boolean> model) {
super(id, translationKey, position, new CheckBoxWithoutLabel(id + "CheckBox", model));
getLabel().addCssClass("checkBoxWithLabel");
}
85. Smell 3
many constructors
public class CheckBox extends LabeledComponent<CheckBoxWithoutLabel> {
private static final long serialVersionUID = -8246992163993958051L;
public CheckBox(IModel<Boolean> model) {
this(null, model);
}
87. Smell 5
FeedbackPanel not inside Form
Page page = (Page) event.getPage();
event.refreshComponents(m_grid, page.getFeedbackPanel());
88. Smell 5
Refreshing FeedbackPanel, Toolbar or
other classes from unexpected locations
// Refresh the toolbar
if (getPage() instanceof Page) {
ToolBar tb = (ToolBar) ((Page) getPage()).getComponent("toolbar");
if (tb != null) {
target.addComponent(tb);
}
}
89. Smell 6
Casting
// Refresh the toolbar
if (getPage() instanceof Page) {
ToolBar tb = (ToolBar) ((Page) getPage()).getComponent("toolbar");
if (tb != null) {
target.addComponent(tb);
}
}
90. Smell 7
Using getParent()
public void setButtonBar(ButtonBar buttonBar) {
if (!buttonBar.getId().equals(s_buttonPanel)) {
throw new WicketRuntimeException("Buttonbar id is wrong.");
}
if (m_buttonBarPanel.getParent() == null) {
if (m_form.get(s_buttonPanel) != null) {
m_form.get(s_buttonPanel)
.replaceWith(m_buttonBarPanel);
} else {
m_form.add(m_buttonBarPanel);
}
}
m_buttonBarPanel.replaceWith(buttonBar);
}
91. Smell 8
Getting components
from the Page
ToolBar tb = (ToolBar) ((Page) getPage()).getComponent("toolbar");
92. Smell 9
Doing layout and styling in Java
ColumnLayoutContainer pptfContainer = new ColumnLayoutContainer("proofPaidTuitionFeeContainer")
.addComponent(sourceLabel, Size.percent(30))
.addComponent(amountLabel, Size.percent(30))
.addComponent(factory.createDateTimeLabel("dateTime", m_dateTimeModel,
LabelPosition.TOP, m_controller.getTimeZoneCurrentUser()))
.newLine();
93. Smell 10
Using onBeforeRender() too often
protected void onBeforeRender() {
super.onBeforeRender();
if (isEnabled()) {
AjaxRequestTarget target = AjaxRequestTarget.get();
if (target != null) {
target.appendJavascript(TextTemplateHeaderContributor.forPlainJs(s_textTemplate,
getHeaderContributor(false)).toString());
} else {
add(TextTemplateHeaderContributor.forJavaScript(s_textTemplate,
getHeaderContributor(true)));
}
}
}
tip: check onConfigure()
(and in this case: check IHeaderContributor)
97. Smell 11
Calling ResourceModel.getObject()
feedback.info(new ResourceModel(XTranslation.PROPERTY).getObject());
feedback.info(getString(XTranslation.PROPERTY));
Call getString only outside constructor!
(check methods that are called by the constructor)
98. Smell 11
Calling ResourceModel.getObject()
feedback.info(new ResourceModel(XTranslation.PROPERTY).getObject());
feedback.info(getString(XTranslation.PROPERTY));
Call getString only outside constructor!
(check methods that are called by the constructor)
Tried to retrieve a localized string for a component that has not yet been added to the page. This can sometimes lead to an invalid or
no localized resource returned. Make sure you are not calling Component#getString() inside your Component's constructor. Offending
component: [MarkupContainer [Component id = panelmenu, page = <No Page>, path = panelmenu.PanelMenu]]