9 Wicket Links and URL generation - Reference Documentation
Authors: Andrea Del Bene, Carsten Hufe, Christian Kroemer, Daniel Bartl
Version: 1.0.0.BUILD-SNAPSHOT
Table of Contents
9 Wicket Links and URL generation
Up to now we used component Link to move from a page to another and we have seen that it is quiet similar to a “click” event handler (see paragraph 2.4).However this component alone is not enough to build all possible kinds of links we may need in our pages. Therefore, Wicket offers other link components suited for those tasks which can not be accomplished with a basic Link.Besides learning new link components, in this chapter we will also see how to customize the page URL generated by Wicket using the encoding facility provided by the framework and the page parameters that can be passed to a target page.9.1 PageParameters
A common practice in web development is to pass data to a page using query string parameters (like ?paramName1=paramValu1¶mName2=paramValue2...). Wicket offers a more flexible and object oriented way to do this with models (we will see them in the next chapter). However, even if we are using Wicket, we still need to use query string parameters to exchange data with other Internet-based services. Consider for example a classic confirmation page which is linked inside an email to let users confirm important actions like password changing or the subscription to a mailing list. This kind of page usually expects to receive a query string parameter containing the id of the action to confirm.Query string parameters can also be referred to as named parameters. In Wicket they are handled with class org.apache.wicket.request.mapper.parameter.PageParameters. Since named parameters are basically name-value pairs, PageParameters works in much the same way as Java Map providing two methods to create/modify a parameter (add(String name, Object value) and set(String name, Object value)), one method to remove an existing parameter (remove(String name)) and one to retrieve the value of a given parameter (get(String name)) . Here is a snippet to illustrate the usage of PageParameters:PageParameters pageParameters = new PageParameters(); //add a couple of parameters pageParameters.add("name", "John"); pageParameters.add("age", 28); //retrieve the value of 'age' parameter pageParameters.get("age");
PageParameters and bookmarkable pages
Base class Page comes with a constructor which takes as input a PageParameters instance. If we use this superclass constructor in our page, PageParameters will be used to build the page URL and it can be retrieved at a later time with the Page's getPageParameters() method.In the following example taken from the PageParametersExample project we have a home page with a link to a second page that uses a version of setResponsePage method that takes as input also a PageParameters to build the target page (named PageWithParameters). The code for the link and for the target page is the following:Link code:add(new Link("pageWithIndexParam") { @Override public void onClick() { PageParameters pageParameters = new PageParameters(); pageParameters.add("foo", "foo"); pageParameters.add("bar", "bar"); setResponsePage(PageWithParameters.class, pageParameters); }});
public class PageWithParameters extends WebPage { //Override superclass constructor public PageWithParameters(PageParameters parameters) { super(parameters); } }
<app root>/PageParametersExample/wicket/bookmarkable/org.wicketTutorial.PageWithParameters?foo=foo&bar=bar
Indexed parameters
Besides named parameters, Wicket also supports indexed parameters. These kinds of parameters are rendered as URL segments placed before named parameters. Let's consider for example the following URL:<application path>/foo/bar?1&baz=baz
PageParameters pageParameters = new PageParameters(); //add a couple of parameters pageParameters.set(0, "foo"); pageParameters.set(1, "bar"); //retrieve the value of the second parameter ("bar") pageParameters.get(1);
add(new Link("pageWithNamedIndexParam") { @Override public void onClick() { PageParameters pageParameters = new PageParameters(); pageParameters.set(0, "foo"); pageParameters.set(1, "bar"); pageParameters.add("baz", "baz"); setResponsePage(PageWithParameters.class, pageParameters); }});
9.2 Bookmarkable links
A link to a bookmarkable page can be built with the link component org.apache.wicket.markup.html.link.BookmarkablePageLink:BookmarkablePageLink bpl=new BookmarkablePageLink(PageWithParameters.class, pageParameters);
9.3 Automatically creating bookmarkable links with tag
Bookmarkable pages can be linked directly inside markup files without writing any Java code. Using <wicket:link> tag we ask Wicket to automatically add bookmarkable links for the anchors wrapped inside it. Here is an example of usage of <wicket:link> tag taken from the home page of the project BookmarkablePageAutoLink:<!DOCTYPE html> <html xmlns:wicket="http://wicket.apache.org"> <head> <meta charset="utf-8" /> <title>Apache Wicket Quickstart</title> </head> <body> <div id="bd"> <wicket:link> <a href="HomePage.html">HomePage</a><br/> <a href="anotherPackage/SubPackagePage.html">SubPackagePage</a> </wicket:link> </div> </body> </html>
<a href="/org/wicketTutorial/anotherPackage/SubPackagePage.html">SubPackagePage</a>
<!DOCTYPE html> <html xmlns:wicket="http://wicket.apache.org"> <head> <meta charset="utf-8" /> <title>Apache Wicket Quickstart</title> </head> <body> <div id="bd"> <wicket:link> <a href="../HomePage.html">HomePage</a><br/> <a href="SubPackagePage.html">SubPackagePage</a> </wicket:link> </div> </body> </html>
<span><em>HomePage</em></span>
@Override public void init() { super.init(); //wrap disabled links with <b> tag getMarkupSettings().setDefaultBeforeDisabledLink("<b>"); getMarkupSettings().setDefaultAfterDisabledLink("</b>"); }
9.4 External links
Since Wicket uses plain HTML markup files as templates, we can place an anchor to an external page directly inside the markup file. When we need to dynamically generate external anchors, we can use link component org.apache.wicket.markup.html.link.ExternalLink. In order to build an external link we must specify the value of the href attribute using a model or a plain string. In the next snippet, given an instance of Person, we generate a Google search query for its full name:Html:<a wicket:id="externalSite">Search me on Google!</a>
Person person = new Person("John", "Smith"); String fullName = person.getFullName(); //Space characters must be replaced by character '+' String googleQuery = "http://www.google.com/search?q=" + fullName.replace(" ", "+"); add(new ExternalLink("externalSite", googleQuery));
<a href="http://www.google.com/search?q=John+Smith">Search me on Google!</a>
<a wicket:id="externalSite">Label goes here...</a>
Person person = new Person("John", "Smith"); String fullName = person.getFullName(); String googleQuery = "http://www.google.com/search?q=" + fullName.replace(" ", "+"); String linkLabel = "Search '" + fullName + "' on Google.";add(new ExternalLink("externalSite", googleQuery, linkLabel));
<a href="http://www.google.com/search?q=John+Smith">Search 'John Smith' on Google.</a>
9.5 Stateless links
Component Link has a stateful nature, hence it cannot be used with stateless pages. To use links with these kinds of pages Wicket provides the convenience org.apache.wicket.markup.html. link.StatelessLink component which is basically a subtype of Link with the stateless hint set to true.Please keep in mind that Wicket generates a new instance of a stateless page also to serve stateless links, so the code inside the onClick() method can not depend on instance variables. To illustrate this potential issue let's consider the following code (from the project StatelessPage) where the value of the variable index is used inside onclick():public class StatelessPage extends WebPage { private int index = 0; public StatelessPage(PageParameters parameters) { super(parameters); } @Override protected void onInitialize() { super.onInitialize(); setStatelessHint(true); add(new StatelessLink("statelessLink") { @Override public void onClick() { //It will always print zero System.out.println(index++); } }); } }
9.6 Generating structured and clear URLs
Having structured URLs in our site is a basic requirement if we want to build an efficient SEO1 strategy, but it also contributes to improve user experience with more intuitive URLs. Wicket provides two different ways to control URL generation. The first (and simplest) is to “mount” one or more pages to an arbitrary path, while a more powerful technique is to use custom implementations of IMapperContext and IPageParametersEncoder interfaces. In the next paragraphs we will learn both of these two techniques.Mounting a single page
With Wicket we can mount a page to a given path in much the same way as we map a servlet filer to a desired path inside file web.xml (see page 9). Using mountPage(String path, Class <T> pageClass) method of the WepApplication class we tell Wicket to respond with a new instance of pageClass whenever a user navigates to the given path. In the application class of the project MountedPagesExample we mount MountedPage to the "/pageMount" path:@Override public void init() { super.init(); mountPage("/pageMount", MountedPage.class); //Other initialization code… }
//it will return "/pageMount" RequestCycle.get().urlFor(MountedPage.class);
public final <T extends Page> void mountPage(final String path,final Class<T> pageClass) { mount(new MountedMapper(path, pageClass)); }
Using parameter placeholders with mounted pages
The path specified for mounted pages can contain dynamic segments which are populated with the values of the named parameters used to build the page. These segments are declared using special segments called parameter placeholders. Consider the path used in the following example:mountPage("/pageMount/${foo}/otherSegm", MountedPageWithPlaceholder.class);
PageParameters pageParameters = new PageParameters(); pageParameters.add("foo", "foo");setResponsePage(MountedPageWithPlaceholder.class, pageParameters)
<Application path>/pageMount/foo/otherSegm
mountPage("/pageMount/#{foo}/otherSegm", MountedPageOptionalPlaceholder.class);
PageParameters pageParameters = new PageParameters();
setResponsePage(MountedPageWithPlaceholder.class, pageParameters);
<Application path>/pageMount/otherSegm
Mounting a package
In addition to mounting a single page, Wicket allows to mount all of the pages inside a package to a given path. Method mountPackage(String path, Class<T> pageClass) of class WepApplication will mount every page inside pageClass's package to the specified path.The resulting URL for package-mounted pages will have the following structure:<Application path>/mountedPath/<PageClassName>[optional query string]
mountPackage("/mountPackage", StatefulPackageMount.class);
<Application path>/mountPackage/StatefulPackageMount?1
Providing custom mapper context to request mappers
Interface org.apache.wicket.request.mapper.IMapperContext is used by request mappers to create new page instances and to retrieve static URL segments used to build and parse page URLs. Here is the list of these segments:- Namespace: it's the first URL segment of non-mounted pages. By default its value is wicket.
- Identifier for non-bookmarkable URLs: it's the segment that identifies non bookmarkable pages. By default its value is page.
- Identifier for bookmarkable URLs: it's the segment that identifies bookmarkable pages. By default its value is bookmarkable (as we have seen before in paragraph 8.1.1).
- Identifier for resources: it's the segment that identifies Wicket resources. Its default value is resources. The topic of resource management will be covered in chapter 13.
public class CustomMapperContext extends DefaultMapperContext{ @Override public String getBookmarkableIdentifier() { return "staticURL"; } @Override public String getPageIdentifier() { return "index"; } }
@Override protected IMapperContext newMapperContext() { return new CustomMapperContext(); }
Controlling how page parameters are encoded with IPageParametersEncoder
Some request mappers (like MountedMapper and PackageMapper) can delegate page parameters encoding/decoding to interface org.apache.wicket.request.mapper.parameter.IPage ParametersEncoder. This entity exposes two methods: encodePageParameters() and decodePageParameters(): the first one is invoked to encode page parameters into an URL while the second one extracts parameters from the URL.Wicket comes with a built-in implementation of this interface which encodes named page parameters as URL segments using the following patter: /paramName1/paramValue1/paramName2/param Value2...This built-in encoder is org.apache.wicket.request.mapper.parameter.UrlPathPage ParametersEncoder class. In the PageParametersEncoderExample project we have manually mounted a MountedMapper that takes as input also an UrlPathPageParametersEncoder:@Override public void init() { super.init(); mount(new MountedMapper("/mountedPath", MountedPage.class, new UrlPathPageParametersEncoder())); }
add(new Link("mountedPage") { @Override public void onClick() { PageParameters pageParameters = new PageParameters(); pageParameters.add("foo", "foo"); pageParameters.add("bar", "bar"); setResponsePage(MountedPage.class, pageParameters); } });
<Application path>/mountedPath/foo/foo/bar/bar?1
Encrypting page URLs
Sometimes URLs are a double–edged sword for our site because they can expose too many details about the internal structure of our web application and malicious users could exploit them to perform a cross-site request forgery .To avoid this kind of security threat we can use the CryptoMapper request mapper which wraps an existing mapper and encrypts the original URL producing a single encrypted segment:Typically, CryptoMapper is registered into a Wicket application as the root request mapper wrapping the default one:@Override public void init() { super.init(); setRootRequestMapper(new CryptoMapper(getRootRequestMapper(), this)); //pages and resources must be mounted after we have set CryptoMapper mountPage("/foo/", HomePage.class);