<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" gd:etag="W/&quot;DE8EQHY8fSp7ImA9WxNVEks.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898</id><updated>2009-10-22T20:00:01.875-07:00</updated><title>Java Thoughts</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/" /><link rel="hub" href="http://pubsubhubbub.appspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>54</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><link rel="self" href="http://feeds.feedburner.com/capesugarbird/javathoughts" type="application/atom+xml" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry gd:etag="W/&quot;CEUGQ3k-eip7ImA9WxJVE08.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-8627469868796556615</id><published>2009-06-29T17:33:00.000-07:00</published><updated>2009-06-29T17:37:02.752-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-29T17:37:02.752-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="css" /><title>CSS load order with Wicket and an external jar</title><content type="html">I had a problem with the CSS from a provided jar (wicketstuff-jquery) overriding my local CSS. To get around this, I made sure the jar's CSS loaded first:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;      add(CSSPackageResource.getHeaderContribution(JQTabbedPanel.class, "jquery.tabs.css"));&lt;br /&gt;      add(CSSPackageResource.getHeaderContribution(MyWicketPage.class, "MyWicketPage.css"));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Wicket is clever enough to not load the same CSS twice. I knew which class and CSS file load by looking at the generated source:&lt;br /&gt;&lt;pre&gt;&lt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt; rel&lt;/span&gt;=&lt;span&gt;"stylesheet" &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;=&lt;span&gt;"text/css" &lt;/span&gt;&lt;span&gt;href&lt;/span&gt;=&lt;span&gt;"resources/org.wicketstuff.jquery.tabs.JQTabbedPanel/jquery.tabs.css" &lt;/span&gt;&lt;span&gt;&lt;span&gt;/&lt;/span&gt;&lt;/span&gt;&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-8627469868796556615?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=ILhd3zMiq4o:hPfNBk4jA2U:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=ILhd3zMiq4o:hPfNBk4jA2U:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=ILhd3zMiq4o:hPfNBk4jA2U:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=ILhd3zMiq4o:hPfNBk4jA2U:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/8627469868796556615/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=8627469868796556615" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/8627469868796556615?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/8627469868796556615?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2009/06/css-load-order-with-wicket-and-external.html" title="CSS load order with Wicket and an external jar" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total></entry><entry gd:etag="W/&quot;Ak8BQXoycCp7ImA9WxJWF0U.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-5686202752009118024</id><published>2009-06-23T12:57:00.000-07:00</published><updated>2009-06-23T13:27:30.498-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-23T13:27:30.498-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="jquery" /><title>Replacing Wicket's FeedbackPanel with jGrowl</title><content type="html">I wanted to replace the standard &lt;a href="http://wicket.apache.org/docs/1.4/org/apache/wicket/markup/html/panel/FeedbackPanel.html"&gt;Wicket FeedbackPanel&lt;/a&gt; with a more modern growl-like presentation based on &lt;a href="http://www.stanlemon.net/projects/jgrowl.html"&gt;jGrowl&lt;/a&gt;. There are &lt;a href="http://www.nabble.com/Notifications-td23808267.html"&gt;several ways&lt;/a&gt; to do this, but I chose &lt;a href="http://pastebin.com/f6db2ec0e"&gt;Alex Objelean's JGrowlBehavior&lt;/a&gt; as my starting point. Our needs were simple. We have two types of feedback messages: component-level errors on form fields, which we display under the form field using &lt;a href="http://londonwicket.org/content/LondonWicket-FormsWithFlair.pdf"&gt;Al Maw's ShinyFormVisitor&lt;/a&gt;, and session-level messages for everything else. We only want to display the session-level messages using JGrowlBehavior, so my version of JGrowlBehavior is quite simple. This works for both full server round trips (the button labeled "Normal OK" in the demo below), and Ajax (the button labeled "Ajax OK").&lt;br /&gt;&lt;h2&gt;Demo&lt;/h2&gt;&lt;br /&gt;&lt;object height="360" width="579"&gt; &lt;param name="movie" value="http://content.screencast.com/users/jsinai/folders/Jing/media/a7a413da-56d7-4ada-8f4c-61e3a1cf0b7e/jingswfplayer.swf"&gt; &lt;param name="quality" value="high"&gt; &lt;param name="bgcolor" value="#FFFFFF"&gt; &lt;param name="flashVars" value="thumb=http://content.screencast.com/users/jsinai/folders/Jing/media/a7a413da-56d7-4ada-8f4c-61e3a1cf0b7e/FirstFrame.jpg&amp;amp;containerwidth=579&amp;amp;containerheight=360&amp;amp;loaderstyle=jing&amp;amp;content=http://content.screencast.com/users/jsinai/folders/Jing/media/a7a413da-56d7-4ada-8f4c-61e3a1cf0b7e/00000002.swf"&gt; &lt;param name="allowFullScreen" value="true"&gt; &lt;param name="scale" value="showall"&gt; &lt;param name="allowScriptAccess" value="always"&gt; &lt;param name="base" value="http://content.screencast.com/users/jsinai/folders/Jing/media/a7a413da-56d7-4ada-8f4c-61e3a1cf0b7e/"&gt;  &lt;embed src="http://content.screencast.com/users/jsinai/folders/Jing/media/a7a413da-56d7-4ada-8f4c-61e3a1cf0b7e/jingswfplayer.swf" quality="high" bgcolor="#FFFFFF" type="application/x-shockwave-flash" allowscriptaccess="always" flashvars="thumb=http://content.screencast.com/users/jsinai/folders/Jing/media/a7a413da-56d7-4ada-8f4c-61e3a1cf0b7e/FirstFrame.jpg&amp;amp;containerwidth=579&amp;amp;containerheight=360&amp;amp;loaderstyle=jing&amp;amp;content=http://content.screencast.com/users/jsinai/folders/Jing/media/a7a413da-56d7-4ada-8f4c-61e3a1cf0b7e/00000002.swf" allowfullscreen="true" base="http://content.screencast.com/users/jsinai/folders/Jing/media/a7a413da-56d7-4ada-8f4c-61e3a1cf0b7e/" scale="showall" height="360" width="579"&gt;&lt;/embed&gt; &lt;/object&gt;&lt;br /&gt;&lt;h2&gt; JGrowlBehavior.java&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;/**&lt;br /&gt;* Attach to any component to display jGrowl messages.&lt;br /&gt;*&lt;br /&gt;* Displays only session-level messages. If you need component-level&lt;br /&gt;* messages, see http://pastebin.com/f6db2ec0e for an example. Basically,&lt;br /&gt;* instead of Session.get().getFeedbackMessages(), you would call&lt;br /&gt;* getComponent().getFeedbackMessage().&lt;br /&gt;*&lt;br /&gt;* Requires the following be included: "jquery.js", "jquery.ui.all.js",&lt;br /&gt;* "jquery.jgrowl.js", "jquery.jgrowl.css". These can be downloaded&lt;br /&gt;* from http://plugins.jquery.com/files/jGrowl-1.2.0.tgz.&lt;br /&gt;*&lt;br /&gt;* @author jsinai&lt;br /&gt;* Based on an example by Alex Objelean, see the above link.&lt;br /&gt;*/&lt;br /&gt;public class JGrowlBehavior extends AbstractDefaultAjaxBehavior&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt; /**&lt;br /&gt;  * Displays an info message that is sticky. The default is non-sticky.&lt;br /&gt;  * Sample usage: session.getFeedbackMessages().add(new FeedbackMessage(null,&lt;br /&gt;  * "my message", JGrowlBehavior.INFO_STICKY));&lt;br /&gt;  */&lt;br /&gt; public static final int INFO_STICKY = 250;&lt;br /&gt;&lt;br /&gt; @Override&lt;br /&gt; protected void respond(AjaxRequestTarget target)&lt;br /&gt; {&lt;br /&gt;  final String feedbackMsg = renderFeedback();&lt;br /&gt;  if (!StringUtils.isEmpty(feedbackMsg))&lt;br /&gt;  {&lt;br /&gt;   target.appendJavascript(feedbackMsg);&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; @Override&lt;br /&gt; public void renderHead(final IHeaderResponse response)&lt;br /&gt; {&lt;br /&gt;  super.renderHead(response);&lt;br /&gt;  final String feedbackMsg = renderFeedback();&lt;br /&gt;  if (!StringUtils.isEmpty(feedbackMsg))&lt;br /&gt;  {&lt;br /&gt;   response.renderOnDomReadyJavascript(feedbackMsg);&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private String renderFeedback()&lt;br /&gt; {&lt;br /&gt;  this.getComponent().getFeedbackMessage();&lt;br /&gt;  final FeedbackMessages fm = Session.get().getFeedbackMessages();&lt;br /&gt;&lt;br /&gt;  final Iterator&amp;lt;FeedbackMessage&amp;gt; iter = fm.iterator();&lt;br /&gt;  final StringBuilder sb = new StringBuilder();&lt;br /&gt;  while (iter.hasNext())&lt;br /&gt;  {&lt;br /&gt;   final FeedbackMessage message = iter.next();&lt;br /&gt;   if ((message.getReporter() != null) || message.isRendered())&lt;br /&gt;   {&lt;br /&gt;    // If a component-level message, don't show it&lt;br /&gt;    continue;&lt;br /&gt;   }&lt;br /&gt;   final String cssClassSuffix = (message.getLevel() == INFO_STICKY) ? "INFO" : message&lt;br /&gt;     .getLevelAsString();&lt;br /&gt;   final Serializable serializable = message.getMessage();&lt;br /&gt;   final String msg = (serializable == null) ? StringUtils.EMPTY : serializable.toString();&lt;br /&gt;   sb.append("$.jGrowl(\"").append(msg).append('\"');&lt;br /&gt;   sb.append(", {");&lt;br /&gt;   sb.append("theme: \'jgrowl-").append(cssClassSuffix).append("\'");&lt;br /&gt;   if (message.getLevel() &amp;gt; FeedbackMessage.INFO)&lt;br /&gt;   {&lt;br /&gt;    sb.append(", sticky: true");&lt;br /&gt;   }&lt;br /&gt;   sb.append("}");&lt;br /&gt;   sb.append(");");&lt;br /&gt;   message.markRendered();&lt;br /&gt;  }&lt;br /&gt;  return sb.toString();&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt; JGrowlPage.java&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;/**&lt;br /&gt;* @author jsinai&lt;br /&gt;*/&lt;br /&gt;public class JGrowlPage extends WebPage {&lt;br /&gt;&lt;br /&gt; public JGrowlPage() {&lt;br /&gt;  super();&lt;br /&gt;  add(CSSPackageResource.getHeaderContribution(JGrowlPage.class,&lt;br /&gt;    "JGrowlPage.css"));&lt;br /&gt;&lt;br /&gt;  Form&amp;lt;?&amp;gt; form = new Form("form") {&lt;br /&gt;&lt;br /&gt;   @Override&lt;br /&gt;   protected void onSubmit() {&lt;br /&gt;    Session.get().error("Test error");&lt;br /&gt;    Session.get().warn("Test warning");&lt;br /&gt;    Session.get().info("Test info");&lt;br /&gt;    Session.get().getFeedbackMessages().add(&lt;br /&gt;      new FeedbackMessage(null, "Test sticky info",&lt;br /&gt;        JGrowlBehavior.INFO_STICKY));&lt;br /&gt;   }&lt;br /&gt;  };&lt;br /&gt;  add(form);&lt;br /&gt;&lt;br /&gt;  AjaxButton b = new AjaxButton("ajaxbutton", form) {&lt;br /&gt;&lt;br /&gt;   @Override&lt;br /&gt;   protected void onSubmit(AjaxRequestTarget target, Form&amp;lt;?&amp;gt; form) {&lt;br /&gt;    target.addComponent(form);&lt;br /&gt;   }&lt;br /&gt;  };&lt;br /&gt;  form.add(b);&lt;br /&gt;  form.add(new JGrowlBehavior());&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt; JGrowlPage.html&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="UTF-8" ?&amp;gt;&lt;br /&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br /&gt;   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&amp;gt;&lt;br /&gt;&amp;lt;wicket:head&amp;gt;&lt;br /&gt; &amp;lt;script type="text/javascript" src="js/jquery.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;script type="text/javascript" src="js/jquery.ui.all.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;script type="text/javascript" src="js/jquery.jgrowl.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;link rel="stylesheet" href="css/jquery.jgrowl.css" /&amp;gt;&lt;br /&gt; &amp;lt;title&amp;gt;Wicket/jGrowl Demo&amp;lt;/title&amp;gt;&lt;br /&gt;&amp;lt;/wicket:head&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt;&amp;lt;h1&amp;gt;Wicket/jGrowl Demo&amp;lt;/h1&amp;gt;&lt;br /&gt;&amp;lt;form wicket:id="form"&amp;gt;&lt;br /&gt;&amp;lt;input type="submit" value="Normal OK"/&amp;gt;&lt;br /&gt;&amp;lt;input type="submit" value="Ajax OK" wicket:id="ajaxbutton"/&amp;gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt; JGrowlPage.css&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;@CHARSET "UTF-8";&lt;br /&gt;&lt;br /&gt;div.jGrowl div.jgrowl-ERROR {&lt;br /&gt; background-color: #BF0B0B;&lt;br /&gt;}&lt;br /&gt;div.jGrowl div.jgrowl-WARNING {&lt;br /&gt; background-color: orange;&lt;br /&gt; color: black;&lt;br /&gt;}&lt;br /&gt;div.jGrowl div.jgrowl-INFO {&lt;br /&gt; background-color: #0C5F0C;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-5686202752009118024?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=-7K8WVns8Lg:Da1v5W-PVCU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=-7K8WVns8Lg:Da1v5W-PVCU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=-7K8WVns8Lg:Da1v5W-PVCU:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=-7K8WVns8Lg:Da1v5W-PVCU:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/5686202752009118024/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=5686202752009118024" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/5686202752009118024?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/5686202752009118024?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2009/06/replacing-wickets-feedbackpanel-with.html" title="Replacing Wicket's FeedbackPanel with jGrowl" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total></entry><entry gd:etag="W/&quot;CkACR308cSp7ImA9WxJXGEg.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-8231117420740265491</id><published>2009-06-12T16:24:00.000-07:00</published><updated>2009-06-12T16:52:46.379-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-12T16:52:46.379-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="jquery" /><title>Calling Wicket from Javascript</title><content type="html">I needed to call into Wicket code from a jQuery function. Building on the sample code from &lt;a href="http://cwiki.apache.org/WICKET/calling-wicket-from-javascript.html"&gt;Calling Wicket from Javascript&lt;/a&gt;, I created an end-to-end working example. In my case I wanted to manipulate a row in my data table when a user clicked a jQuery widget in that row. I needed to pass the markup id from the widget to the Wicket handler. In my case, My item type is DefaultMutableTreeNode. Note that contrary to what the wiki page says, I did not need to put a script tag in my html code. &lt;br /&gt;&lt;br /&gt;&lt;h2&gt;MyDataView.java&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;@Override&lt;br /&gt;protected void populateItem(final Item&amp;lt;DefaultMutableTreeNode&amp;gt; item)&lt;br /&gt;{&lt;br /&gt; super.populateItem(item);&lt;br /&gt; // Important!!&lt;br /&gt; item.setOutputMarkupId(true);&lt;br /&gt; ...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;MyDataPanel.java&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;public class MyDataPanel extends DataPanel&lt;DefaultMutableTreeNode&gt;&lt;br /&gt;{&lt;br /&gt;&lt;br /&gt; public MyDataPanel(String id, final HtDataView&lt;DefaultMutableTreeNode&gt; dataView,&lt;br /&gt;   WebPage parent)&lt;br /&gt; {&lt;br /&gt;  ...&lt;br /&gt;&lt;br /&gt;  final AbstractDefaultAjaxBehavior behave = new AbstractDefaultAjaxBehavior()&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt;   @Override&lt;br /&gt;   protected void respond(final AjaxRequestTarget target)&lt;br /&gt;   {&lt;br /&gt;    final String paramMarkupId = RequestCycle.get().getRequest().getParameter("markupId");&lt;br /&gt;    MyDataPanel.this.visitChildren(Item.class,&lt;br /&gt;    new Component.IVisitor&amp;lt;Item&amp;lt;DefaultMutableTreeNode&amp;gt;&amp;gt;()&lt;br /&gt;    {&lt;br /&gt;     public Object component(Item&amp;lt;DefaultMutableTreeNode&amp;gt; item)&lt;br /&gt;     {&lt;br /&gt;      if (StringUtils.equals(paramMarkupId, item.getMarkupId()))&lt;br /&gt;      {&lt;br /&gt;       ...your code here...&lt;br /&gt;      }&lt;br /&gt;      return STOP_TRAVERSAL;&lt;br /&gt;     }&lt;br /&gt;    });&lt;br /&gt;   }&lt;br /&gt;  };&lt;br /&gt;  parent.add(behave);&lt;br /&gt;  final CharSequence url = behave.getCallbackUrl();&lt;br /&gt;  add(new AbstractBehavior()&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt;   @Override&lt;br /&gt;   public void renderHead(IHeaderResponse response)&lt;br /&gt;   {&lt;br /&gt;    String js = "function callWicket(markupId) { var wcall = wicketAjaxGet ('"&lt;br /&gt;    + url + "&amp;markupId='+markupId, function() { }, function() { } ) }";&lt;br /&gt;    response.renderJavascript(js, "myScript");&lt;br /&gt;   }&lt;br /&gt;  });&lt;br /&gt;... &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;MyJQueryStuff.js&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;$.fn.toggleBranch = function() {&lt;br /&gt;... &lt;br /&gt;     var idAttr = $(this).attr("id");&lt;br /&gt;   callWicket(idAttr);&lt;br /&gt;... &lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-8231117420740265491?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=lMtZelZsv-4:u1dIWx9mKfM:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=lMtZelZsv-4:u1dIWx9mKfM:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=lMtZelZsv-4:u1dIWx9mKfM:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=lMtZelZsv-4:u1dIWx9mKfM:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/8231117420740265491/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=8231117420740265491" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/8231117420740265491?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/8231117420740265491?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2009/06/calling-wicket-from-javascript.html" title="Calling Wicket from Javascript" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total></entry><entry gd:etag="W/&quot;DE8NQ3s_cCp7ImA9WxJQFU4.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-3634763101860132985</id><published>2009-05-28T11:53:00.000-07:00</published><updated>2009-05-28T11:54:52.548-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-28T11:54:52.548-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="gwt" /><title>Google Wave</title><content type="html">I saw the first demo of Google Wave today at Google I/O. It's a collaboration platform that combines the best characteristics of email, bulletin boards, instant messaging. It also has some of the characteristics of a social network site. It started out as the Next Big Thing from the Rasmussen brothers, the inventors of Google Maps. It's been a couple of years in development and it's not yet publicly released. It consists of the client program, a set of APIs, and a protocol.  &lt;br /&gt;&lt;br /&gt;The user interface is really well designed, but its real strength is the real time updates, taking the IM paradigm to the next level, i.e. collaboration. This is a really great feature because it means you can collaborate on the same "wave" (conversation) and see everyone's updates in real time.  &lt;br /&gt;&lt;br /&gt;While it didn't start that way, it seems now to be aimed at stealing some of Facebook's thunder. Google wants to do an end run around Facebook by open sourcing the source code, but even more importantly, by allowing anyone to host a Wave service. This will enable enterprises to host their own private instance; but the really powerful thing is that instances can be federated, i.e. they can communicate with each other, but they've taken care to make sure that privacy is maintained.&lt;br /&gt;&lt;br /&gt;The user interface is built using GWT. This is a huge endorsement for GWT, which has suffered from a reputation of being a toy that isn't used for the heavy-duty customer-facing apps. Lars Rasmussen, who has demonstrated he's a great Javascript programmer, now prefers GWT to Javascript programming. I may have to tear myself away from Wicket and try it.&lt;br /&gt;&lt;br /&gt;The APIs and protocol will be available at the same time as the product itself.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-3634763101860132985?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=uNOAFOSKTEY:1dNVq85Dh64:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=uNOAFOSKTEY:1dNVq85Dh64:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=uNOAFOSKTEY:1dNVq85Dh64:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=uNOAFOSKTEY:1dNVq85Dh64:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/3634763101860132985/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=3634763101860132985" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/3634763101860132985?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/3634763101860132985?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2009/05/google-wave.html" title="Google Wave" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C04DQHo6eip7ImA9WxVbEUo.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-5001803193075008894</id><published>2009-03-27T09:28:00.000-07:00</published><updated>2009-03-27T09:39:31.412-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-27T09:39:31.412-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="spring" /><title>A trick to guarantee Spring load order without using depends-on</title><content type="html">&lt;p&gt;There are times when you want one Spring bean instantiated before another. If bean A depends on bean B, you are guaranteed that B will be instantiated before A. But what if there is no such dependency? In older (i.e. pre-annotation) versions of Spring, the solution is to use the depends-on attribute in the bean definition in your application context. But if you are using annotations exclusively, depends-on isn't available.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;The trick is simple: inject an unused dependency using @Resource.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;A.java&lt;/h3&gt;&lt;br /&gt;&lt;pre&gt;@Service&lt;br /&gt;public class A&lt;br /&gt;{&lt;br /&gt; @SuppressWarnings("unused")&lt;br /&gt; @Resource&lt;br /&gt; private B unusedButNeededForSpringBeanLoadOrder;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-5001803193075008894?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=bCGD6LZ-WDo:UjdV1aq9Aj8:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=bCGD6LZ-WDo:UjdV1aq9Aj8:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=bCGD6LZ-WDo:UjdV1aq9Aj8:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=bCGD6LZ-WDo:UjdV1aq9Aj8:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/5001803193075008894/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=5001803193075008894" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/5001803193075008894?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/5001803193075008894?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2009/03/trick-to-guarantee-spring-load-order.html" title="A trick to guarantee Spring load order without using depends-on" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total></entry><entry gd:etag="W/&quot;AkYBR3o5eip7ImA9WxVWGUg.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-2576504906672666528</id><published>2009-03-01T11:19:00.000-08:00</published><updated>2009-03-01T17:42:36.422-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-01T17:42:36.422-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="jquery" /><title>A jQuery tree table for Wicket</title><content type="html">&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_SisVgwGycDA/Sart63R25sI/AAAAAAAADS4/VVXcoLkuwoY/s1600-h/treetable-sample.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 329px; height: 159px;" src="http://2.bp.blogspot.com/_SisVgwGycDA/Sart63R25sI/AAAAAAAADS4/VVXcoLkuwoY/s400/treetable-sample.png" alt="" id="BLOGGER_PHOTO_ID_5308316706336597698" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;I needed to enhance my &lt;a href="http://wicket.apache.org/"&gt;Wicket&lt;/a&gt; &lt;a href="http://wicket.apache.org/docs/1.4/org/apache/wicket/markup/repeater/data/DataView.html"&gt;DataView&lt;/a&gt;, which uses an html table and a particular look and feel, to render as a treetable in certain cases. The Wicket built-in &lt;a href="http://wicket.apache.org/docs/1.4/org/apache/wicket/extensions/markup/html/tree/table/TreeTable.html"&gt;TreeTable&lt;/a&gt; does not meet my needs, because it renders table cells as divs. Also I like the idea of finding easy ways to integrate powerful javascript plugins with Wicket.&lt;br /&gt;&lt;br /&gt;I found that what I wanted to do was indeed possible with just one line of javascript: &lt;code&gt;$("#treetable").treeTable()&lt;/code&gt;. It doesn't get simpler than that.&lt;br /&gt;&lt;br /&gt;I found two popular tree table &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; plugins, namely &lt;a href="http://plugins.jquery.com/project/treeTable"&gt;treeTable&lt;/a&gt; and &lt;a href="http://plugins.jquery.com/project/jQTreeTable"&gt;jQTreeTable&lt;/a&gt;. After some playing around, I decided that treeTable was simpler and met my needs. To set up my Wicket webapp to use it, I downloaded the &lt;a href="http://ludo.cubicphuse.nl/jquery-plugins/treeTable/src/javascripts/jquery.treeTable.js"&gt;.js file&lt;/a&gt; to src/main/webapp/js, the &lt;a href="http://ludo.cubicphuse.nl/jquery-plugins/treeTable/src/stylesheets/jquery.treeTable.css"&gt;CSS file&lt;/a&gt; to  to src/main/webapp/css, and the necessary images (which are a bit harder to get at than they should be): &lt;a href="http://ludo.cubicphuse.nl/jquery-plugins/treeTable/src/images/toggle-expand-dark.png"&gt;toggle-expand-dark.png&lt;/a&gt;, &lt;a href="http://ludo.cubicphuse.nl/jquery-plugins/treeTable/src/images/toggle-collapse-dark.png"&gt;toggle-collapse-dark.png&lt;/a&gt;, &lt;a href="http://ludo.cubicphuse.nl/jquery-plugins/treeTable/src/images/toggle-collapse-light.png"&gt;toggle-collapse-light.png&lt;/a&gt;, &lt;a href="http://ludo.cubicphuse.nl/jquery-plugins/treeTable/src/images/toggle-expand-light.png"&gt;toggle-expand-light.png&lt;/a&gt; to  to src/main/webapp/images.&lt;br /&gt;&lt;br /&gt;The sample code below is the simplest way I could think of to show how to marry the jQuery tree table to Wicket, namely using a Loop. There are many other ways to do it, including what I am using in production, namely the DataPanel-DataProvider-DataView pattern.  The picture above is a screenshot of my sample code.&lt;br /&gt;&lt;br /&gt;The code that actually renders the tree table is a small part of the whole (i.e. the Loop); the rest is there to build the model, and other supporting code. Pay closest attention to the header in the html code.&lt;br /&gt;&lt;br /&gt;As of this writing, I'm using jQuery 1.2.6 and Wicket 1.4-rc2. &lt;br /&gt;&lt;br /&gt;&lt;h2&gt;TreeTable.html&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br /&gt;    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&amp;gt;&lt;br /&gt;  &amp;lt;wicket:head&amp;gt;&lt;br /&gt;    &amp;lt;script type="text/javascript" src="js/jquery.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;    &amp;lt;script type="text/javascript" src="js/jquery.treeTable.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;    &amp;lt;link href="css/jquery.treeTable.css" rel="stylesheet" type="text/css" /&amp;gt;&lt;br /&gt;    &amp;lt;script type="text/javascript"&amp;gt;&lt;br /&gt;        $(document).ready(function()  {&lt;br /&gt;          $("#treetable").treeTable();&lt;br /&gt;        });&lt;br /&gt;    &amp;lt;/script&amp;gt;&lt;br /&gt;   &amp;lt;/wicket:head&amp;gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt; &amp;lt;table id="treetable" border="1"&amp;gt;&lt;br /&gt;  &amp;lt;tr&amp;gt;&lt;br /&gt;   &amp;lt;th&amp;gt;ID&amp;lt;/th&amp;gt;&lt;br /&gt;   &amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;&lt;br /&gt;   &amp;lt;th&amp;gt;Description&amp;lt;/th&amp;gt;&lt;br /&gt;  &amp;lt;/tr&amp;gt;&lt;br /&gt;  &amp;lt;tr wicket:id="row"&amp;gt;&lt;br /&gt;   &amp;lt;td wicket:id="id"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;   &amp;lt;td wicket:id="name"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;   &amp;lt;td wicket:id="description"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;  &amp;lt;/tr&amp;gt;&lt;br /&gt; &amp;lt;/table&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;TreeTable.java&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;public class TreeTable extends WebPage {&lt;br /&gt;&lt;br /&gt; public TreeTable() {&lt;br /&gt;  super();&lt;br /&gt;&lt;br /&gt;  // Create a TreeModel or any kind of hierarchical data model&lt;br /&gt;  final TreeModel treeModel = getTreeModel();&lt;br /&gt;  // It's convenient to have an ordered list of tree nodes to iterate over&lt;br /&gt;  final List&amp;lt;DefaultMutableTreeNode&amp;gt; treeAsList = treeToList(treeModel);&lt;br /&gt;  // This map lets a child node look up the html id of its rendered parent tag&lt;br /&gt;  final Map&amp;lt;DefaultMutableTreeNode, String&amp;gt; treeMap = new HashMap&amp;lt;DefaultMutableTreeNode, String&amp;gt;();&lt;br /&gt;&lt;br /&gt;  add(new Loop("row", treeAsList.size()) {&lt;br /&gt;   private static final long serialVersionUID = 3443157723178494017L;&lt;br /&gt;&lt;br /&gt;   @Override&lt;br /&gt;   protected void populateItem(LoopItem item) {&lt;br /&gt;    final int index = item.getIteration();&lt;br /&gt;    final DefaultMutableTreeNode node = treeAsList.get(index);&lt;br /&gt;    final MyModelObject model = (MyModelObject) node&lt;br /&gt;      .getUserObject();&lt;br /&gt;    // Table cells&lt;br /&gt;    item.add(new Label("id", model.id));&lt;br /&gt;    item.add(new Label("name", model.name));&lt;br /&gt;    item.add(new Label("description", model.description));&lt;br /&gt;    // Build the tree hierarchy&lt;br /&gt;    // The id is necessary for the parent-child relationship&lt;br /&gt;    item.setOutputMarkupId(true);&lt;br /&gt;    treeMap.put(node, item.getMarkupId());&lt;br /&gt;    final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) node&lt;br /&gt;      .getParent();&lt;br /&gt;    if (parentNode != null) {&lt;br /&gt;     final String parentId = treeMap.get(parentNode);&lt;br /&gt;     item.add(new AttributeAppender("class", new Model&amp;lt;String&amp;gt;(&lt;br /&gt;       "child-of-" + parentId), " "));&lt;br /&gt;    }&lt;br /&gt;   }&lt;br /&gt;  });&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private List&amp;lt;DefaultMutableTreeNode&amp;gt; treeToList(TreeModel treeModel) {&lt;br /&gt;  final List&amp;lt;DefaultMutableTreeNode&amp;gt; ret = new ArrayList&amp;lt;DefaultMutableTreeNode&amp;gt;();&lt;br /&gt;  final DefaultMutableTreeNode node = (DefaultMutableTreeNode) treeModel&lt;br /&gt;    .getRoot();&lt;br /&gt;  recurse(ret, node);&lt;br /&gt;  return ret;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private void recurse(List&amp;lt;DefaultMutableTreeNode&amp;gt; ret,&lt;br /&gt;   DefaultMutableTreeNode node) {&lt;br /&gt;  ret.add(node);&lt;br /&gt;  for (Enumeration&amp;lt;?&amp;gt; e = node.children(); e.hasMoreElements();) {&lt;br /&gt;   final DefaultMutableTreeNode child = (DefaultMutableTreeNode) e&lt;br /&gt;     .nextElement();&lt;br /&gt;   recurse(ret, child);&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; /** A sample tree model */&lt;br /&gt; private TreeModel getTreeModel() {&lt;br /&gt;&lt;br /&gt;  final DefaultMutableTreeNode ROOT = new DefaultMutableTreeNode(&lt;br /&gt;    new MyModelObject("ROOT", "This is root",&lt;br /&gt;      "Root is the base of the tree"));&lt;br /&gt;&lt;br /&gt;  final DefaultMutableTreeNode A = new DefaultMutableTreeNode(&lt;br /&gt;    new MyModelObject("A", "This is A", "A is a child of ROOT"));&lt;br /&gt;  ROOT.add(A);&lt;br /&gt;  final DefaultMutableTreeNode B = new DefaultMutableTreeNode(&lt;br /&gt;    new MyModelObject("B", "This is B", "B is a child of ROOT"));&lt;br /&gt;  ROOT.add(B);&lt;br /&gt;  final DefaultMutableTreeNode AA = new DefaultMutableTreeNode(&lt;br /&gt;    new MyModelObject("AA", "This is AA", "AA is a child of A"));&lt;br /&gt;  A.add(AA);&lt;br /&gt;  final DefaultMutableTreeNode BB = new DefaultMutableTreeNode(&lt;br /&gt;    new MyModelObject("BB", "This is B", "BB is a child of B"));&lt;br /&gt;  B.add(BB);&lt;br /&gt;&lt;br /&gt;  final DefaultTreeModel tree = new DefaultTreeModel(ROOT);&lt;br /&gt;  return tree;&lt;br /&gt; }&lt;br /&gt; /** A sample model object for the tree nodes */&lt;br /&gt; private class MyModelObject implements Serializable {&lt;br /&gt;  private static final long serialVersionUID = -3058898204144314012L;&lt;br /&gt;  private final String id, name, description;&lt;br /&gt;&lt;br /&gt;  public MyModelObject(String id, String name, String description) {&lt;br /&gt;   this.id = id;&lt;br /&gt;   this.name = name;&lt;br /&gt;   this.description = description;&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To get the expand and collapse icons to render the way I preferred, I made a minor change to the javascript to make the margin and padding be 100% CSS-driven.&lt;br /&gt;&lt;br /&gt;In jquery.treeTable.js, I changed the line:&lt;br /&gt;&lt;pre&gt;cell.prepend('&amp;lt;span style="margin-left: -' + options.indent + 'px;&lt;br/&gt; padding-left: ' + options.indent + 'px" class="expander"&amp;gt;&amp;lt;/span&amp;gt;');&lt;/pre&gt;&lt;br /&gt;To:&lt;br /&gt;&lt;pre&gt;cell.prepend('&amp;lt;span class="expander"&amp;gt;&amp;lt;/span&amp;gt;');&lt;/pre&gt;&lt;br /&gt;And in jquery.treeTable.css, I added the last two lines (margin-left and padding-left) to ".treeTable tr td .expander":&lt;br /&gt;&lt;pre&gt;.treeTable tr td .expander {&lt;br /&gt;  background-position: left center;&lt;br /&gt;  background-repeat: no-repeat;&lt;br /&gt;  cursor: pointer;&lt;br /&gt;  padding: 0;&lt;br /&gt;  zoom: 1; /* IE7 Hack */  &lt;br /&gt;  margin-left: -3px;&lt;br /&gt;  padding-left: 15px;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-2576504906672666528?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=42V-1F3Gqgw:6x0sbA7BH2Y:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=42V-1F3Gqgw:6x0sbA7BH2Y:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=42V-1F3Gqgw:6x0sbA7BH2Y:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=42V-1F3Gqgw:6x0sbA7BH2Y:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/2576504906672666528/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=2576504906672666528" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/2576504906672666528?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/2576504906672666528?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2009/03/jquery-tree-table-for-wicket.html" title="A jQuery tree table for Wicket" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_SisVgwGycDA/Sart63R25sI/AAAAAAAADS4/VVXcoLkuwoY/s72-c/treetable-sample.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C0AHQ34-fip7ImA9WxJSFEo.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-4853781735974184176</id><published>2009-02-19T09:26:00.000-08:00</published><updated>2009-05-04T14:15:32.056-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-04T14:15:32.056-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="jquery" /><title>Adding jQuery tooltips to Wicket pages</title><content type="html">&lt;p&gt;Tooltips are little popup windows that appear on mouse over and are one handy way to provide context-sensitive help to users.  It's easy to add tooltips to form fields (or any other tags, really) using &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; and Wicket.  See the screenshot for an example.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_SisVgwGycDA/Sf9VWKtj7CI/AAAAAAAADTg/N5okMwXOoDE/s1600-h/Picture+4.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 394px; height: 195px;" src="http://1.bp.blogspot.com/_SisVgwGycDA/Sf9VWKtj7CI/AAAAAAAADTg/N5okMwXOoDE/s400/Picture+4.png" alt="" id="BLOGGER_PHOTO_ID_5332074323150892066" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Here's how to add tooltips to your Wicket forms. I'm using the label tag to associate the text next to the field with its field, and assigning the tooltip to the label field also. This means that the user will see the tooltip whether they mouse over the label or the field. The first example is the simplest, where I provide a string for the tooltip directly in the code. The second example uses Wicket's powerful model capability, which means you can get the tooltip string from a properties file, a bean property, even a database.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;There are many jQuery plugins that can display tooltips. I'm using &lt;a href="http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/"&gt;this one&lt;/a&gt;., which I found easy to use and very well-behaved on various browsers. There are lots of options for styling, but I'm using the default style.&lt;/p&gt;&lt;p&gt;I'm using version 1.4-rc2 of Wicket, version 1.2.6 of jQuery, and version 1.3 of the jQuery Tooltip plugin. I put the .js files in the folder src/main/webapp/js of my maven project, and the .css file in the folder src/main/webapp/css of my maven project.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Tooltips.html&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br /&gt;"&lt;a class="linkification-ext" href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" title="Linkification: http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&lt;/a&gt;"&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;html xmlns="&lt;a class="linkification-ext" href="http://www.w3.org/1999/xhtml" title="Linkification: http://www.w3.org/1999/xhtml"&gt;http://www.w3.org/1999/xhtml&lt;/a&gt;"&amp;gt;&lt;br /&gt;&amp;lt;wicket:head&amp;gt;&lt;br /&gt;&amp;lt;script type="text/javascript" src="js/jquery.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;script type="text/javascript" src="js/jquery.tooltip.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;script type="text/javascript"&amp;gt;&lt;br /&gt;    $(function() {&lt;br /&gt;     $('.tooltip').tooltip();&lt;br /&gt;    });&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;link rel="stylesheet" href="css/jquery.tooltip.css" /&amp;gt;&lt;br /&gt;&amp;lt;/wicket:head&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt;&amp;lt;form action="" wicket:id="myform"&amp;gt;&lt;br /&gt;&amp;lt;fieldset&amp;gt;&lt;br /&gt; &amp;lt;label for="one" wicket:id="label.one"&amp;gt;My text field 1:&amp;lt;/label&amp;gt;&lt;br /&gt; &amp;lt;input id="one" wicket:id="text.one" type="text"/&amp;gt;&lt;br /&gt;&amp;lt;/fieldset&amp;gt;&lt;br /&gt;&amp;lt;fieldset&amp;gt;&lt;br /&gt; &amp;lt;label for="two" wicket:id="label.two"&amp;gt;My text field 2:&amp;lt;/label&amp;gt;&lt;br /&gt; &amp;lt;input id="two" wicket:id="text.two" type="text"/&amp;gt;&lt;br /&gt;&amp;lt;/fieldset&amp;gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Tooltips.java&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;public class Tooltips extends WebPage {&lt;br /&gt;&lt;br /&gt;public Tooltips() {&lt;br /&gt;  super();&lt;br /&gt;&lt;br /&gt;  Form&amp;lt;String&amp;gt; form = new Form&amp;lt;String&amp;gt;("myform");&lt;br /&gt;  add(form);&lt;br /&gt;  &lt;br /&gt;  SimpleAttributeModifier am1 = new SimpleAttributeModifier("title", "hi tooltip 1");&lt;br /&gt;  AttributeAppender tt = new AttributeAppender("class", new Model&amp;lt;String&amp;gt;("tooltip"), " ");&lt;br /&gt;  Label label1 = new Label("label.one", new Model&amp;lt;String&amp;gt;("My Form Field Label 1:"));&lt;br /&gt;  label1.add(am1);&lt;br /&gt;  label1.add(tt);&lt;br /&gt;  form.add(label1);&lt;br /&gt;  TextField&amp;lt;String&amp;gt; textField1 = new TextField&amp;lt;String&amp;gt;("text.one");&lt;br /&gt;  textField1.add(am1);&lt;br /&gt;  textField1.add(tt);&lt;br /&gt;  form.add(textField1);&lt;br /&gt;  &lt;br /&gt;  AttributeModifier am2 = new AttributeModifier("title", true, new Model&amp;lt;String&amp;gt;("hi tooltip 2"));&lt;br /&gt;  Label label2 = new Label("label.two", new Model&amp;lt;String&amp;gt;("My Form Field Label 2:"));&lt;br /&gt;  label2.add(am2);&lt;br /&gt;  label2.add(tt);&lt;br /&gt;  form.add(label2);&lt;br /&gt;  TextField&amp;lt;String&amp;gt; textField2 = new TextField&amp;lt;String&amp;gt;("text.two");&lt;br /&gt;  textField2.add(am2);&lt;br /&gt;  textField2.add(tt);&lt;br /&gt;  form.add(textField2);&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-4853781735974184176?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=e7ei8resFes:K5kA6nPgOVo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=e7ei8resFes:K5kA6nPgOVo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=e7ei8resFes:K5kA6nPgOVo:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=e7ei8resFes:K5kA6nPgOVo:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/4853781735974184176/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=4853781735974184176" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/4853781735974184176?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/4853781735974184176?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2009/02/adding-jquery-tooltips-to-wicket-pages.html" title="Adding jQuery tooltips to Wicket pages" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_SisVgwGycDA/Sf9VWKtj7CI/AAAAAAAADTg/N5okMwXOoDE/s72-c/Picture+4.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CUcBSHc5eip7ImA9WxVQGUk.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-712596255022276439</id><published>2009-02-06T09:11:00.000-08:00</published><updated>2009-02-06T09:57:39.922-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-06T09:57:39.922-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="hibernate" /><category scheme="http://www.blogger.com/atom/ns#" term="jpa" /><category scheme="http://www.blogger.com/atom/ns#" term="ejb3" /><category scheme="http://www.blogger.com/atom/ns#" term="spring" /><title>JPA and multiple persistence units</title><content type="html">I have JPA/EJB3 @Entities in two separate jars that are loaded by my webapp. As strange as this sounds, it's not that easy to define ORM entities in more than one jar file and have your application load them. It seems this is not a common requirement, yet for a really modular application, it seems obvious to me that you might do this.&lt;br /&gt;&lt;br /&gt;One supposedly standard way to solve this is to use the jar-file tag in persistence.xml. But it doesn't solve the problem because you have to use absolute URLs, which is not workable for production.&lt;br /&gt;&lt;br /&gt;I found the solution in &lt;a href="http://forum.springsource.org/showthread.php?t=61763"&gt;this post&lt;/a&gt;, which was to override persistence unit processing. In addition to the following code, I had to set up my Spring configuration in a particular way to make it all work properly:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;&lt;br /&gt;import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* This merges all JPA entities from multiple jars. To use it, all entities must&lt;br /&gt;* be listed in their respective persistence.xml files using the &amp;lt;class&amp;gt; tag.&lt;br /&gt;*&lt;br /&gt;* @see http://forum.springsource.org/showthread.php?t=61763&lt;br /&gt;*/&lt;br /&gt;public class MergingPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor&lt;br /&gt;{&lt;br /&gt; Map&amp;lt;String, List&amp;lt;String&amp;gt;&amp;gt; puiClasses = new HashMap&amp;lt;String, List&amp;lt;String&amp;gt;&amp;gt;();&lt;br /&gt;&lt;br /&gt; public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui)&lt;br /&gt; {&lt;br /&gt;  List&amp;lt;String&amp;gt; classes = puiClasses.get(pui.getPersistenceUnitName());&lt;br /&gt;  if (classes == null)&lt;br /&gt;  {&lt;br /&gt;   classes = new ArrayList&amp;lt;String&amp;gt;();&lt;br /&gt;   puiClasses.put(pui.getPersistenceUnitName(), classes);&lt;br /&gt;  }&lt;br /&gt;  pui.getManagedClassNames().addAll(classes);&lt;br /&gt;&lt;br /&gt;  final List&amp;lt;String&amp;gt; names = pui.getManagedClassNames();&lt;br /&gt;  classes.addAll(pui.getManagedClassNames());&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To use it, I found that this only works if do all of the following:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;All your entities need to be mentioned in your persistence.xml files. Normally you don't have to do this is if you set up entity scanning in your Spring config&lt;br /&gt;&lt;/li&gt;&lt;li&gt;All your persistence units use the same name, and furthermore it has to be the default name, namely "default"&lt;/li&gt;&lt;li&gt;You have to use a particular entity manager factory, namely LocalContainerEntityManagerFactoryBean&lt;/li&gt;&lt;li&gt;You have to have define your persistence provider in persistence.xml, in our case  org.hibernate.ejb.HibernatePersistence&lt;/li&gt;&lt;li&gt;Each persistence.xml file needs a unique name, because they are enumerated in the application context.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Here is an example of a persistence.xml:&lt;br /&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;persistence version="1.0"&lt;br /&gt; xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt; xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"&amp;gt;&lt;br /&gt; &amp;lt;persistence-unit name="default" transaction-type="RESOURCE_LOCAL"&amp;gt;&lt;br /&gt; &lt;br /&gt;  &amp;lt;provider&amp;gt;org.hibernate.ejb.HibernatePersistence&amp;lt;/provider&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;!-- Entities have to be enumerated here for MergingPersistenceUnitPostProcessor --&amp;gt;&lt;br /&gt;  &amp;lt;class&amp;gt;com.my.Foo&amp;lt;/class&amp;gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here is an excerpt from my application context. The whole point is that I can now have as many persistence.xml files as I like, in different jar files:&lt;br /&gt;&lt;pre&gt;&amp;lt;bean id="pum"&lt;br /&gt; class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager"&amp;gt;&lt;br /&gt; &amp;lt;property name="persistenceXmlLocations"&amp;gt;&lt;br /&gt;  &amp;lt;list&amp;gt;&lt;br /&gt;   &amp;lt;value&amp;gt;classpath*:META-INF/persistence1.xml&amp;lt;/value&amp;gt;&lt;br /&gt;   &amp;lt;value&amp;gt;classpath*:META-INF/persistence2.xml&amp;lt;/value&amp;gt;&lt;br /&gt;  &amp;lt;/list&amp;gt;&lt;br /&gt; &amp;lt;/property&amp;gt;&lt;br /&gt; &amp;lt;property name="persistenceUnitPostProcessors"&amp;gt;&lt;br /&gt;       &amp;lt;bean class="com.my.MergingPersistenceUnitPostProcessor"/&amp;gt;&lt;br /&gt; &amp;lt;/property&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&amp;lt;bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"&amp;gt;&lt;br /&gt; &amp;lt;property name="persistenceUnitManager" ref="pum"/&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-712596255022276439?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=BK3kAti3Jos:PAPfHeGfDec:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=BK3kAti3Jos:PAPfHeGfDec:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=BK3kAti3Jos:PAPfHeGfDec:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=BK3kAti3Jos:PAPfHeGfDec:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/712596255022276439/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=712596255022276439" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/712596255022276439?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/712596255022276439?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2009/02/jpa-and-multiple-persistence-units.html" title="JPA and multiple persistence units" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total></entry><entry gd:etag="W/&quot;A0UNSX49eyp7ImA9WxVQFkQ.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-9066762113324629716</id><published>2009-02-03T13:24:00.000-08:00</published><updated>2009-02-03T14:14:58.063-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-03T14:14:58.063-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="jpa" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="ejb3" /><category scheme="http://www.blogger.com/atom/ns#" term="spring" /><category scheme="http://www.blogger.com/atom/ns#" term="ehcache" /><title>JPA / EJB3 Persistence and EhCache</title><content type="html">We had an issue with a particular web page that took too long to load. We could have done a whole lot of code optimization, but we thought we'd give a second level cache a try, since it's something we needed to do anyway. The bottom line is that it gave us a significant boost in performance, for very little additional work.&lt;br /&gt;&lt;br /&gt;We are using Spring, JPA/EJB3, and Wicket. After some investigation, &lt;a href="http://ehcache.sourceforge.net/"&gt;Ehcache&lt;/a&gt; seemed like the best choice because it's popular, it's free and it's very well supported.&lt;br /&gt;&lt;br /&gt;We only had time for the absolutely minimal implementation. Fortunately, all we needed to do was add an annotation to the entities we wanted cached, edit persistence.xml, and add ehcache.xml. We didn't even have to add ehcache as a Maven dependency because Hibernate's EJB3 implementation (hibernate-entitymanager) was already depending on it.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;src/main/java/com/my/Foo.java&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;...&lt;br /&gt;@Entity&lt;br /&gt;@Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)&lt;br /&gt;public class Foo implements Serializable {&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;We are using NONSTRICT_READ_WRITE as our strategy but to be frank, we copied this from an example and it seems to work fine.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;src/main/resources/META-INF/persistence.xml&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;persistence version="1.0"&lt;br /&gt; xmlns="http://java.sun.com/xml/ns/persistence" &lt;br /&gt; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt; xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;persistence-unit name="default" transaction-type="RESOURCE_LOCAL"&amp;gt;&lt;br /&gt; &lt;br /&gt;  &amp;lt;properties&amp;gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;   &amp;lt;!-- 2nd level cache --&amp;gt;&lt;br /&gt;   &amp;lt;property name="hibernate.cache.provider_class"&lt;br /&gt;    value="net.sf.ehcache.hibernate.SingletonEhCacheProvider" /&amp;gt;&lt;br /&gt;   &amp;lt;property name="net.sf.ehcache.configurationResourceName"&lt;br /&gt;    value="/ehcache.xml" /&amp;gt;&lt;br /&gt;   &amp;lt;property name="hibernate.cache.use_query_cache" value="true" /&amp;gt;&lt;br /&gt;   &amp;lt;property name="hibernate.cache.use_second_level_cache"&lt;br /&gt;    value="true" /&amp;gt;&lt;br /&gt;   &amp;lt;property name="hibernate.generate_statistics" value="true" /&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;/properties&amp;gt;&lt;br /&gt; &amp;lt;/persistence-unit&amp;gt;&lt;br /&gt;&amp;lt;/persistence&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The key piece of information here is that we are using SingletonEhCacheProvider. If you don't do this, you'll get new instances of your entities when cached versions are what you really want.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;src/main/resources/ehcache.xml&lt;/h2&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;ehcache&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;diskStore path="java.io.tmpdir"/&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;defaultCache&lt;br /&gt;            maxElementsInMemory="10000"&lt;br /&gt;            eternal="false"&lt;br /&gt;            timeToIdleSeconds="120"&lt;br /&gt;            timeToLiveSeconds="120"&lt;br /&gt;            overflowToDisk="true"&lt;br /&gt;            diskPersistent="false"&lt;br /&gt;            diskExpiryThreadIntervalSeconds="120"&lt;br /&gt;            memoryStoreEvictionPolicy="LRU"/&amp;gt;&lt;br /&gt;&lt;br /&gt;     &amp;lt;cache name="com.my.Foo"&lt;br /&gt;           maxElementsInMemory="1000"/&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/ehcache&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;We used the defaults for all the settings. You have to enumerate all the entities you want cached in ehcache.xml. And that's it!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-9066762113324629716?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=_eK5tY2HhxU:KF6ywf1d9wg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=_eK5tY2HhxU:KF6ywf1d9wg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=_eK5tY2HhxU:KF6ywf1d9wg:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=_eK5tY2HhxU:KF6ywf1d9wg:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/9066762113324629716/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=9066762113324629716" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/9066762113324629716?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/9066762113324629716?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2009/02/jpa-ejb3-persistence-and-ehcache.html" title="JPA / EJB3 Persistence and EhCache" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total></entry><entry gd:etag="W/&quot;D0cGQ3k8eip7ImA9WxVQEko.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-3618035728393192977</id><published>2009-01-29T16:10:00.000-08:00</published><updated>2009-01-29T16:23:42.772-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-29T16:23:42.772-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><title>Wicket and Default Focus Behavior</title><content type="html">This seems to come up a lot: "Is there a behavior (or some other way) for having a field receive the focus when the page loads?  For instance, in a login form, you'd want the focus to go to the username field". I've solved this a couple of different ways before, but when I found &lt;a href="http://www.nabble.com/Default-Focus-Behavior--td15934889.html"&gt;James Carman's tip&lt;/a&gt; I realized I already had a convenient place to automatically enable this behavior for every form in my Wicket app.&lt;br /&gt;&lt;br /&gt;I'm already using &lt;a href="http://londonwicket.org/content/LondonWicket-FormsWithFlair.pdf"&gt;Alastair Maw's technique&lt;/a&gt; for attaching error messages to form fields. This visits all form components and attaches an error behavior to each one. This would be another behavior that would just be attached to the first editable form field. Turns out it works really well. I decided I only wanted to set focus on a text field or a dropdown, but you can decide for yourself.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;public class ShinyFormVisitor implements IVisitor, Serializable&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = 9060018214520265174L;&lt;br /&gt; private final Set&amp;lt;FormComponent&amp;gt; visited = new HashSet&amp;lt;FormComponent&amp;gt;();&lt;br /&gt; private boolean found = false;&lt;br /&gt;&lt;br /&gt; public Object component(Component c)&lt;br /&gt; {&lt;br /&gt;  if (!visited.contains(c) &amp;amp;&amp;amp; c instanceof FormComponent &amp;amp;&amp;amp; !(c instanceof Button))&lt;br /&gt;  {&lt;br /&gt;   final FormComponent fc = (FormComponent) c;&lt;br /&gt;   visited.add(fc);&lt;br /&gt;   fc.add(new ValidationMsgBehavior());&lt;br /&gt;   fc.add(new ErrorHighlightBehavior());&lt;br /&gt;   if (!found &amp;amp;&amp;amp; fc.isEnabled() &amp;amp;&amp;amp; fc.isVisible() &amp;amp;&amp;amp; (fc instanceof DropDownChoice || fc instanceof AbstractTextComponent))&lt;br /&gt;   {&lt;br /&gt;    found = true;&lt;br /&gt;    fc.add(new DefaultFocusBehavior());&lt;br /&gt;   }&lt;br /&gt;  }&lt;br /&gt;  return IVisitor.CONTINUE_TRAVERSAL;&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In the above, a DefaultFocusBehavior is attached to the first form field of the right type. Now for DefaultFocusBehavior itself:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;/**&lt;br /&gt;* @see http://www.nabble.com/Default-Focus-Behavior--td15934889.html&lt;br /&gt;*/&lt;br /&gt;public class DefaultFocusBehavior extends AbstractBehavior&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = -4891399118136854774L;&lt;br /&gt;&lt;br /&gt; private Component component;&lt;br /&gt;&lt;br /&gt; @Override&lt;br /&gt; public void bind(Component component)&lt;br /&gt; {&lt;br /&gt;  if (!(component instanceof FormComponent))&lt;br /&gt;  {&lt;br /&gt;   throw new IllegalArgumentException("DefaultFocusBehavior: component must be instanceof FormComponent");&lt;br /&gt;  }&lt;br /&gt;  this.component = component;&lt;br /&gt;  component.setOutputMarkupId(true);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; @Override&lt;br /&gt; public void renderHead(IHeaderResponse iHeaderResponse)&lt;br /&gt; {&lt;br /&gt;  super.renderHead(iHeaderResponse);&lt;br /&gt;  iHeaderResponse.renderOnLoadJavascript("document.getElementById('"&lt;br /&gt;    + component.getMarkupId() + "').focus();");&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-3618035728393192977?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=OyKEwjnmL7Y:2NtQmYNOu6s:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=OyKEwjnmL7Y:2NtQmYNOu6s:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=OyKEwjnmL7Y:2NtQmYNOu6s:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=OyKEwjnmL7Y:2NtQmYNOu6s:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/3618035728393192977/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=3618035728393192977" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/3618035728393192977?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/3618035728393192977?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2009/01/wicket-and-default-focus-behavior.html" title="Wicket and Default Focus Behavior" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C04DQXo4eCp7ImA9WxRbE04.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-2080659710430149657</id><published>2008-12-03T09:52:00.000-08:00</published><updated>2008-12-03T11:19:30.430-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-03T11:19:30.430-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="jpa" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="spring" /><title>Using Wicket with Spring 2.5 annotations</title><content type="html">We upgraded to Spring 2.5 and wanted to make use of the latest annotations in conjunction with JPA and Wicket's @SpringBean annotation. We are using Wicket 1.3.4 as of this writing. I felt it's worth blogging about, because even though it looks straightforward, there was pain and suffering required to get to what you see below.&lt;br /&gt;&lt;br /&gt;Starting from the Wicket end, here's what we wanted to end up with. The middleware team created an API for the GUI to use.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;MyBasePage.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;public class MyBasePage extends WebPage {&lt;br /&gt;...&lt;br /&gt; @SpringBean(name="myApiImpl")&lt;br /&gt; private MyAPI myApi;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;For a component like a Wicket page, this is all you need to do to get access to the spring bean. It will be automatically injected for you, provided everything is set up correctly (more on this below). Note the name attribute, which we found was necessary. It's the name of the API implementation class, with the first letter in lowercase.&lt;br /&gt;&lt;br /&gt;For a non-component class like a data provider, you need to add a line to the constructor:&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;MyDataProvider.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;public class MyDataProvider extends SortableDataProvider {&lt;br /&gt;...&lt;br /&gt; @SpringBean(name="myApiImpl")&lt;br /&gt; private MyAPI myApi;&lt;br /&gt;&lt;br /&gt; public MyDataProvider()&lt;br /&gt; {&lt;br /&gt;  ...&lt;br /&gt;  // Injects the spring bean(s)&lt;br /&gt;  InjectorHolder.getInjector().inject(this);&lt;br /&gt; }&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Your Wicket application class needs to add a spring component instantiation listener:&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;MyApplication.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;public class MyApplication extends WebApplication {&lt;br /&gt;...&lt;br /&gt; private static final String[] appContexts = {"applicationContext.xml"};&lt;br /&gt; &lt;br /&gt; @Override&lt;br /&gt; protected void init()&lt;br /&gt; {&lt;br /&gt;...&lt;br /&gt;  final ApplicationContext ctx = new ClassPathXmlApplicationContext(&lt;br /&gt;    appContexts);&lt;br /&gt;  addComponentInstantiationListener(new SpringComponentInjector(this, ctx));&lt;br /&gt; }&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Your web.xml has to have a JPA OpenEntityManagerInViewFilter and a Spring ContextLoaderListener:&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;web.xml&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;web-app ...&amp;gt;&lt;br /&gt; &amp;lt;context-param&amp;gt;&lt;br /&gt;  &amp;lt;param-name&amp;gt;contextConfigLocation&amp;lt;/param-name&amp;gt;&lt;br /&gt;  &amp;lt;param-value&amp;gt;classpath:applicationContext.xml&amp;lt;/param-value&amp;gt;&lt;br /&gt; &amp;lt;/context-param&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;filter&amp;gt;&lt;br /&gt;  &amp;lt;filter-name&amp;gt;openEntityManagerInViewFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;  &amp;lt;filter-class&amp;gt;&lt;br /&gt;   org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter&lt;br /&gt;  &amp;lt;/filter-class&amp;gt;&lt;br /&gt; &amp;lt;/filter&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;filter-mapping&amp;gt;&lt;br /&gt;  &amp;lt;filter-name&amp;gt;openEntityManagerInViewFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;  &amp;lt;url-pattern&amp;gt;/*&amp;lt;/url-pattern&amp;gt;&lt;br /&gt; &amp;lt;/filter-mapping&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;listener&amp;gt;&lt;br /&gt;  &amp;lt;listener-class&amp;gt;&lt;br /&gt;   org.springframework.web.context.ContextLoaderListener&lt;br /&gt;  &amp;lt;/listener-class&amp;gt;&lt;br /&gt; &amp;lt;/listener&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;lt;/web-app&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now on to the middleware. Our API implementation has the @Service annotation, which designates it as a Spring service component. As far as I can tell, there's nothing too special about being a service component versus just an @Component, except that it makes it more readable. I think if you're using Spring Web Flow, it has more meaning. Note the injection of our DAO using the @Resource annotation:&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;MyApiImpl.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;@Service&lt;br /&gt;public class MyApiImpl implements MyAPI, Serializable {&lt;br /&gt;...&lt;br /&gt; @Resource private MyDao dao;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The DAO implementation has the @Repository annotation, which designates it as a Spring DAO component, which does have some meaning in that it does exception translation from ugly JDBC exceptions to better Spring exceptions. Note we found we had to inject an extended PersistenceContext. This is not thread safe, but we haven't had any problems with it. We made the methods that persist data transactional using @Transactional. I've included one of them as an example. The generic objects with type T are persistent objects that are annotated with @Entity.&lt;br /&gt; &lt;br /&gt;&lt;h3&gt;MyDaoImpl.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;@Repository&lt;br /&gt;public class MyDaoImpl implements MyDao {&lt;br /&gt;...&lt;br /&gt; private EntityManager entityManager;&lt;br /&gt;...&lt;br /&gt; @PersistenceContext(type = PersistenceContextType.EXTENDED)&lt;br /&gt; public void setEntityManager(final EntityManager entityManager)&lt;br /&gt; {&lt;br /&gt;  this.entityManager = entityManager;&lt;br /&gt; }&lt;br /&gt;...&lt;br /&gt; @Transactional&lt;br /&gt; public &lt;T&gt; void saveObject(final T t) {&lt;br /&gt;  try {&lt;br /&gt;   entityManager.persist(t);&lt;br /&gt;   entityManager.flush();&lt;br /&gt;  }&lt;br /&gt;  catch (Exception e) {&lt;br /&gt;...&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Finally, our Spring application context is quite small because most of what it does is enable all the annotations. The JPA persistence.xml file is quite standard and therefore not shown.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;applicationContext.xml&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;beans xmlns="http://www.springframework.org/schema/beans" ...&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"&amp;gt;&lt;br /&gt; &amp;lt;property name="persistenceUnitName" value="MYPERSISTENCEUNIT"/&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;!-- For @Transactional --&amp;gt;&lt;br /&gt;    &amp;lt;tx:annotation-driven transaction-manager="txManager" /&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;!-- For @PersistenceContext --&amp;gt;&lt;br /&gt;    &amp;lt;bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /&amp;gt;&lt;br /&gt;    &amp;lt;!-- Translates native resource exceptions to Spring's &lt;br /&gt;  DataAccessException hierarchy --&amp;gt;&lt;br /&gt;    &amp;lt;bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/&amp;gt;&lt;br /&gt;    &amp;lt;!-- For JSR-250 annotations such as @Resource --&amp;gt;&lt;br /&gt;    &amp;lt;bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager"&amp;gt;&lt;br /&gt;        &amp;lt;property name="entityManagerFactory" ref="entityManagerFactory"/&amp;gt;&lt;br /&gt;        &amp;lt;property name="dataSource" ref="dataSource"/&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"&amp;gt;&lt;br /&gt;        &amp;lt;!-- The usual properties --&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt; &lt;br /&gt;    &amp;lt;!-- Registers Spring's standard post-processors for annotation-based&lt;br /&gt;  configuration like @Repository --&amp;gt;&lt;br /&gt;    &amp;lt;context:annotation-config /&amp;gt;&lt;br /&gt;    &amp;lt;!-- From this package on down is what Spring will scan for its &lt;br /&gt;  annotations like @Repository --&amp;gt;&lt;br /&gt;    &amp;lt;context:component-scan base-package="com.my"/&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/beans&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-2080659710430149657?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=MWGDLZVOM1g:LQGt4RAEPKQ:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=MWGDLZVOM1g:LQGt4RAEPKQ:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=MWGDLZVOM1g:LQGt4RAEPKQ:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=MWGDLZVOM1g:LQGt4RAEPKQ:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/2080659710430149657/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=2080659710430149657" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/2080659710430149657?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/2080659710430149657?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2008/12/using-wicket-with-spring-25-annotations.html" title="Using Wicket with Spring 2.5 annotations" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total></entry><entry gd:etag="W/&quot;DEMNSXkzeyp7ImA9WxRbE08.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-7042309198522834205</id><published>2008-11-21T16:10:00.000-08:00</published><updated>2008-12-03T09:48:18.783-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-03T09:48:18.783-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="archiva" /><category scheme="http://www.blogger.com/atom/ns#" term="maven" /><title>Installing Archiva</title><content type="html">&lt;a href="http://archiva.apache.org/"&gt;Archiva&lt;/a&gt; is an open-source proxy for &lt;a href="http://maven.apache.org/"&gt;Maven&lt;/a&gt;. It allows you to create an internal repository for your company that you can back up or check in, and makes downloads of dependencies much faster.  This is how I installed it.&lt;br /&gt;&lt;br /&gt;As of the time of writing, the current version is 1.1.3. Using a browser, I first downloaded the WAR distribution to a CentOS box. I tried to deploy it in tomcat 6.0.18, first running under the default version of java, which was 5, then under java 1.6.0_10. Unfortunately, despite following all the tomcat-specific instructions,  it  wouldn't run, so I downloaded the standalone distribution instead.&lt;br /&gt;&lt;br /&gt;I couldn't run the  standalone distribution either, until I installed java 6. To uninstall the default jdk:&lt;br /&gt;&lt;br /&gt;$ yum remove java&lt;br /&gt;&lt;br /&gt;I downloaded and ran jdk-6u10-linux-i586-rpm.bin from  &lt;a href="http://java.sun.com/"&gt;java.sun.com&lt;/a&gt;. This installs Java 6.&lt;br /&gt;&lt;br /&gt;Having downloaded apache-archiva-1.1.3-bin.zip (the standalone distribution), I unzipped it to /opt, then ran /opt/apache-archiva-1.1.3/bin/archiva.&lt;br /&gt;&lt;br /&gt;In our case, we created the DNS name "repo" for the box. So the app was visible at &lt;a style="background-color: rgb(255, 249, 171);" class="linkification-ext" href="http://repo:8080/archiva/" title="Linkification: http://repo:8080/archiva/"&gt;http://repo:8080/archiva/&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I then needed to point my maven builds at the new repository. On my development machine, I created settings.xml (see below) in &amp;lt;maven home&amp;gt;, which in my case is ~/.m2.  This tells maven to use our internal repo as the proxy for all downloaded artifacts (the line       &amp;lt;mirrorOf&amp;gt;*&amp;lt;/mirrorOf&amp;gt;). This can be changed to limit the set of artifacts for which you want to use archiva.&lt;br /&gt;&lt;br /&gt;&amp;lt;settings&amp;gt;&lt;br /&gt; &amp;lt;mirrors&amp;gt;&lt;br /&gt;   &amp;lt;mirror&amp;gt;&lt;br /&gt;     &amp;lt;id&amp;gt;archiva.default&amp;lt;/id&amp;gt;&lt;br /&gt;         &amp;lt;url&amp;gt;http://repo:8080/archiva/repository/internal&amp;lt;/url&amp;gt;&lt;br /&gt;     &amp;lt;mirrorOf&amp;gt;*&amp;lt;/mirrorOf&amp;gt;&lt;br /&gt;   &amp;lt;/mirror&amp;gt;&lt;br /&gt; &amp;lt;/mirrors&amp;gt;&lt;br /&gt;&amp;lt;/settings&amp;gt;&lt;br /&gt;&lt;br /&gt;To prime the archiva repository, I removed everything in my local repository (~/.m2/repository) and did a clean build. Archiva automatically downloads and stores the artifacts in /opt/apache-archiva-1.1.3/data/repositories/internal, which you can back up, or check in, if you are paranoid about such things.  You can then browse to see what was downloaded at http://repo:8080/archiva/browse.&lt;br /&gt;&lt;br /&gt;I then  provided all our developers with the settings.xml file, and I was done. Note: you can forgo settings.xml and instead put the same info in your pom.xml files, but this way the pom.xml files are more portable and isolated from the corporate repository.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-7042309198522834205?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=8QxB_CTGnIg:f0cXdTuIDEg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=8QxB_CTGnIg:f0cXdTuIDEg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=8QxB_CTGnIg:f0cXdTuIDEg:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=8QxB_CTGnIg:f0cXdTuIDEg:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/7042309198522834205/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=7042309198522834205" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/7042309198522834205?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/7042309198522834205?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2008/11/installing-archiva.html" title="Installing Archiva" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total></entry><entry gd:etag="W/&quot;A0QFSXY8fCp7ImA9WxdXFE0.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-3785511418386868659</id><published>2008-06-23T16:30:00.000-07:00</published><updated>2008-06-25T08:55:18.874-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-25T08:55:18.874-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="hibernate" /><category scheme="http://www.blogger.com/atom/ns#" term="ejb3" /><category scheme="http://www.blogger.com/atom/ns#" term="spring" /><title>Migration to Spring</title><content type="html">&lt;p&gt;We've decided to migrate to Spring. We are already using Hibernate, but want to improve our server architecture to use &lt;a href="http://www.martinfowler.com/articles/injection.html"&gt;Dependency Injection&lt;/a&gt;. Why am I blogging about this when so much has been written about it? Believe it or not, a lot of Java shops  still haven't migrated their apps to Spring, but are thinking about it, and the more recipes there are out there, the easier it is for people to do.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Why Spring and not EJB3? The differences are minimal for the features we care about. For example, we are fine with using XML configuration for dependency injection instead of EJB3-style annotations. We do not use stateful session beans, which seems to be one of the few EJB features that Spring does not support.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The benefits we believe we will get are:&lt;br /&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Loosely-coupled objects&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Simpler transaction code&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Less dependency on JBoss&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;h2&gt;Loosely-coupled objects&lt;/h2&gt;&lt;p&gt;Before we embarked on this migration, we already had a pretty good architecture based on the &lt;a href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html"&gt;DAO/DTO pattern&lt;/a&gt;, and a facade that insulates our presentation layer from the server's implementation. The most compelling reason to use Dependency Injection, other than it incrementally improves our architecture and our flexibility, is that it makes our code much easier to test. You can now test one component in isolation from all the others on which it doesn't depend. Before this change, when you ran a single unit test, the test would pull in the whole code base. In fact, before this change, you had to deploy the entire code base to the app server to test a single method. Now you can run (and debug!) a single method in a unit test in the IDE.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;An example of incremental improvement is that all of our DAO objects are created by factory classes. Spring allows us to get rid of all the factory code altogether. While it's not a lot of code, every line of code has a maintainence cost. Until the advent of technologies like Spring, it was quite a bit of work to create a truly loosely-coupled architecture. At some point I would inevitably find myself creating an implementation in code, thereby creating coupling. Spring solves the problem once and for all, allowing you to properly program to interfaces rather than implementations. Loose coupling is one of those best practices that has more benefits down the line than initially meets the eye.&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;Simpler transaction code&lt;br /&gt;&lt;/h2&gt;&lt;p&gt;One of Spring's advantages is the reduction of the amount of code you have to write and maintain. In the case of transactions, you can get rid of all the code that obtains a Hibernate session, starts a transaction, and rolls it back for each type of error that's encountered. The code is instead replaced by (at a minimum) one annotation per DAO class. Another significant benefit is that Spring automatically rolls back transactions in the event of an exception, unless you tell it otherwise. It also takes care of closing the session. Both of these features reduce two common causes of bugs.&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;Less dependency on JBoss&lt;br /&gt;&lt;/h2&gt;&lt;p&gt;One of Spring's advantages is that you need not depend on an application server to provide EJB services. In fact, by using Spring and Hibernate, you can dispense altogether with the traditional EJB container. Also, we have certain partners that prefer we use a different app server than JBoss. While JBoss and other app servers have implemented Spring and Hibernate's pioneering ideas in the form of EJB3, we would rather take the initial step of switching to Spring before going all the way to EJB3.&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;How we did it&lt;br /&gt;&lt;/h2&gt;&lt;h3&gt;Slow start&lt;br /&gt;&lt;/h3&gt;&lt;p&gt;The first question was how to inject the Spring-instantiated singleton beans into our existing code without requiring major surgery. We wanted to ease into Spring, and not have to take a year to rewrite our entire code base. In my past experience, I added a context loader listener to the  web application, then accessing the web application context from a servlet. In our case, the server code is packaged in an EAR file, and the presentation layer is in separate application, packaged in a separate WAR file. We wanted to keep any knowledge that Spring is even being used out of the presentation layer code. So we ended up creating a bean lookup class called SpringContextLoader, which has static getters for all the beans. This was a good way to get started, because any call like:&lt;br /&gt;&lt;/p&gt;&lt;pre class="prettyprint"&gt;FooFactory.getFooInstance()&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;could be search-and-replaced with&lt;br /&gt;&lt;/p&gt;&lt;pre class="prettyprint"&gt;SpringContextLoader.getFooInstance()&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;But over time we changed as many of these as possible to use dependency injection. The nice thing was that it didn't all have to be done at once.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;SpringContextLoader itself gets a handle to the Spring application context as follows:&lt;br /&gt;&lt;/p&gt;&lt;pre class="prettyprint"&gt;package com.my.server.spring;&lt;br /&gt;&lt;br /&gt;public class SpringContextLoader&lt;br /&gt;{&lt;br /&gt; private static ApplicationContext ctx;&lt;br /&gt; public static ApplicationContext getApplicationContext()&lt;br /&gt; {&lt;br /&gt;   if (ctx == null)&lt;br /&gt;   {&lt;br /&gt;  ctx = new ClassPathXmlApplicationContext(&lt;br /&gt;   "com/my/server/spring/applicationContext.xml");&lt;br /&gt;   }&lt;br /&gt;   return ctx;&lt;br /&gt; }&lt;br /&gt; ...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;All that is required is to colocate the Spring configuration file, applicationContext.xml, next to  SpringContextLoader.java. The webapp didn't need to even be told that Spring was being used. In our case, all our services are started by a JMX bean, so we changed the first line of the "start" method of this bean to call:&lt;br /&gt;&lt;/p&gt;&lt;pre class="prettyprint"&gt; SpringContextLoader.getApplicationContext()&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This takes care of instantiating the Spring  beans before they are used.&lt;br /&gt;&lt;/p&gt;&lt;p&gt; The GUI code also uses the SpringContextLoader to get at the beans that it needs. At some point I would like to replace this with EJB3 annotation-based injection.&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;Transactions&lt;br /&gt;&lt;/h3&gt;&lt;p&gt;I won't go over the contents of applicationContext.xml that relate to dependency injection, since it's covered so well in many other places. But I will do so when it comes to transactions.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;To move from coding Hibernate sessions directly to using Spring's Hibernate support and Spring transactions, this is the pattern we started with:&lt;br /&gt;&lt;/p&gt;&lt;pre class="prettyprint"&gt;public class FooDAOImpl implements FooDAO {&lt;br /&gt;   public void saveFoo(Foo foo) throws MyFooException {&lt;br /&gt;       try {&lt;br /&gt;           Context ctx = new InitialContext();&lt;br /&gt;           SessionFactory sessionFactory =&lt;br /&gt;           (SessionFactory) ctx.lookup("java:/hibernate/SessionFactory");&lt;br /&gt;           Session session = sessionFactory.openSession();&lt;br /&gt;           Transaction tx = session.beginTransaction();&lt;br /&gt;           fooDTO = new FooDTO(foo);&lt;br /&gt;           session.save(fooDTO);&lt;br /&gt;           tx.commit();&lt;br /&gt;       } catch (Exception e) {&lt;br /&gt;           tx.rollback();&lt;br /&gt;           throw MyFooException(e);&lt;br /&gt;       } finally {&lt;br /&gt;           if (session != null &amp;amp;&amp;amp; session.isOpen()) {&lt;br /&gt;           session.close();&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;This is the pattern we ended with:&lt;br /&gt;&lt;/p&gt;&lt;pre class="prettyprint"&gt;public class FooDAOImpl extends HibernateDaoSupport implements FooDAO {&lt;br /&gt;   @Transactional(rollbackFor={MyFooException.class})&lt;br /&gt;   public void saveFoo(Foo foo) throws MyFooException {&lt;br /&gt;       try {&lt;br /&gt;           FooDTO fooDTO = new FooDTO(foo);&lt;br /&gt;           getHibernateTemplate().save(fooDTO);&lt;br /&gt;       } catch (Exception e) {&lt;br /&gt;           throw MyFooException(e);&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;The application context&lt;br /&gt;&lt;/h3&gt;&lt;p&gt;As you can see, there's a lot less code.  To achieve it, here's what needed to go in applicationContext.xml. We obtain the session factory from JNDI, but in your case you may do it differently. The session factory also takes care of resolving all the hibernate mapping files (not shown):&lt;br /&gt;&lt;/p&gt;&lt;pre class="prettyprint"&gt;&amp;lt;beans xmlns="http://www.springframework.org/schema/beans"&lt;br /&gt; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt; xmlns:tx="http://www.springframework.org/schema/tx"&lt;br /&gt; xsi:schemaLocation="http://www.springframework.org/schema/beans&lt;br /&gt;        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd&lt;br /&gt;        http://www.springframework.org/schema/aop&lt;br /&gt;        http://www.springframework.org/schema/aop/spring-aop-2.0.xsd&lt;br /&gt;        http://www.springframework.org/schema/tx&lt;br /&gt;        http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Spring/Hibernate Session Factory (obtained from JDNI) --&amp;gt;&lt;br /&gt;&amp;lt;bean id="sessionFactory"&lt;br /&gt;class="org.springframework.jndi.JndiObjectFactoryBean"&amp;gt;&lt;br /&gt;   &amp;lt;property name="jndiName"&amp;gt;&lt;br /&gt;       &amp;lt;value&amp;gt;java:/hibernate/SessionFactory&amp;lt;/value&amp;gt;&lt;br /&gt;   &amp;lt;/property&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&amp;lt;!-- Spring/Hibernate Transaction Support (in our case, JTA provided by JBoss) --&amp;gt;&lt;br /&gt;&amp;lt;bean id="txManager"&lt;br /&gt;   class="org.springframework.transaction.jta.JtaTransactionManager"&amp;gt;&lt;br /&gt;       &amp;lt;property name="userTransactionName"&amp;gt;&lt;br /&gt;           &amp;lt;value&amp;gt;UserTransaction&amp;lt;/value&amp;gt;&lt;br /&gt;       &amp;lt;/property&amp;gt;&lt;br /&gt;       &amp;lt;property name="transactionManagerName" value="java:/TransactionManager"&amp;gt;&amp;lt;/property&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&amp;lt;!-- Spring/Hibernate Template --&amp;gt;&lt;br /&gt;&amp;lt;bean id="hibernateTemplate"&lt;br /&gt;   class="org.springframework.orm.hibernate3.HibernateTemplate"&amp;gt;&lt;br /&gt;   &amp;lt;property name="sessionFactory"&amp;gt;&lt;br /&gt;       &amp;lt;ref bean="sessionFactory" /&amp;gt;&lt;br /&gt;   &amp;lt;/property&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&amp;lt;!-- This is a bean post processor.&lt;br /&gt;It gets a chance to look at every bean in this file as it is created.&lt;br /&gt;If the bean contains the @Transactional annotation,&lt;br /&gt;a transactional proxy is automatically created to wrap it.&lt;br /&gt; --&amp;gt;&lt;br /&gt;&amp;lt;tx:annotation-driven transaction-manager="txManager" /&amp;gt;&lt;br /&gt; &lt;br /&gt;&amp;lt;!-- Bean definitions start here --&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Problems&lt;br /&gt;&lt;/h2&gt;&lt;h3&gt;Session was already closed&lt;br /&gt;&lt;/h3&gt;&lt;p&gt;We ran into the dreaded "org.hibernate.SessionException: Session was  already closed" quite a bit. The problem appears to happen when static singletons that were not instantiated by Spring attempted to access Spring beans during the initialization process.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;The solution to most of these was to delay all such accesses till after initialization occurred, typically by lazily accessing the beans the first time they are needed instead of in the singleton's constructors.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;But this did not solve all of the cases. The source of the rest turned out to be that we were mixing transaction managers. We were using the Hibernate transaction manager in Spring, while the rest of the code was using the JTA transaction manager that was configured in the JBoss setup. We changed the following:&lt;/p&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;bean id=&amp;quot;txManager&amp;quot;&lt;br /&gt; class=&amp;quot;org.springframework.orm.hibernate3.HibernateTransactionManager&amp;quot;&amp;gt;&lt;br /&gt; &amp;lt;property name=&amp;quot;sessionFactory&amp;quot; ref=&amp;quot;sessionFactory&amp;quot; /&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;To:&lt;/p&gt;&lt;pre class="prettyprint"&gt;&amp;lt;bean id=&amp;quot;txManager&amp;quot;&lt;br /&gt; class=&amp;quot;org.springframework.transaction.jta.JtaTransactionManager&amp;quot;&amp;gt;&lt;br /&gt; &amp;lt;property name=&amp;quot;userTransactionName&amp;quot;&amp;gt;&lt;br /&gt;   &amp;lt;value&amp;gt;UserTransaction&amp;lt;/value&amp;gt;&lt;br /&gt; &amp;lt;/property&amp;gt;&lt;br /&gt; &amp;lt;property name=&amp;quot;transactionManagerName&amp;quot; value=&amp;quot;java:/TransactionManager&amp;quot;&amp;gt;&amp;lt;/property&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;Proxy problems&lt;br /&gt;&lt;/h3&gt;&lt;p&gt;Another thing that took a while to understand was "org.springframework.beans.TypeMismatchException:  Failed to convert property value of type [$ProxyNN] to required type Foo". This happens because we are using AOP autoproxying: Spring creates a subclass at runtime called a proxy that contains the additional transactional code. It turns out there are two ways in which proxies can be created: JDK dynamic proxies, which works with interfaces, the other one utilizes CGLib and is based on concrete classes (you can read more on this &lt;a href="http://insufficientinformation.blogspot.com/2007/12/spring-dynamic-proxies-vs-cglib-proxies.html"&gt;here&lt;/a&gt;). Even though we include cglib.jar in our deployment, for some reason there are cases where Spring could not instantiate a proxied subclass of a concrete class like Foo. The solution was to create an interface IFoo, and change the code to operate on IFoo instead of Foo. This is better practice anyway and it solved the problem.&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;Could not unbind factory from JNDI&lt;br /&gt;&lt;/h3&gt;&lt;p&gt;Another type of exception we got was related to resolving a JNDI name when creating the Hibernate session factory:&lt;br /&gt;&lt;/p&gt;&lt;pre class="prettyprint"&gt;[SessionFactoryObjectFactory] Could not unbind factory from JNDI&lt;br /&gt; javax.naming.NoInitialContextException:&lt;br /&gt; Need to specify class name in environment or system property,&lt;br /&gt; or as an applet parameter, or in an application resource file:&lt;br /&gt; java.naming.factory.initial&lt;br /&gt; at javax.naming.spi.NamingManager.getInitialContext()&lt;/pre&gt;&lt;br /&gt;&lt;p&gt; It turned out to be an unnecessary attribute in our hibernate.cfg.xml file:&lt;br /&gt;&lt;/p&gt;&lt;pre class="prettyprint"&gt;&amp;lt;session-factory name="TestSessionFactory"&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Needed to be changed to:&lt;br /&gt;&lt;/p&gt;&lt;pre class="prettyprint"&gt;&amp;lt;session-factory&amp;gt; &lt;/pre&gt;&lt;br /&gt;&lt;p&gt;There is no need to specify a session factory in hibernate.cfg.xml since it's already supplied by Spring. &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-3785511418386868659?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=dTOuSXwhYeA:ag_fS3yWmD0:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=dTOuSXwhYeA:ag_fS3yWmD0:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=dTOuSXwhYeA:ag_fS3yWmD0:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=dTOuSXwhYeA:ag_fS3yWmD0:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/3785511418386868659/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=3785511418386868659" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/3785511418386868659?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/3785511418386868659?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2008/06/migration-to-spring.html" title="Migration to Spring" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total></entry><entry gd:etag="W/&quot;CEUGR387fip7ImA9WxdXE04.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-813030695794898196</id><published>2008-04-17T15:49:00.000-07:00</published><updated>2008-06-24T11:30:26.106-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T11:30:26.106-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="ajax" /><title>Spawning a thread for a lengthy operation in Wicket</title><content type="html">&lt;p&gt;Sometimes you need to do time-consuming tasks and for whatever reason, you cannot perform that operation using Ajax. I needed to support the upload of files that could potentially take a long time, and since the file upload field in Wicket doesn't support Ajax, it got me thinking of a generic way to do this. Note that although I applied this technique to a file upload, it can be used for anything.&lt;/p&gt;&lt;br /&gt;&lt;p&gt; What I came up with was a page that shows a message to the user that the lengthy operation is occurring (in the form of a spinner/wait icon and some text), and when the operation completes the spinner is hidden and the message changes. The lengthy operation itself happens in a separate thread.&lt;/p&gt;&lt;br /&gt;&lt;p&gt; If the user leaves the page while the operation is running and then returns, he will see the busy message, and the form fields are disabled. If he returns to the page after the operation completes, he sees the normal page, that is, he can start another operation if he likes. &lt;/p&gt;&lt;br /&gt;&lt;p&gt;This is a summary of what I needed this page to do: &lt;/p&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt; &lt;li&gt;Don't run the operation using Ajax; instead start a background thread &lt;/li&gt;&lt;br /&gt; &lt;li&gt;Allow users to leave the page, perform other tasks, then return to see if the operation has completed&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Display a spinner and a message during the operation, and use Ajax to hide the spinner and change the message when the operation completes&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Disable the form fields while the operation is running to prevent the user from running another operation in parallel  &lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;p&gt;The solution consists of the UploadPage.java that spawns the thread that performs the operation, and MySession.java that saves the state of the operation in the session:&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;UploadPage.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;package wicket.quickstart;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.InputStream;&lt;br /&gt;&lt;br /&gt;import org.apache.wicket.AttributeModifier;&lt;br /&gt;import org.apache.wicket.ajax.AjaxSelfUpdatingTimerBehavior;&lt;br /&gt;import org.apache.wicket.behavior.HeaderContributor;&lt;br /&gt;import org.apache.wicket.markup.html.WebMarkupContainer;&lt;br /&gt;import org.apache.wicket.markup.html.WebPage;&lt;br /&gt;import org.apache.wicket.markup.html.basic.Label;&lt;br /&gt;import org.apache.wicket.markup.html.form.Button;&lt;br /&gt;import org.apache.wicket.markup.html.form.Form;&lt;br /&gt;import org.apache.wicket.markup.html.form.upload.FileUpload;&lt;br /&gt;import org.apache.wicket.markup.html.form.upload.FileUploadField;&lt;br /&gt;import org.apache.wicket.model.AbstractReadOnlyModel;&lt;br /&gt;import org.apache.wicket.model.PropertyModel;&lt;br /&gt;import org.apache.wicket.util.time.Duration;&lt;br /&gt;&lt;br /&gt;public class UploadPage extends WebPage&lt;br /&gt;{&lt;br /&gt;  private static final long serialVersionUID = 4692700450356316897L;&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Transient because no need to serialize&lt;br /&gt;   */&lt;br /&gt;  private transient FileUpload uploadFile;&lt;br /&gt;&lt;br /&gt;  public UploadPage()&lt;br /&gt;  {&lt;br /&gt;    add(HeaderContributor.forCss(UploadPage.class, "UploadPage.css"));&lt;br /&gt;&lt;br /&gt;    // Reset the completion flag. It's used to display a message that is&lt;br /&gt;    // only displayed once in the status label, using ajax when the upload&lt;br /&gt;    // is complete. When the user visits the page again, we don't want to&lt;br /&gt;    // display this message.&lt;br /&gt;    getMySession().setUploadComplete(false);&lt;br /&gt;&lt;br /&gt;    final WebMarkupContainer spinner = new WebMarkupContainer("spinner");&lt;br /&gt;    // This AttributeModifier is used to hide the spinner when the upload is&lt;br /&gt;    // finished.&lt;br /&gt;    final AttributeModifier am = new AttributeModifier("style", true,&lt;br /&gt;        new AbstractReadOnlyModel()&lt;br /&gt;        {&lt;br /&gt;          private static final long serialVersionUID = 2013912742253160111L;&lt;br /&gt;&lt;br /&gt;          public Object getObject()&lt;br /&gt;          {&lt;br /&gt;            return (getMySession().isUploading()) ? ""&lt;br /&gt;                : "display:none";&lt;br /&gt;          }&lt;br /&gt;        });&lt;br /&gt;    spinner.add(am);&lt;br /&gt;    spinner.add(new AjaxSelfUpdatingTimerBehavior(Duration.seconds(5)));&lt;br /&gt;    add(spinner);&lt;br /&gt;&lt;br /&gt;    final Label status = new Label("uploadstatus",&lt;br /&gt;        new AbstractReadOnlyModel()&lt;br /&gt;        {&lt;br /&gt;          private static final long serialVersionUID = 938943178761943953L;&lt;br /&gt;&lt;br /&gt;          @Override&lt;br /&gt;          public Object getObject()&lt;br /&gt;          {&lt;br /&gt;            if (getMySession().isUploading())&lt;br /&gt;            {&lt;br /&gt;              return getString("uploading");&lt;br /&gt;            }&lt;br /&gt;            else if (getMySession().isUploadComplete())&lt;br /&gt;            {&lt;br /&gt;              return getString("uploadcomplete");&lt;br /&gt;            }&lt;br /&gt;            else&lt;br /&gt;            {&lt;br /&gt;              return "";&lt;br /&gt;            }&lt;br /&gt;          }&lt;br /&gt;        });&lt;br /&gt;    status.add(new AjaxSelfUpdatingTimerBehavior(Duration.seconds(5)));&lt;br /&gt;    add(status);&lt;br /&gt;&lt;br /&gt;    final Form myform = new Form("myform");&lt;br /&gt;&lt;br /&gt;    final FileUploadField uploadField = new FileUploadField("uploadFile",&lt;br /&gt;        new PropertyModel(this, "uploadFile"));&lt;br /&gt;    uploadField.setEnabled(!getMySession().isUploading());&lt;br /&gt;    myform.add(uploadField);&lt;br /&gt;&lt;br /&gt;    final Button cancel = new Button("cancel")&lt;br /&gt;    {&lt;br /&gt;      private static final long serialVersionUID = 691332069442892669L;&lt;br /&gt;&lt;br /&gt;      @Override&lt;br /&gt;      public void onSubmit()&lt;br /&gt;      {&lt;br /&gt;        setResponsePage(HomePage.class);&lt;br /&gt;      }&lt;br /&gt;    };&lt;br /&gt;    cancel.setEnabled(!getMySession().isUploading());&lt;br /&gt;    myform.add(cancel);&lt;br /&gt;&lt;br /&gt;    final Button ok = new Button("ok")&lt;br /&gt;    {&lt;br /&gt;      private static final long serialVersionUID = -590104379892310699L;&lt;br /&gt;&lt;br /&gt;      @Override&lt;br /&gt;      public void onSubmit()&lt;br /&gt;      {&lt;br /&gt;        if (getMySession().isUploading())&lt;br /&gt;          return;&lt;br /&gt;        // Start a thread that will continue running even if the user&lt;br /&gt;        // goes to another page.&lt;br /&gt;        final ImportThread it = new ImportThread(getMySession(),&lt;br /&gt;            uploadFile);&lt;br /&gt;        it.start();&lt;br /&gt;        // Refresh the page in order to disable the form field and&lt;br /&gt;        // buttons.&lt;br /&gt;        setResponsePage(UploadPage.class);&lt;br /&gt;      }&lt;br /&gt;    };&lt;br /&gt;    ok.setEnabled(!getMySession().isUploading());&lt;br /&gt;    myform.add(ok);&lt;br /&gt;&lt;br /&gt;    add(myform);&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public FileUpload getImportFile()&lt;br /&gt;  {&lt;br /&gt;    return uploadFile;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void setImportFile(FileUpload uploadFile)&lt;br /&gt;  {&lt;br /&gt;    this.uploadFile = uploadFile;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /** This class does the actual uploading */&lt;br /&gt;  private static class ImportThread extends Thread&lt;br /&gt;  {&lt;br /&gt;    private final MySession session;&lt;br /&gt;    private final FileUpload uploadFile;&lt;br /&gt;&lt;br /&gt;    public ImportThread(MySession session, FileUpload uploadFile)&lt;br /&gt;    {&lt;br /&gt;      this.session = session;&lt;br /&gt;      this.uploadFile = uploadFile;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @SuppressWarnings("static-access")&lt;br /&gt;    public void run()&lt;br /&gt;    {&lt;br /&gt;      session.setIsUploading(true);&lt;br /&gt;      try&lt;br /&gt;      {&lt;br /&gt;        @SuppressWarnings("unused")&lt;br /&gt;        final InputStream in = uploadFile.getInputStream();&lt;br /&gt;        // Do something with input stream here...&lt;br /&gt;        // Sleep to simulate time-consuming work&lt;br /&gt;        Thread.sleep(10000);&lt;br /&gt;        session.setUploadComplete(true);&lt;br /&gt;      }&lt;br /&gt;      catch (IOException e)&lt;br /&gt;      {&lt;br /&gt;        session.error(e.getMessage());&lt;br /&gt;      }&lt;br /&gt;      catch (InterruptedException e)&lt;br /&gt;      {&lt;br /&gt;        session.error(e.getMessage());&lt;br /&gt;      }&lt;br /&gt;      finally&lt;br /&gt;      {&lt;br /&gt;        session.setIsUploading(false);&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public MySession getMySession()&lt;br /&gt;  {&lt;br /&gt;    return (MySession) getSession();&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;UploadPage.html&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&lt;br /&gt;       xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en"&lt;br /&gt;       lang="en"&amp;gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt;     &amp;lt;p&amp;gt;&amp;lt;wicket:link&amp;gt;&amp;lt;a href="HomePage.html"&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/wicket:link&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt; &amp;lt;div class="spinner" wicket:id="spinner"&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&lt;br /&gt;     &amp;lt;div class="uploadstatus"&amp;gt;&lt;br /&gt;         &amp;lt;span wicket:id="uploadstatus"&amp;gt;[Importing]&amp;lt;/span&amp;gt;&lt;br /&gt;     &amp;lt;/div&amp;gt;&lt;br /&gt;     &amp;lt;div class="uploadform"&amp;gt;&lt;br /&gt;     &amp;lt;form wicket:id="myform"&amp;gt;&lt;br /&gt;       &amp;lt;table&amp;gt;&lt;br /&gt;       &amp;lt;tr&amp;gt;&lt;br /&gt;       &amp;lt;td&amp;gt;File:&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type="file" wicket:id="uploadFile" /&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;       &amp;lt;/tr&amp;gt;&lt;br /&gt;       &amp;lt;tr&amp;gt;&lt;br /&gt;       &amp;lt;td&amp;gt;&amp;lt;input type="submit" wicket:id="ok" value="OK" /&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;       &amp;lt;td&amp;gt;&amp;lt;input type="submit" wicket:id="cancel" value="Cancel" /&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;       &amp;lt;/tr&amp;gt;&lt;br /&gt;       &amp;lt;/table&amp;gt;&lt;br /&gt;     &amp;lt;/form&amp;gt;&lt;br /&gt;     &amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;UploadPage.css&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;div.spinner {&lt;br /&gt;   float: left;&lt;br /&gt;   margin-left: 15px;&lt;br /&gt;   margin-bottom: 15px;&lt;br /&gt;   padding-left: 16px;&lt;br /&gt;   background-image:url(/app/resources/org.apache.wicket.ajax.AbstractDefaultAjaxBehavior/indicator.gif);&lt;br /&gt;   background-repeat: no-repeat;&lt;br /&gt;   width: 16;&lt;br /&gt;   height: 16;&lt;br /&gt;   background-color: transparent;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;div.uploadstatus {&lt;br /&gt;   float: left;&lt;br /&gt;   margin-left: 20px;&lt;br /&gt;   margin-bottom: 15px;&lt;br /&gt;   width: 90%;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;div.uploadform {&lt;br /&gt;   float: left;&lt;br /&gt;   width: 100%;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;UploadPage.properties&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;uploading=Uploading file, please wait...&lt;br /&gt;uploadcomplete=File upload is complete.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;MySession.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;package wicket.quickstart;&lt;br /&gt;&lt;br /&gt;import org.apache.wicket.Request;&lt;br /&gt;import org.apache.wicket.protocol.http.WebSession;&lt;br /&gt;&lt;br /&gt;public final class MySession extends WebSession&lt;br /&gt;{&lt;br /&gt;  private static final long serialVersionUID = 159108722454986819L;&lt;br /&gt;&lt;br /&gt;  /** Tracks the status of the lengthy process of uploading. &lt;br /&gt;   * These are declared volatile to make sure the JVM writes the value&lt;br /&gt;   * of the flag from the ImportThread to the Wicket thread.&lt;br /&gt;   */&lt;br /&gt;  private volatile boolean uploading, uploadComplete;&lt;br /&gt;&lt;br /&gt;  protected MySession(Request req)&lt;br /&gt;  {&lt;br /&gt;    super(req);&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public boolean isUploading()&lt;br /&gt;  {&lt;br /&gt;    return uploading;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Set when the upload thread starts, and reset when the upload ends or&lt;br /&gt;   * fails.&lt;br /&gt;   */&lt;br /&gt;  public void setIsUploading(boolean uploading)&lt;br /&gt;  {&lt;br /&gt;    this.uploading = uploading;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public boolean isUploadComplete()&lt;br /&gt;  {&lt;br /&gt;    return uploadComplete;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Set when the upload thread succeeds, and reset when the upload page is&lt;br /&gt;   * reloaded.&lt;br /&gt;   */&lt;br /&gt;  public void setUploadComplete(boolean uploadComplete)&lt;br /&gt;  {&lt;br /&gt;    this.uploadComplete = uploadComplete;&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-813030695794898196?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=6J9BVAiKe7A:vWEXkQSbw2U:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=6J9BVAiKe7A:vWEXkQSbw2U:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=6J9BVAiKe7A:vWEXkQSbw2U:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=6J9BVAiKe7A:vWEXkQSbw2U:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/813030695794898196/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=813030695794898196" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/813030695794898196?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/813030695794898196?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2008/04/spawning-thread-for-lengthy-operation.html" title="Spawning a thread for a lengthy operation in Wicket" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total></entry><entry gd:etag="W/&quot;DEABQXY-eyp7ImA9WxdXE08.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-3648813210581395752</id><published>2008-03-19T16:33:00.000-07:00</published><updated>2008-06-24T09:59:10.853-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T09:59:10.853-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="ajax" /><title>An Ajax Button with an Overlay Div  and Wait Indicator</title><content type="html">&lt;p&gt;I needed an Ajax button that disables the web page and displays a spinner (a.k.a. wait indicator, working indicator,  wait icon, hourglass) while working. The disabling is done by superimposing a div over the entire visible part of the page that has a 50% alpha blending value. &lt;/p&gt;&lt;br /&gt;&lt;p&gt;This example uses existing Wicket 1.3.0, but is probably backwards compatible. It's been tested on Firefox and Internet Explorer.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;The code was inspired by the Veil component by Igor Vaynberg in &lt;a href="http://wicketstuff.org/confluence/display/STUFFWIKI/wicketstuff-minis"&gt;wicketstuff-minis&lt;/a&gt;. My goal was to make this as minimal as possible to provide a skeleton you can build upon. Igor's component is more general and more capable, but I needed a point solution. &lt;/p&gt;&lt;br /&gt;&lt;p&gt;First, an example of how to use it:&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;HomePage.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;package wicket.quickstart;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;import org.apache.wicket.ajax.AjaxRequestTarget;&lt;br /&gt;import org.apache.wicket.markup.html.WebPage;&lt;br /&gt;import org.apache.wicket.markup.html.basic.Label;&lt;br /&gt;import org.apache.wicket.markup.html.form.Form;&lt;br /&gt;import org.apache.wicket.model.Model;&lt;br /&gt;import org.apache.wicket.model.PropertyModel;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;public class HomePage extends WebPage&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt; private int counter;&lt;br /&gt;&lt;br /&gt; public HomePage()&lt;br /&gt; {&lt;br /&gt;  final Label counterLabel = new Label("counter", new PropertyModel(this,&lt;br /&gt;    "counter"));&lt;br /&gt;  counterLabel.setOutputMarkupId(true);&lt;br /&gt;  add(counterLabel);&lt;br /&gt;&lt;br /&gt;  final Form myform = new Form("myform");&lt;br /&gt;  myform.add(new MyAjaxButton("button", new Model("Test"), "mybody")&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt;   @Override&lt;br /&gt;   protected void onSubmit(AjaxRequestTarget target, Form form)&lt;br /&gt;   {&lt;br /&gt;    try&lt;br /&gt;    {&lt;br /&gt;     // Add some delay to demonstrate the effect&lt;br /&gt;     Thread.sleep(1000);&lt;br /&gt;    }&lt;br /&gt;    catch (InterruptedException e)&lt;br /&gt;    {&lt;br /&gt;     // Ignore&lt;br /&gt;    }&lt;br /&gt;    // Update the counter label&lt;br /&gt;    counter++;&lt;br /&gt;    target.addComponent(counterLabel);&lt;br /&gt;   }&lt;br /&gt;  });&lt;br /&gt;  add(myform);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public int getCounter()&lt;br /&gt; {&lt;br /&gt;  return counter;&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;HomePage.html&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&lt;br /&gt;xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en"&lt;br /&gt;lang="en"&amp;gt;&lt;br /&gt;  &amp;lt;head&amp;gt;&lt;br /&gt;    &amp;lt;title&amp;gt;Wicket Quickstart Archetype Homepage&amp;lt;/title&amp;gt;&lt;br /&gt;  &amp;lt;/head&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;body id="mybody"&amp;gt;&lt;br /&gt;      &amp;lt;strong&amp;gt;Wicket Quickstart Archetype Homepage&amp;lt;/strong&amp;gt;&lt;br /&gt;&lt;br /&gt;      &amp;lt;p&amp;gt;Counter: &lt;br /&gt;      &amp;lt;span wicket:id="counter"&amp;gt;counter value&amp;lt;/span&amp;gt;&lt;br /&gt;      &amp;lt;/p&amp;gt;&lt;br /&gt;&lt;br /&gt;      &amp;lt;form wicket:id="myform"&amp;gt;&lt;br /&gt;        &amp;lt;input type="submit" wicket:id="button" /&amp;gt;&lt;br /&gt;      &amp;lt;/form&amp;gt;&lt;br /&gt;  &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Now the code for MyAjaxButton. This is the class that implements the mask:&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;MyAjaxButton.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;package wicket.quickstart;&lt;br /&gt;&lt;br /&gt;import org.apache.wicket.ResourceReference;&lt;br /&gt;import org.apache.wicket.ajax.IAjaxCallDecorator;&lt;br /&gt;import org.apache.wicket.ajax.markup.html.form.AjaxButton;&lt;br /&gt;import org.apache.wicket.behavior.HeaderContributor;&lt;br /&gt;import org.apache.wicket.markup.html.resources.JavascriptResourceReference;&lt;br /&gt;import org.apache.wicket.model.IModel;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Superclass for all Ajax buttons that display a mask over the whole&lt;br /&gt; * page and a spinner on top of that.&lt;br /&gt; * &lt;br /&gt; * @author jsinai&lt;br /&gt; */&lt;br /&gt;public abstract class MyAjaxButton extends AjaxButton&lt;br /&gt;{&lt;br /&gt; private static final ResourceReference JS = new JavascriptResourceReference(&lt;br /&gt;   MyAjaxButton.class, "MyAjaxButton.js");&lt;br /&gt; private static final ResourceReference CSS = new ResourceReference(&lt;br /&gt;   MyAjaxButton.class, "MyAjaxButton.css");&lt;br /&gt;&lt;br /&gt; private final String markupId;&lt;br /&gt; &lt;br /&gt; public MyAjaxButton(String id, IModel m, String markupId)&lt;br /&gt; {&lt;br /&gt;  super(id, null);&lt;br /&gt;  this.setModel(m);&lt;br /&gt;  this.markupId = markupId;&lt;br /&gt;  add(HeaderContributor.forJavaScript(JS));&lt;br /&gt;  add(HeaderContributor.forCss(CSS));&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; /**&lt;br /&gt;  * The call decorator is what displays the mask and spinner&lt;br /&gt;  */&lt;br /&gt; @Override&lt;br /&gt; protected IAjaxCallDecorator getAjaxCallDecorator()&lt;br /&gt; {&lt;br /&gt;  return new IAjaxCallDecorator()&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt;   public CharSequence decorateScript(CharSequence script)&lt;br /&gt;   {&lt;br /&gt;    return "Mask.show('" + markupId + "');" + script;&lt;br /&gt;   }&lt;br /&gt;   public CharSequence decorateOnFailureScript(CharSequence script)&lt;br /&gt;   {&lt;br /&gt;    return "Mask.hide('" + markupId + "');" + script;&lt;br /&gt;   }&lt;br /&gt;   public CharSequence decorateOnSuccessScript(CharSequence script)&lt;br /&gt;   {&lt;br /&gt;    return "Mask.hide('" + markupId + "');" + script;&lt;br /&gt;   }&lt;br /&gt;  };&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;The Javascript for the button implements the masking behavior:&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;MyAjaxButton.js&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;/**&lt;br /&gt; Inspired by the Veil component by Igor Vaynberg in wicketstuff-minis&lt;br /&gt; http://wicketstuff.org/confluence/display/STUFFWIKI/wicketstuff-minis&lt;br /&gt; wicketstuff-minis is released under the Apache 2 License&lt;br /&gt; http://apache.org/licenses/LICENSE-2.0.html &lt;br /&gt;*/&lt;br /&gt;Mask = { };&lt;br /&gt; &lt;br /&gt;/**&lt;br /&gt; Shows a mask and a spinner over the element with the specified id&lt;br /&gt;*/&lt;br /&gt;Mask.show = function(targetId)&lt;br /&gt;{&lt;br /&gt;  var target=document.getElementById(targetId);&lt;br /&gt;  var mask=document.createElement("div");&lt;br /&gt;  mask.innerHTML="&amp;amp;nbsp;";&lt;br /&gt;  mask.className="wicket-mask";&lt;br /&gt;  mask.style.cursor="not-allowed";&lt;br /&gt;  mask.style.zIndex="5000";&lt;br /&gt;  mask.id="wicket_mask_"+targetId;&lt;br /&gt;  document.body.appendChild(mask);&lt;br /&gt;  Mask.offsetMask(mask);&lt;br /&gt;&lt;br /&gt;  var spinner=document.createElement("div");&lt;br /&gt;  spinner.innerHTML="&amp;amp;nbsp;";&lt;br /&gt;  spinner.className="wicket-spinner";&lt;br /&gt;  spinner.style.cursor="not-allowed";&lt;br /&gt;  spinner.style.zIndex="6000";&lt;br /&gt;  spinner.id="wicket_spinner_"+targetId;&lt;br /&gt;  document.body.appendChild(spinner);&lt;br /&gt;  Mask.centerSpinner(spinner);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; Hides the mask and spinner&lt;br /&gt;*/&lt;br /&gt;Mask.hide = function(targetId)&lt;br /&gt;{&lt;br /&gt;  var mask=document.getElementById("wicket_mask_"+targetId);&lt;br /&gt;  if (mask!=null) {&lt;br /&gt;   mask.style.display="none";&lt;br /&gt;   document.body.removeChild(mask);&lt;br /&gt;  }&lt;br /&gt;  var spinner=document.getElementById("wicket_spinner_"+targetId);&lt;br /&gt;  if (spinner!=null) {&lt;br /&gt;   spinner.style.display="none";&lt;br /&gt;   document.body.removeChild(spinner);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Places the spinner at the center of the viewport.&lt;br /&gt; */ &lt;br /&gt;Mask.centerSpinner = function(spinner)&lt;br /&gt;{&lt;br /&gt;  var width = document.body.clientWidth;&lt;br /&gt;  var height = document.body.clientHeight;&lt;br /&gt;  &lt;br /&gt;  var offsetX =   document.body.scrollLeft;&lt;br /&gt;  var offsetY =  document.body.scrollTop;&lt;br /&gt;  &lt;br /&gt;  var left = (width / 2) - 24 + offsetX;&lt;br /&gt;  var top = (height / 2) - 24 + offsetY;&lt;br /&gt;  &lt;br /&gt;  spinner.style.left = left + "px";&lt;br /&gt;  spinner.style.top = top + "px";&lt;br /&gt;}&lt;br /&gt; &lt;br /&gt;/**&lt;br /&gt; * Offsets the mask to the scroll position.&lt;br /&gt; */ &lt;br /&gt;Mask.offsetMask = function(mask)&lt;br /&gt;{&lt;br /&gt;  var offsetX =   document.body.scrollLeft;&lt;br /&gt;  var offsetY =  document.body.scrollTop;&lt;br /&gt;  &lt;br /&gt;  mask.style.left = offsetX + "px";&lt;br /&gt;  mask.style.top = offsetY + "px";&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;The CSS determines the alpha blending and size of the mask, and which spinner we are using:&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;MyAjaxButton.css&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;/**&lt;br /&gt; Inspired by the Veil component by Igor Vaynberg in wicketstuff-minis&lt;br /&gt; http://wicketstuff.org/confluence/display/STUFFWIKI/wicketstuff-minis&lt;br /&gt; wicketstuff-minis is released under the Apache 2 License&lt;br /&gt; http://apache.org/licenses/LICENSE-2.0.html &lt;br /&gt;*/&lt;br /&gt;div.wicket-mask {&lt;br /&gt; position:absolute;top:0;left:0;&lt;br /&gt; width:100%;height:100%;&lt;br /&gt; background:rgb(230,230,230);&lt;br /&gt; opacity:.50;&lt;br /&gt; filter:alpha(opacity=50);&lt;br /&gt; -moz-opacity:0.5;&lt;br /&gt; text-decoration:none;&lt;br /&gt;}&lt;br /&gt;div.wicket-spinner {&lt;br /&gt; position:absolute;&lt;br /&gt; background-image:url(/app/resources/org.apache.wicket.ajax.AbstractDefaultAjaxBehavior/indicator.gif);&lt;br /&gt; background-repeat: no-repeat;&lt;br /&gt; width: 16;&lt;br /&gt; height: 16;&lt;br /&gt; background-color: transparent;&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-3648813210581395752?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=3m2QtuXsucI:hHiFRhUN9ek:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=3m2QtuXsucI:hHiFRhUN9ek:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=3m2QtuXsucI:hHiFRhUN9ek:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=3m2QtuXsucI:hHiFRhUN9ek:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/3648813210581395752/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=3648813210581395752" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/3648813210581395752?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/3648813210581395752?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2008/03/ajax-button-with-overlay-div-and-wait.html" title="An Ajax Button with an Overlay Div  and Wait Indicator" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total></entry><entry gd:etag="W/&quot;DEIAQ3g7eCp7ImA9WxdXE08.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-1936614422154587225</id><published>2008-02-05T12:39:00.000-08:00</published><updated>2008-06-24T09:55:42.600-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T09:55:42.600-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="internationalization" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="i18n" /><title>A simple context-sensitive help system using Wicket (redux)</title><content type="html">In &lt;a href="http://javathoughts.capesugarbird.com/2007/06/simple-context-sensitive-help-system.html"&gt;this post&lt;/a&gt;, I showed a simple context-sensitive online help system using Wicket. I wanted to enhance this capability to support these features:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Load arbitrary html files without requiring an associated Wicket class&lt;/li&gt;&lt;li&gt;Support anchors within html files&lt;/li&gt;&lt;li&gt;Supply the names of the html files and anchors in properties files&lt;/li&gt;&lt;li&gt;Support internationalised help&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Pop up the help in an external window, enabling the user to keep working in the main browser window&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Reuse the same help window every time the user clicks a help button&lt;/li&gt;&lt;li&gt;Use an image button for the help link&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Supply internationalized alt text for the help button&lt;/li&gt;&lt;li&gt;Automatically make the help button invisible if no help file name is supplied&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;I am using Wicket 1.3.0, but it should work for prior releases too.&lt;br /&gt; &lt;br /&gt;&lt;h2&gt;Example&lt;/h2&gt;&lt;br /&gt;First, an example of how to use it:&lt;br /&gt;&lt;h3&gt;MyWebPage.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt; add(new HelpButtonPanel("help"));&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;MyWebPage.html&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;   &amp;lt;div wicket:id="help" class="help"&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;MyWebPage.properties&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;# This translates to the URL /doc/MyWebPage.html#&lt;br /&gt;help.filename=MyWebPage&lt;br /&gt;help.anchor=&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;The Implementation&lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;HelpButtonPanel.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;import org.apache.commons.lang.StringUtils;&lt;br /&gt;import org.apache.wicket.AttributeModifier;&lt;br /&gt;import org.apache.wicket.ResourceReference;&lt;br /&gt;import org.apache.wicket.markup.html.image.Image;&lt;br /&gt;import org.apache.wicket.markup.html.link.ExternalLink;&lt;br /&gt;import org.apache.wicket.markup.html.link.PopupSettings;&lt;br /&gt;import org.apache.wicket.markup.html.panel.Panel;&lt;br /&gt;import org.apache.wicket.model.AbstractReadOnlyModel;&lt;br /&gt;import org.apache.wicket.model.IModel;&lt;br /&gt;import org.apache.wicket.model.StringResourceModel;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* A help button that pops up a separate window.&lt;br /&gt;*&lt;br /&gt;* The help is expected to be in the "doc" directory, a sibling to WEB-INF.&lt;br /&gt;*&lt;br /&gt;* The help filename is required, the anchor is optional. These strings go in a&lt;br /&gt;* property file. Do NOT provide the .html extension. Example:&lt;br /&gt;*&lt;br /&gt;* help.filename=foohelpfilename&lt;br /&gt;*&lt;br /&gt;* help.anchor=foohelpanchorname&lt;br /&gt;*&lt;br /&gt;*/&lt;br /&gt;public class HelpButtonPanel extends Panel&lt;br /&gt;{&lt;br /&gt; private final StringResourceModel helpFilenameModel, anchorModel;&lt;br /&gt;&lt;br /&gt; /**&lt;br /&gt;  * Use this constructor for the main help button on a page. It reads the&lt;br /&gt;  * strings help.filename, help.anchor from the web page properties.&lt;br /&gt;  */&lt;br /&gt; public HelpButtonPanel(String id)&lt;br /&gt; {&lt;br /&gt;  this(id, null);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; /**&lt;br /&gt;  * Use this constructor for other help on a page. It reads the strings&lt;br /&gt;  * help.filename, help.anchor from the model you supply.&lt;br /&gt;  */&lt;br /&gt; public HelpButtonPanel(String id, IModel model)&lt;br /&gt; {&lt;br /&gt;  super(id);&lt;br /&gt;&lt;br /&gt;  if (model == null)&lt;br /&gt;   model = new WebPageModel();&lt;br /&gt;&lt;br /&gt;  // Read the strings "help.filename", "help.anchor" from the supplied&lt;br /&gt;  // property model&lt;br /&gt;  helpFilenameModel = new StringResourceModel("help.filename", this,&lt;br /&gt;    model);&lt;br /&gt;  anchorModel = new StringResourceModel("help.anchor", this, model);&lt;br /&gt;&lt;br /&gt;  // Create the link to pop up the help&lt;br /&gt;  final HelpLink helplink = new HelpLink("helplink", this,&lt;br /&gt;    helpFilenameModel, anchorModel);&lt;br /&gt;  add(helplink);&lt;br /&gt;&lt;br /&gt;  // The help link is an image&lt;br /&gt;  final Image img = new Image("helpimg", new ResourceReference(&lt;br /&gt;    ImageAnchor.class, "help.png"));&lt;br /&gt;  helplink.add(img);&lt;br /&gt;&lt;br /&gt;  // Alt text for the image&lt;br /&gt;  final StringResourceModel altModel = new StringResourceModel("help",&lt;br /&gt;    this, null);&lt;br /&gt;  img.add(new AttributeModifier("alt", altModel));&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; /** Hide the help button if no help file is supplied. */&lt;br /&gt; @Override&lt;br /&gt; protected void onBeforeRender()&lt;br /&gt; {&lt;br /&gt;  setVisible(!StringUtils.isEmpty(helpFilenameModel.getString()));&lt;br /&gt;  super.onBeforeRender();&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; /** A model that reads the help filename from the web page's properties file */&lt;br /&gt; private class WebPageModel extends AbstractReadOnlyModel&lt;br /&gt; {&lt;br /&gt;  private static final long serialVersionUID = 6407656031992546286L;&lt;br /&gt;&lt;br /&gt;  @Override&lt;br /&gt;  public Object getObject()&lt;br /&gt;  {&lt;br /&gt;   return getWebPage().getModelObject();&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; /** A link that pops up an external window containing the help text */&lt;br /&gt; private static class HelpLink extends ExternalLink&lt;br /&gt; {&lt;br /&gt;  private static final long serialVersionUID = 4409345256205400217L;&lt;br /&gt;&lt;br /&gt;  public HelpLink(String id, HelpButtonPanel parent,&lt;br /&gt;    IModel helpFilenameModel, IModel anchorModel)&lt;br /&gt;  {&lt;br /&gt;   // Format in HelpButtonPanel.properties:&lt;br /&gt;   // /doc/{filename}.html#{anchor}&lt;br /&gt;   super(id, new StringResourceModel("helplink.href", parent, parent&lt;br /&gt;     .getModel(), new Object[]&lt;br /&gt;   { helpFilenameModel, anchorModel }));&lt;br /&gt;&lt;br /&gt;   final PopupSettings p = new PopupSettings(PopupSettings.RESIZABLE&lt;br /&gt;     | PopupSettings.SCROLLBARS);&lt;br /&gt;   p.setWidth(400).setHeight(400).setTop(23);&lt;br /&gt;   // Set the window name so we can reuse the same window instance.&lt;br /&gt;   p.setWindowName("HelpWindow");&lt;br /&gt;   setPopupSettings(p);&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There is a reference above to ImageAnchor. This is a technique I use to load images from a common location. See &lt;a href="http://javathoughts.capesugarbird.com/2007/06/colocating-images-with-wicket.html"&gt;this post&lt;/a&gt; for where I blogged about it.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;HelpButtonPanel.html&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;html xmlns:wicket&amp;gt;&lt;br /&gt;&amp;lt;wicket:panel&amp;gt;&lt;br /&gt;   &amp;lt;a wicket:id="helplink"&amp;gt;&lt;br /&gt;     &amp;lt;img wicket:id="helpimg" alt="Help" border="0" /&amp;gt;&lt;br /&gt;   &amp;lt;/a&amp;gt;&lt;br /&gt;&amp;lt;/wicket:panel&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;At the beginning of this post I said this solution supports internationalised help. How does it do this? Well, since the names of the help files are pulled from properties files, for which there can be one for each locale, the names of the help files can be different for each locale. An enhancement would be to automatically look for all the variations of the filename using locale, style and variation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-1936614422154587225?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=Fs62C64zTSI:yRxDpikzf9E:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=Fs62C64zTSI:yRxDpikzf9E:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=Fs62C64zTSI:yRxDpikzf9E:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=Fs62C64zTSI:yRxDpikzf9E:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/1936614422154587225/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=1936614422154587225" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/1936614422154587225?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/1936614422154587225?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2008/02/simple-context-sensitive-help-system.html" title="A simple context-sensitive help system using Wicket (redux)" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total></entry><entry gd:etag="W/&quot;A04ER30-eSp7ImA9WxZbFU4.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-8505778645398665457</id><published>2008-01-11T14:46:00.000-08:00</published><updated>2008-04-18T10:31:46.351-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-18T10:31:46.351-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tapestry" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="applet" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="swing" /><category scheme="http://www.blogger.com/atom/ns#" term="ajax" /><category scheme="http://www.blogger.com/atom/ns#" term="j2ee" /><category scheme="http://www.blogger.com/atom/ns#" term="gui" /><category scheme="http://www.blogger.com/atom/ns#" term="struts" /><category scheme="http://www.blogger.com/atom/ns#" term="php" /><category scheme="http://www.blogger.com/atom/ns#" term="maven" /><category scheme="http://www.blogger.com/atom/ns#" term="jsf" /><title>A Year of Wicket</title><content type="html">Update: screenshots of this product can be seen at &lt;a href="http://www.redbooks.ibm.com/redbooks/SG247555/8-1-3.htm"&gt;IBM Redbooks&lt;/a&gt;. Note: there are many more screens in the application than are shown here. There's quite a bit of Ajax used that isn't evident from the screenshots, such as for page navigation and tabs.&lt;br /&gt;&lt;br /&gt;I've been working with Wicket for almost a year. We've just released our first product that uses Wicket for the user interface, and so it seems like a good time to take stock. Unfortunately, it's not a public site, it's an installable enterprise product, so I can't show it to you. If you don't want to read further, here's the executive summary: Wicket rocks!&lt;br /&gt;&lt;br /&gt;I was hired as the GUI Architect for this project. I came to it with many years of GUI experience, mostly using Swing, but without a lot of web development experience.&lt;br /&gt;&lt;br /&gt;Because of my Java and Swing background, I was drawn to Wicket. It maps fairly closely to the Swing model of development. So does GWT, but when I evaluated it, it seemed so different from other J2EE frameworks that I felt it was a step too far. No HTML, and no WAR files, for example. This made my colleagues nervous, who were used to Struts and PHP. Me too, as a matter of fact.&lt;br /&gt;&lt;br /&gt;I had done some pretty serious prototyping for another project with Tapestry, and there were certain things I liked, like runtime bytecode generation. But the learning curve was pretty steep. At one point I needed to create a custom component, and to do so I needed to learn about engine services and other arcane things that I felt made the process too hard. By contrast, custom components are Wicket's bread and butter, and they are very easy to build.&lt;br /&gt;&lt;br /&gt;I also took a close look at JSF. It seemed overly complex to me, and not much of a departure from the Struts era. It came across as a technology designed by committee, with the combination of several complementary libraries required to get the job done, and there are still too many configuration files.&lt;br /&gt;&lt;br /&gt;So we decided to use Wicket. Luckily, my management and colleagues were open-minded enough to give it a try. The application was a management console for an enterprise application. It did not require high user traffic, so were weren't concerned about what people talk about as the biggest issue with Wicket, namely scalability. We certainly have had no performance problems ourselves.&lt;br /&gt;&lt;br /&gt;Right out of the gate I was able to create a framework with Wicket 1.2.6 that my colleagues could fill out, consisting of a navigation tree, a base page, database-driven tables, graphic buttons, page headers, tabbed panels, and a CSS-based look and feel.&lt;br /&gt;&lt;br /&gt;One of Wicket's advantages is the strict separation of design from behavior, that is, HTML from code. While we did not have a web designer on the team who built the HTML (the developers did this), and therefore didn't get any mileage from the separation in that sense, we definitely gained from having all the behavior in Java code, because it gave us all the power of refactoring, compile-time error checking, and maximum reusability.&lt;br /&gt;&lt;br /&gt;What I mean by maximum reusability is that I could create a component with quite a bit of logic behind it, e.g. a button that disables itself based on various combinations of server state, and all it would take to add it to a page would be &lt;code&gt;&amp;lt;input type="submit" wicket:id="button" /&amp;gt;&lt;/code&gt; in the HTML, and &lt;code&gt;new MyButton("button")&lt;/code&gt; in the Java code. In other words, it looks no different than adding an ordinary button. I could then change the behavior of MyButton as needed without affecting the HTML. And of course, there are no XML files to worry about keeping up to date, like struts-config.xml.&lt;br /&gt;&lt;br /&gt;I made a big effort to encourage reusability on the team, which of course is one of the big reasons to use Wicket. So we made sure there was no cut-and-paste reuse, and instead if a component did not have a necessary feature, I spent the time to add that feature. This paid off in spades later in the project, since if a bug was found or the look and feel needed tweaking, it typically only needed a change in one place. The number of bugs related to errors in the Wicket code was tiny. All the bugs were about the business behavior, that is, misunderstandings between product management and engineering, which is where you want any bugs to be.&lt;br /&gt;&lt;br /&gt;We had some unusual requirements, for example, there were some legacy applets that we needed to support because we didn't have the time to convert them to Wicket. It turned out to be easy to get the applets to communicate with the server using Wicket. We also embedded the applet page in a zero-height frame to avoid reloading the applets with each page view. The web pages were displayed in another frame. Again, Wicket was able to support this.&lt;br /&gt;&lt;br /&gt;As a result of all this, we actually stayed ahead of our middle tier colleagues for much of the product cycle. I was very pleased with the result.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Observations&lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;Models&lt;/h3&gt;&lt;br /&gt;Anyone who learns Wicket will tell you that the key to mastering it is understanding how models work. Models are the "M" in "MVC", and all decent frameworks are architected this way. But Wicket takes it to its logical extreme, meaning that all data, including resources, should be obtained through models, and it takes a bit of getting used to.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Fragments&lt;/h3&gt;&lt;br /&gt;The most complex reusable component we built was a custom form. In our case, I wanted all our forms to look the same, using the same mix of basic form controls. So I wanted a way to build forms entirely in code. I achieved this by using Wicket fragments. These are like mini panels and are very useful.&lt;br /&gt;&lt;br /&gt;The result is that a client can create a very powerful form control using one line of code, e.g. a text field that has an attached label, marks the form as dirty using the onchange event, lays itself out in a pleasing way, can hide itself and its label dynamically (using Ajax), can disable itself based on session properties, and using a common CSS look and feel. Oh, and supports accessibility by using the label tag properly. No doubt I did it this way because of my Swing background, but it paid off for us. Interestingly, by the time it was done, it looked much like a GWT widget, but that's probably because GWT was also inspired by Swing.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Upgrade to Wicket 1.3&lt;/h3&gt;&lt;br /&gt;After our first release, we decided to upgrade, which I was nervous about, but it's always the best option when using an open source library so you can get support from the team and stay current with features and bug fixes. This turned out to be OK but not painless. Certainly it was a lot easier than going from Tapestry 4 to 5, which pretty much required a complete rewrite. The migration guide helped somewhat (it was incomplete and not very explanatory) but it still required a fair amount of work and investigation. One example: you now need to supply a MarkupProvider for fragments; I still don't know why, since it's not explained adequately anywhere. But we got it done without much code surgery, which shows that Wicket cares about backwards compatibility.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Ajax conversion&lt;/h3&gt;&lt;br /&gt;After we upgraded to Wicket 1.3, we decided the time had come to convert as much of our interface to use Ajax as possible in order to make the user experience more interactive and up to date. This is where Wicket really shone, but was also where we ran into some difficulties. It was surprisingly easy to Ajaxify the paging tables and to create lazy loading panels (panels that fill themselves lazily in when loading a page), a little less so to Ajaxify tabbed panels, and not easy to figure out how to create an Ajax button that pops up a confirmation (yes/no) dialog before proceeding with the submit action. The latter is because for someone like myself, who is much more at home with Java than Javascript, finding out how to do anything out of the ordinary with Wicket and Ajax is painful. This is one area where the docs and examples can be improved, particularly with so many applications going Ajax. In the end, though, we were very pleased with how little code change it required to the application as a whole.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Separation between presentation and business logic&lt;/h3&gt;&lt;br /&gt;One of the criteria in my choice of Wicket was, does it promote good separation between presentation and business logic? Our experience says yes. We have a well-defined API layer that the middleware provides, and the Wicket code makes calls to it (exclusively from Java code) at exactly the right level of abstraction. For example, when you add an employee to the employees table, you would do it in the onSubmit() method of the Add Employee button. Furthermore, you handle exceptions in the same place, which 90% of the time, add an error message to the feedback panel on the same page. It ends up being a small amount of localized code, all in the same place.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Resources and Localization&lt;/h3&gt;&lt;br /&gt;Wicket has a nice model whereby it looks for resource strings in the properties file for the component in question, and if it doesn't find it there, it walks up the component hierarchy to find it. This worked well for us, but sometimes it can be difficult to work out where Wicket is trying to find a resource. Some people prefer to put all strings in one file; Wicket supports this too, but I feel it's easier to tame resource string proliferation if the strings are closest to where they are used.&lt;br /&gt;&lt;br /&gt;We made use of the "style" session attribute for branding purposes. The style is intended to "skin" your application but it works well for branding too. It works by adding another suffix to the filename of a properties file that it's looking for.&lt;br /&gt;&lt;br /&gt;One of the things that could be improved is the explanation of resource-related APIs. For example I still don't know when I should use &lt;code&gt;component.getString(), ResourceReference, or StringResourceModel&lt;/code&gt;. As with most mature frameworks, over time the number of APIs has proliferated, and sometimes it's hard to keep them straight. This is a pity given Wicket's very clear mission of simplicity.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Wicket Bench&lt;/h3&gt;&lt;br /&gt;We ended up using a free Eclipse plugin called Wicket Bench, which is surprisingly useful. When you open a Wicket page (a Java class), it looks for other files with the same name but different suffixes (e.g. html, css, properties) and loads them in different tabs in the same view, so it's easy to switch between them.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Maven&lt;/h3&gt;&lt;br /&gt;When we upgraded to Wicket 1.3 we decided to switch from Ant to Maven for our builds, with mixed results. First of all, the middleware team didn't want to switch to Maven, and Maven doesn't play too well in a heterogeneous environment. Second, I found I had to add Ant tasks to Maven to do simple things like deploying my WAR file to JBoss. It blows me away that Maven doesn't have a simple copy task built in.&lt;br /&gt;&lt;br /&gt;On the plus side, it was trivial to upgrade from Wicket 1.3 BETA, to RC, to GA by just changing the pom.xml file. And the Maven integration with Eclipse is very nice.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Links&lt;/h3&gt;&lt;br /&gt;Tip: make sure you create links lazily. That is, don't create the web page that is the destination of the link until onClick() is called. This was our biggest performance bottleneck. Fortunately, Wicket 1.3 has deprecated the constructor that takes a WebPage and so it encourages you to do the right thing.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Data Tables&lt;/h3&gt;&lt;br /&gt;We've found there tends to be a proliferation of the trio of classes that implements a paging data table, namely DataProvider, DataPanel and DataView, along with their supporting properties files. Keeping them in sync can be a pain. It would be nice if there was a single class in the API that implements the common case where all three are used once to implement one table, i.e. where there's no reuse of a DataProvider for other tables.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Things I'd love to see&lt;/h3&gt;&lt;br /&gt;I can't wait for the release that was going to be Wicket 2.0, namely a reorganization of the API to provide better error messages and support for generics. One of the hardest things to deal with is the obscurity of error messages. In this one sense I preferred Tapestry.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Summary&lt;/h3&gt;&lt;br /&gt;I wholeheartedly recommend Wicket!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-8505778645398665457?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=PONxICfnFas:aHV8i9OdPPM:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=PONxICfnFas:aHV8i9OdPPM:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=PONxICfnFas:aHV8i9OdPPM:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=PONxICfnFas:aHV8i9OdPPM:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/8505778645398665457/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=8505778645398665457" title="14 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/8505778645398665457?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/8505778645398665457?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2008/01/year-of-wicket.html" title="A Year of Wicket" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">14</thr:total></entry><entry gd:etag="W/&quot;DE8AR3Y6eip7ImA9WxdXE08.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-3529214076430613491</id><published>2008-01-11T13:51:00.000-08:00</published><updated>2008-06-24T10:00:46.812-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T10:00:46.812-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="ajax" /><title>A Wicket 1.3 Ajax button with a confirmation dialog</title><content type="html">&lt;p&gt;This example is exactly the kind of thing I like to post: a small example that solves a general problem. In this case it's how to add a confirmation dialog to a Wicket Ajax button. This is harder than it needs to be because it's one of the few areas in Wicket that I feel is not adequately documented (the area in question being marrying Javascript with Wicket). My use case is the usual deletion warning: "Are you sure you want to delete?". This might work for Wicket 1.2.6, but I've only tested it on 1.3.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;The solution is to add an AjaxCallDecorator to your button, as follows:&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;MyAjaxDeleteButton.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;public class MyAjaxDeleteButton extends IndicatingAjaxButton&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = -8294444630452310166L;&lt;br /&gt;&lt;br /&gt; private final IModel confirm;&lt;br /&gt;&lt;br /&gt; public MyAjaxDeleteButton(String id, IModel confirm, IModel label)&lt;br /&gt; {&lt;br /&gt;  super(id, null);&lt;br /&gt;  this.setModel(label);&lt;br /&gt;  this.confirm = confirm;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; @Override&lt;br /&gt; protected IAjaxCallDecorator getAjaxCallDecorator()&lt;br /&gt; {&lt;br /&gt;  return new AjaxPreprocessingCallDecorator(super.getAjaxCallDecorator())&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = 7495281332320552876L;&lt;br /&gt;&lt;br /&gt;   @Override&lt;br /&gt;   public CharSequence preDecorateScript(CharSequence script)&lt;br /&gt;   {&lt;br /&gt;    return "if(!confirm('" + confirm.getObject()&lt;br /&gt;      + "')) return false;" + script;&lt;br /&gt;   }&lt;br /&gt;  };&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Note that the confirmation message is passed in as a model. This is because, if you use a localized string, and you look it up using Localizer.getString() in the constructor of your page, Wicket will complain because it can't properly navigate the component hierarchy to resolve localized properties until after all components have been constructed. If you use a model, you won't have this problem.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Example of how to use it:&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;MyDataPanel.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt; private class DeleteButton extends MyAjaxDeleteButton&lt;br /&gt; {&lt;br /&gt;  private static final long serialVersionUID = 4966354164332401574L;&lt;br /&gt;&lt;br /&gt;  private DeleteButton()&lt;br /&gt;  {&lt;br /&gt;   // MyDataPanel.properties contains:&lt;br /&gt;   // confirmdelete=Are you sure you want to delete?&lt;br /&gt;   // delete=Delete&lt;br /&gt;   super("delete", new StringResourceModel("confirmdelete", MyDataPanel.this,&lt;br /&gt;     null), new StringResourceModel("delete", MyDataPanel.this,&lt;br /&gt;     null)); &lt;br /&gt;  }&lt;br /&gt;  @Override&lt;br /&gt;  protected void onSubmit(AjaxRequestTarget target, Form form)&lt;br /&gt;  {&lt;br /&gt;   // Update the feedback panel&lt;br /&gt;   final MyBasePage page = (MyBasePage) this.getWebPage();&lt;br /&gt;   target.addComponent(page.getFeedback());&lt;br /&gt;   &lt;br /&gt;   // Do the actual deletion here...&lt;br /&gt;&lt;br /&gt;   // Update the data panel&lt;br /&gt;   target.addComponent(MyDataPanel.this);&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Note that I'm adding my feedback panel to the Ajax target. This way, any errors will be shown as well. This does depend on calling &lt;code&gt;feedback.setOutputMarkupId(true)&lt;/code&gt; in your base page.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-3529214076430613491?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=xG4bs5inIUk:vo8WTJiNZBs:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=xG4bs5inIUk:vo8WTJiNZBs:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=xG4bs5inIUk:vo8WTJiNZBs:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=xG4bs5inIUk:vo8WTJiNZBs:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/3529214076430613491/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=3529214076430613491" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/3529214076430613491?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/3529214076430613491?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2008/01/wicket-13-ajax-button-with-confirmation.html" title="A Wicket 1.3 Ajax button with a confirmation dialog" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total></entry><entry gd:etag="W/&quot;DE4ARnwyfSp7ImA9WxdXE08.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-8599336593093909903</id><published>2007-12-19T10:03:00.000-08:00</published><updated>2008-06-24T10:02:27.295-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T10:02:27.295-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="suckerfish" /><title>Suckerfish dropdowns for Wicket (redux)</title><content type="html">I have contributed the code from my &lt;a href="http://javathoughts.capesugarbird.com/2007/11/suckerfish-dropdowns-for-wicket.html"&gt;original post&lt;/a&gt; to &lt;a href="http://wicketstuff.org"&gt;Wicket Stuff&lt;/a&gt; as the projects &lt;a href="https://wicket-stuff.svn.sourceforge.net/svnroot/wicket-stuff/trunk/wicketstuff-suckerfish"&gt;wicketstuff-suckerfish&lt;/a&gt; and &lt;a href="https://wicket-stuff.svn.sourceforge.net/svnroot/wicket-stuff/trunk/wicketstuff-suckerfish-examples"&gt;wicketstuff-suckerfish-examples&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I've made some improvements to the original code. Dropdowns are built hierarchically from Wicket Fragments so in theory there's no limit to how many levels deep you can go; and associating a link with any menu item is optional, so for example you can have a top-level menu bar with no links, just labels. I've tested it with IE6, IE7 and FF2.&lt;br /&gt;&lt;br /&gt;This project shows how useful Wicket Fragments can be. My original post didn't use fragments and was limited to one level of dropdowns. Using fragments kept the code short and allows an unlimited number of levels of dropdowns (although the CSS will need to be improved to go beyond two levels).&lt;br /&gt;&lt;br /&gt;An example to show the second improvement (a top-level menu bar with no links, just labels, taken from the code in &lt;a href="https://wicket-stuff.svn.sourceforge.net/svnroot/wicket-stuff/trunk/wicketstuff-suckerfish-examples"&gt;wicketstuff-suckerfish-examples&lt;/a&gt;):&lt;br /&gt;&lt;pre class="prettyprint"&gt;// Create the menubar&lt;br /&gt;final SuckerfishMenuPanel mb = new SuckerfishMenuPanel("menuBar");&lt;br /&gt;add(mb);&lt;br /&gt;&lt;br /&gt;// Create a menu. Clicking on the menu itself will take you to the home page&lt;br /&gt;final SuckerfishMenuPanel.MenuItem mi = new SuckerfishMenuPanel.MenuItem(&lt;br /&gt;  new BookmarkablePageLink(SuckerfishMenuPanel.LINK_ID,&lt;br /&gt;    HomePage.class), "Home");&lt;br /&gt;mb.addMenu(mi);&lt;br /&gt;&lt;br /&gt;// Create a second menu without a link to the home page&lt;br /&gt;final SuckerfishMenuPanel.MenuItem mi2 = new SuckerfishMenuPanel.MenuItem(&lt;br /&gt;  "Home");&lt;br /&gt;mb.addMenu(mi2);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Discussion of the code (from &lt;a href="https://wicket-stuff.svn.sourceforge.net/svnroot/wicket-stuff/trunk/wicketstuff-suckerfish"&gt;wicketstuff-suckerfish&lt;/a&gt;):&lt;br /&gt;&lt;br /&gt;From SuckerfishMenuPanel.html:&lt;br /&gt;The menu bar is an unordered list of menu item fragments:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;ul id="nav"&amp;gt;&lt;br /&gt;  &amp;lt;li id="topmenuitems"&amp;gt;&lt;br /&gt; &amp;lt;span id="menuitemfragment"&amp;gt;link&amp;lt;/span&amp;gt;&lt;br /&gt;  &amp;lt;/li&amp;gt;&lt;br /&gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;A menu item fragment contains a link fragment and an unordered list of menu item fragments:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;wicket:fragment id="MENUITEMFRAGMENT"&amp;gt;&lt;br /&gt;    &amp;lt;span id="linkfragment"&amp;gt;link&amp;lt;/span&amp;gt;&lt;br /&gt;    &amp;lt;ul id="menuitemlist"&amp;gt;&lt;br /&gt;      &amp;lt;li id="menuitemlinks"&amp;gt;&lt;br /&gt;          &amp;lt;span id="menuitemfragment"&amp;gt;link&amp;lt;/span&amp;gt;&lt;br /&gt;      &amp;lt;/li&amp;gt;&lt;br /&gt;    &amp;lt;/ul&amp;gt;&lt;br /&gt;&amp;lt;/wicket:fragment&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;A "link fragment" can in fact be one of two types of fragments, because the link part is optional: one with a hyperlink, and one consisting just of text. The following shows the type with a hyperlink. It consists of an anchor that embeds a span:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;wicket:fragment id="LINKFRAGMENT"&amp;gt;&lt;br /&gt;    &amp;lt;a href="#" id="linkid" class="childmenu"&amp;gt;&lt;br /&gt;        &amp;lt;span id="linktext" class="childmenutext"&amp;gt;Link Text&amp;lt;/span&amp;gt;&lt;br /&gt;    &amp;lt;/a&amp;gt;&lt;br /&gt;&amp;lt;/wicket:fragment&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;A text fragment is a degenerate link fragment without the hyperlink:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;wicket:fragment id="TEXTFRAGMENT"&amp;gt;&lt;br /&gt;    &amp;lt;span id="linktext" class="childmenutext"&amp;gt;Link Text&amp;lt;/span&amp;gt;&lt;br /&gt;&amp;lt;/wicket:fragment&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This structure allows you to embed menu item fragments to any depth.&lt;br /&gt;&lt;br /&gt;Here's the code for MenuItemFragment. It takes a MenuItem as a constructor argument, and depending on whether a Link is provided, it constructs a LinkFragment or a TextFragment. It then recursively creates more MenuItemFragments by passing the list of children of the MenuItem to a SubMenuListView.&lt;br /&gt;&lt;br /&gt;One standard Wicket trick to note is the use of a WebMarkupContainer. This is used for the UL tag, for which there is no standard Wicket component. We need it so that we can optionally hide it if the UL has no children. You might ask, why construct it only to then hide it? That's the way Wicket works. If it's referred to in the html, it must be constructed.&lt;br /&gt;&lt;pre class="prettyprint"&gt;private final class MenuItemFragment extends Fragment&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = 0L;&lt;br /&gt;&lt;br /&gt; public MenuItemFragment(MenuItem menuItem)&lt;br /&gt; {&lt;br /&gt;  super("menuitemfragment", "MENUITEMFRAGMENT",&lt;br /&gt;    SuckerfishMenuPanel.this);&lt;br /&gt;  // Add the menu's label (hyperlinked if a link is provided)&lt;br /&gt;  if (menuItem.getLink() != null)&lt;br /&gt;  {&lt;br /&gt;   add(new LinkFragment(menuItem.getLink(), menuItem.getLabel()));&lt;br /&gt;  }&lt;br /&gt;  else&lt;br /&gt;  {&lt;br /&gt;   add(new TextFragment(menuItem.getLabel()));&lt;br /&gt;  }&lt;br /&gt;  final WebMarkupContainer menuitemul = new WebMarkupContainer(&lt;br /&gt;    "menuitemlist");&lt;br /&gt;  add(menuitemul);&lt;br /&gt;  // Hide the &amp;lt;ul&amp;gt; tag if there are no submenus&lt;br /&gt;  menuitemul.setVisible(menuItem.getChildren().size() &amp;gt; 0);&lt;br /&gt;  // Add the submenus&lt;br /&gt;  menuitemul.add(new SubMenuListView("menuitemlinks", menuItem&lt;br /&gt;    .getChildren()));&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-8599336593093909903?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=YyjBdnUuDzE:uyrKvUbh-fE:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=YyjBdnUuDzE:uyrKvUbh-fE:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=YyjBdnUuDzE:uyrKvUbh-fE:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=YyjBdnUuDzE:uyrKvUbh-fE:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/8599336593093909903/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=8599336593093909903" title="11 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/8599336593093909903?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/8599336593093909903?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2007/12/suckerfish-dropdowns-for-wicket-redux.html" title="Suckerfish dropdowns for Wicket (redux)" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">11</thr:total></entry><entry gd:etag="W/&quot;DUcBQnc-fip7ImA9WxdXE08.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-4390329566672928474</id><published>2007-12-04T12:13:00.001-08:00</published><updated>2008-06-24T10:04:13.956-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T10:04:13.956-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="branding" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="maven" /><title>Branding a Wicket application with Maven 2</title><content type="html">&lt;p&gt;This is a follow up to my &lt;a href="http://javathoughts.capesugarbird.com/2007/08/branding-wicket-application-take-2.html"&gt;post&lt;/a&gt; on how to brand a Wicket application using Ant filtering. This is an update on how to do the same thing with Maven. It's a repeat of that post, but with the Ant section changed to use Maven instead.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;The Style feature in Wicket is for skinning your application, which for a corporate site is another way of saying branding. Having set the style property in the session, it is then available to you anywhere in the application where you need to use it. In addition, the property file resolver automatically takes style into account. There is also some support built into ResourceReference to enable resolution of resources other than property files.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;First some examples of how to use this solution, then how to implement it.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;h2&gt;Simplest Example&lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;Index.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;add(new Label("page.title", getString("page.title")));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Index.html&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;span wicket:id="page.title"&amp;gt;Page Title Goes Here&amp;lt;/span&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Index_companya.properties&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;page.title=Welcome to Company A!&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Index_companyb.properties&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;page.title=Welcome to Company B!&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;There are two properties files, distinguished by "_brand", and Wicket automatically resolves them.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;An alternative to the above, showing how to do the same thing with one properties file:&lt;/h2&gt;&lt;br /&gt;&lt;p&gt;This is an alternative to creating separate properties files for each brand. The style property in the session gives you the flexibility to put all the branding strings in one property file. This can be useful if you have complex strings containing lots of variable interpolations, or if you want to minimize the number of properties files that need to be sent to a translator.&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;Index.properties&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;page.title=Welcome to ${pageTitleBrand}!&lt;br /&gt;page.title.companya=Company A&lt;br /&gt;page.title.companyb=Company B&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Index.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;add(new Label("page.title", new StringResourceModel("page.title", this, new Model(this))));&lt;br /&gt;...&lt;br /&gt;public String getPageTitleBrand()&lt;br /&gt;{&lt;br /&gt;return getString("page.title."+this.getSession().getStyle());&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Example of branding CSS (style sheets)&lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;Index.css&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;/* Image supplied by branded CSS */&lt;br /&gt;#top-section {&lt;br /&gt;position: relative;&lt;br /&gt;left: 0;&lt;br /&gt;top: 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Index._companya.css&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;#top-section {&lt;br /&gt;background-image: url("/app/images/banner_companya.gif");&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Index._companyb.css&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;#top-section {&lt;br /&gt;background-image: url("/app/images/banner_companyb.gif");&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Index.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;/* All CSS styles except for those that are branded */&lt;br /&gt;add(HeaderContributor.forCss(Index.class, "Index.css"));&lt;br /&gt;/* Branded CSS */&lt;br /&gt;add(HeaderContributor.forCss(new ResourceReference(Index.class,&lt;br /&gt;"Index.css", null, this.getSession().getStyle())));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Here we make use of Wicket's support for styles in ResourceReference. Of course, it would be nicer if Wicket's support for the resolution of CSS (and other resource file types) was as automatic as for property files. That is, if you use the constructor ResourceReference(Index.class, "Index.css") it should automatically look for Index_brand.css.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Branding Images&lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;FaviconLink.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;public class FaviconLink extends ExternalLink&lt;br /&gt;{&lt;br /&gt;public FaviconLink()&lt;br /&gt;{&lt;br /&gt;super("favicon", "/app/images/favicon_"+Session.get().getStyle()+".ico");&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Note that in this case we use &lt;code&gt;Session.get().getStyle()&lt;/code&gt; because ExternalLink does not have instance access to the session. Fortunately, it's easy enough to get at it.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;How it works&lt;/h2&gt;&lt;br /&gt;&lt;p&gt;A convenient way to initialize the the session with the brand is to set it in web.xml:&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;web.xml&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;&amp;lt;filter&amp;gt;&lt;br /&gt;&amp;lt;filter-name&amp;gt;myapp&amp;lt;/filter-name&amp;gt;&lt;br /&gt;&amp;lt;filter-class&amp;gt;&lt;br /&gt; org.apache.wicket.protocol.http.WicketFilter&lt;br /&gt;&amp;lt;/filter-class&amp;gt;&lt;br /&gt;&amp;lt;init-param&amp;gt;&lt;br /&gt;   &amp;lt;param-name&amp;gt;applicationClassName&amp;lt;/param-name&amp;gt;&lt;br /&gt;   &amp;lt;param-value&amp;gt;wicket.quickstart.QuickStartApplication&amp;lt;/param-value&amp;gt;&lt;br /&gt;&amp;lt;/init-param&amp;gt;&lt;br /&gt;&amp;lt;init-param&amp;gt;&lt;br /&gt;   &amp;lt;param-name&amp;gt;brand&amp;lt;/param-name&amp;gt;&lt;br /&gt;   &amp;lt;param-value&amp;gt;companya&amp;lt;/param-value&amp;gt;&lt;br /&gt; &amp;lt;/init-param&amp;gt;&lt;br /&gt;&amp;lt;/filter&amp;gt;&lt;br /&gt;...&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;QuickStartSession.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;protected QuickStartSession(final WebApplication application)&lt;br /&gt;{&lt;br /&gt;super(application);&lt;br /&gt;// Read brand from web.xml&lt;br /&gt;String brand = ((WebApplication) Application.get()).getWicketFilter()&lt;br /&gt;.getFilterConfig().getInitParameter("brand");&lt;br /&gt;if (brand == null)&lt;br /&gt;{&lt;br /&gt;brand = "companya";&lt;br /&gt;}&lt;br /&gt;this.setStyle(brand);&lt;br /&gt;}&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Now to use Maven filtering to set the brand. Put your pre-filtered web.xml in src/main/resources (or wherever you like, actually):&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;web.xml&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;  &amp;lt;init-param&amp;gt;&lt;br /&gt;    &amp;lt;param-name&amp;gt;brand&amp;lt;/param-name&amp;gt;&lt;br /&gt;    &amp;lt;param-value&amp;gt;${brand}&amp;lt;/param-value&amp;gt;&lt;br /&gt;  &amp;lt;/init-param&amp;gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;pom.xml&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;&amp;lt;build&amp;gt;&lt;br /&gt;  &amp;lt;resources&amp;gt;&lt;br /&gt;    &amp;lt;resource&amp;gt;&lt;br /&gt;      &amp;lt;!-- Copies web.xml to target/classes and filters ${brand} in the process --&amp;gt;&lt;br /&gt;      &amp;lt;filtering&amp;gt;true&amp;lt;/filtering&amp;gt;&lt;br /&gt;      &amp;lt;directory&amp;gt;src/main/resources&amp;lt;/directory&amp;gt;&lt;br /&gt;      &amp;lt;includes&amp;gt;&lt;br /&gt;        &amp;lt;include&amp;gt;**/web.xml&amp;lt;/include&amp;gt;&lt;br /&gt;      &amp;lt;/includes&amp;gt;&lt;br /&gt;    &amp;lt;/resource&amp;gt;&lt;br /&gt;  &amp;lt;/resources&amp;gt;&lt;br /&gt;  &amp;lt;plugin&amp;gt;&lt;br /&gt;    &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;&lt;br /&gt;    &amp;lt;artifactId&amp;gt;maven-war-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;    &amp;lt;configuration&amp;gt;      &lt;br /&gt;        &amp;lt;!-- Gets the filtered web.xml from target/classes --&amp;gt;&lt;br /&gt;        &amp;lt;webXml&amp;gt;target/classes/web.xml&amp;lt;/webXml&amp;gt;&lt;br /&gt;    &amp;lt;/configuration&amp;gt;&lt;br /&gt;  &amp;lt;/plugin&amp;gt;&lt;br /&gt;&amp;lt;/build&amp;gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;This will copy web.xml from src/main/resources to the location target/classes and in the process replace the branding token. Then the war target builds the WAR using the filtered web.xml.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-4390329566672928474?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=HaPpV5zf6Mc:3K6GsRB0AS4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=HaPpV5zf6Mc:3K6GsRB0AS4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=HaPpV5zf6Mc:3K6GsRB0AS4:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=HaPpV5zf6Mc:3K6GsRB0AS4:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/4390329566672928474/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=4390329566672928474" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/4390329566672928474?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/4390329566672928474?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2007/12/branding-wicket-application-with-maven.html" title="Branding a Wicket application with Maven 2" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total></entry><entry gd:etag="W/&quot;DUUBSXc7fip7ImA9WxdXE08.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-2812184469876191620</id><published>2007-11-30T11:03:00.000-08:00</published><updated>2008-06-24T10:07:38.906-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T10:07:38.906-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="ajax" /><title>An Ajax tabbed panel with lazy loading tabs and a wait indicator for Wicket</title><content type="html">&lt;p&gt;I needed an Ajax Tabbed Panel with tabs that load only when clicked and and a wait indicator (a.k.a. hourglass or wait cursor) when the tab is being loaded. This example just shows how to use existing Wicket 1.3 components for those who haven't used these components before.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;First, an example of how to use it:&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;MyTabPage.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;public class MyTabPage extends WebPage&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt; public MyTabPage()&lt;br /&gt; {&lt;br /&gt;  add(HeaderContributor.forCss(MyTabPage.class, "MyTabPage.css"));&lt;br /&gt;&lt;br /&gt;  final List&lt;MyTab&gt; tabs = new ArrayList&lt;MyTab&gt;();&lt;br /&gt;  &lt;br /&gt;  tabs.add(new MyTab("Tab One")&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = 1L;&lt;br /&gt;   @Override&lt;br /&gt;   public Panel createPanel()&lt;br /&gt;   {&lt;br /&gt;    return new TabOnePanel();&lt;br /&gt;   }&lt;br /&gt;  });&lt;br /&gt;  tabs.add(new MyTab("Tab Two")&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = 1L;&lt;br /&gt;   @Override&lt;br /&gt;   public Panel createPanel()&lt;br /&gt;   {&lt;br /&gt;    return new TabTwoPanel();&lt;br /&gt;   }&lt;br /&gt;  });&lt;br /&gt;  final MyTabbedPanel tabpanel = new MyTabbedPanel("tabpanel", tabs);&lt;br /&gt;  add(tabpanel);&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;MyTabPage.html&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&lt;br /&gt;  xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en"&lt;br /&gt;  lang="en"&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;body&amp;gt;&lt;br /&gt;   &amp;lt;div wicket:id="tabpanel"&amp;gt;[tabbed panel will be here]&amp;lt;/div&amp;gt;&lt;br /&gt; &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;The CSS for the project:&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;MyTabPage.css&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;div.mytabpanel div.tab-row{&lt;br /&gt;  float:left;&lt;br /&gt;  width: 100%;&lt;br /&gt;  border-bottom: 1px solid #3e6581;&lt;br /&gt;  background-color: #e6e6e6;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;div.mytabpanel div.tab-row ul {&lt;br /&gt;  margin:0;&lt;br /&gt;  list-style:none;&lt;br /&gt;}&lt;br /&gt;  &lt;br /&gt;div.mytabpanel div.tab-row li {&lt;br /&gt;  float:left;&lt;br /&gt;  width: 10%;&lt;br /&gt;}&lt;br /&gt;  &lt;br /&gt;div.mytabpanel div.tab-row a {&lt;br /&gt;  display:block;&lt;br /&gt;  text-decoration:none;&lt;br /&gt;  font-weight:bold;&lt;br /&gt;  color:#949494;&lt;br /&gt;}&lt;br /&gt;  &lt;br /&gt;div.mytabpanel div.tab-row li.selected a {&lt;br /&gt;  color:#333;&lt;br /&gt;}&lt;br /&gt; &lt;br /&gt;div.mytab {&lt;br /&gt;  padding: 5em;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;The tabbed panel class:&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;MyTabbedPanel.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;public class MyTabbedPanel extends Panel&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt; private final TabbedPanel tabbedPanel;&lt;br /&gt;&lt;br /&gt; public MyTabbedPanel(String id, List&lt;MyTab&gt; tabs)&lt;br /&gt; {&lt;br /&gt;  super(id);&lt;br /&gt;&lt;br /&gt;  tabbedPanel = new AjaxTabbedPanel("mytabpanel", tabs)&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = 1L;&lt;br /&gt;   // Override newLink to supply an IndicatingAjaxLink (wait cursor)&lt;br /&gt;   @Override&lt;br /&gt;   protected WebMarkupContainer newLink(String linkId, final int index)&lt;br /&gt;   {&lt;br /&gt;    final WebMarkupContainer c = new IndicatingAjaxLink(linkId)&lt;br /&gt;    {&lt;br /&gt;     private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt;     public void onClick(AjaxRequestTarget target)&lt;br /&gt;     {&lt;br /&gt;      setSelectedTab(index);&lt;br /&gt;      if (target != null)&lt;br /&gt;      {&lt;br /&gt;       target.addComponent(MyTabbedPanel.this);&lt;br /&gt;      }&lt;br /&gt;      onAjaxUpdate(target);&lt;br /&gt;     }&lt;br /&gt;    };&lt;br /&gt;    return c;&lt;br /&gt;   }&lt;br /&gt;  };&lt;br /&gt;  tabbedPanel.setOutputMarkupId(true);&lt;br /&gt;  MyTabbedPanel.this.setOutputMarkupId(true);&lt;br /&gt;  add(tabbedPanel);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public static class MyTab extends AbstractTab&lt;br /&gt; {&lt;br /&gt;  private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt;  private Panel p;&lt;br /&gt;&lt;br /&gt;  public MyTab(String label)&lt;br /&gt;  {&lt;br /&gt;   super(new Model(label));&lt;br /&gt;  }&lt;br /&gt;  @Override&lt;br /&gt;  public Panel getPanel(String panelId)&lt;br /&gt;  {&lt;br /&gt;   if (p == null)&lt;br /&gt;   {&lt;br /&gt;    // Lazily create the panel&lt;br /&gt;    p = createPanel();&lt;br /&gt;    if (!TabbedPanel.TAB_PANEL_ID.equals(p.getId()))&lt;br /&gt;    {&lt;br /&gt;     throw new IllegalArgumentException(&lt;br /&gt;       "Panel id must be TabbedPanel.TAB_PANEL_ID");&lt;br /&gt;    }&lt;br /&gt;    p.setOutputMarkupId(true);&lt;br /&gt;   }&lt;br /&gt;   return p;&lt;br /&gt;  }&lt;br /&gt;  protected Panel createPanel()&lt;br /&gt;  {&lt;br /&gt;   throw new IllegalArgumentException("Must provide a panel");&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;MyTabbedPanel.html&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&lt;br /&gt; xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en" lang="en"&amp;gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt;&amp;lt;wicket:panel&amp;gt;&lt;br /&gt; &amp;lt;div wicket:id="mytabpanel" class="mytabpanel"&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;/wicket:panel&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Finally, a simple panel displayed by a tab:&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;TabOnePanel.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;public class TabOnePanel extends Panel&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt; public TabOnePanel()&lt;br /&gt; {&lt;br /&gt;  super(TabbedPanel.TAB_PANEL_ID);&lt;br /&gt;&lt;br /&gt;  add(new Label("mylabel", "Tab One Contents"));&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;TabOnePanel.html&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&amp;gt;&lt;br /&gt;  &amp;lt;body&amp;gt;&lt;br /&gt;    &amp;lt;wicket:panel&amp;gt;&lt;br /&gt;      &amp;lt;div wicket:id="mylabel" class="mytab"&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt; &amp;lt;/wicket:panel&amp;gt;&lt;br /&gt;  &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-2812184469876191620?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=qLM4Z2MNdos:Ez89jZfRJyg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=qLM4Z2MNdos:Ez89jZfRJyg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=qLM4Z2MNdos:Ez89jZfRJyg:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=qLM4Z2MNdos:Ez89jZfRJyg:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/2812184469876191620/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=2812184469876191620" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/2812184469876191620?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/2812184469876191620?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2007/11/ajax-tabbed-panel-with-lazy-loading.html" title="An Ajax tabbed panel with lazy loading tabs and a wait indicator for Wicket" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total></entry><entry gd:etag="W/&quot;DUQHSX8yfSp7ImA9WxdXE08.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-5540685366108049045</id><published>2007-11-16T20:19:00.000-08:00</published><updated>2008-06-24T10:08:58.195-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T10:08:58.195-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="ajax" /><category scheme="http://www.blogger.com/atom/ns#" term="onbeforeunload" /><title>Warning on page exit for Wicket 1.2.x</title><content type="html">&lt;p&gt;I needed to warn the user if s/he leaves a form that hasn't been submitted. I was surprised that this requirement wasn't very commonly solved on the Net, and even less so for Wicket. There are more complicated solutions out there, but none of them was super simple. This is the simplest solution I could come up with.&lt;br /&gt;&lt;br /&gt;After researching it I found out that to make this warning work you need to trap the Javascript event called &lt;em&gt;onbeforeunload&lt;/em&gt;. This event is especially for this purpose and is supported by the major browsers. If you return a string from this event (typically "Are you sure you want to leave this page?"), the browser will display this string in a confirm dialog. If the user clicks OK, the page is unloaded and the form is submitted (or the link is followed), but if the user clicks Cancel, control returns to the page and the form is not submitted.&lt;br /&gt;&lt;/p&gt;My solution consists of one form and one short javascript file. First, an example of how to use it:&lt;br /&gt;&lt;h3&gt;WarnOnExitPage.java&lt;br /&gt;&lt;/h3&gt;&lt;pre class="prettyprint"&gt;public class WarnOnExitPage extends WebPage&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt; private String name;&lt;br /&gt; private String email;&lt;br /&gt;&lt;br /&gt; public WarnOnExitPage(final PageParameters parameters)&lt;br /&gt; {&lt;br /&gt;  final WarnOnExitForm form = new WarnOnExitForm("myform");&lt;br /&gt;&lt;br /&gt;  final TextField nameField = new TextField("namefield",&lt;br /&gt;    new PropertyModel(this, "name"));&lt;br /&gt;  // Note the use of a specialized addFormField method. This&lt;br /&gt;  // appends the necessary javascript.&lt;br /&gt;  form.addFormField(nameField);&lt;br /&gt;&lt;br /&gt;  final TextField emailField = new TextField("emailfield",&lt;br /&gt;    new PropertyModel(this, "email"));&lt;br /&gt;  form.addFormField(emailField);&lt;br /&gt;&lt;br /&gt;  // Note the use of a specialized addButton method. This&lt;br /&gt;  // appends the necessary javascript.&lt;br /&gt;  form.addButton(new Button("okbutton")&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt;   @Override&lt;br /&gt;   protected void onSubmit()&lt;br /&gt;   {&lt;br /&gt;    setResponsePage(HomePage.class);&lt;br /&gt;   }&lt;br /&gt;  });&lt;br /&gt;  add(form);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public String getName()&lt;br /&gt; {&lt;br /&gt;  return name;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public void setName(String name)&lt;br /&gt; {&lt;br /&gt;  this.name = name;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public String getEmail()&lt;br /&gt; {&lt;br /&gt;  return email;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public void setEmail(String email)&lt;br /&gt; {&lt;br /&gt;  this.email = email;&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;WarnOnExitPage.html&lt;br /&gt;&lt;/h3&gt;&lt;pre class="prettyprint"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&lt;br /&gt;  xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en"&lt;br /&gt;  lang="en"&amp;gt;&lt;br /&gt;&amp;lt;head&amp;gt;&lt;br /&gt; &amp;lt;title&amp;gt;Wicket Quickstart Archetype Homepage&amp;lt;/title&amp;gt;&lt;br /&gt;&amp;lt;/head&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;strong&amp;gt;Wicket Quickstart Archetype Homepage&amp;lt;/strong&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;form wicket:id="myform"&amp;gt;&lt;br /&gt;   &amp;lt;fieldset&amp;gt;&lt;br /&gt;     &amp;lt;table&amp;gt;&lt;br /&gt;       &amp;lt;tr&amp;gt;&lt;br /&gt;         &amp;lt;td&amp;gt;Name:&amp;lt;/td&amp;gt;&lt;br /&gt;&lt;br /&gt;         &amp;lt;td&amp;gt;&lt;br /&gt;           &amp;lt;input wicket:id="namefield" type="text" /&amp;gt;&lt;br /&gt;         &amp;lt;/td&amp;gt;&lt;br /&gt;       &amp;lt;/tr&amp;gt;&lt;br /&gt;&lt;br /&gt;       &amp;lt;tr&amp;gt;&lt;br /&gt;         &amp;lt;td&amp;gt;Email:&amp;lt;/td&amp;gt;&lt;br /&gt;&lt;br /&gt;         &amp;lt;td&amp;gt;&lt;br /&gt;           &amp;lt;input wicket:id="emailfield" type="text" /&amp;gt;&lt;br /&gt;         &amp;lt;/td&amp;gt;&lt;br /&gt;       &amp;lt;/tr&amp;gt;&lt;br /&gt;&lt;br /&gt;       &amp;lt;tr&amp;gt;&lt;br /&gt;         &amp;lt;td&amp;gt; &amp;lt;/td&amp;gt;&lt;br /&gt;&lt;br /&gt;         &amp;lt;td&amp;gt;&lt;br /&gt;           &amp;lt;input wicket:id="okbutton" type="submit" /&amp;gt;&lt;br /&gt;         &amp;lt;/td&amp;gt;&lt;br /&gt;       &amp;lt;/tr&amp;gt;&lt;br /&gt;     &amp;lt;/table&amp;gt;&lt;br /&gt;   &amp;lt;/fieldset&amp;gt;&lt;br /&gt; &amp;lt;/form&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;wicket:link&amp;gt;&lt;br /&gt; &amp;lt;a href="HomePage.html"&amp;gt;HomePage&amp;lt;/a&amp;gt;&lt;br /&gt; &amp;lt;/wicket:link&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Now the solution:&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;WarnOnExitForm.java&lt;br /&gt;&lt;/h3&gt;&lt;pre class="prettyprint"&gt;public class WarnOnExitForm extends Form&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt; private boolean firstTimeThru = true;&lt;br /&gt;&lt;br /&gt; public WarnOnExitForm(String id)&lt;br /&gt; {&lt;br /&gt;  super(id);&lt;br /&gt;  // Don't confirm when the user hits the enter key&lt;br /&gt;  add(new SimpleAttributeModifier("onsubmit", "dontConfirm();"));&lt;br /&gt;  // Turn off autocomplete because IE will not register an entry&lt;br /&gt;  // as a change if it came from the autocomplete cache&lt;br /&gt;  add(new SimpleAttributeModifier("autocomplete", "off"));&lt;br /&gt;  add(HeaderContributor.forJavaScript(WarnOnExitForm.class, "WarnOnExit.js"));&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public void addFormField(FormComponent c)&lt;br /&gt; {&lt;br /&gt;  add(c);&lt;br /&gt;  c.add(new SimpleAttributeModifier("onchange", "setDirty();"));&lt;br /&gt;  // onblur is not always reliable when it comes to the back&lt;br /&gt;  // button, but it works for other situations&lt;br /&gt;  c.add(new SimpleAttributeModifier("onblur", "setDirty();"));&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public void addButton(Button b)&lt;br /&gt; {&lt;br /&gt;  add(b);&lt;br /&gt;  // Don't confirm when the user hits a form button&lt;br /&gt;  b.add(new SimpleAttributeModifier("onclick", "dontConfirm();"));&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; /** We must add attributes to the page's body tag in onAttach()&lt;br /&gt;   * because the body tag is not available at construction time&lt;br /&gt;   */&lt;br /&gt; @Override&lt;br /&gt; protected void onAttach()&lt;br /&gt; {&lt;br /&gt;  super.onAttach();&lt;br /&gt;&lt;br /&gt;  if (firstTimeThru)&lt;br /&gt;  {&lt;br /&gt;   final WebMarkupContainer c = getWebPage().getBodyContainer()&lt;br /&gt;     .getBodyContainer();&lt;br /&gt;   c.add(new SimpleAttributeModifier("onload",&lt;br /&gt;     "FORMCONFIRM=true;FORMISDIRTY=false;"));&lt;br /&gt;   c.add(new SimpleAttributeModifier("onbeforeunload",&lt;br /&gt;     "return warnOnPageExit('" + getString("formdirtymessage")&lt;br /&gt;       + "');"));&lt;br /&gt;   firstTimeThru = false;&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;WarnOnExitForm.properties&lt;br /&gt;&lt;/h3&gt;&lt;pre class="prettyprint"&gt;formdirtymessage=You have attempted to leave this page. \&lt;br /&gt;If you have made any changes to the fields without clicking \&lt;br /&gt;the OK button, your changes will be lost. Are you sure you \&lt;br /&gt;want to exit this page?&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;WarnOnExit.js&lt;br /&gt;&lt;/h3&gt;&lt;pre class="prettyprint"&gt;function warnOnPageExit(formdirtymessage)&lt;br /&gt;{&lt;br /&gt; if (FORMCONFIRM &amp;amp;&amp;amp; FORMISDIRTY){return formdirtymessage};&lt;br /&gt;}&lt;br /&gt;function setDirty()&lt;br /&gt;{&lt;br /&gt; FORMISDIRTY=true;&lt;br /&gt;}&lt;br /&gt;function dontConfirm()&lt;br /&gt;{&lt;br /&gt; FORMCONFIRM=false;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;As you can see, the solution is a simple one. However, it does gloss over some things:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Wicket modifies the body tag when you add an Ajax behavior to your page. This could overwrite your body tag modifications&lt;/li&gt;&lt;li&gt;onAttach() is not available in Wicket 1.3&lt;/li&gt;&lt;li&gt;getBodyContainer() is not available in Wicket 1.3&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-5540685366108049045?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=9gfnNKjusT4:VJjv8SJzkRc:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=9gfnNKjusT4:VJjv8SJzkRc:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=9gfnNKjusT4:VJjv8SJzkRc:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=9gfnNKjusT4:VJjv8SJzkRc:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/5540685366108049045/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=5540685366108049045" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/5540685366108049045?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/5540685366108049045?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2007/11/warning-on-page-exit-for-wicket-12x.html" title="Warning on page exit for Wicket 1.2.x" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DUMGQXo5fyp7ImA9WxdXE08.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-7923839363861343219</id><published>2007-11-15T09:23:00.000-08:00</published><updated>2008-06-24T10:10:20.427-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T10:10:20.427-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="css" /><title>Suckerfish dropdowns for Wicket</title><content type="html">&lt;p&gt;Note: there is an &lt;a href="http://javathoughts.capesugarbird.com/2007/12/suckerfish-dropdowns-for-wicket-redux.html"&gt;updated post&lt;/a&gt; on this topic.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;We wanted to add Suckerfish dropdowns to our Wicket site. Suckerfish dropdowns were first mentioned on &lt;a href="http://www.alistapart.com/articles/dropdowns/"&gt;A List Apart&lt;/a&gt; and have since become very popular as super-simple, lightweight, easy-to-use Javascript menus. The licensing is liberal too, see the "Free source" section on the &lt;a href="http://www.alistapart.com/copyright/"&gt;Copyright&lt;/a&gt; page. Since then they have been refined as "&lt;a href="http://www.htmldog.com/articles/suckerfish/dropdowns/"&gt;Son of Suckerfish&lt;/a&gt;".&lt;br /&gt;&lt;/p&gt;&lt;p&gt;As is common with Wicket, this turned out to be easy to do. All we need was single-level menus (i.e. no sub-submenus), but it should be straightforward to extend this to support that. I also wanted to keep it as simple as possible for illustration purposes. The solution consists of just one class.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Note: this has only been tested with Wicket 1.3.0.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;First an example of how to use it.&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;MyWebPage.java&lt;br /&gt;&lt;/h3&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt;// Create the menubar&lt;br /&gt;final SuckerfishMenuPanel mb = new SuckerfishMenuPanel("menuBar");&lt;br /&gt;add(mb);&lt;br /&gt;// Create a menu&lt;br /&gt;SuckerfishMenuPanel.MenuItem mi =&lt;br /&gt; new SuckerfishMenuPanel.MenuItem(new&lt;br /&gt;     BookmarkablePageLink(SuckerfishMenuPanel.LINK_ID, MyFirstPage.class),&lt;br /&gt;         "myFirstLabel");&lt;br /&gt;mb.addMenu(mi);&lt;br /&gt;// Create a submenu&lt;br /&gt;SuckerfishMenuPanel.MenuItem submi =&lt;br /&gt; new SuckerfishMenuPanel.MenuItem(new&lt;br /&gt;     BookmarkablePageLink(SuckerfishMenuPanel.LINK_ID, MySecondPage.class),&lt;br /&gt;         "mySecondLabel");&lt;br /&gt;mi.addMenu(submi);&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;MyWebPage.html&lt;br /&gt;&lt;/h3&gt;&lt;pre class="prettyprint"&gt;...&lt;br /&gt; &amp;lt;div wicket:id="menuBar" class="menubar"&amp;gt;[MenuBar Here]&amp;lt;/div&amp;gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Now the implementation. Note that these menus consist of Links. This gives you the freedom to pass in whatever type of Link you need, except for ExternalLink, which unfortunately does not inherit from Link.&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;SuckerfishMenuPanel.java&lt;br /&gt;&lt;/h3&gt;&lt;pre class="prettyprint"&gt;/**&lt;br /&gt;* See http://www.alistapart.com/articles/dropdowns/&lt;br /&gt;* License is at&lt;br /&gt;* http://www.alistapart.com/copyright/, see "Free source"&lt;br /&gt;* Currently only supports one level of dropdowns&lt;br /&gt;*/&lt;br /&gt;public class SuckerfishMenuPanel extends Panel&lt;br /&gt;{&lt;br /&gt;private static final long serialVersionUID = -21832859336423477L;&lt;br /&gt;&lt;br /&gt;public static final String LINK_ID = "linkid";&lt;br /&gt;public static final String LINK_TEXT_ID = "linktext";&lt;br /&gt;private final List&lt;menuitem&gt; topMenuItems = new ArrayList&lt;menuitem&gt;();&lt;br /&gt;&lt;br /&gt;public SuckerfishMenuPanel(String id)&lt;br /&gt;{&lt;br /&gt;super(id);&lt;br /&gt;// Add the Suckerfish CSS&lt;br /&gt;add(HeaderContributor.forCss(SuckerfishMenuPanel.class,&lt;br /&gt; "SuckerfishMenuPanel.css"));&lt;br /&gt;// Add the top menus&lt;br /&gt;add(new ListView("menubarlinks", new PropertyModel(this, "topMenuItems"))&lt;br /&gt;{&lt;br /&gt;private static final long serialVersionUID = -5875124377225299067L;&lt;br /&gt;&lt;br /&gt;@Override&lt;br /&gt;protected void populateItem(ListItem item)&lt;br /&gt;{&lt;br /&gt; final MenuItem menuItem = (MenuItem) item.getModelObject();&lt;br /&gt; item.add(menuItem.getLink());&lt;br /&gt; final WebMarkupContainer menuitemul = new WebMarkupContainer("menuitemul");&lt;br /&gt; item.add(menuitemul);&lt;br /&gt; // Hide the ul tag if there are no submenus&lt;br /&gt; menuitemul.setVisible(menuItem.getChildren().size() &gt; 0);&lt;br /&gt; // Add the submenus&lt;br /&gt; final ListView menuitemlinks =&lt;br /&gt;&lt;/menuitem&gt;&lt;/menuitem&gt;        &lt;menuitem&gt;&lt;menuitem&gt;new ListView("menuitemlinks", menuItem.getChildren())&lt;br /&gt; {&lt;br /&gt;  private static final long serialVersionUID = -2997784619579088676L;&lt;br /&gt;&lt;br /&gt;  @Override&lt;br /&gt;  protected void populateItem(ListItem item)&lt;br /&gt;  {&lt;br /&gt;   final MenuItem menuItem = (MenuItem) item&lt;br /&gt;     .getModelObject();&lt;br /&gt;   item.add(menuItem.getLink());&lt;br /&gt;  }&lt;br /&gt; };&lt;br /&gt; menuitemul.add(menuitemlinks);&lt;br /&gt;}&lt;br /&gt;});&lt;br /&gt;}&lt;br /&gt;/** Add one menu item */&lt;br /&gt;public void addMenu(MenuItem menu)&lt;br /&gt;{&lt;br /&gt;if (!menu.link.getId().equals(LINK_ID))&lt;br /&gt;{&lt;br /&gt;throw new IllegalArgumentException(&lt;br /&gt;  "The id must be SuckerfishMenuPanel.LINK_ID");&lt;br /&gt;}&lt;br /&gt;topMenuItems.add(menu);&lt;br /&gt;}&lt;br /&gt;/** Add all menus at once */&lt;br /&gt;public void setMenuItems(List&lt;menuitem&gt; menuItems)&lt;br /&gt;{&lt;br /&gt;this.topMenuItems.clear();&lt;br /&gt;this.topMenuItems.addAll(menuItems);&lt;br /&gt;}&lt;br /&gt;/** Lightweight menu object that stores a menu Link and its label */&lt;br /&gt;public static class MenuItem&lt;br /&gt;{&lt;br /&gt;private final Link link;&lt;br /&gt;private final List&lt;menuitem&gt; subMenuItems = new ArrayList&lt;menuitem&gt;();&lt;br /&gt;&lt;br /&gt;public MenuItem(Link link, String label)&lt;br /&gt;{&lt;br /&gt;this.link = link;&lt;br /&gt;this.link.add(new Label(LINK_TEXT_ID, label));&lt;br /&gt;}&lt;br /&gt;/** Add one menu item */&lt;br /&gt;public void addMenu(MenuItem menu)&lt;br /&gt;{&lt;br /&gt;if (!menu.link.getId().equals(LINK_ID))&lt;br /&gt;{&lt;br /&gt; throw new IllegalArgumentException(&lt;br /&gt;   "The id must be SuckerfishMenuPanel.LINK_ID");&lt;br /&gt;}&lt;br /&gt;subMenuItems.add(menu);&lt;br /&gt;}&lt;br /&gt;/** Add all menus at once */&lt;br /&gt;public void setMenuItems(List&lt;menuitem&gt; menuItems)&lt;br /&gt;{&lt;br /&gt;this.subMenuItems.clear();&lt;br /&gt;for (MenuItem child : menuItems)&lt;br /&gt;{&lt;br /&gt; addMenu(child);&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public Link getLink()&lt;br /&gt;{&lt;br /&gt;return link;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public List&lt;menuitem&gt; getChildren()&lt;br /&gt;{&lt;br /&gt;return subMenuItems;&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;}&lt;/menuitem&gt;&lt;/menuitem&gt;&lt;/menuitem&gt;&lt;/menuitem&gt;&lt;/menuitem&gt;&lt;/menuitem&gt;&lt;/menuitem&gt;&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;SuckerfishMenuPanel.html&lt;br /&gt;&lt;/h3&gt;&lt;pre class="prettyprint"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&lt;br /&gt;xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en" lang="en"&amp;gt;&lt;br /&gt;&amp;lt;wicket:head&amp;gt;&lt;br /&gt;&amp;lt;script language="JavaScript" type="text/javascript"&amp;gt;&lt;br /&gt;myHover = function() {&lt;br /&gt;var sfEls = document.getElementById("nav").getElementsByTagName("LI");&lt;br /&gt;for (var i=0; i&amp;lt;sfEls.length; i++) {&lt;br /&gt;sfEls[i].onmouseover=function() {&lt;br /&gt;this.className+=" myhover";&lt;br /&gt;}&lt;br /&gt;sfEls[i].onmouseout=function() {&lt;br /&gt;this.className=this.className.replace(new RegExp(" myhover\\b"), "");&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;if (window.attachEvent) window.attachEvent("onload", myHover);&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;/wicket:head&amp;gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt;&amp;lt;wicket:panel&amp;gt;&lt;br /&gt;    &amp;lt;ul id="nav"&amp;gt;&lt;br /&gt;      &amp;lt;li wicket:id="menubarlinks"&amp;gt;&lt;br /&gt;       &amp;lt;a href="#" wicket:id="linkid"&lt;br /&gt;         class="topmenu"&amp;gt;&amp;lt;span wicket:id="linktext"&amp;gt;Link Text&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;        &amp;lt;ul wicket:id="menuitemul"&amp;gt;&lt;br /&gt;         &amp;lt;li wicket:id="menuitemlinks"&amp;gt;&lt;br /&gt;          &amp;lt;a href="#" wicket:id="linkid"&lt;br /&gt;             class="childmenu"&amp;gt;&amp;lt;span wicket:id="linktext"&amp;gt;Link Text&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;         &amp;lt;/li&amp;gt;&lt;br /&gt;        &amp;lt;/ul&amp;gt;&lt;br /&gt;      &amp;lt;/li&amp;gt;&lt;br /&gt;    &amp;lt;/ul&amp;gt;&lt;br /&gt;&amp;lt;/wicket:panel&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The CSS below is for horizontal menus. It can be changed to accommodate vertical menus. I'm using a background image (gradient.gif). You will either need to supply your own or just use a plain background.&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;SuckerfishMenuPanel.css&lt;br /&gt;&lt;/h3&gt;&lt;pre class="prettyprint"&gt;#nav, #nav ul {&lt;br /&gt;float: left;&lt;br /&gt;width: 99.9%;&lt;br /&gt;list-style: none;&lt;br /&gt;line-height: 1;&lt;br /&gt;font-weight: bold;&lt;br /&gt;padding: 0;&lt;br /&gt;margin: 0;&lt;br /&gt;border: 1px solid #3e6581;&lt;br /&gt;background:#BCCDDB&lt;br /&gt;url(/images/gradient.gif)&lt;br /&gt;repeat-x 0% 0%;&lt;br /&gt;}&lt;br /&gt;#nav a {&lt;br /&gt;display: block;&lt;br /&gt;width: 10em;&lt;br /&gt;w\idth: 6em;&lt;br /&gt;color: #7C6240;&lt;br /&gt;text-decoration: none;&lt;br /&gt;padding: 0.25em;&lt;br /&gt;}&lt;br /&gt;#nav a.topmenu {&lt;br /&gt;width: 10em;&lt;br /&gt;text-align: center;&lt;br /&gt;width: auto;&lt;br /&gt;}&lt;br /&gt;#nav a.topmenu span {&lt;br /&gt;color: white;&lt;br /&gt;}&lt;br /&gt;#nav a.childmenu {&lt;br /&gt;width: 13em;&lt;br /&gt;}&lt;br /&gt;#nav a.childmenu span {&lt;br /&gt;color: white;&lt;br /&gt;}&lt;br /&gt;#nav li {&lt;br /&gt;float: left;&lt;br /&gt;padding-left: 0.5em;&lt;br /&gt;padding-right: 0.5em;&lt;br /&gt;padding-top: 0.5em;&lt;br /&gt;padding-bottom: 0.5em;&lt;br /&gt;width: auto;&lt;br /&gt;background:#BCCDDB&lt;br /&gt;url(/images/gradient.gif)&lt;br /&gt;repeat-x 0% 0%;&lt;br /&gt;}&lt;br /&gt;#nav li ul {&lt;br /&gt;position: absolute;&lt;br /&gt;left: -999em;&lt;br /&gt;height: auto;&lt;br /&gt;width: 14.4em;&lt;br /&gt;w\idth: 13.9em;&lt;br /&gt;font-weight: normal;&lt;br /&gt;margin: 0;&lt;br /&gt;}&lt;br /&gt;#nav li li {&lt;br /&gt;padding-right: 1em;&lt;br /&gt;width: 13em&lt;br /&gt;padding-left: 0;&lt;br /&gt;padding-top: 0.25em;&lt;br /&gt;padding-bottom: 0.25em;&lt;br /&gt;}&lt;br /&gt;#nav li ul a {&lt;br /&gt;width: 13em;&lt;br /&gt;w\idth: 9em;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;#nav li ul ul {&lt;br /&gt;margin: -1.75em 0 0 14em;&lt;br /&gt;}&lt;br /&gt;#nav li:hover ul ul, #nav li:hover ul ul ul, #nav li.myhover ul ul, #nav li.myhover ul ul ul {&lt;br /&gt;left: -999em;&lt;br /&gt;}&lt;br /&gt;#nav li:hover ul, #nav li li:hover ul, #nav li li li:hover ul, #nav li.myhover ul, #nav li li.myhover ul, #nav li li li.myhover ul {&lt;br /&gt;left: auto;&lt;br /&gt;}&lt;br /&gt;#nav li:hover, #nav li.myhover {&lt;br /&gt;background:#C8D7E3&lt;br /&gt;url(/images/gradient-highlighted.gif)&lt;br /&gt;repeat-x;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-7923839363861343219?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=6kafwwz1nM8:zppazIDrAwo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=6kafwwz1nM8:zppazIDrAwo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=6kafwwz1nM8:zppazIDrAwo:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=6kafwwz1nM8:zppazIDrAwo:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/7923839363861343219/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=7923839363861343219" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/7923839363861343219?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/7923839363861343219?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2007/11/suckerfish-dropdowns-for-wicket.html" title="Suckerfish dropdowns for Wicket" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total></entry><entry gd:etag="W/&quot;DUMMR3c8fip7ImA9WxdXE08.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-4050498605638202046</id><published>2007-09-27T13:44:00.000-07:00</published><updated>2008-06-24T10:11:26.976-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T10:11:26.976-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="ajax" /><title>An Ajax Select All checkbox for Wicket</title><content type="html">&lt;p&gt;I had a need to enable and disable a button based on whether an item in a list was checked. I also wanted a Select All checkbox that would change the state of the button and all the checkboxes in the list. And finally, I wanted the Select All checkbox to deselect if any of the checkboxes in the list was unchecked. &lt;/p&gt;&lt;br /&gt;&lt;p&gt;Wicket has CheckGroupSelector, CheckGroup and Check that all work together to change the state of a group of checkboxes, but they are not Ajax-enabled. &lt;/p&gt;&lt;br /&gt;&lt;p&gt;I wanted the simplest possible solution that would achieve this. Luckily, Wicket (1.2.6) came to the rescue yet again and made it quite easy. &lt;/p&gt;&lt;br /&gt;&lt;p&gt;I couldn't just Ajax-enable Check because it does not inherit from FormComponent and therefore you cannot add an AjaxFormComponentUpdatingBehavior to it, which is the easiest way to Ajax-enable a form item. So instead I use AjaxCheckBox in two places, for Select All and in the list.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;My list class is called MyAjaxEnabledList. Tester.java contains a form that embeds a MyAjaxEnabledList. The MyAjaxEnabledList constructor is passed a list of items of type MyListItem. MyListItem implements the interface MyAjaxEnabledList.ISelected, which guarantees the presence of the methods get/setSelected() that the checkbox requires.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;The key is the onUpdate() method of the anonymous subclass of MyAjaxEnabledList. It enables or disables the delete button based on the list's check group model. This is the feature that is not available if you use CheckGroupSelector, CheckGroup and Check &lt;/p&gt;&lt;br /&gt;&lt;h3&gt;Tester.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;public class Tester extends WebPage&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = 7859298577765782995L;&lt;br /&gt;&lt;br /&gt; final Button delete;&lt;br /&gt;&lt;br /&gt; public Tester()&lt;br /&gt; {&lt;br /&gt;  final List&lt;MyListItem&gt; items = Arrays.asList(new MyListItem[]&lt;br /&gt;  { new MyListItem("one"), new MyListItem("two") });&lt;br /&gt;&lt;br /&gt;  final MyAjaxEnabledList&lt;MyListItem&gt; myList = new MyAjaxEnabledList&lt;MyListItem&gt;(&lt;br /&gt;    "mylist", items)&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = -6274910651773243643L;&lt;br /&gt;&lt;br /&gt;   @Override&lt;br /&gt;   protected void onUpdate(AjaxRequestTarget target)&lt;br /&gt;   {&lt;br /&gt;    target.addComponent(delete);&lt;br /&gt;    delete.setEnabled(getCheckGroupModel().size() &gt; 0);&lt;br /&gt;   }&lt;br /&gt;  };&lt;br /&gt;&lt;br /&gt;  delete = new Button("delete", new Model("Delete"))&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = -2732052672075770498L;&lt;br /&gt;&lt;br /&gt;   @Override&lt;br /&gt;   protected void onSubmit()&lt;br /&gt;   {&lt;br /&gt;    for (MyListItem s : myList.getCheckGroupModel())&lt;br /&gt;    {&lt;br /&gt;     System.out.println("You are deleting: " + s);&lt;br /&gt;    }&lt;br /&gt;    setResponsePage(Tester.class);&lt;br /&gt;   }&lt;br /&gt;  };&lt;br /&gt;  delete.setEnabled(false);&lt;br /&gt;  delete.setOutputMarkupId(true);&lt;br /&gt;&lt;br /&gt;  Form form = new Form("myform");&lt;br /&gt;  form.add(myList);&lt;br /&gt;  form.add(delete);&lt;br /&gt;  add(form);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; class MyListItem implements MyAjaxEnabledList.ISelected&lt;br /&gt; {&lt;br /&gt;  private String item;&lt;br /&gt;  private boolean selected;&lt;br /&gt;&lt;br /&gt;  public MyListItem(String item)&lt;br /&gt;  {&lt;br /&gt;   this.item = item;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public boolean isSelected()&lt;br /&gt;  {&lt;br /&gt;   return selected;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void setSelected(boolean selected)&lt;br /&gt;  {&lt;br /&gt;   this.selected = selected;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  @Override&lt;br /&gt;  public String toString()&lt;br /&gt;  {&lt;br /&gt;   return item;&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Tester.html&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&lt;br /&gt;xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en"&lt;br /&gt;lang="en"&amp;gt;&lt;br /&gt;  &amp;lt;body&amp;gt;&lt;br /&gt;    &amp;lt;form wicket:id="myform"&amp;gt;&lt;br /&gt;      &amp;lt;span wicket:id="mylist"&amp;gt;&lt;br /&gt;      &amp;lt;/span&amp;gt;&lt;br /&gt;      &amp;lt;input type="submit" wicket:id="delete" /&amp;gt;&lt;br /&gt;    &amp;lt;/form&amp;gt;&lt;br /&gt;  &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;MyAjaxEnabledList contains two uses of MyAjaxCheckBox. This is a subclass of AjaxCheckBox that implements the method isChecked() (see below). The first use is the Select All ("allChecked"), and the second is in the list ("selected"). Both update the client ("MyAjaxEnabledList.this.onUpdate(target)") in addition to updating the check group model.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;MyAjaxEnabledList.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;public class MyAjaxEnabledList&lt;T extends MyAjaxEnabledList.ISelected&gt; extends&lt;br /&gt;  Panel&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = -8101172306096365329L;&lt;br /&gt;&lt;br /&gt; /** Maintains a list of checked items. */&lt;br /&gt; private final List&lt;T&gt; checkGroupModel = new ArrayList&lt;T&gt;();&lt;br /&gt; /** The list of items in the table */&lt;br /&gt; private final List&lt;T&gt; rows = new ArrayList&lt;T&gt;();&lt;br /&gt; /** True if all items are selected */&lt;br /&gt; private boolean allChecked;&lt;br /&gt;&lt;br /&gt; public MyAjaxEnabledList(String id, final List&lt;T&gt; items)&lt;br /&gt; {&lt;br /&gt;  super(id);&lt;br /&gt;  this.rows.addAll(items);&lt;br /&gt;  final List&lt;MyAjaxCheckBox&gt; checks = new ArrayList&lt;MyAjaxCheckBox&gt;();&lt;br /&gt;  final MyAjaxCheckBox checkGroup = new MyAjaxCheckBox("allChecked",&lt;br /&gt;    new PropertyModel(this, "allChecked"))&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = -5765551380737131761L;&lt;br /&gt;&lt;br /&gt;   @Override&lt;br /&gt;   protected void onUpdate(AjaxRequestTarget target)&lt;br /&gt;   {&lt;br /&gt;    final boolean isSelected = isChecked();&lt;br /&gt;    if (isSelected)&lt;br /&gt;    {&lt;br /&gt;     checkGroupModel.addAll(rows);&lt;br /&gt;    }&lt;br /&gt;    else&lt;br /&gt;    {&lt;br /&gt;     checkGroupModel.clear();&lt;br /&gt;    }&lt;br /&gt;    for (MyAjaxCheckBox check : checks)&lt;br /&gt;    {&lt;br /&gt;     target.addComponent(check);&lt;br /&gt;    }&lt;br /&gt;    for (ISelected row : rows)&lt;br /&gt;    {&lt;br /&gt;     row.setSelected(isSelected);&lt;br /&gt;    }&lt;br /&gt;    MyAjaxEnabledList.this.onUpdate(target);&lt;br /&gt;   }&lt;br /&gt;  };&lt;br /&gt;  add(checkGroup);&lt;br /&gt;&lt;br /&gt;  // Initialize the table&lt;br /&gt;  add(new ListView("rows", new PropertyModel(this, "rows"))&lt;br /&gt;  {&lt;br /&gt;   private static final long serialVersionUID = -7831503487781845806L;&lt;br /&gt;&lt;br /&gt;   // Casting model object to T&lt;br /&gt;   @SuppressWarnings("unchecked")&lt;br /&gt;   @Override&lt;br /&gt;   protected void populateItem(ListItem item)&lt;br /&gt;   {&lt;br /&gt;    final T row = (T) item.getModelObject();&lt;br /&gt;    item.setModel(new CompoundPropertyModel(row));&lt;br /&gt;    MyAjaxCheckBox check = new MyAjaxCheckBox("selected")&lt;br /&gt;    {&lt;br /&gt;     private static final long serialVersionUID = -5766661380737131761L;&lt;br /&gt;&lt;br /&gt;     @Override&lt;br /&gt;     protected void onUpdate(AjaxRequestTarget target)&lt;br /&gt;     {&lt;br /&gt;      if (isChecked())&lt;br /&gt;      {&lt;br /&gt;       if (!checkGroupModel.contains(row))&lt;br /&gt;        checkGroupModel.add(row);&lt;br /&gt;      }&lt;br /&gt;      else&lt;br /&gt;      {&lt;br /&gt;       checkGroupModel.remove(row);&lt;br /&gt;      }&lt;br /&gt;      setAllChecked(checkGroupModel.size() == rows.size());&lt;br /&gt;      target.addComponent(checkGroup);&lt;br /&gt;      MyAjaxEnabledList.this.onUpdate(target);&lt;br /&gt;     }&lt;br /&gt;    };&lt;br /&gt;    checks.add(check);&lt;br /&gt;    item.add(check);&lt;br /&gt;    item.add(new Label("item", row.toString()));&lt;br /&gt;   }&lt;br /&gt;  });&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public List&lt;T&gt; getCheckGroupModel()&lt;br /&gt; {&lt;br /&gt;  return checkGroupModel;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; /** Subclasses can override */&lt;br /&gt; protected void onUpdate(AjaxRequestTarget target)&lt;br /&gt; {&lt;br /&gt;  // Does nothing&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public List&lt;T&gt; getRows()&lt;br /&gt; {&lt;br /&gt;  return rows;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public void setRows(List&lt;T&gt; rows)&lt;br /&gt; {&lt;br /&gt;  this.rows.clear();&lt;br /&gt;  this.rows.addAll(rows);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public boolean isAllChecked()&lt;br /&gt; {&lt;br /&gt;  return allChecked;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public void setAllChecked(boolean allChecked)&lt;br /&gt; {&lt;br /&gt;  this.allChecked = allChecked;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public interface ISelected&lt;br /&gt; {&lt;br /&gt;  public boolean isSelected();&lt;br /&gt;&lt;br /&gt;  public void setSelected(boolean selected);&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;MyAjaxEnabledList.html&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&lt;br /&gt;xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en"&lt;br /&gt;lang="en"&amp;gt;&lt;br /&gt;  &amp;lt;body&amp;gt;&lt;br /&gt;    &amp;lt;wicket:panel&amp;gt;&lt;br /&gt;      &amp;lt;table&amp;gt;&lt;br /&gt;        &amp;lt;tbody&amp;gt;&lt;br /&gt;          &amp;lt;tr&amp;gt;&lt;br /&gt;            &amp;lt;th&amp;gt;&lt;br /&gt;            &amp;lt;input type="checkbox" wicket:id="allChecked" /&amp;gt;&lt;br /&gt;&lt;br /&gt;            Selected&amp;lt;/th&amp;gt;&lt;br /&gt;&lt;br /&gt;            &amp;lt;th&amp;gt;&lt;br /&gt;              &amp;lt;wicket:message key="columntitle"&amp;gt;&lt;br /&gt;              &amp;nbsp;&amp;lt;/wicket:message&amp;gt;&lt;br /&gt;            &amp;lt;/th&amp;gt;&lt;br /&gt;          &amp;lt;/tr&amp;gt;&lt;br /&gt;&lt;br /&gt;          &amp;lt;tr wicket:id="rows"&amp;gt;&lt;br /&gt;            &amp;lt;td&amp;gt;&lt;br /&gt;              &amp;lt;input type="checkbox" wicket:id="selected" /&amp;gt;&lt;br /&gt;            &amp;lt;/td&amp;gt;&lt;br /&gt;&lt;br /&gt;            &amp;lt;td&amp;gt;&lt;br /&gt;              &amp;lt;span wicket:id="item"&amp;gt;Item&amp;lt;/span&amp;gt;&lt;br /&gt;            &amp;lt;/td&amp;gt;&lt;br /&gt;          &amp;lt;/tr&amp;gt;&lt;br /&gt;        &amp;lt;/tbody&amp;gt;&lt;br /&gt;      &amp;lt;/table&amp;gt;&lt;br /&gt;    &amp;lt;/wicket:panel&amp;gt;&lt;br /&gt;  &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Finally, here is MyAjaxCheckBox:&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;MyAjaxCheckBox.java&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;/**&lt;br /&gt; * An AjaxCheckBox with some convenience methods&lt;br /&gt; */&lt;br /&gt;public abstract class MyAjaxCheckBox extends AjaxCheckBox&lt;br /&gt;{&lt;br /&gt; private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt; public MyAjaxCheckBox(final String id)&lt;br /&gt; {&lt;br /&gt;  this(id, null);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public MyAjaxCheckBox(final String id, IModel model)&lt;br /&gt; {&lt;br /&gt;  super(id, model);&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; public boolean isChecked()&lt;br /&gt; {&lt;br /&gt;  final String value = getValue();&lt;br /&gt;  if (value != null)&lt;br /&gt;  {&lt;br /&gt;   try&lt;br /&gt;   {&lt;br /&gt;    return Strings.isTrue(value);&lt;br /&gt;   }&lt;br /&gt;   catch (StringValueConversionException e)&lt;br /&gt;   {&lt;br /&gt;    return false;&lt;br /&gt;   }&lt;br /&gt;  }&lt;br /&gt;  return false;&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-4050498605638202046?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=6fWUE5tatMs:vNHC4u_BWFg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=6fWUE5tatMs:vNHC4u_BWFg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=6fWUE5tatMs:vNHC4u_BWFg:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=6fWUE5tatMs:vNHC4u_BWFg:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/4050498605638202046/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=4050498605638202046" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/4050498605638202046?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/4050498605638202046?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2007/09/ajax-select-all-checkbox-for-wicket.html" title="An Ajax Select All checkbox for Wicket" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DUIBR3o9fSp7ImA9WxdXE08.&quot;"><id>tag:blogger.com,1999:blog-4262232281084864898.post-8923911978320717401</id><published>2007-09-23T19:24:00.000-07:00</published><updated>2008-06-24T10:12:36.465-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T10:12:36.465-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="wicket" /><category scheme="http://www.blogger.com/atom/ns#" term="eclipse" /><category scheme="http://www.blogger.com/atom/ns#" term="maven" /><title>Qwicket, maven and eclipse</title><content type="html">I want to start using &lt;a href="http://en.wikipedia.org/wiki/Java_Persistence_API"&gt;JPA&lt;/a&gt; with Wicket, and the quickest way was to start with &lt;a href="http://www.antwerkz.com/qwicket/app/home"&gt;Qwicket&lt;/a&gt;, a project that already has done the heavy lifting. I also wanted to get a bit more experience with &lt;a href="http://maven.apache.org/"&gt;maven&lt;/a&gt;, which is touted as a successor to &lt;a href="http://ant.apache.org/"&gt;ant&lt;/a&gt;. Furthermore, I wanted to be able to build and run my maven-managed application from eclipse. And lastly, I wanted to use &lt;a href="http://www.mysql.com/"&gt;MySQL&lt;/a&gt;. Qwicket does come with maven support, but it's managed from an ant script. I wanted native maven support.&lt;br /&gt;&lt;br /&gt;Here's what I did to change qwicket so it fits my requirements:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;This assumes MySQL is installed and running.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Create a schema called qwicket in the MySQL catalog. You can just leave it empty.&lt;/li&gt;&lt;li&gt;Install maven 2 for the command line (&lt;a href="http://maven.apache.org/download.html"&gt;http://maven.apache.org/download.html&lt;/a&gt;)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Install the eclipse maven 2 plugin (&lt;a href="http://m2eclipse.codehaus.org"&gt;http://m2eclipse.codehaus.org&lt;/a&gt;)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Start a DOS shell (this will work for other shells too)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;I'm using C:\eclipseproj as my eclipse project directory, but it could be anything:&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;C:\eclipseproj&gt;mvn archetype:create -DgroupId=qwicket -DartifactId=qwicket&lt;br /&gt;    -DarchetypeArtifactId=maven-archetype-webapp&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Note the last argument. It creates an appropriate directory structure for a web application.&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;C:\eclipseproj&gt;cd qwicket&lt;br /&gt;C:\eclipseproj&gt;mvn eclipse:eclipse&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;In eclipse, import the project into your workspace:&lt;/li&gt;&lt;/ul&gt;File --&gt; Import --&gt; Import Existing Projects into Workspace&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Edit pom.xml as follows:&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;pom.xml&lt;/h3&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;project xmlns="http://maven.apache.org/POM/4.0.0"&lt;br /&gt;xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&amp;gt;&lt;br /&gt;&amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;&lt;br /&gt;&amp;lt;groupId&amp;gt;qwicket&amp;lt;/groupId&amp;gt;&lt;br /&gt;&amp;lt;artifactId&amp;gt;qwicket&amp;lt;/artifactId&amp;gt;&lt;br /&gt;&amp;lt;packaging&amp;gt;war&amp;lt;/packaging&amp;gt;&lt;br /&gt;&amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;&lt;br /&gt;&amp;lt;name&amp;gt;qwicket Maven Webapp&amp;lt;/name&amp;gt;&lt;br /&gt;&amp;lt;url&amp;gt;http://maven.apache.org&amp;lt;/url&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;properties&amp;gt;&lt;br /&gt; &amp;lt;wicket.version&amp;gt;1.2.6&amp;lt;/wicket.version&amp;gt;&lt;br /&gt; &amp;lt;jetty.version&amp;gt;6.1.4&amp;lt;/jetty.version&amp;gt;&lt;br /&gt;&amp;lt;/properties&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;dependencies&amp;gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;3.8.1&amp;lt;/version&amp;gt;&lt;br /&gt;  &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt; &amp;lt;!--  WICKET DEPENDENCIES --&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;wicket&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;wicket&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;${wicket.version}&amp;lt;/version&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;wicket&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;wicket-extensions&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;${wicket.version}&amp;lt;/version&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;!-- LOGGING DEPENDENCIES --&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;org.slf4j&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;slf4j-log4j12&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;1.0.1&amp;lt;/version&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;!--  JETTY DEPENDENCIES FOR TESTING  --&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;org.mortbay.jetty&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;jetty&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;${jetty.version}&amp;lt;/version&amp;gt;&lt;br /&gt;  &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;org.mortbay.jetty&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;jetty-util&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;${jetty.version}&amp;lt;/version&amp;gt;&lt;br /&gt;  &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;org.mortbay.jetty&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;jetty-management&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;${jetty.version}&amp;lt;/version&amp;gt;&lt;br /&gt;  &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;!--  SPRING and HIBERNATE --&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;spring&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;2.0&amp;lt;/version&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;org.hibernate&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;hibernate&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;3.2.0.ga&amp;lt;/version&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;org.hibernate&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;hibernate-annotations&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;3.2.0.ga&amp;lt;/version&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;wicket&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;wicket-spring-annot&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;1.2.6&amp;lt;/version&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;!--  EMAIL --&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;commons-email&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;commons-email&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;1.0&amp;lt;/version&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;!--  MYSQL --&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;mysql&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;mysql-connector-java&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;3.1.14&amp;lt;/version&amp;gt;&lt;br /&gt; &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/dependencies&amp;gt;&lt;br /&gt;&amp;lt;build&amp;gt;&lt;br /&gt; &amp;lt;finalName&amp;gt;qwicket&amp;lt;/finalName&amp;gt;&lt;br /&gt; &amp;lt;resources&amp;gt;&lt;br /&gt;  &amp;lt;resource&amp;gt;&lt;br /&gt;   &amp;lt;filtering&amp;gt;false&amp;lt;/filtering&amp;gt;&lt;br /&gt;   &amp;lt;directory&amp;gt;src/main/resources&amp;lt;/directory&amp;gt;&lt;br /&gt;  &amp;lt;/resource&amp;gt;&lt;br /&gt;  &amp;lt;resource&amp;gt;&lt;br /&gt;   &amp;lt;filtering&amp;gt;false&amp;lt;/filtering&amp;gt;&lt;br /&gt;   &amp;lt;directory&amp;gt;src/main/java&amp;lt;/directory&amp;gt;&lt;br /&gt;   &amp;lt;includes&amp;gt;&lt;br /&gt;    &amp;lt;include&amp;gt;**&amp;lt;/include&amp;gt;&lt;br /&gt;   &amp;lt;/includes&amp;gt;&lt;br /&gt;   &amp;lt;excludes&amp;gt;&lt;br /&gt;    &amp;lt;exclude&amp;gt;**/*.java&amp;lt;/exclude&amp;gt;&lt;br /&gt;   &amp;lt;/excludes&amp;gt;&lt;br /&gt;  &amp;lt;/resource&amp;gt;&lt;br /&gt; &amp;lt;/resources&amp;gt;&lt;br /&gt; &amp;lt;plugins&amp;gt;&lt;br /&gt;  &amp;lt;plugin&amp;gt;&lt;br /&gt;   &amp;lt;groupId&amp;gt;org.mortbay.jetty&amp;lt;/groupId&amp;gt;&lt;br /&gt;   &amp;lt;artifactId&amp;gt;maven-jetty-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;/plugin&amp;gt;&lt;br /&gt;  &amp;lt;plugin&amp;gt;&lt;br /&gt;   &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;&lt;br /&gt;   &amp;lt;artifactId&amp;gt;maven-compiler-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;   &amp;lt;configuration&amp;gt;&lt;br /&gt;    &amp;lt;source&amp;gt;1.5&amp;lt;/source&amp;gt;&lt;br /&gt;    &amp;lt;target&amp;gt;1.5&amp;lt;/target&amp;gt;&lt;br /&gt;   &amp;lt;/configuration&amp;gt;&lt;br /&gt;  &amp;lt;/plugin&amp;gt;&lt;br /&gt; &amp;lt;/plugins&amp;gt;&lt;br /&gt;&amp;lt;/build&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;Now we are ready to work with qwicket.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Unpack qwicket.zip to qwickettmp&lt;/li&gt;&lt;br /&gt;&lt;li&gt;In the Windows file explorer:&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Delete qwicket\src\main\java&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Copy qwickettmp\src\java to qwicket\src\main&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Delete qwicket\src\main\webapp&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Copy qwickettmp\src\web to qwicket\src\main, rename to webapp&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Right click on the project in eclipse, add the java folder to the source path&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Copy qwicket\src\main\webapp\WEB-INF\classes\log4j.properties to src\main\resources (I'm not sure why log4j doesn't pick it up from the original location)&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Note that webapp\classes\application.properties is not used in my implementation.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Open qwicket.myapp.util.Start.java&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Change the lines&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;     ptabs.setContextPath("/");&lt;br /&gt;    ptabs.setWar("build/war");&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;to&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;     ptabs.setContextPath("/qwicket");&lt;br /&gt;    ptabs.setWar("target/qwicket.war");&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Edit applicationContext.xml as follows:&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;beans&amp;gt;&lt;br /&gt; &amp;lt;bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"&amp;gt;&lt;br /&gt;     &amp;lt;property name="driverClassName"&amp;gt;&lt;br /&gt;         &amp;lt;value&amp;gt;com.mysql.jdbc.Driver&amp;lt;/value&amp;gt;&lt;br /&gt;     &amp;lt;/property&amp;gt;&lt;br /&gt;     &amp;lt;property name="url"&amp;gt;&lt;br /&gt;         &amp;lt;value&amp;gt;jdbc:mysql://localhost/qwicket&amp;lt;/value&amp;gt;&lt;br /&gt;     &amp;lt;/property&amp;gt;&lt;br /&gt;     &amp;lt;property name="username"&amp;gt;&amp;lt;value&amp;gt;your username here&amp;lt;/value&amp;gt;&amp;lt;/property&amp;gt;&lt;br /&gt;     &amp;lt;property name="password"&amp;gt;&amp;lt;value&amp;gt;your password here&amp;lt;/value&amp;gt;&amp;lt;/property&amp;gt;&lt;br /&gt; &amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Hibernate SessionFactory --&amp;gt;&lt;br /&gt; &amp;lt;bean id="sessionFactory" class="qwicket.myapp.QwicketSessionFactory" abstract="false" singleton="true" lazy-init="default" autowire="default" dependency-check="default"&amp;gt;&lt;br /&gt;     &amp;lt;property name="dataSource"&amp;gt;&lt;br /&gt;         &amp;lt;ref local="dataSource" /&amp;gt;&lt;br /&gt;     &amp;lt;/property&amp;gt;&lt;br /&gt; &amp;lt;property name="annotatedClasses"&amp;gt;&lt;br /&gt;  &amp;lt;list&amp;gt;&lt;br /&gt;   &amp;lt;value&amp;gt;qwicket.myapp.model.User&amp;lt;/value&amp;gt;&lt;br /&gt;  &amp;lt;/list&amp;gt;&lt;br /&gt; &amp;lt;/property&amp;gt;&lt;br /&gt;     &amp;lt;property name="hibernateProperties"&amp;gt;&lt;br /&gt;         &amp;lt;props&amp;gt;&lt;br /&gt;             &amp;lt;prop key="hibernate.dialect"&amp;gt;org.hibernate.dialect.MySQLDialect&amp;lt;/prop&amp;gt;&lt;br /&gt;             &amp;lt;prop key="hibernate.show_sql"&amp;gt;true&amp;lt;/prop&amp;gt;&lt;br /&gt;         &amp;lt;/props&amp;gt;&lt;br /&gt;     &amp;lt;/property&amp;gt;&lt;br /&gt; &amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Services --&amp;gt;&lt;br /&gt;&amp;lt;bean id="userService"&lt;br /&gt; class="qwicket.myapp.service.impl.UserServiceImpl"&amp;gt;&lt;br /&gt; &amp;lt;property name="userDao" ref="userDAO" /&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- End Services --&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- DAOs --&amp;gt;&lt;br /&gt;&amp;lt;bean id="userDAO" class="qwicket.myapp.dao.impl.UserDaoImpl"&amp;gt;&lt;br /&gt; &amp;lt;property name="sessionFactory"&amp;gt;&lt;br /&gt;  &amp;lt;ref local="sessionFactory" /&amp;gt;&lt;br /&gt; &amp;lt;/property&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&amp;lt;!-- End DAOs --&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Application --&amp;gt;&lt;br /&gt;&amp;lt;bean id="QwicketApplication"&lt;br /&gt; class="qwicket.myapp.QwicketApplication" /&amp;gt;&lt;br /&gt;&amp;lt;/beans&amp;gt;&lt;br /&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;beans&amp;gt;&lt;br /&gt; &amp;lt;bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"&amp;gt;&lt;br /&gt;     &amp;lt;property name="driverClassName"&amp;gt;&lt;br /&gt;         &amp;lt;value&amp;gt;com.mysql.jdbc.Driver&amp;lt;/value&amp;gt;&lt;br /&gt;     &amp;lt;/property&amp;gt;&lt;br /&gt;     &amp;lt;property name="url"&amp;gt;&lt;br /&gt;         &amp;lt;value&amp;gt;jdbc:mysql://localhost/qwicket&amp;lt;/value&amp;gt;&lt;br /&gt;     &amp;lt;/property&amp;gt;&lt;br /&gt;     &amp;lt;property name="username"&amp;gt;&amp;lt;value&amp;gt;root&amp;lt;/value&amp;gt;&amp;lt;/property&amp;gt;&lt;br /&gt;     &amp;lt;property name="password"&amp;gt;&amp;lt;value&amp;gt;admin&amp;lt;/value&amp;gt;&amp;lt;/property&amp;gt;&lt;br /&gt; &amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Hibernate SessionFactory --&amp;gt;&lt;br /&gt; &amp;lt;bean id="sessionFactory" class="qwicket.myapp.QwicketSessionFactory" abstract="false" singleton="true" lazy-init="default" autowire="default" dependency-check="default"&amp;gt;&lt;br /&gt;     &amp;lt;property name="dataSource"&amp;gt;&lt;br /&gt;         &amp;lt;ref local="dataSource" /&amp;gt;&lt;br /&gt;     &amp;lt;/property&amp;gt;&lt;br /&gt; &amp;lt;property name="annotatedClasses"&amp;gt;&lt;br /&gt;  &amp;lt;list&amp;gt;&lt;br /&gt;   &amp;lt;value&amp;gt;qwicket.myapp.model.User&amp;lt;/value&amp;gt;&lt;br /&gt;  &amp;lt;/list&amp;gt;&lt;br /&gt; &amp;lt;/property&amp;gt;&lt;br /&gt;     &amp;lt;property name="hibernateProperties"&amp;gt;&lt;br /&gt;         &amp;lt;props&amp;gt;&lt;br /&gt;             &amp;lt;prop key="hibernate.dialect"&amp;gt;org.hibernate.dialect.MySQLDialect&amp;lt;/prop&amp;gt;&lt;br /&gt;             &amp;lt;prop key="hibernate.show_sql"&amp;gt;true&amp;lt;/prop&amp;gt;&lt;br /&gt;         &amp;lt;/props&amp;gt;&lt;br /&gt;     &amp;lt;/property&amp;gt;&lt;br /&gt; &amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Services --&amp;gt;&lt;br /&gt;&amp;lt;bean id="userService"&lt;br /&gt; class="qwicket.myapp.service.impl.UserServiceImpl"&amp;gt;&lt;br /&gt; &amp;lt;property name="userDao" ref="userDAO" /&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- End Services --&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- DAOs --&amp;gt;&lt;br /&gt;&amp;lt;bean id="userDAO" class="qwicket.myapp.dao.impl.UserDaoImpl"&amp;gt;&lt;br /&gt; &amp;lt;property name="sessionFactory"&amp;gt;&lt;br /&gt;  &amp;lt;ref local="sessionFactory" /&amp;gt;&lt;br /&gt; &amp;lt;/property&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&amp;lt;!-- End DAOs --&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Application --&amp;gt;&lt;br /&gt;&amp;lt;bean id="QwicketApplication"&lt;br /&gt; class="qwicket.myapp.QwicketApplication" /&amp;gt;&lt;br /&gt;&amp;lt;/beans&amp;gt;&lt;br /&gt;&lt;/pre&gt;Now to set up eclipse to build the application:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Open the External Tools Dialog from the toolbar&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Select "m2 build" in tree&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Click the New icon&lt;/li&gt;&lt;br /&gt;&lt;li&gt;For Name type "qwicket war"&lt;/li&gt;&lt;br /&gt;&lt;li&gt;For Base directory click Browse Workspace, select qwicket&lt;/li&gt;&lt;br /&gt;&lt;li&gt;For Goals type "clean compile war:war" (an alternative is "clean package")&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Click Apply, then Run&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;To run the application:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Open the Run Dialog from the toolbar&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Click Java Application in the tree&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Click the New icon&lt;/li&gt;&lt;br /&gt;&lt;li&gt;For Project: Browse, then select qwicket&lt;/li&gt;&lt;br /&gt;&lt;li&gt;For the Main class: Browse, then select qwicket.myapp.util.Start&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Click Apply, then Run&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Note: you can also run the app using maven: "mvn jetty:run". If you choose to do this, there's no longer a need for Start.java. Also, maven will automatically build and package your war if necessary.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4262232281084864898-8923911978320717401?l=javathoughts.capesugarbird.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=R4XYmujPRqs:ZYU_cMa5vRU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=R4XYmujPRqs:ZYU_cMa5vRU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?a=R4XYmujPRqs:ZYU_cMa5vRU:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/capesugarbird/javathoughts?i=R4XYmujPRqs:ZYU_cMa5vRU:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://javathoughts.capesugarbird.com/feeds/8923911978320717401/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4262232281084864898&amp;postID=8923911978320717401" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/8923911978320717401?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4262232281084864898/posts/default/8923911978320717401?v=2" /><link rel="alternate" type="text/html" href="http://javathoughts.capesugarbird.com/2007/09/qwicket-maven-and-eclipse.html" title="Qwicket, maven and eclipse" /><author><name>Julian Sinai</name><uri>http://www.blogger.com/profile/06209296951442764412</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="07518040812032340810" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total></entry></feed>
