Apache Wicket Workshop RWX 2010 - Bitly

1 downloads 115 Views 15MB Size Report
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