1 Start with plain HTML
A page begins as an HTML file. The wicket:id attribute marks the tag a component will render; the dashed bead is the component it waits for. There is no template language: the file stays HTML that any editor or browser opens.
OrderPage.html
<html>
<body>
<p wicket:id="message">Message</p>
</body>
</html>
OrderPage as a tree after step 1
- OrderPage page
OrderPage.html
<html><body> <p wicket:id="message">Message</p></body></html>
2 Give the tag a component
Behaviour lives in a Java class with the same name. add(...) puts a Label into the page’s tree under the id "message", and Wicket renders it into the tag with that wicket:id. The ribbon joins the page to its first component.
OrderPage.java
public class OrderPage extends WebPage {
public OrderPage() {
add(new Label("message", "Check both addresses."));
}
}
OrderPage.html
<html>
<body>
<p wicket:id="message">Message</p>
</body>
</html>
OrderPage as a tree after step 2
-
OrderPage page
- "message" Label
Tap a component to light its lines in the files below. Lines marked + are new in this step.
OrderPage.java
public class OrderPage extends WebPage { public OrderPage() { add(new Label("message", "Check both addresses.")); }}
OrderPage.html
<html><body> <p wicket:id="message">Message</p></body></html>
3 Add a form with fields
Components nest the way tags do. The form is a child of the page; the text fields and the button are children of the form, in the Java and in the HTML. A CompoundPropertyModel binds each field to the property of the address with the field’s id.
OrderPage.java
public class OrderPage extends WebPage {
public OrderPage() {
add(new Label("message", "Check both addresses."));
Form<Address> form = new Form<>("order",
new CompoundPropertyModel<>(billingAddress));
form.add(new TextField<String>("street"));
form.add(new TextField<String>("city"));
form.add(new Button("save"));
add(form);
}
}
OrderPage.html
<html>
<body>
<p wicket:id="message">Message</p>
<form wicket:id="order">
<input wicket:id="street">
<input wicket:id="city">
<button wicket:id="save">Save</button>
</form>
</body>
</html>
OrderPage as a tree after step 3
-
OrderPage page
- "message" Label
-
"order" Form
- "street" TextField
- "city" TextField
- "save" Button
Tap a component to light its lines in the files below. Lines marked + are new in this step.
OrderPage.java
public class OrderPage extends WebPage { public OrderPage() { add(new Label("message", "Check both addresses.")); Form<Address> form = new Form<>("order", new CompoundPropertyModel<>(billingAddress)); form.add(new TextField<String>("street")); form.add(new TextField<String>("city")); form.add(new Button("save")); add(form); }}
OrderPage.html
<html><body> <p wicket:id="message">Message</p> <form wicket:id="order"> <input wicket:id="street"> <input wicket:id="city"> <button wicket:id="save">Save</button> </form></body></html>
4 Reuse a panel, twice
Move the fields into AddressPanel, a Java class with its own markup inside <wicket:panel> and its own model. The form uses it twice, for billing and for shipping, and the same branch grows under each: one class, one file of markup, two instances in the tree.
OrderPage.java
public class OrderPage extends WebPage {
public OrderPage() {
add(new Label("message", "Check both addresses."));
Form<Void> form = new Form<>("order");
form.add(new AddressPanel("billing", billingAddress));
form.add(new AddressPanel("shipping", shippingAddress));
form.add(new Button("save"));
add(form);
}
}
AddressPanel.java
public class AddressPanel extends Panel {
public AddressPanel(String id, IModel<Address> address) {
super(id, new CompoundPropertyModel<>(address));
add(new TextField<String>("street"));
add(new TextField<String>("city"));
}
}
OrderPage.html
<html>
<body>
<p wicket:id="message">Message</p>
<form wicket:id="order">
<div wicket:id="billing"></div>
<div wicket:id="shipping"></div>
<button wicket:id="save">Save</button>
</form>
</body>
</html>
AddressPanel.html
<wicket:panel>
<input wicket:id="street">
<input wicket:id="city">
</wicket:panel>
OrderPage as a tree after step 4
-
OrderPage page
- "message" Label
-
"order" Form
-
"billing" AddressPanel
- "street" TextField
- "city" TextField
-
"shipping" AddressPanel
- "street" TextField
- "city" TextField
- "save" Button
-
"billing" AddressPanel
Tap a component to light its lines in the files below. Lines marked + are new in this step.
OrderPage.java
public class OrderPage extends WebPage { public OrderPage() { add(new Label("message", "Check both addresses.")); Form<Void> form = new Form<>("order"); form.add(new AddressPanel("billing", billingAddress)); form.add(new AddressPanel("shipping", shippingAddress)); form.add(new Button("save")); add(form); }}
OrderPage.html
<html><body> <p wicket:id="message">Message</p> <form wicket:id="order"> <div wicket:id="billing"></div> <div wicket:id="shipping"></div> <button wicket:id="save">Save</button> </form></body></html>
AddressPanel.java
public class AddressPanel extends Panel { public AddressPanel(String id, IModel<Address> address) { super(id, new CompoundPropertyModel<>(address)); add(new TextField<String>("street")); add(new TextField<String>("city")); }}
AddressPanel.html
<wicket:panel> <input wicket:id="street"> <input wicket:id="city"></wicket:panel>
5 Update it from Java
Wicket renders on the server and keeps the page’s state there, in this tree. A click on the AjaxLink runs onClick in Java; target.add(shipping) renders that one branch again and Wicket swaps it into the page in the browser. You write no JavaScript.
OrderPage.java
public class OrderPage extends WebPage {
public OrderPage() {
add(new Label("message", "Check both addresses."));
Form<Void> form = new Form<>("order");
form.add(new AddressPanel("billing", billingAddress));
AddressPanel shipping =
new AddressPanel("shipping", shippingAddress);
shipping.setOutputMarkupPlaceholderTag(true);
shipping.setVisible(false);
form.add(new AjaxLink<Void>("shipElsewhere") {
@Override
public void onClick(AjaxRequestTarget target) {
shipping.setVisible(!shipping.isVisible());
target.add(shipping);
}
});
form.add(shipping);
form.add(new Button("save"));
add(form);
}
}
AddressPanel.java
public class AddressPanel extends Panel {
public AddressPanel(String id, IModel<Address> address) {
super(id, new CompoundPropertyModel<>(address));
add(new TextField<String>("street"));
add(new TextField<String>("city"));
}
}
OrderPage.html
<html>
<body>
<p wicket:id="message">Message</p>
<form wicket:id="order">
<div wicket:id="billing"></div>
<a wicket:id="shipElsewhere">
Ship to another address</a>
<div wicket:id="shipping"></div>
<button wicket:id="save">Save</button>
</form>
</body>
</html>
AddressPanel.html
<wicket:panel>
<input wicket:id="street">
<input wicket:id="city">
</wicket:panel>
OrderPage as a tree after step 5
-
OrderPage page
- "message" Label
-
"order" Form
-
"billing" AddressPanel
- "street" TextField
- "city" TextField
- "shipElsewhere" AjaxLink
-
"shipping" AddressPanel
- "street" TextField
- "city" TextField
- "save" Button
-
"billing" AddressPanel
Tap a component to light its lines in the files below. Lines marked + are new in this step.
OrderPage.java
public class OrderPage extends WebPage { public OrderPage() { add(new Label("message", "Check both addresses.")); Form<Void> form = new Form<>("order"); form.add(new AddressPanel("billing", billingAddress)); AddressPanel shipping = new AddressPanel("shipping", shippingAddress); shipping.setOutputMarkupPlaceholderTag(true); shipping.setVisible(false); form.add(new AjaxLink<Void>("shipElsewhere") { @Override public void onClick(AjaxRequestTarget target) { shipping.setVisible(!shipping.isVisible()); target.add(shipping); } }); form.add(shipping); form.add(new Button("save")); add(form); }}
OrderPage.html
<html><body> <p wicket:id="message">Message</p> <form wicket:id="order"> <div wicket:id="billing"></div> <a wicket:id="shipElsewhere"> Ship to another address</a> <div wicket:id="shipping"></div> <button wicket:id="save">Save</button> </form></body></html>
AddressPanel.java
public class AddressPanel extends Panel { public AddressPanel(String id, IModel<Address> address) { super(id, new CompoundPropertyModel<>(address)); add(new TextField<String>("street")); add(new TextField<String>("city")); }}
AddressPanel.html
<wicket:panel> <input wicket:id="street"> <input wicket:id="city"></wicket:panel>
6 Test it without a browser
WicketTester renders the page in a plain unit test: no browser, no server. It finds a component by its path in the tree, so "order:shipping" is the ribbon from the form to the panel. The test clicks the Ajax link and checks that the panel came back.
OrderPageTest.java
@Test void shipsToAnotherAddress() {
tester.startPage(OrderPage.class);
tester.assertLabel("message", "Check both addresses.");
tester.clickLink("order:shipElsewhere");
tester.assertComponentOnAjaxResponse("order:shipping");
}
AddressPanel.java
public class AddressPanel extends Panel {
public AddressPanel(String id, IModel<Address> address) {
super(id, new CompoundPropertyModel<>(address));
add(new TextField<String>("street"));
add(new TextField<String>("city"));
}
}
OrderPage.html
<html>
<body>
<p wicket:id="message">Message</p>
<form wicket:id="order">
<div wicket:id="billing"></div>
<a wicket:id="shipElsewhere">
Ship to another address</a>
<div wicket:id="shipping"></div>
<button wicket:id="save">Save</button>
</form>
</body>
</html>
AddressPanel.html
<wicket:panel>
<input wicket:id="street">
<input wicket:id="city">
</wicket:panel>
OrderPage as a tree after step 6
-
OrderPage page
- "message" Label
-
"order" Form
-
"billing" AddressPanel
- "street" TextField
- "city" TextField
- "shipElsewhere" AjaxLink
-
"shipping" AddressPanel
- "street" TextField
- "city" TextField
- "save" Button
-
"billing" AddressPanel
Tap a component to light its lines in the files below. Lines marked + are new in this step.
OrderPageTest.java
@Test void shipsToAnotherAddress() { tester.startPage(OrderPage.class); tester.assertLabel("message", "Check both addresses."); tester.clickLink("order:shipElsewhere"); tester.assertComponentOnAjaxResponse("order:shipping");}
OrderPage.html
<html><body> <p wicket:id="message">Message</p> <form wicket:id="order"> <div wicket:id="billing"></div> <a wicket:id="shipElsewhere"> Ship to another address</a> <div wicket:id="shipping"></div> <button wicket:id="save">Save</button> </form></body></html>
AddressPanel.java
public class AddressPanel extends Panel { public AddressPanel(String id, IModel<Address> address) { super(id, new CompoundPropertyModel<>(address)); add(new TextField<String>("street")); add(new TextField<String>("city")); }}
AddressPanel.html
<wicket:panel> <input wicket:id="street"> <input wicket:id="city"></wicket:panel>
7 Secure by default
Every page is sent with a strict Content Security Policy, without unsafe-inline. Wicket puts a fresh nonce on every script and stylesheet a component contributes to the page, here the Ajax link’s. You only add what your application needs, such as one more host for images.
How Wicket keeps applications secure Next: build your first application
OrderPage.java
public class OrderPage extends WebPage {
public OrderPage() {
add(new Label("message", "Check both addresses."));
Form<Void> form = new Form<>("order");
form.add(new AddressPanel("billing", billingAddress));
AddressPanel shipping =
new AddressPanel("shipping", shippingAddress);
shipping.setOutputMarkupPlaceholderTag(true);
shipping.setVisible(false);
form.add(new AjaxLink<Void>("shipElsewhere") {
@Override
public void onClick(AjaxRequestTarget target) {
shipping.setVisible(!shipping.isVisible());
target.add(shipping);
}
});
form.add(shipping);
form.add(new Button("save"));
add(form);
}
}
AddressPanel.java
public class AddressPanel extends Panel {
public AddressPanel(String id, IModel<Address> address) {
super(id, new CompoundPropertyModel<>(address));
add(new TextField<String>("street"));
add(new TextField<String>("city"));
}
}
MyApplication.java
public class MyApplication extends WebApplication {
@Override
protected void init() {
super.init();
getCspSettings().blocking()
.add(CSPDirective.IMG_SRC,
"https://images.example.org");
}
}
OrderPage.html
<html>
<body>
<p wicket:id="message">Message</p>
<form wicket:id="order">
<div wicket:id="billing"></div>
<a wicket:id="shipElsewhere">
Ship to another address</a>
<div wicket:id="shipping"></div>
<button wicket:id="save">Save</button>
</form>
</body>
</html>
AddressPanel.html
<wicket:panel>
<input wicket:id="street">
<input wicket:id="city">
</wicket:panel>
The response to the browser
Content-Security-Policy:
default-src 'none';
script-src 'strict-dynamic' 'nonce-R3x';
style-src 'nonce-R3x';
img-src 'self'
https://images.example.org;
connect-src 'self'; font-src 'self'; …
<head>
<script nonce="R3x"
src="…/wicket-ajax-jquery.js">
</script>
<script nonce="R3x">
Wicket.Ajax.ajax({"e":"click",
"c":"shipElsewhere3", …});
</script>
</head>
OrderPage as a tree after step 7
-
OrderPage page
- "message" Label
-
"order" Form
-
"billing" AddressPanel
- "street" TextField
- "city" TextField
- "shipElsewhere" AjaxLink
-
"shipping" AddressPanel
- "street" TextField
- "city" TextField
- "save" Button
-
"billing" AddressPanel
Tap a component to light its lines in the files below. Lines marked + are new in this step.
OrderPage.java
public class OrderPage extends WebPage { public OrderPage() { add(new Label("message", "Check both addresses.")); Form<Void> form = new Form<>("order"); form.add(new AddressPanel("billing", billingAddress)); AddressPanel shipping = new AddressPanel("shipping", shippingAddress); shipping.setOutputMarkupPlaceholderTag(true); shipping.setVisible(false); form.add(new AjaxLink<Void>("shipElsewhere") { @Override public void onClick(AjaxRequestTarget target) { shipping.setVisible(!shipping.isVisible()); target.add(shipping); } }); form.add(shipping); form.add(new Button("save")); add(form); }}
OrderPage.html
<html><body> <p wicket:id="message">Message</p> <form wicket:id="order"> <div wicket:id="billing"></div> <a wicket:id="shipElsewhere"> Ship to another address</a> <div wicket:id="shipping"></div> <button wicket:id="save">Save</button> </form></body></html>
AddressPanel.java
public class AddressPanel extends Panel { public AddressPanel(String id, IModel<Address> address) { super(id, new CompoundPropertyModel<>(address)); add(new TextField<String>("street")); add(new TextField<String>("city")); }}
AddressPanel.html
<wicket:panel> <input wicket:id="street"> <input wicket:id="city"></wicket:panel>
MyApplication.java
public class MyApplication extends WebApplication { @Override protected void init() { super.init(); getCspSettings().blocking() .add(CSPDirective.IMG_SRC, "https://images.example.org"); }}
The response to the browser
Content-Security-Policy: default-src 'none'; script-src 'strict-dynamic' 'nonce-R3x'; style-src 'nonce-R3x'; img-src 'self' https://images.example.org; connect-src 'self'; font-src 'self'; …<head> <script nonce="R3x" src="…/wicket-ajax-jquery.js"> </script> <script nonce="R3x"> Wicket.Ajax.ajax({"e":"click", "c":"shipElsewhere3", …}); </script></head>
Lines marked + are new in their step. Point at a component, or tab to the tree and use the arrow keys, to follow it from its Java line to its tag.