kinabalu @ irc://irc.freenode.net - ##wicket, ##java kinabalu @ twitter ... Can send complex Java objects to client from
Apache Wicket Workshop
Andrew Lombardi Owner, Tech Evangelist Mystic Coders, LLC andrew AT mysticcoders DOT com kinabalu @ irc://irc.freenode.net - ##wicket, ##java kinabalu @ twitter
Slides available here:
1-up
http://bit.ly/wicket-workshop-rwx2010 http://bit.ly/wicket-workshop-rwx2010-2up
2-up
10 Years in business Software Consultants International Speaker Training Apache Wicket Contributor To our success!
What is a Wicket?
Wicket is... Wicket is a component-based web framework using Java and HTML.
The Cooking show model
GWT
• Pro • Use Java for client-side Javascript • Can send complex Java objects to client from server • Scalable since state is on server • Rich widget library • Simple for AJAX • Con • SEO unfriendly • Difficult to test • Data security problems (client-side state) • GWT compiler is slow • Front-end design in Java • Maven support is flaky • Not easily bookmarkable
JSF • Pro • Commercial support • Sun standard • Advanced tooling • Rich widget / component library • Con • Sun standard • JSP tag soup • Library jarhell • Painful without Facelets • Multiple implementations to choose from
Wicket
• Pro • True separation of layout and code • Component based (true reusability) • Simple AJAX support with existing components • Rich community support mailing list and widget library • Just Java and Just HTML • Easy to refactor and debug • Object oriented by design • Con • Documentation and examples • More initial Java code • AJAX support requires server round-trip • Wicket internals difficult to grok • Object oriented by design
What we’ll cover 1. Your first app 2. Presentation basics 3. Unit testing 4. Accepting user input 5. AJAX
wicket-quickstart or the Wicket maven archetype headstart
Visit Run
http://wicket.apache.org/quickstart.html mvn archetype:create -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=1.4.9 -DgroupId=com.mycompany -DartifactId=myproject
Exercise Run
cd myproject/ mvn jetty:run
E D
O M Quickstart
Simplest Wicket App Page class
Page HTML
Application class
Component mapping
add(new Label(“message”, model))
...
WicketTester Isolates your Page tests without requiring the servlet container
JUnit
or
TestNG
WicketTester startPage -Tell’s WicketTester to run your page assertRenderedPage - Assert the Page rendered with no error assertLabel - Assert Label with given component id and data rendered
Create
New page that output’s “Hello, World!”
Test
Page renders Label output’s correct
Exercise
E D
O M Test page and label render
A Link in Wicket is the component model’s way of linking together Pages
Linking 101 Page 1
Page 2
BookmarkablePageLink - bookmarkable link to a Wicket page Link - onClick event to “do something” when link clicked ExternalLink - link to non-Wicket managed resource
Create
Two new pages where Page A links to Page B
Test
Page A renders Click link to Page B and renders
Exercise
E D
O M Link pages Test render
URL Mounting mount - Mount a URL based on coding strategy or package mountBookmarkablePage - Mount’s a bookmarkable page to a given URL path mountSharedResource - Mount’s a shared resource to a given URL path
URL Mounting BookmarkablePageRequestTargetUrlCodingStrategy Example: /path/to/page/name-a/value-a/name-b/value-b QueryStringUrlCodingStrategy Example: /path/to/page?name-a=value-a&name-b=value-b IndexedParamUrlCodingStrategy Example: /path/to/page/value-a/value-b
IRequestTargetUrlCodingStrategy decode - returns the decoded request target for given request parameters encode - return the encoded url for the given request target matches - is this strategy applicable for the provided request target
Markup Inheritance
Navigation Footer Base Page Page A class
!! My Company !
Markup Inheritance
! !Copyright 2010
Markup Inheritance
Page A class
extends
Base Page class
Create
Base Page and a Child Page with markup
Exercise
E D
O M Markup inheritance
Forms in Wicket allow you to accept, process and validate user input.
Example Form Elements
Form Processing Flow
Start
required check
fail
validate input
fail
convert input
push input
onSubmit
fail onError
Form Hierarchy Form “myform” TextField “name” TextField “email” TextArea “comment” Button “add”
Create
Form with fields for Name, Email, and Comment
Test
Fill out form and receive no errors
Exercise
What happened? To persist Form state
between Page’s Wicket requires a Model for
each FormComponent or parent
Not using models? new Label("street", ! ! customer.getAddress().getStreet());
•Label doesn’t get notified if address / street /
customer changes •Possible NullPointerException if customer or address is null
PropertyModel saves the day new Label("street", ! ! new PropertyModel(customer, “address.street”));
•Safe from NullPointerException’s •Dynamic
Add
PropertyModel used on each Form field
Exercise
E D
O M PropertyModel
Form Validation in Wicket is attached to components or the form.
FormTester An extension of
WicketTester with ability to unit test Form entry and submission
FormTester setValue - Set’s the FormComponent’s value submit - Call’s the default submit event submitLink - Submit’s a SubmitLink which may be outside of the Form
Component Colon Notation new Form(“myform”)
Component Colon Notation new Form(“myform”) myform
Component Colon Notation
new Form(“myform”) .add(new TextField(“name”) myform:name
Component Colon Notation
new Form(“myform”) .add(new TextField(“email”) myform:email
Component Colon Notation
new Form(“myform”) .add(new TextField(“comment”) myform:comment
Component Colon Notation
new Form(“myform”) .add(new Button(“add”) myform:add
Component Colon Notation
new Form(“myform”) .add(new CheckGroup(“group”) .add(new Check(“mycheck”) myform:group:mycheck
Add
Validate name is required, and email valid
Test
Fill out form and receive no errors
Exercise
E D
O M Form Validation
Wicket Validators NumberValidator - Validate’s max, min, range for a Number StringValidator - Validate’s max, min, length for a String PatternValidator - Validate’s the String input matches the regex pattern EmailAddressValidator - Validate’s field contains a valid email EqualPasswordInputValidator - Form validation of two password fields being equal
Feedback Messages • Page class • Component class • Application class • Application base class
Feedback Filters ComponentFeedbackMessageFilter - Gives only messages for a specific component ContainerFeedbackMessageFilter - Gives only messages for a specific container component and its children ErrorLevelFeedbackMessageFilter - Gives only messages at a certain error level (or higher)
Add
FeedbackPanel to our existing Form
Test
Browser test the 2 validator cases
Exercise
E D
O M FeedbackPanel
A Model in Wicket allows components to retrieve and store data.
IModel getObject():T setObject(T)
Component id
Label
TextField
Page
Flow of Model Data Mystic Paste
Paste
Renders
Controller
Receiv
es
TextArea
View Gets
Sets Locator
IModel
Gets Model
PasteItem content
Sets
Wicket Core Models Model
Description
Model
Simple model used to store static content, or used as a base class for dynamic behavior.
PropertyModel
Uses a property expression to dynamically access a property of your domain objects.
CompoundPropertyModel
Uses component identifiers as property expressions to bind components to its domain objects.
LoadableDetachableModel
Abstract model for quickly creating detachable models.
ResourceModel
Easy-to-use model for retrieving messages from resource bundles.
StringResourceModel
Advanced model for retrieving messages from resource bundles; supports property expressions and MessageFormat substitutions.
Add
CompoundPropertyModel for the parent Form
Exercise
E D
O M
CompoundPropertyModel
Session usage in Wicket is managed by detaching unnecessary objects.
IDetachable detach()
Model value : Serializable
IModel getObject():T setObject(T)
LoadableDetachableModel new ViewPaste(new Model(pasteItem))
new ViewPaste(new DetachablePasteModel (pasteItem))
Session
Session
PasteItem Object
Identifier
Large
Small
ViewPaste
ViewPaste
IDetachable detach()
Model value : Serializable
IModel getObject():T setObject(T)
LoadableDetachableModel new ViewPaste(new Model(pasteItem))
new ViewPaste(new DetachablePasteModel (pasteItem))
Session
Session
PasteItem Object
Identifier
Large
Small
ViewPaste
ViewPaste
Nested Models public class DefaultWhenNullModel implements IModel { ! private static final long serialVersionUID = 1L; ! !
private final IModel nestedModel; private final T defaultValue;
! ! ! !
public DefaultWhenNullModel(IModel nestedModel, T defaultValue) { ! this.nestedModel = nestedModel; ! this.defaultValue = defaultValue; }
! ! ! !
public T getObject() { ! T val = nestedModel.getObject(); ! return val == null ? defaultValue : val; }
! ! !
public void setObject(T object) { ! nestedModel.setObject(object); }
! ! ! }
public void detach() { ! nestedModel.detach(); }
Nested Models
Form form = new Form(new CompoundPropertyModel( new ProfileDetachableModel())); form.add(new TextField("firstName"));
AbstractReadOnlyModel Label registrationCountLabel = new Label("registrationCount", ! new AbstractReadOnlyModel() { !
private static final long serialVersionUID = 1L;
! public Long getObject() { ! ! return profileDao.countRegistrations(); ! } });
MyPage properties
MyPage properties
ResourceModel Form form = new MyForm("myForm", new CompoundPropertyModel(...)); form.add(new Label("firstNameLabel", new ResourceModel("label.firstName")));
Form properties
label.firstName firstNameLabel.label.firstName myForm.firstNameLabel.label.firstName MyForm properties
The component’s path:
myForm:firstNameLabel
MyApp properties
ComponentStringResourceLoader Application properties
StringResourceModel MyPage.java Profile profile = new Profile(); profile.setFirstName("Werner"); profile.setLastName("Brandis"); add(new Label("verifyMessage", ! ! new StringResourceModel("verify.message", this, new Model(profile)) ); MyPage.properties
verify.message=Hi. My name is ${firstName} ${lastName}. passport. Verify me.
My voice is my
Wicket Message Tag MyPage.html Default Replaced Title ! This text will never be seen ! [firstName] ! [lastName] ! ! ! !
MyPage.java add(new Label(“firstName”, new PropertyModel(profile, “firstName”))); add(new Label(“lastName”, new PropertyModel(profile, “lastName”))); add(new BookmarkablePageLink(“verifyLink”, VerifyMePage.class)); MyPage.properties verify.message=Hi. My name is ${firstName} ${lastName}. passport. ${verifyLink} me. page.title=Sneakers Quotes verify=Verify
My voice is my
Wicket Message Tag MyPage.html
MyPage.properties movie.image.path=images/sneakers_coverart.jpg movie.image.title=Sneakers
Advanced Models - Pitfalls public FooPanel(String id,IModel userModel) { super(id,userModel); final User user = getModelObject(); IModel model = new LoadableDetachableModel() { private static final long serialVersionUID = 1L; @Override protected List load() { return getWebSession() .getUserService() .getUserFriends(user); } }; }
Advanced Models - Pitfalls public FooPanel(String id,IModel userModel) { super(id,userModel); final User user = getModelObject(); IModel model = new LoadableDetachableModel() { private static final long serialVersionUID = 1L; @Override protected List load() { return getWebSession() .getUserService() .getUserFriends(user); } }; }
Stale User entity serialized with each page
Advanced Models - Pitfalls public FooPanel(String id,IModel userModel) { super(id, userModel); final User user = getModelObject(); IModel model = new LoadableDetachableModel() { private static final long serialVersionUID = 1L;
@Override protected List load() { User u = FooPanel.this.getModelObject(); return getWebSession() .getUserService() .getUserFriends(u); } }; }
The entity is fetched each page view, and NOT serialized with the page. The only thing that gets serialized is the fetching model.
Advanced Models - Pitfalls
public FooPanel(String id, IModel userModel) { super(id, userModel); User user = getModelObject(); add(new Label("name", new PropertyModel(user, "screenName"))); }
Advanced Models - Pitfalls
public FooPanel(String id, IModel userModel) { super(id, userModel); User user = getModelObject(); add(new Label("name", new PropertyModel(user, "screenName"))); }
User not serialized with each page view, PropertyModel holds only a reference
Advanced Models - Pitfalls
public FooPanel(String id,IModel userModel) { super(id, userModel); add(new Label("name", new PropertyModel(userModel,"screenName"))); }
PropertyModel holds a reference instead to the Model which fetches User on page view. User object is fresh and not serialized with Page
Advanced Models - Pitfalls
public FooPanel(String id,User user) { super(id, new Model(user)); }
Advanced Models - Pitfalls
public FooPanel(String id,User user) { super(id, new Model(user)); }
User object is stale and stored with Page
Advanced Models - Pitfalls public FooPanel(String id, User user) { ! super(id); ! final int id = user.getId(); ! setModel(new LoadableDetachableModel() { ! ! @Override ! ! protected User load() { ! ! ! return getUserDao().findById(id); ! ! } ! }); }
User object not stale and refreshed via DAO upon each page view
Repeaters in Wicket allow you to show blocks of markup multiple times.
ListView Hierarchy ListView “mylist” Label “0:name” Label “0:email” Label “1:name” Label “1:email”
Repeaters • RepeatingView • RefreshingView • ListView • DataView • GridView • DataTable
Add
A ListView to show our Guestbook entries
Exercise
E D
O M ListView
Behaviors in Wicket are decorators for components.
Behaviors
• Can manipulate component tags • Add custom Javascript to components • Add AJAX behavior to components • Header contribution (AJAX only)
AbstractBehavior beforeRender - Called before component render afterRender - Called after the component render bind - bind our behavior to the specific component onComponentTag - Called when component tag is in the process of rendering renderHead - Allows behavior to contribute to the markup head (i.e. add javascript include, etc)
E D
O M Behavior
SimpleAttributeModifier add(new Label(“message”, “Hello, World!”));
Hello, World!
SimpleAttributeModifier add(new Label(“message”, “Hello, World!”)); .add(new SimpleAttributeModifier(“style”, “color: red;”)); Hello, World!
O M
SimpleAttributeModifier Behavior
E D
AJAX
AJAX
O M E D Click Counter with AJAX
AJAX
setOutputMarkupId(true); target.addComponent(Component c);
O M
AutoCompleteTextField AJAX
E D
Tips on configuring Wicket for the real world
C U D O R P
N O I T
Configuration Modes
development deployment
development mode error pages • descriptive markup reloading • dynamic caching • no javascript / css • no optimizations mistakes early • discover (serialization, missing
•
components) Ajax debugger visible
deployment mode markup resources • cache checks • no stack trace errors to front• no end / compress JavaScript • minify wicket tags • no • no ajax debugger visible
configuring deployment System property: -Dwicket.configuration=deployment
servlet/filter init param configuration deployment
context parameter configuration deployment
configuring deployment
context parameter configuration deployment
configuring deployment
Add Spring using Maven ! org.apache.wicket ! wicket-spring ! ${wicket.version} ! org.apache.wicket ! wicket-ioc ! ${wicket.version} ! org.springframework ! spring ! 2.5.5
Spring Integration public class GuestbookPage extends WebPage { ! @SpringBean private GuestbookDao guestbookDao; public GuestbookPage() {
SpringComponentInjector •Uses annotation @SpringBean on fields to inject from Spring •Guesses bean to use by type unless explicitly set in the annotation •Annotation is processed inside of Wicket Component’s only
Or is it?
SpringComponentInjector InjectorHolder.getInjector().inject(this)
•Allow’s you to decorate non-
Component’s to process @SpringBean annotations
wicket-auth-roles with Maven
! org.apache.wicket ! wicket-auth-roles ! ${wicket.version}
wicket-auth-roles
public class WicketApplication extends WebApplication { ! public Class