Geertjan's Blog

  • July 2, 2007

Visual Editor (Part 5)

Geertjan Wielenga
Product Manager
The linkButtonPressed method is invoked when a LinkButton is pressed. What we want to happen at that point is... the related help topic should open! That way, someone working on the helpset has a central place from which to open all the topics and edit them. Extremely handy. But how to do it? Every toc item that we have identified as such, because of the presence of a 'target' attribute, looks similar to this in the XML file:

<tocitem text="Refactoring an Enterprise Bean" target="refactor_ejb"></tocitem>

The 'target' attribute links it to a 'target' attribute in a separate so-called map file, such as, in the case above, this one:

<mapID target="refactor_ejb" url="ejb/editing/refactor_ejb.html"></tocitem>

In other words, we need to programmatically match these two 'target' attributes and then grab the related 'url' value and open the referenced HTML page in the IDE. How to do that? Follow the steps below.

  1. We need to be able to access the 'target' and 'url' attributes in the map file, so use the JAXB Wizard as described in the first part of this series, and generate Java objects for the http://java.sun.com/products/javahelp/map_2_0.dtd.

  2. In your project, create a class called TocUtilities. To clean up your project, move all the various utility methods into that class. Then add the following:

    • A method for accessing the map file. Unlike the toc file, which is provided by the data object that is loaded when we double-cick the node in one of the explorer windows, we have no immediate way of accessing the map file. We need to access the map file so that we can unmarshall it, via the Java object created in the previous step. Here is an extremely useful utility method that iterates through all the files in the folder where the toc file is, until it finds a file that ends with 'jhm', of which there really should only be one (it's an assumption, but a valid one) and then identifies that as the map file:

      public File getMapFile(TocDataObject dObj) {
      File mapFile = null;
      FileObject f = FileUtil.toFileObject(new File("/" + dObj.getPrimaryFile().getParent().getPath()));
      DataFolder df = DataFolder.findFolder(f);
      DataObject[] objs;
      objs = df.getChildren();
      for (int i = 0; i < objs.length; i++) {
      DataObject dataObject = objs[i];
      if (dataObject.getName().endsWith("jhm")) {
      mapFile = FileUtil.toFile(dataObject.getPrimaryFile());
      return mapFile;

      If we were really pedantic, we would create Java objects for the 'hs' file, which is where the location of the 'jhm' file is defined, and then identify the location of the 'jhm' file by traversing the 'hs' file. But, we are not really pedantic, so we won't go down that cumbersome and unrewarding route. (Plus, we would need to assume the folder where the 'hs' file is found, or work it out by reading the manifest file, all of which would not really get us much further because our initial assumption, that the map file is in the same folder as the toc file, is unlikely to be proved wrong.)

    • A method that unmarshalls the file identified by the previous method:

      public Map unmarshallMapFile(TocDataObject dObj) {
      File mapFile = getMapFile(dObj);
      //Make sure you get the correct import statement here,
      //because there are several map classes available from the JDK:
      Map map = new Map();
      try {
      JAXBContext jaxbCtx = JAXBContext.newInstance(map.getClass().getPackage().getName());
      Unmarshaller unmarshaller = jaxbCtx.createUnmarshaller();
      map = (Map) unmarshaller.unmarshal(mapFile);
      } catch (JAXBException ex) {
      // TODO handle exception
      Logger.getLogger("global").log(Level.SEVERE, null, ex);
      return map;

      This code is the same as before, for unmarshalling the toc. However, here we first find the map file. We find it in a separate method, because maybe we might want to find it at a later stage again, and at that point we don't want to have to unmarshall the file again, because doing that once is enough.

    • A method that iterates through the map file and matches the 'target' attribute with a 'target' attribute from the toc file. These attributes are unique, so if we have a match, then we know for sure that the 'url' attribute of the map element is the URL that points to the related HTML file. So that URL is what we return at the end of this method:

      public String matchTargets(TocDataObject dObj, String target) {
      List listMap = unmarshallMapFile(dObj).getMapID();
      MapID id = null;
      for (int i = 0; i < listMap.size(); i++) {
      id = listMap.get(i);
      if (target.equals(id.getTarget())) {
      return id.getUrl();
      return "Broken link!";

  3. Now we need an editor in which to open the identified HTML file. We need to use the DataEditorSupport class to associate our data object with an editor. So here is our editor support class, it is absolutely simple. It tells the editor that we want the file to be editable, for example, because of the definition of the save() method. When we use this class later, the file will open in the HTML editor, because of the file's ".html" file extension:

    public final class HTMLTopicEditor extends DataEditorSupport {
    public HTMLTopicEditor(DataObject d) {
    super(d, new E(d));
    private static final class E extends DataEditorSupport.Env implements SaveCookie {
    public E(DataObject d) {
    protected FileObject getFile() {
    return getDataObject().getPrimaryFile();
    protected FileLock takeLock() throws IOException {
    return super.getDataObject().getPrimaryFile().lock();
    public void save() throws IOException {
    HTMLTopicEditor ed = (HTMLTopicEditor)this.findCloneableOpenSupport();

  4. Up until this point, our TocPanel class had no need of using the TocDataObject. Now, however, we need to have access to it so that we can pass the data object to our matchTargets method in the TocUtilities class. So change the PanelFactory class, to enable the data object to be passed in to the TocPanel class. Then modify the TocPanel constructor to receive the data object.

  5. Finally, after all the preparation, we are ready to implement the linkButtonPressed method, in our TocPanel class. Here it is:

    public void linkButtonPressed(Object ddBean, String target) {
    String url = utils.matchTargets(dObj, target);
    String fullUrl = "/" + dObj.getPrimaryFile().getParent().getPath() + "/" + fullUrl;
    FileObject fo = FileUtil.toFileObject(new File(url));
    if (!(fo == null)) {
    try {
    new HTMLTopicEditor(TocDataObject.find(fo)).open();
    } catch (DataObjectNotFoundException ex) {
    } else {
    JOptionPane.showMessageDialog(null, fullUrl);

    If there is a match, we open the HTML file in our editor. If there's no match, we tell the user in the simplest possible way.

And now, you're able to click a LinkButton to open the related HTML file! Hurray!

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.