14. Component queueing
So far to build component hierarchy we have explicitly added each component and container in accordance with the corresponding markup. This necessary step can involve repetitive and boring code which must be changed every time we decide to change markup hierarchy. Component queueing is a new feature in Wicket 7 that solves this problem allowing Wicket to build component hierarchy in Java automatically making your code simpler and more maintainable. This chapter should serve as a short introduction to what Component Queueing is and what problems it is trying to solve.
14.1. Markup hierarchy and code
With Wicket as developers we use to define the hierarchy of components in the markup templates:
<form wicket:id='customer'>
<input wicket:id='first' type='text'/>
<input wicket:id='last' type='text'/>
<div wicket:id="child">
<input wicket:id='first' type='text'/>
<input wicket:id='last' type='text'/>
<input wicket:id='dob' type='date'/>
</div>
</form>
and then we repeat the same hierarchy in Java code:
Form<Void> form = new Form<>("customer");
add(form);
form.add(new TextField("first"));
form.add(new TextField("last"));
WebMarkupContainer child=new WebMarkupContainer("child");
form.add(child);
child.add(new TextField("first"));
child.add(new TextField("last"));
child.add(new TextField("dob"));
The need for the hierarchy in the markup is obvious, it is simply how the markup works. On the Java side of things it may not be immediately apparent. After all, why can we not write the code like this?
add(new Form<Void>("customer"));
add(new TextField("first"));
add(new TextField("last"));
WebMarkupContainer child=new WebMarkupContainer("child");
add(child);
add(new TextField("first"));
add(new TextField("last"));
add(new TextField("dob"));
There are a couple of reasons:
Ambiguities that happen with duplicate ids
Inheriting state from parent to child
We will examine these below.
14.1.1. Markup Id Ambiguities
In the example above we have a form that collects the name of a customer along with the name of their child and the child’s date of birth. We mapped the name of the customer and child to form components with wicket ids first and last. If we were to add all the components to the same parent we would get an error because we cannot have two components with the same wicket id under the same parent (two components with id first and two with id last). Without hierarchy in Java we would have to make sure that all wicket ids in a markup file are unique, no small feat in a non-trivial page or panel. But, with hierarchy on the Java side we just have to make sure that no parent has two children with the same id, which is trivial.
14.1.2. Inheriting State From Parents
Suppose we wanted to hide form fields related to the child in the example above when certain conditions are met. Without hierarchy we would have to modify the first, last, and dob fields to implement the visibility check. Worse, whenever we would add a new child related field we would have to remember to implement the same check; this is a maintenance headache. With hierarchy this is easy, simply hide the parent container and all children will be hidden as well — the code lives in one place and is automatically inherited by all descendant components. Thus, hierarchy on the Java side allows us to write succinct and maintainable code by making use of the parent-child relationship of components.
14.1.3. Pain Points of the Java-Side Hierarchy
While undeniably useful, the Java-side hierarchy can be a pain to maintain. It is very common to get requests to change things because the designer needs to wrap some components in a div with a dynamic style or class attribute. Essentially we want to go from:
<form wicket:id='customer'>
<input wicket:id='first' type='text'/>
<input wicket:id='last' type='text'/>
To:
<form wicket:id='customer'>
<div wicket:id='container'>
<input wicket:id='first' type='text'/>
<input wicket:id='last' type='text'/>
</div>
Seems simple enough, but to do so we need to create the new container, find the code that adds all the components that have to be relocated and change it to add to the new container instead. This code:
Form<Void> form = new Form<>("customer");
add(form);
form.add(new TextField("first"));
form.add(new TextField("last"));
Will become:
Form<Void> form = new Form<>("customer");
add(form);
WebMarkupContainer container=new WebMarkupContainer("container");
form.add(container);
container.add(new TextField("first"));
container.add(new TextField("last"));
Another common change is to tweak the nesting of markup tags. This is something a designer should be able to do on their own if the change is purely visual, but cannot if it means Wicket components will change parents.
In large pages with a lot of components these kinds of simple changes tend to cause a lot of annoyance for the developers.
14.1.4. Component Queueing To The Rescue
The idea behind component queueing is simple: instead of adding components to their parents directly, the developer can queue them in any ancestor and have Wicket automatically ‘dequeue’ them to the correct parent using the hierarchy defined in the markup. This will give us the best of both worlds: the developer only has to define the hierarchy once in markup, and have it automatically constructed in Java land.
That means we can go from code like this:
Form<Void> form = new Form<>("customer");
add(form);
form.add(new TextField("first"));
form.add(new TextField("last"));
WebMarkupContainer child=new WebMarkupContainer("child");
form.add(child);
child.add(new TextField("first"));
child.add(new TextField("last"));
child.add(new TextField("dob"));
To code like this:
queue(new Form("customer"));
queue(new TextField("first"));
queue(new TextField("last"));
WebMarkupContainer child=new WebMarkupContainer("child");
queue(child);
child.queue(new TextField("first"));
child.queue(new TextField("last"));
child.queue(new TextField("dob"));
Note that we had to queue child’s first and last name fields to the child container in order to disambiguate their wicket ids. |
The code above does not look shorter or that much different, so where is the advantage?
Suppose our designer wants us to wrap the customer’s first and last name fields with a div that changes its styling based on some condition. We saw how to do that above, we had to create a container and then reparent the two TextField components into it. Using queueing we can skip the second step, all we have to do is add the following line:
queue(new WebMarkupContainer("container"));
When dequeueing Wicket will automatically reparent the first and last name fields into the container for us.
If the designer later wanted to move the first name field out of the div we just added for them they could do it all by themselves without requiring any changes in the Java code. Wicket would dequeue the first name field into the form and the last name field into the container div.
14.2. Improved auto components
Auto components, such as Enclosure, are a very useful feature of Wicket, but they have always been a pain to implement and use.
Suppose we have:
<wicket:enclosure childId="first">
<input wicket:id="first" type="text"/>
<input wicket:id="last" type="text"/>
</wicket:enclosure>
Together with:
add(new TextField("first").setRequired(true).setVisible(false));
add(new TextField("last").setRequired(true));
When developing auto components the biggest pain point is in figuring out who the children of the auto component are. In the markup above the enclosure is a parent of the text fields, but in Java it would be a sibling because auto components do not modify the java-side hierarchy. So when the Enclosure is looking for its children it has to parse the markup to figure out what they are. This is not a trivial task.
Because auto components do not insert themselves properly into the Java hierarchy they are also hard for users to use. For example, the documentation of Enclosure does not recommend it to be used to wrap form components like we have above. When the page renders the enclosure will be hidden because first component is not visible. However, when we submit the form, last component will raise a required error. This is because last is not made a child of the hidden enclosure and therefore does not know its hidden — so it will try to process its input and raise the error.
Had we used queue instead of add in the code above, everything would work as expected. As part of Queueing implementation Wicket will properly insert auto components into the Java hierarchy. Furthermore, auto components will remain in the hierarchy instead of being added before render and removed afterwords. This is a big improvement because developers will no longer have to parse markup to find the children components — since children will be added to the enclosure by the dequeueing. Likewise, user restrictions are removed as well; the code above would work as expected.
14.3. When are components dequeued?
Once you call queue(), when are the components dequeued into the page hierarchy? When is it safe to call getParent() or use methods such as isVisibleInHierarchy() which rely on component’s position in hierarchy?
The components are dequeued as soon as a path is available from Page to the component they are queued into. The dequeue operation needs access to markup which is only available once the Page is known (because the Page object controls the extension of the markup).
If the Page is known at the time of the queue() call (eg if its called inside onInitialize()) the components are dequeued before queue() returns.
14.4. Restrictions of queueing
14.4.1. Ancestors
Suppose on a user profile panel we have the following code:
queue(new Label("first"));
queue(new Label("last"));
WebMarkupContainer secure=new WebMarkupContainer("secure") {
void onConfigure() {
super.onConfigure();
setVisible(isViewingOwnProfile());
}
};
queue(secure);
secure.queue(new Label("creditCardNumber"));
secure.queue(new Label("creditCardExpiry"));
What is to prevent someone with access to markup from moving the creditCardNumber label out of the secure div, causing a big security problem for the site?
Wicket will only dequeue components either to the component they are queued to or any of its descendants.
In the code above this is the reason why we queued the creditCardNumber label into the secure container. That means it can only be dequeued into the secure container’s hierarchy.
This restriction allows developers to enforce certain parent-child relationships in their code.
14.4.2. Regions
Dequeuing of components will not happen across components that implement the org.apache.wicket.IQueueRegion interface. This interface is implemented by all components that provide their own markup such as: Page, Panel, Border, Fragment. This is done so that if both a page and panel contain a component with id foo the one queued into the page will not be dequeued into the panel. This minimizes confusion and debugging time. The rule so far is that if a component provides its own markup only components queued inside it will be dequeued into it.
14.5. Summary
Component queueing is a new and improved way of creating the component hierarchy in Wicket 7. By having to define the hierarchy only once in markup we can make the Java-side code simpler and more maintainable.