blob: a964def0b81bb3fa2878bda6b212ebc07474bdd1 [file] [log] [blame]
<chapter id="wug.linkomatic">
<title>Linkomatic</title>
<subtitle>All about hyperlinks</subtitle>
<sect1>
<title>Building and Running Linkomatic</title>
<para>The Linkomatic application (found in
<filename>WICKET_HOME/examples/Linkomatic</filename>) can be built
and run in the same way as HelloWorld. It demonstrates the various
flavors of hyperlinks available in Wicket. Each link type has its
own well-defined purpose and to write a serious web application,
you will need to be familiar with each. When you build and run the
Linkomatic application, you should see something like this:</para>
<mediaobject>
<imageobject>
<imagedata fileref="images/image004-linkomatic.png"
format="PNG"></imagedata>
</imageobject>
</mediaobject>
<para>Each link on the page demonstrates one of the link types and will
be discussed below. Before reading on, take a moment to see what
clicking on each link does.</para>
</sect1>
<sect1>
<title>The Linkomatic Source Code</title>
<para>One slight difference from the HelloWorld application we
introduced in the last chapter is the <filename>web.xml</filename>
descriptor for Linkomatic:</para>
<programlisting><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Linkomatic</display-name>
<servlet>
<servlet-name>Linkomatic</servlet-name>
<servlet-class>linkomatic.LinkomaticApplication</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Linkomatic</servlet-name>
<url-pattern>/LinkomaticApplication/*</url-pattern>
</servlet-mapping>
</web-app>]]></programlisting>
<para>Instead of mapping all URLs to the LinkomaticApplication servlet,
the descriptor maps only URLs beginning with LinkomaticApplication.
The reason for this is that Linkomatic contains a static image
(ImageMap.gif) which it wants the web application container to
serve, and the web server cannot serve the image if the URL to the
image is mapped to the LinkomaticApplication servlet. By
restricting the dynamic part of the site to URLs beginning with
LinkomaticApplication, the web server can serve static content from
other URLs.</para>
<para>But doing this creates a problem. Since the LinkomaticApplication
does not handle the default resource path of "/", users will now
have to go to: <ulink
url="http://localhost:8080/Linkomatic/LinkomaticApplication"></ulink>
(or on a production website with Linkomatic running on the root web
application context: <ulink
url="http://server/LinkomaticApplication"></ulink>). We can solve
this problem by redirecting them from the default "welcome" page
for the web server, <filename>index.html</filename>. The
<filename>index.html</filename> page for Linkomatic looks like
this:</para>
<programlisting><![CDATA[<html>
<head>
<meta http-equiv="Refresh" content="0; url=LinkomaticApplication">
</head>
</html>]]></programlisting>
<para>The <token>META</token> tag in the header of
<filename>index.html</filename> causes a redirect to the URL
LinkomaticApplication after 0 seconds.</para>
<para>Once again, our application class sets its home page, in this
case to <classname>linkomatic.Home</classname>:</para>
<programlisting><![CDATA[public class LinkomaticApplication extends
WebApplication { public LinkomaticApplication() {
getSettings().setHomePage(Home.class); } }]]></programlisting>
<para>For Linkomatic, our Home page is considerably more complex and
demonstrates all the possible ways to employ hyperlinks in a Wicket
application. Below is the source to Home, followed by the
corresponding HTML file. It will take a few pages to explain
everything that is going on, but take a look for a moment just to
see what you can figure out "by inspection".</para>
<programlisting><![CDATA[public class Home extends WebPage
{
public Home(final PageParameters parameters)
{
// Action link counts link clicks
Link actionLink = new Link("actionLink")
{
public void linkClicked(RequestCycle cycle)
{
linkClickCount++;
// Redirect back to result to avoid refresh updating the link count
cycle.setRedirect(true);
}
};
actionLink.add(new Label("linkClickCount", this));
add(actionLink);
// Link to Page1 is a simple external page link
add(new ExternalPageLink("page1Link", Page1.class));
// Link to Page2 is automaticLink, so no code
// Link to Page3 is an external link which takes a parameter
add(new ExternalPageLink("page3Link", Page3.class).setParameter("id", 3));
// Link to BookDetails page
add(new PageLink("bookDetailsLink", new BookDetails(new Book("The Hobbit"))));
// Delayed link to BookDetails page
add(new PageLink("bookDetailsLink2", new IPageLink()
{
public Page getPage()
{
return new BookDetails(new Book("Inside The Matrix"));
}
public Class getPageClass()
{
return BookDetails.class;
}
}));
// Image map link example
add(new ImageMap("imageMap")
.addRectangleLink(0, 0, 100, 100, new ExternalPageLink("page1", Page1.class))
.addCircleLink(160, 50, 35, new ExternalPageLink("page2", Page2.class))
.addPolygonLink(new int[] { 212, 79, 241, 4, 279, 54, 212, 79 },
new ExternalPageLink("page3", Page3.class)));
// Popup example
add(new ExternalPageLink("popupLink", Page1.class).setPopupDimensions(100, 100));
}
public int getLinkClickCount()
{
return linkClickCount;
}
public void setLinkClickCount(int linkClickCount)
{
this.linkClickCount = linkClickCount;
}
private int linkClickCount = 0;
}]]></programlisting>
<programlisting><![CDATA[<html>
<body>
<a id = "wcn-actionLink">Action link clicked <span id = "wcn-linkClickCount">0</span>
times</a><p>
<a id = "wcn-page1Link">Click this ExternalPageLink to go to Page1</a><p>
<a id = "wcn-[autolink]" href = "Page2.html">Click this [autolinkautolink] to go to
Page2</a><p>
<a id = "wcn-page3Link">Click this ExternalPageLink to go to Page3</a><p>
<a id = "wcn-[autolink]" href = "Page4.html">Click here to go to Page4</a><p>
<a id = "wcn-bookDetailsLink">Click this PageLink for BookDetails</a><p>
<a id = "wcn-bookDetailsLink2">Click this PageLink for BookDetails</a>
</body>
</html>]]></programlisting>
</sect1>
<sect1>
<title>Action Links</title>
<para>The first type of link demonstrated in Home is the “action” link,
which is of type Link. The actionLink is a link which displays a
counter in its own hyperlink text. When the link is clicked, the
counter increments:</para>
<mediaobject>
<imageobject>
<imagedata fileref="images/image005-actionlink1.png"
format="PNG"></imagedata>
</imageobject>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/image006-actionlink2.png"
format="PNG"></imagedata>
</imageobject>
</mediaobject>
<para>Even though action links are a little more complex than the other
link types, they are a more fundamental concept, and so we will
cover them first. Since it’s fairly involved, let’s take this
example in small bites. In the overall flow, three things
happen:</para>
<programlistingco>
<areaspec>
<area id="linkomatic.createlink" coords="1"></area>
<area id="linkomatic.addlabel" coords="10"></area>
<area id="linkomatic.addlink" coords="11"></area>
</areaspec>
<programlisting><![CDATA[Link actionLink = new Link("actionLink")
{
public void linkClicked(RequestCycle cycle)
{
linkClickCount++;
// Redirect back to result to avoid refresh updating the link count
cycle.setRedirect(true);
}
};
actionLink.add(new Label("linkClickCount", this));
add(actionLink);
]]></programlisting>
<calloutlist>
<callout arearefs="linkomatic.createlink">
<para>A Link named actionLink is constructed</para>
</callout>
<callout arearefs="linkomatic.addlabel">
<para>A Label component is added to actionLink.</para>
</callout>
<callout arearefs="linkomatic.addlink">
<para>The actionLink is added to the page.</para>
</callout>
</calloutlist>
</programlistingco>
<para>The construction of <varname>actionLink</varname> involves
something known as an <firstterm>anonymous class</firstterm>. What
is really going on in the first 10 lines or so is the creation and
instantiation of an anonymous subclass of
<classname>Link</classname>. Since the <classname>Link</classname>
class declares <methodname>linkClicked</methodname> as
<token>abstract</token>, we must implement
<methodname>linkClicked</methodname> in the anonymous subclass we
are declaring. The result of:</para>
<programlisting><![CDATA[Link actionLink = new Link("actionLink")
{
public void linkClicked(RequestCycle cycle)
{
...
}
};
]]></programlisting>
<para>is a new instance of <classname>Link</classname> named
"<property>actionLink</property>" whose
<methodname>linkClicked()</methodname> method will be called when
the link is clicked.</para>
<para>The first thing that happens when
<methodname>linkClicked</methodname> is called is that the
<property>linkClickCount</property> is incremented. The
<property>linkClickCount</property> field is a beans property
because it has a getter and a setter:</para>
<programlisting lang="java"><![CDATA[public class Home extends WebPage
{
public int getLinkClickCount()
{
return linkClickCount;
}
public void setLinkClickCount(int linkClickCount)
{
this.linkClickCount = linkClickCount;
}
private int linkClickCount = 0;
}]]></programlisting>
<para>Having this <property>linkClickCount</property> bean property,
the Home page itself can serve as a model for the label, which is
what is happening in this line:</para>
<programlisting><![CDATA[actionLink.add(new Label("linkClickCount", this));]]>
</programlisting>
<para>The value <structfield>this</structfield> can be passed to the
<classname>Label</classname> constructor because the Home page
itself is a bean component model with the property linkClickCount.
When the label inside the link component renders, it will get its
value from the linkClickCount bean property of the Home page
model.</para>
<para>One last question you might have about the action link example is
what this line does:</para>
<programlisting><![CDATA[cycle.setRedirect(true);]]></programlisting>
<para>Since the action link is handled by the page object that
constructed the link, the URL that invokes the link will be in the
user’s browser:</para>
<mediaobject>
<imageobject>
<imagedata fileref="images/image007-actionlink3.png"
format="PNG"></imagedata>
</imageobject>
</mediaobject>
<para>With this URL in the browser’s URL field, hitting return will
cause the same action link to be called again. Since we want to
count actual action link clicks and not link listener invocations,
we can redirect the user back to the Home page. This changes the
URL to:</para>
<mediaobject>
<imageobject>
<imagedata fileref="images/image008-actionlink4.png"
format="PNG"></imagedata>
</imageobject>
</mediaobject>
<para>Now hitting return in the browser will just retrieve the same
page.</para>
<para>The other place where
<methodname>RequestCycle.setRedirect</methodname> is used is in
forms processing to avoid triggering browser POST warnings when the
user goes back to a form using the back button in their browser. We
will be talking a lot more about forms later in this User’s
Guide.</para>
</sect1>
<sect1>
<title>External Page Links</title>
<para>External page links are created by creating an instance of the
ExternalPageLink component (and associating the link component with
some HTML using its name). The simplest kind of external page link
is next on the Home page:</para>
<programlisting><![CDATA[add(new ExternalPageLink("page1Link", Page1.class));]]></programlisting>
<para>This link will instantiate the page class
<classname>Page1</classname>, which is trivial and simply displays
"<computeroutput>Welcome to Page1</computeroutput>". Note that the
page will not be <emphasis>created</emphasis> until the link is
clicked.</para>
<para>Because it is tedious and time-consuming to create external page
links by instantiating components, a special shorthand is allowed
in your HTML. The link to Page2 in <filename>Home.html</filename>
looks like this:</para>
<programlisting><![CDATA[<a id = "wcn-[autolink]" href = "Page2.html">Click this [autolink] to go to Page2</a>]]></programlisting>
<para>Giving a link the component name <symbol>"[autolink]"</symbol>
causes the framework to look at the <property>href</property>
property and do the equivalent of:</para>
<programlisting><![CDATA[add(new ExternalPageLink("[autolink]-0", Page2.class));]]></programlisting>
<para>only without you having to write any code.</para>
<para>Finally, external page links can have parameters. The example
link which goes to Page3, takes an <parameter>id</parameter>
parameter that is typical of "details" pages which display
information about something.</para>
<programlisting><![CDATA[add(new ExternalPageLink("page3Link", Page3.class).setParameter("id", 3));]]></programlisting>
<para>The implementation of Page3 retrieves the id parameter from the
PageParameters passed to the page and shows the id in a Label
component:</para>
<programlisting><![CDATA[public class Page3 extends WebPage
{
public Page3(PageParameters parameters)
{
add(new Label("id", parameters.getString("id")));
}
}]]></programlisting>
<para>The body of Page3.html references the label component that shows
the id:</para>
<programlisting><![CDATA[Your ExternalPageLink parameter "id" has the value "<span id = "wcn-id">-1</span>"]]></programlisting>
<para>The main benefit of creating an external page link is that it is
bookmarkable. The downside is that changing the link’s
implementation will break any links stored in users’ browsers. If
you expose Page3 as an external page with an id parameter, you are,
in a sense, exposing a public API to your website, and you will
have to continue supporting that, or face frustrated or lost
users.</para>
</sect1>
<sect1>
<title>Page Links</title>
<para>Page links which are not external / bookmarkable typically instantiate pages using constructor
parameters that modify the behavior of the page. The first book details link links to a BookDetails
page that takes a Book. It simply passes a new Book object to the constructor:</para>
<programlisting><![CDATA[add(new PageLink("bookDetailsLink", new BookDetails(new Book("The Hobbit"))));]]></programlisting>
<para><classname>Book</classname> is a simple bean with a title property and <classname>BookDetails</classname> is a trivial page that displays the title
using a <classname>Label</classname> component.</para>
<para>This is great stuff, but in many applications, it will prove too expensive (or even impossible!) to
instantiate every page that is linked to by a given page. After all, to instantiate the page that is
being linked to, that page may need to create yet more links and those links yet more pages, and so
on until the recursion gets out of control.</para>
<para>The solution to this problem is to delay page construction until such time as the link is clicked on.
This is demonstrated by the second book details example:</para>
<programlisting><![CDATA[add(new PageLink("bookDetailsLink2", new IPageLink()
{
public Page getPage()
{
return new BookDetails(new Book("Inside The Matrix"));
}
public Class getPageClass()
{
return BookDetails.class;
}
}));]]></programlisting>
<para>Once again, we have a simple anonymous class, this time implementing the <interfacename>IPageLink</interfacename> interface
by providing an implementation of <methodname>getPage()</methodname> which will instantiate the <classname>BookDetails</classname> page when
the link is clicked on as well as <methodname>getPageClass()</methodname> which returns the class of Page that will be
returned by <methodname>getPage()</methodname>. The reasoning behind <methodname>getPageClass()</methodname> will be explained in the next chapter
on the Navomatic example.</para>
</sect1>
<sect1>
<title>Image Maps</title>
<para>An image map example is shown near the bottom of the Home page as a graphical image map of a
square, a circle and a triangle. The image map component will only attach to an image tag such as
the one in <filename>Home.html</filename>:</para>
<programlisting><![CDATA[<img border = "0" id = "wcn-imageMap" src = "ImageMap.gif">]]></programlisting>
<para>The ImageMap component itself is constructed with a name matching the component name in
<filename>Home.html</filename>. Then links (of any of the types discussed in this chapter) are added to the image map
using the <methodname>addRectangleLink</methodname>, <methodname>addCircleLink</methodname> and <methodname>addPolygonLink</methodname> methods:</para>
<programlisting><![CDATA[add(new ImageMap("imageMap")
.addRectangleLink(0, 0, 100, 100, new ExternalPageLink("page1", Page1.class))
.addCircleLink(160, 50, 35, new ExternalPageLink("page2", Page2.class))
.addPolygonLink(new int[] { 212, 79, 241, 4, 279, 54, 212, 79 },
new ExternalPageLink("page3", Page3.class)));]]></programlisting>
<para>Finally, the image map is added to the page.</para>
</sect1>
<sect1>
<title>Popup Links</title>
<para>Our final example demonstrates how to make any link into a popup link:</para>
<programlisting><![CDATA[add(new ExternalPageLink("popupLink", Page1.class).setPopupDimensions(100, 100));]]></programlisting>
<para>Simply call <methodname>setPopupDimensions</methodname> on this link, passing in the width and height you desire for the
popup window. In addition you can call <methodname>setPopupScrollBars</methodname> to enable scroll bars in the popup
window.</para>
</sect1>
</chapter>