Thursday, September 6, 2012

Netbeans 7.2 File Template Module with Wizard Tutorial

This tutorial complements the Netbeans File Template Module Tutorial on how to get the parameters for Freemarker template from wizards. So I would suggest you to read the just-mentioned tutorial first to understand the basics. The Netbeans File Template Module Tutorial (aka NFTMT) only briefly mentioned that you can use wizards to get variables for Freemarker templates. In their exact words, "The value of "names" could come from a variety of places, typically from a wizard panel, where the user, in this case, would have selected a set of names from a list.". Not so insightful, is it?
I was completely lost on how to use a wizard together with Freemarker templates. I did lots of googling in 2 days but only found outdated tutorials (for Netbeans 6.9, where layer.xml still exists), some briefly mentioned comments and tiny code snippets. Combining all those in my code chimpanzee brain, I have got a working and updated (for Netbeans 7.2) solution on how to use wizards together with Freemarker templates.

Adding a Wizard

Assuming you have followed the steps in NFTMT and get a working a file template, the first thing you need to do before adding a wizard is to delete the package-info.java file. Sorry, the wizards don't like package-info or maybe I, a mere code chimpanzee, can't please the wizards enough to work with package-info.
Add a wizard to the module project by right-clicking on org.myorg.additionalfiletemplates and choosing New > Wizard.


Select New File in wizard type dialog since the module is all about creating a new file. Put any number of wizard panels you want in Number of Wizard Panels textbox. For this tutorial, we will just put 1. Going Next, you will need to add Class Name Prefix and Display Name. Here you will need to select a icon file of 16x16 pixels so prepare it beforehand.


After clicking Finish, you will see 4 new entries added to your project: a VisualPanel, a WizardIterator, a WizardPanel and an html file. You can delete the newly added hTMLTemplate.html file because you will be using Description.html for template description.


In this tutorial, we will reuse Netbeans' new HTML file dialog with an extra text field added to enter the title of the HTML page. Firstly, we will open the HTMLTemplateVisualPanel1.java to add a label and a text field. You will need GUI Builder plugin for Java SE installed in your Netbeans IDE to add label and text field graphically as shown. Change the variable names jLabel1 and jTextField1 to jlblTitle and jtxtTitle respectively and edit text on label to Title:.
Make changes as follows to HTMLTemplateVisualPanel1.java.

    static final String TITLE = "Title";

    ...

    @Override
    public String getName() {
        return "Name, Location and Title";
    }

    public String getTitle() {
        return jtxtTitle.getText();
    }

In HTMLTemplateWizardPanel1.java:

    @Override
    public void storeSettings(WizardDescriptor wiz) {
        // use wiz.putProperty to remember current panel state
        wiz.putProperty(HTMLTemplateVisualPanel1.TITLE, getTitleFromVisualPanel());
    }

    private String getTitleFromVisualPanel() {
        return getComponent().getTitle();
    }


Then, add "Project API", "Project UI API", "Nodes API", "Lookup API" and "File System API" libraries to the Netbeans module project. And add content="%Your Template File%" and scriptEngine="freemarker" values in @TemplateRegistration above HTMLTemplateWizardIterator class declaration.
@TemplateRegistration(
        folder = "JSP_Servlet", 
        displayName = "#HTMLTemplateWizardIterator_displayName", 
        iconBase = "org/myorg/additionalfiletemplates/html.png", 
        content = "HTML.html",
        description = "Description.html",
        scriptEngine = "freemarker")
@Messages("HTMLTemplateWizardIterator_displayName=HTML with Title")
public final class HTMLTemplateWizardIterator implements WizardDescriptor.InstantiatingIterator<WizardDescriptor> {

Change getPanels() function in HTMLTemplateWizardIterator.java as follows:

private List<WizardDescriptor.Panel<WizardDescriptor>> getPanels() {
  if (panels == null) {
    panels = new ArrayList<WizardDescriptor.Panel<WizardDescriptor>>();
            
    // Change to default new file panel and add our panel at bottom
    Project p = Templates.getProject(wizard);
    SourceGroup[] groups = ProjectUtils.getSources(p).getSourceGroups(Sources.TYPE_GENERIC);

    // SimpleTargetChooser is the default new file panel,
    // Add our panel at the bottom
    WizardDescriptor.Panel<WizardDescriptor> advNewFilePanel  = Templates.buildSimpleTargetChooser(p, groups).bottomPanel(new HTMLTemplateWizardPanel1()).create();
    panels.add(advNewFilePanel);

    String[] steps = createSteps();
    for (int i = 0; i < panels.size(); i++) {
      Component c = panels.get(i).getComponent();
      if (steps[i] == null) {
        // Default step name to component name of panel. Mainly
        // useful for getting the name of the target chooser to
        // appear in the list of steps.
        steps[i] = c.getName();
      }
      if (c instanceof JComponent) { // assume Swing components
        JComponent jc = (JComponent) c;
        jc.putClientProperty(WizardDescriptor.PROP_CONTENT_SELECTED_INDEX, i);
        jc.putClientProperty(WizardDescriptor.PROP_CONTENT_DATA, steps);
        jc.putClientProperty(WizardDescriptor.PROP_AUTO_WIZARD_STYLE, true);
        jc.putClientProperty(WizardDescriptor.PROP_CONTENT_DISPLAYED, true);
        jc.putClientProperty(WizardDescriptor.PROP_CONTENT_NUMBERED, true);
      }
    }
  }
  return panels;
}

After that, edit the instantiate() method to read user-defined values and process the FreeMarker Template.
    @Override
    public Set<?> instantiate() throws IOException {
        
        FileObject createdFile = null;
        
        // Read Title from wizard 
        String HtmlTitle = (String) wizard.getProperty(HTMLTemplateVisualPanel1.TITLE);
        
        // FreeMarker Template will get its variables from HashMap.
        // HashMap key is the variable name.
        Map args = new HashMap();
        args.put("title", HtmlTitle);

        //Get the template and convert it:
        FileObject template = Templates.getTemplate(wizard);
        DataObject dTemplate = DataObject.find(template);

        //Get the package:
        FileObject dir = Templates.getTargetFolder(wizard);
        DataFolder df = DataFolder.findFolder(dir);

        //Get the class:
        String targetName = Templates.getTargetName(wizard);

        //Define the template from the above,
        //passing the package, the file name, and the map of strings to the template:
        DataObject dobj = dTemplate.createFromTemplate(df, targetName, args);

        //Obtain a FileObject:
        createdFile = dobj.getPrimaryFile();
        
        // Return the created file.
        return Collections.singleton(createdFile);
    }


Finally, use title variable in FreeMarker Template file HTML.html.
<!DOCTYPE html>
<html>
  <head>
    <title>${title}</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <div>TODO write content</div>
  </body>
</html>

"Clean and Build" the project and "Install/Reload in Development IDE" to test your new module (New File > Web > HTML with Title). You will see the ${title} is replaced with whatever you entered in the wizard text field. That's all, folks!

(Please check out Geertjan's blog https://blogs.oracle.com/geertjan/ also if you want to know more about Netbeans module development. )