Tuesday, February 05, 2008

A simple context-sensitive help system using Wicket (redux)

In this post, I showed a simple context-sensitive online help system using Wicket. I wanted to enhance this capability to support these features:
  • Load arbitrary html files without requiring an associated Wicket class
  • Support anchors within html files
  • Supply the names of the html files and anchors in properties files
  • Support internationalised help
  • Pop up the help in an external window, enabling the user to keep working in the main browser window
  • Reuse the same help window every time the user clicks a help button
  • Use an image button for the help link
  • Supply internationalized alt text for the help button
  • Automatically make the help button invisible if no help file name is supplied

I am using Wicket 1.3.0, but it should work for prior releases too.

Example


First, an example of how to use it:

MyWebPage.java


...
add(new HelpButtonPanel("help"));
...

MyWebPage.html


...
<div wicket:id="help" class="help"></div>
...

MyWebPage.properties


...
# This translates to the URL /doc/MyWebPage.html#
help.filename=MyWebPage
help.anchor=
...

The Implementation


HelpButtonPanel.java


import org.apache.commons.lang.StringUtils;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.ResourceReference;
import org.apache.wicket.markup.html.image.Image;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.html.link.PopupSettings;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.StringResourceModel;

/**
* A help button that pops up a separate window.
*
* The help is expected to be in the "doc" directory, a sibling to WEB-INF.
*
* The help filename is required, the anchor is optional. These strings go in a
* property file. Do NOT provide the .html extension. Example:
*
* help.filename=foohelpfilename
*
* help.anchor=foohelpanchorname
*
*/
public class HelpButtonPanel extends Panel
{
private final StringResourceModel helpFilenameModel, anchorModel;

/**
* Use this constructor for the main help button on a page. It reads the
* strings help.filename, help.anchor from the web page properties.
*/
public HelpButtonPanel(String id)
{
this(id, null);
}

/**
* Use this constructor for other help on a page. It reads the strings
* help.filename, help.anchor from the model you supply.
*/
public HelpButtonPanel(String id, IModel model)
{
super(id);

if (model == null)
model = new WebPageModel();

// Read the strings "help.filename", "help.anchor" from the supplied
// property model
helpFilenameModel = new StringResourceModel("help.filename", this,
model);
anchorModel = new StringResourceModel("help.anchor", this, model);

// Create the link to pop up the help
final HelpLink helplink = new HelpLink("helplink", this,
helpFilenameModel, anchorModel);
add(helplink);

// The help link is an image
final Image img = new Image("helpimg", new ResourceReference(
ImageAnchor.class, "help.png"));
helplink.add(img);

// Alt text for the image
final StringResourceModel altModel = new StringResourceModel("help",
this, null);
img.add(new AttributeModifier("alt", altModel));
}

/** Hide the help button if no help file is supplied. */
@Override
protected void onBeforeRender()
{
setVisible(!StringUtils.isEmpty(helpFilenameModel.getString()));
super.onBeforeRender();
}

/** A model that reads the help filename from the web page's properties file */
private class WebPageModel extends AbstractReadOnlyModel
{
private static final long serialVersionUID = 6407656031992546286L;

@Override
public Object getObject()
{
return getWebPage().getModelObject();
}
}

/** A link that pops up an external window containing the help text */
private static class HelpLink extends ExternalLink
{
private static final long serialVersionUID = 4409345256205400217L;

public HelpLink(String id, HelpButtonPanel parent,
IModel helpFilenameModel, IModel anchorModel)
{
// Format in HelpButtonPanel.properties:
// /doc/{filename}.html#{anchor}
super(id, new StringResourceModel("helplink.href", parent, parent
.getModel(), new Object[]
{ helpFilenameModel, anchorModel }));

final PopupSettings p = new PopupSettings(PopupSettings.RESIZABLE
| PopupSettings.SCROLLBARS);
p.setWidth(400).setHeight(400).setTop(23);
// Set the window name so we can reuse the same window instance.
p.setWindowName("HelpWindow");
setPopupSettings(p);
}
}
}

There is a reference above to ImageAnchor. This is a technique I use to load images from a common location. See this post for where I blogged about it.

HelpButtonPanel.html


<html xmlns:wicket>
<wicket:panel>
<a wicket:id="helplink">
<img wicket:id="helpimg" alt="Help" border="0" />
</a>
</wicket:panel>
</html>

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.

4 comments:

Eelco Hillenius said...

Also note that you can use the XML format for messages, see http://chillenious.wordpress.com/2006/11/13/wicket-now-supports-resource-bundles-in-xml-format/

Thanks for the post!

swapnil said...
This comment has been removed by the author.
swapnil said...

hi Can you give a precise example for this i am trying same thing,but i am facing pbms like,
1. model = new WebPageModel()----for all pages i am getting null as model.
2.help.filename, help.anchor------what should be values for them
3.helplink.href what should value and what will be file name.
4.i am getting blank content at link.

Julian Sinai said...

Swapnil:

1. model = new WebPageModel()----for all pages i am getting null as model.

There must be something wrong with your code, because WebPageModel is a class you create. Try substituting getWebPage().getModelObject(), or pass a model into the class that uses it (HelpButtonPanel).

2.help.filename, help.anchor------what should be values for them

There are examples in the post. For example if your help page is myhelp.html, then use myhelp for help.filename, and an empty string for help.anchor.

3.helplink.href what should value and what will be file name.

helplink.href goes only in one place: HelpButtonPanel.properties, and its value is:
helplink.href=/doc/{0}.html#{1}

4.i am getting blank content at link.

Try the above first and see if it's still blank.