Photo: Veronika Biro on Unsplash

Documentation

How Wicket works

One small order page in seven steps, from plain HTML to a tested page with Ajax that is secure by default. Each step adds a few lines of Java or HTML and grows the page’s component tree.

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.

Wicket tags and attributes Next: Give the tag a component

OrderPage.html

<html>
<body>
  <p wicket:id="message">Message</p>
</body>
</html>

OrderPage as a tree after step 1

  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.

Components and markup Next: Add a form with fields

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

  1. OrderPage page
    1. "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.

Models and component data Next: Reuse a panel, twice

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

  1. OrderPage page
    1. "message" Label
    2. "order" Form
      1. "street" TextField
      2. "city" TextField
      3. "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.

Build a reusable panel Next: Update it from Java

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

  1. OrderPage page
    1. "message" Label
    2. "order" Form
      1. "billing" AddressPanel
        1. "street" TextField
        2. "city" TextField
      2. "shipping" AddressPanel
        1. "street" TextField
        2. "city" TextField
      3. "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<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.

How Wicket Ajax works Next: Test it without a browser

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

  1. OrderPage page
    1. "message" Label
    2. "order" Form
      1. "billing" AddressPanel
        1. "street" TextField
        2. "city" TextField
      2. "shipElsewhere" AjaxLink
      3. "shipping" AddressPanel
        1. "street" TextField
        2. "city" TextField
      4. "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<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.

Test pages and components Next: Secure by default

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

  1. OrderPage page
    1. "message" Label
    2. "order" Form
      1. "billing" AddressPanel
        1. "street" TextField
        2. "city" TextField
      2. "shipElsewhere" AjaxLink
      3. "shipping" AddressPanel
        1. "street" TextField
        2. "city" TextField
      4. "save" Button

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

  1. OrderPage page
    1. "message" Label
    2. "order" Form
      1. "billing" AddressPanel
        1. "street" TextField
        2. "city" TextField
      2. "shipElsewhere" AjaxLink
      3. "shipping" AddressPanel
        1. "street" TextField
        2. "city" TextField
      4. "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<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.