6. Keeping control over HTML
Many Wicket newbies are initially scared by its approach to web development because they have the impression that the component-oriented nature of the framework prevents them from having direct control over the generated markup. This is due to the fact that many developers come from other server-side technologies like JSP where we physically implement the logic that controls how the final HTML is generated.
This chapter will prevent you from having any initial misleading feeling about Wicket showing you how to control and manipulate the generated HTML with the built-in tools shipped with the framework.
6.1. Hiding or disabling a component
At the end of the previous chapter we have seen how to hide a component calling its method setVisible. In a similar fashion, we can also decide to disable a component using method setEnabled. When a component is disabled all the links inside it will be in turn disabled (they will be rendered as ) and it can not fire JavaScript events.
Class Component provides two getter methods to determinate if a component is visible or enabled: isVisible and isEnabled.
Even if nothing prevents us from overriding these two methods to implement a custom logic to determinate the state of a component, we should keep in mind that methods isVisible and isEnabled are called multiple times before a component is fully rendered. Hence, if we place non-trivial code inside these two methods, we can sensibly deteriorate the responsiveness of our pages.
As we will see in the next chapter, class Component provides method onConfigure which is more suited to contain code that contributes to determinate component states because it is called just once during rendering phase of a request.
6.2. Modifing tag attributes
To modify tag attributes in a component’s HTML markup we can use class org.apache.wicket.AttributeModifier. This class extends org.apache.wicket.behavior.Behavior and can be added to any component via the Component‘s add method. Class Behavior is used to expand component functionalities and it can also modify component markup. We will see this class in detail later in chapter 19.1.
As first example of attribute manipulation let’s consider a Label component bound to the following markup:
<span wicket:id="simpleLabel"></span>
Suppose we want to add some style to label content making it red and bolded. We can add to the label an AttributeModifier which creates the tag attribute style with value color:red;font-weight:bold:
label.add(new AttributeModifier("style", "color:red;font-weight:bold"));
If attribute style already exists in the original markup, it will be replaced with the value specified by AttributeModifier. If we don’t want to overwrite the existing value of an attribute we can use subclass AttributeAppender which will append its value to the existing one:
label.add(new AttributeAppender("style", "color:red;font-weight:bold"));
We can also create attribute modifiers using factory methods provided by class AttributeModifier and it’s also possible to prepend a given value to an existing attribute:
//replaces existing value with the given one
label.add(AttributeModifier.replace("style", "color:red;font-weight:bold"));
//appends the given value to the existing one
label.add(AttributeModifier.append("style", "color:red;font-weight:bold"));
//prepends the given value to the existing one
label.add(AttributeModifier.prepend("style", "color:red;font-weight:bold"));
6.3. Generating tag attribute ‘id’
Tag attribute id plays a crucial role in web development as it allows JavaScript to identify a DOM element. That’s why class Component provides two dedicated methods to set this attribute. With method setOutputMarkupId(boolean output) we can decide if the id attribute will be rendered or not in the final markup (by default is not rendered). The value of this attribute will be automatically generated by Wicket and it will be unique for the entire page. If we need to specify this value by hand, we can use method setMarkupId(String id). The value of the id can be retrieved with method getMarkupId().
Wicket generates markup ids using an instance of interface org.apache.wicket.IMarkupIdGenerator. The default implementation is org.apache.wicket.DefaultMarkupIdGenerator and it uses a session-scoped counter to generate the final id. A different generator can be set with the markup settings class org.apache.wicket.settings.MarkupSettings available in the application class:
@Override
public void init()
{
super.init();
getMarkupSettings().setMarkupIdGenerator(myGenerator);
}
6.4. Creating in-line panels with WebMarkupContainer
Creating custom panels is a great way to handle complex user interfaces. However, sometimes we may need to create a panel which is used only by a specific page and only for a specific task.
In situations like these org.apache.wicket.markup.html.WebMarkupContainer component is better suited than custom panels because it can be directly attached to a tag in the parent markup without needing a corresponding html file (hence it is less reusable). Let’s consider for example the main page of a mail service where users can see a list of received mails. Suppose that this page shows a notification box where user can see if new messages have arrived. This box must be hidden if there are no messages to display and it would be nice if we could handle it as if it was a Wicket component.
Suppose also that this information box is a
<div wicket:id="informationBox">
//here's the body
You've got <span wicket:id="messagesNumber"></span> new messages.
</div>
Under those conditions we can consider using a WebMarkupContainer component rather than implementing a new panel. The code needed to handle the information box inside the page could be the following:
//Page initialization code
WebMarkupContainer informationBox = new WebMarkupContainer ("informationBox");
informationBox.add(new Label("messagesNumber", messagesNumber));
add(informationBox);
//If there are no new messages, hide informationBox
informationBox.setVisible(false);
As you can see in the snippet above we can handle our information box from Java code as we do with any other Wicket component.
Note also that we may later choose to make information box visible by calling setVisible(true), upon for example an AJAX request (we will be covering such an example in chapter 19.2.8).
6.5. Working with markup fragments
Another circumstance in which we may prefer to avoid the creation of custom panels is when we want to conditionally display small fragments of markup in a page. In this case if we decided to use panels, we would end up having a huge number of small panel classes with their related markup file.
To better cope with situations like this, Wicket defines component Fragment in package org.apache.wicket.markup.html.panel. Just like its parent component WebMarkupContainer, Fragment doesn’t have its own markup file but it uses a markup fragment defined in the markup file of its parent container, which can be a page or a panel. The fragment must be delimited with tag
In the following example we have defined a fragment in a page and we used it as content area:
Page markup:
<html>
...
<body>
...
<div wicket:id="contentArea"></div>
<wicket:fragment wicket:id="fragmentId">
<p>News available</p>
</wicket:fragment>
</body>
</html>
Java code:
Fragment fragment = new Fragment ("contentArea", "fragmentId", this);
add(fragment);
When the page is rendered, markup inside the fragment will be inserted inside div element:
<html>
...
<body>
...
<div wicket:id="contentArea">
<p>News available</p>
</div>
</body>
</html>
Fragments can be very helpful with complex pages or components. For example let’s say that we have a page where users can register to our forum. This page should first display a form where user must insert his/her personal data (name, username, password, email and so on), then, once the user has submitted the form, the page should display a message like “Your registration is complete! Please check your mail to activate your user profile.”.
Instead of displaying this message with a new component or in a new page, we can define two fragments: one for the initial form and one to display the confirmation message. The second fragment will replace the first one after the form has been submitted:
Page markup:
<html>
<body>
<div wicket:id="contentArea"></div>
<wicket:fragment wicket:id="formFrag">
<!-- Form markup goes here -->
</wicket:fragment>
<wicket:fragment wicket:id="messageFrag">
<!-- Message markup goes here -->
</wicket:fragment>
</body>
</html>
Java code:
Fragment fragment = new Fragment ("contentArea", "formFrag", this);
add(fragment);
//form has been submitted
Fragment fragment = new Fragment ("contentArea", "messageFrag", this);
replace(fragment);
6.6. Adding header contents to the final page
Panel’s markup can also contain HTML tags which must go inside header section of the final page, like tags or . To tell Wicket to put these tags inside page , we must surround them with the
Considering the markup of a generic panel, we can use
<wicket:head>
<script type="text/javascript">
function myPanelFunction(){
}
</script>
<style>
.myPanelClass{
font-weight: bold;
color: red;
}
</style>
</wicket:head>
<body>
<wicket:panel>
</wicket:panel>
</body>
Wicket will take care of placing the content of
The <wicket:head> tag can also be used with children pages/panels which extend parent markup using tag <wicket:extend>. |
The content of the <wicket:head> tag is added to the header section once per component class. In other words, if we add multiple instances of the same panel to a page, the <head> tag will be populated just once with the content of <wicket:head>. |
The <wicket:head> tag is ideal if we want to define small in-line blocks of CSS or JavaScript. However Wicket provides also a more sophisticated technique to let components contribute to header section with in-line blocks and resource files like CSS or JavaScript files. We will see this technique later in chapter 16. |
6.7. Using stub markup in our pages/panels
Wicket’s
<html>
<head>
</head>
<body>
<wicket:remove>
<!-- Stub markup goes here -->
</wicket:remove>
</body>
</html>
6.8. How to render component body only
When we bind a component to its corresponding tag we can choose to get rid of this outer tag in the final markup. If we call method setRenderBodyOnly(true) on a component Wicket will remove the surrounding tag.
For example given the following markup and code:
HTML markup:
<html>
<head>
<title>Hello world page</title>
</head>
<body>
<div wicket:id="helloWorld">[helloWorld]</div>
</body>
</html>
Java code:
Label label = new Label("helloWorld", “Hello World!”);
label.setRenderBodyOnly(true);
add(label);
the output will be:
<html>
<head>
<title>Hello world page</title>
</head>
<body>
Hello World!
</body>
</html>
As you can see the Our data are rarely displayed alone without a caption or other graphic elements that make clear the meaning of their value. For example: Wicket comes with a nice utility tag called Now if component totalAmount is not visible, its description (Total amount:) will be automatically hidden. If we have more than a Wicket component inside child attribute supports also nested components with a colon-separated path: Component org.apache.wicket.markup.html.border.Border is a special purpose container created to enclose its tag body with its related markup. Just like panels and pages, borders also have their own markup file which is defined following the same rules seen for panels and pages. In this file The we would obtain the following resulting HTML: Border can also contain children components which can be placed either inside its markup file or inside its corresponding HTML tag. In the first case children must be added to the border component with method addToBorder(Component…), while in the second case we must use the add(Component…) method. The following example illustrates both use cases: Border class: Border Markup: Border tag: Initialization code for border: In this chapter we have seen the tools provided by Wicket to gain complete control over the generated HTML. However we didn’t see yet how we can repeat a portion of HTML with Wicket. With classic server-side technologies like PHP or JSP we use loops (like while or for) inside our pages to achieve this result. To perform this task Wicket provides a special-purpose family of components called repeaters and designed to repeat their markup body to display a set of items. But to fully understand how these components work, we must first learn more of Wicket’s basics. That’s why repeaters will be introduced later in chapter 13.6.9. Hiding decorating elements with the wicket:enclosure tag
<label>Total amount: </label><span wicket:id="totalAmount"></span>
<wicket:enclosure>
<label>Total amount: </label><span wicket:id="totalAmount"></span>
</wicket:enclosure>
<wicket:enclosure child="totalAmount">
<label>Total amount: </label><span wicket:id="totalAmount"></span><br/>
<label>Expected delivery date: </label><span wicket:id="delivDate"></span>
</wicket:enclosure>
<wicket:enclosure child="totalAmountContainer:totalAmount">
<div wicket:id="totalAmountContainer">
<label>Total amount: </label><span wicket:id="totalAmount"></span>
</div>
<label>Expected delivery date: </label><span wicket:id="delivDate"></span>
</wicket:enclosure>
<wicket:enclosure> is nice and prevents that users have to add boilerplate to their application. But it is not without problems. The child components are children in the markup, but the auto-component generated for the enclosure tag will not magically re-parent the child components. Thus the markup hierarchy and the component hierarchy will be out of sync. The automatically created enclosure container will be created along side its “children” with both attached to the very same parent container. That leads to a tricky situation since e.g. onBeforeRender() will be called for enclosure children even if the enclosure is made invisible by it controlling child. On top auto-components cannot keep any state. A new instance is created during each render process and automatically deleted at the end. That implies that we cannot prevent validation() from being called, since validation() is called before the actual render process has started. Where any of these problems apply, you may replace the tag and manually add a EnclosureContainer which basically does the same. But instead of adding the children to the Page, Panel, whatever, you must add the children to this container in order to keep the component hierarchy in sync. 6.10. Surrounding existing markup with Border
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
<head></head>
<body>
<!-- everything above <wicket:border> tag will be discarded...-->
<wicket:border>
<div>
foo<br />
<wicket:body/><br />
buz <br />
</div>
</wicket:border>
<!-- everything below </wicket:border> tag will be discarded...-->
</body>
</html>
<span wicket:id="myBorder">
bar
</span>
<span wicket:id="myBorder">
<div>
foo<br />
bar<br />
buz <br />
</div>
</span>
public class MyBorder extends Border {
public MyBorder(String id) {
super(id);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
<head></head>
<body>
<wicket:border>
<div>
<div wicket:id="childMarkup"></div>
<wicket:body/><br />
</div>
</wicket:border>
</body>
</html>
<div wicket:id="myBorder">
<span wicket:id="childTag"></span>
</div>
MyBorder myBorder = new MyBorder("myBorder");
myBorder.addToBorder(new Label("childMarkup", "Child inside markup."));
myBorder.add(new Label("childTag", "Child inside tag."));
add(myBorder);
6.11. Summary