Skip to main content

Customizing oXygen Author Component

In this article I'm going to guide you through the process of customization of oXygen Author Component. More specifically I'm going to create a new operation for the DITA framework that will generate and insert an xml fragment into the current document. While you can find the official documentation on this topic very useful, it misses any real code examples. Of course, you can download the author component startup project but it'll help those looking how to integrate the component as a Java Applet, thus, it's slightly irrelevant for us.

Requirements
In order to make a customization for the Author Component you need to have an oXygen standalone installation. For the development of Java customizations you'll need oxygen.jar on the classpath. This library is available in the oXygen installation directory as well as in the Author SDK project and in the author component startup project. Moreover, inside last two resources you can find javadocs and partial sources for oXygen classes. Unfortunately, these resources are not published in any Maven repository. So I've put them to the private corporate one to be able to create a pure Maven project. This is not a must though.

Customization
The customization process includes the following steps:
  1. First we should implement our own Java extension of the ro.sync.ecss.extensions.api.AuthorOperation interface which will show the custom dialog and then will use oXygen API to insert the proper code fragment at the caret position. The oXygen application is swing-based, so the custom dialog should extend javax.swing.JDialog. However, in our case it'd make more sense to reuse oXygen code and extend ro.sync.ecss.extensions.commons.ui.OKCancelDialog instead.
  2. Then pack the compiled classes into a JAR, say custom.jar, and put it inside the corresponding oXygen framework directory (or anywhere else of your choice).
  3. Then run the standalone oXygen installation, navigate to the Preferences->Document Type Associations page, edit the DITA document type and in the Classpath tab add a reference to the custom.jar. Then in the Author tab open the Actions subtab, create a new action based on your operation and then add it to the toolbar and menus in the corresponding subtabs. This configuration change will be saved in the OXYGEN_INSTALL_DIR\frameworks\dita\dita.framework file.
  4. As soon as a new operation works fine in the oXygen standalone installation, you can bundle the OXYGEN_INSTALL_DIR\frameworks\dita folder with the Author Component by wrapping it to obtain the frameworks.zip.jar archive. This last step is required if you want to distribute your change with a Java Applet.
Now let's look at the code snippet for the custom operation:
@API(type = APIType.INTERNAL, src = SourceType.PUBLIC)
public class InsertMyImageOperation extends InsertFragmentOperation {

    private static final String ARGUMENT_FRAGMENT = "fragment";

    public InsertMyImageOperation () {
        super();
    }

    /**
     * Loads the xml fragment and inserts it at the caret position.
     */
    @Override
    public void doOperation(AuthorAccess authorAccess, ArgumentsMap args) 
            throws AuthorOperationException {
        String xmlFragment = loadImageXmlFragment(authorAccess, args);
        super.doOperation(authorAccess, argsWithXmlFragment(args, xmlFragment));
    }

    /**
     * @see ro.sync.ecss.extensions.api.Extension#getDescription()
     */
    @Override
    public String getDescription() {
        return "Insert a custom image";
    }

    /**
     * Creates the custom dialog that returns the dialog info object.
     * The dialog info is used for xml fragment generation.
     */
    private String loadImageXmlFragment(AuthorAccess authorAccess, 
                                        ArgumentsMap args) {
        InsertMyImageDialog myImageDialog = new InsertMyImageDialog(
                (Frame) authorAccess.getWorkspaceAccess().getParentFrame(),
                authorAccess.getAuthorResourceBundle(), true);
        myImageDialog.setLocationRelativeTo(
                (Component) authorAccess.getWorkspaceAccess().getParentFrame());

        MyDialogInfo info = myImageDialog.showDialog();
        return MyImageFragmentGenerator.generateXml(info);
    }

    /**
     * Supplies the parent class arguments with the custom xml fragment.
     */
    private ArgumentsMap argsWithXmlFragment(final ArgumentsMap args, 
                                             final String xmlFragment) {
        return new ArgumentsMap() {
            @Override
            public Object getArgumentValue(String argumentName) {
                if (argumentName.equals(ARGUMENT_FRAGMENT)) {
                    return xmlFragment;
                } else {
                    return args.getArgumentValue(argumentName);
                }
            }
        };
    }
}
So the operation simply shows a custom dialog to the user, waits until it returns the required information, generates an xml fragment based on the user-supplied data and uses the oXygen API to insert the fragment properly.
I've reused a complex logic of ro.sync.ecss.extensions.commons.operations.InsertFragmentOperation that not only inserts the fragment for me but also provides some useful arguments that can be adjusted in the oXygen settings.
Here is the code snippet for the custom dialog:
@API(type = APIType.INTERNAL, src = SourceType.PUBLIC)
public class InsertMyImageDialog extends OKCancelDialog {

    public InsertMyImageDialog(Frame parentFrame, 
                               AuthorResourceBundle authorResourceBundle, 
                               boolean modal) {
        super(parentFrame, "My title", modal);
        JPanel mainPanel = new JPanel(new GridBagLayout());

        // Here custom components are being created and added to the mainPanel.

        add(mainPanel);
        configureSize();
    }

    /**
     * Shows the dialog and returns the information
     * that is required to generate the xml fragment.
     */
    public MyDialogInfo showDialog() {
        setVisible(true);
        return (getResult() == RESULT_OK) ? createMyDialogInfo() : null;
    }

    /**
     * Creates the information object with the dialog data.
     */
    private MyDialogInfo createMyDialogInfo() {
        MyDialogInfo info = new MyDialogInfo();

        // Here info object is being filled with the data from dialog components.

        return info;
    }

    /**
     * Sets proper dialog size.
     */
    private void configureSize() {
        setResizable(true);
        setMinimumSize(new Dimension(0, 0));
        pack();
        setMinimumSize(getSize());
    }
}
I've cleaned up the code to show the structure better. I've used two more custom classes above: MyDialogInfo and MyImageFragmentGenerator. Their meaning and logic must be clear from the code so I've left them out.

Comments

  1. Hi Ivan,

    Great blog post, your details are concise and exact.
    One more thing, for Oxygen 16 we are working to offer the Author Component project as a Maven archetype.

    Regards,
    Radu

    Radu Coravu
    XML Editor, Schema Editor and XSLT Editor/Debugger
    http://www.oxygenxml.com

    ReplyDelete
    Replies
    1. Thanks, Radu! Actually you helped a lot with your comments in emails while I was working on this. Maven support would be very useful indeed.

      Delete
  2. We also had a similar but not that detailed topic in the Oxygen users manual:

    http://www.oxygenxml.com/doc/ug-oxygen/#tasks/addCustomActionHowTo.html

    ReplyDelete

Post a Comment

Popular posts from this blog

DynamicReports and Spring MVC integration

This is a tutorial on how to exploit DynamicReports reporting library in an existing Spring MVC based web application. It's a continuation to the previous post where DynamicReports has been chosen as the most appropriate solution to implement an export feature in a web application (for my specific use case). The complete code won't be provided here but only the essential code snippets together with usage remarks. Also I've widely used this tutorial that describes a similar problem for an alternative reporting library.
So let's turn to the implementation description and start with a short plan of this how-to:
Adding project dependencies.Implementing the Controller part of the MVC pattern.Modifying the View part of the MVC pattern.Modifying web.xml.Adding project dependencies
I used to apply Maven Project Builder throughout my Java applications, thus the dependencies will be provided in the Maven format.

Maven project pom.xml file:
net.sourceforge.dynamicreportsdynamicrepo…

Choosing Java reporting tool - part 2

I've provided a general overview of possible solutions to get a reporting/exporting functionality in the previous post. This is the second overview of alternatives based on JasperReports reporting engine.

Since the previous part I've done the following:
Implemented a simple report using both DynamicJasper and DynamicReports to compare them from technical side.Investigated JasperServer features and tried to implement a simple report for JasperServer instance (it appeared we already have a ready licensed installation of JasperServer that makes it unreasonable to install a fresh one).
First, the comparison results of Java libraries (DynamicJasper and DynamicReports):
Both libraries suffer from poor-quality or missing Java docs but they look a bit better in DynamicJasper.Taking into account the point 1, a developer has to use online documentation and to review the code. Here the code looks definitely nicer and more readable for DynamicReports. With respect t…

Do It Yourself Java Profiling

This article is a free translation of the Russian one that is a transcript of the Russian video lecture done by Roman Elizarov at the Application Developer Days 2011 conference.
The lecturer talked about profiling of Java applications without any standalone tools. Instead, it's suggested to use internal JVM features (i.e. threaddumps, java agents, bytecode manipulation) to implement profiling quickly and efficiently. Moreover, it can be applied on Production environments with minimal overhead. This concept is called DIY or "Do It Yourself". Below the lecture's text and slides begin.
Today I'm giving a lecture "Do It Yourself Java Profiling". It's based on the real life experience that was gained during more than 10 years of developing high-loaded finance applications that work with huge amounts of data, millions currency rate changes per second and thousands of online users. As a result, we have to deal with profiling. Application profiling is an i…