4.3.2. Adding menu items

As a simple example we'll add a menu item to the context menu of the product-tree. When the user clicks on the menu item, a message box showing the text "Hello World!" shall be opened.
The label of the menu item shall be "Show Hello". Additionally, an icon shall be displayed on the left side of the label as shown in the following example:

Figure 4.3.2. Menu item example

As the text displayed in the user interface should depend on the user interface language, we'll add the phrases "Show Hello" and "Hello World!" as properties to the locale.properties file (see Chapter 4.2, Creating a plug-in package):

my_plugin.description = A simple Hello World Plug-in
my_plugin.show_hello = Show Hello
my_plugin.hello_world = Hello World!

Listing 4.3.7. locale.properties (hello world example)

To avoid naming conflicts with other plug-ins, any added property name in locale.properties should start with the plug-in identifier followed by a dot (here: my_plugin.).
By adding the text phrases to the locale.properties file, we can later on provide translations of these text phrases for different user interface languages. For example, to support the German language, you just need to add a file named locale_de.properties to the plugin package that contains the German translations of the phrases:

my_plugin.description = Ein einfaches Hallo Welt Plug-in
my_plugin.show_hello = Zeige Hallo
my_plugin.hello_world = Hallo Welt!

Listing 4.3.8. locale_de.properties (hello world example)

The next step is to create the plugin implementation by extending the class org.docma.plugin.web.DefaultWebPlugin and overwriting the onInitMainWindow() method:
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
package myexample;
 
import org.docma.plugin.*;
import org.docma.plugin.web.*;
 
public class MyMenuPlugin extends DefaultWebPlugin {
 
static final String MY_ITEM_ID = "my_plugin_menu_item";
 
@Override
public void onInitMainWindow(WebPluginContext ctx,
WebUserSession sess)
{
String item_label = sess.getLabel("my_plugin.show_hello");
String icon_url = "plugins/my_plugin/images/my_icon.png";
sess.addMenuItem(ctx, "treemenu", MY_ITEM_ID, item_label,
icon_url, null, false);
}
}

Listing 4.3.9. MyMenuPlugin.java

The first operation in onInitMainWindow()retrieves the item text by calling the method getLabel() on the WebUserSession instance. The method getLabel() returns the string for the passed property name (here: "my_plugin.show_hello"). The property name can be any property defined in locale.properties. The menu item is created by calling the method addMenuItem(). The signature of this method is:
void addMenuItem(WebPluginContext ctx, String parentMenuId,
String itemId, String title, String iconUrl,
String neighbourId, boolean insertBefore);
Following a description of the parameters:
ctx
The first parameter is of type WebPluginContext and defines the owner of the new menu item.
parentMenuId
The parameter parentMenuId identifies an existing menu, where the item shall be inserted as sub-element. Following table lists the predefined menus that exist in Docmenta:
Menu IDDescription
contentmenu The toolbar menu.
treemenu The context menu of the product-tree.
treemenuPaste The sub-menu "Paste" in the context-menu of the product-tree.
treemenuExtra The sub-menu "Extra" in the context-menu of the product-tree.
In the example above, the string "treemenu" is passed as parent menu identifier, i.e. the menu item is inserted in the context menu of the product-tree.
itemId
The parameter itemId defines the identifier of the new menu item. This has to be a string that uniquely identifies the menu item within the complete user interface. To avoid naming conflicts (e.g. with other plug-ins), it is recommended to include the plug-in identifier as prefix of the item identifier. In the example above the identifier "my_plugin_menu_item" has been chosen.
title
The displayed label of the menu item.
iconUrl
The relative URL of the icon that shall be displayed next to the label. The URL has to be relative to the web-application root. In the example above, the URL has been set to "plugins/my_plugin/images/my_icon.png", i.e. an image named my_icon.png has to be placed in the plug-in package in the sub-folder web/plugins/my_plugin/images.
neighbourId
The parameter neighbourId defines an existing menu entry on the same level where the new item shall be inserted. If null is passed, then the new item is added as last element.
insertBefore
The parameter insertBefore defines whether the new item shall be inserted before or after the entry that is identified by the parameter neighbourId. If neighbourId is null, then this parameter has no effect.
You can now create a zip package and install the plugin as described in Chapter 4.2, Creating a plug-in package. The content of the package should be as follows:

Figure 4.3.3. Package structure of menu plug-in

Until now the plug-in just adds a "Show Hello" menu item as last entry to the context menu:

Figure 4.3.4. Menu item added as last entry of the context menu

If you click on the menu item, nothing happens. To introduce some functionality we have to be able to react on user interface (UI) events. The following extended version of the listing above adds a listener for UI events to the plug-in. The line numbers of the added lines are highlighted:
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
package myexample;
 
import org.docma.plugin.*;
import org.docma.plugin.web.*;
 
public class MyMenuPlugin extends DefaultWebPlugin
implements UIListener {
 
static final String MY_ITEM_ID = "my_plugin_menu_item";
 
@Override
public void onInitMainWindow(WebPluginContext ctx,
WebUserSession sess)
{
String item_label = sess.getLabel("my_plugin.show_hello");
String icon_url = "plugins/my_plugin/images/my_icon.png";
sess.addMenuItem(ctx, "treemenu", MY_ITEM_ID, item_label,
icon_url, null, false);
sess.setUIListener(ctx, this);
}
 
@Override
public void onEvent(UIEvent evt)
{
String targetId = evt.getTargetId();
if (MY_ITEM_ID.equals(targetId) && evt.isClick()) {
WebUserSession sess = evt.getSession();
String msg = sess.getLabel("my_plugin.hello_world");
sess.showMessage(msg);
}
}
}

Listing 4.3.10. MyMenuPlugin.java (extended)

In the Docmenta Plug-in API a listener for UI events can be any object that implements the org.docma.plugin.web.UIListener interface. In the listing above, the plug-in itself is defined as UI listener. This is done by adding implements UIListener to the class declaration. Furthermore, for the implementation of the UIListener interface, the method onEvent(UIEvent evt) has been added. Finally, the plug-in instance needs to be registered as listener for UI events. In the listing above this is done by the line sess.setUIListener(ctx, this).
The onEvent() method contains the code that reacts on UI events. All event data can be retrieved from the object that is passed in the parameter evt. In our example, the first operation is to check if the event is caused by the menu item with identifier "my_plugin_menu_item". As you might guess, the expression evt.getTargetId() returns the identifier of the component that received the event. This check is necessary, because all UI components that are added by the same plug-in share the same listener, i.e. the listener that has been registered by the setUIListener() method.
Additionally it is checked if the event is actually a click event. This is done by including the expression evt.isClick() in the condition. Checking the event type should always be done, because the same component could receive events of different types. For example, a button could receive click- and focus-events. But an action should probably be executed, only if the click-event is received. Also be aware that new event types might be added with newer versions of Docmenta. Your plug-in should be prepared for this.
Finally, if a click-event for the added menu item has been detected, the text to be displayed in the message box is retrieved from the locale.properties file. This is done by the expression sess.getLabel("my_plugin.hello_world"). As you can see, the WebUserSession instance can be retrieved from the UIEvent instance through the getSession() method. The text is then displayed in a message box by calling the utility method showMessage().
When you install the plug-in and click on the "Show Hello" menu item, a message box similar to the following should be shown:

Figure 4.3.5. Hello World message box

Define menu item position
In the example above, the new menu item is just added as last element to the menu. The Plug-in API provides more options where to add a new menu entry. If you want the new item to be added as first entry in the menu, then you have to pass the value true for the insertBefore argument (and keep the null value for the neighbourId argument). In other words, the addMenuItem() call has to be changed as follows:
sess.addMenuItem(ctx, "treemenu", MY_ITEM_ID, item_label,
                 icon_url, null, true);
You can also insert a new menu item before or after an existing menu entry. For this you have to pass the identifier of the existing entry in the neighbourId parameter. Following illustration shows all identifiers of the existing entries in the standard Docmenta context menu.

Figure 4.3.6. Context menu identifiers

The identifiers of the entries in the toolbar menu are shown in the following illustration:

Figure 4.3.7. Toolbar menu identifiers

For example, to insert the menu item before the "Extra" sub-menu, the addMenuItem() call has to be changed as follows:
sess.addMenuItem(ctx, "treemenu", MY_ITEM_ID, item_label,
                 icon_url, "treemenuExtra", true);
Adding a separator
To add a separator line between your menu item and the "Extra" entry, following code can be used:
sess.addMenuItem(ctx, "treemenu", MY_ITEM_ID, item_label,
                 icon_url, "treemenuExtra", true);
sess.addMenuSeparator(ctx, "treemenu", "my_plugin_separator",
                      "treemenuExtra", true);
The method addMenuSeparator() works the same way as addMenuItem(), except that it does not require the parameters for the label and icon. In the example above, the identifier "my_plugin_separator" is used for the separator. The result should be as follows:

Figure 4.3.8. Menu separator example

Adding a sub-menu
You can also create your own sub-menu as shown in the following example:
final String SUBMENU_ID = "my_plugin_submenu";
String submenu_label = sess.getLabel("my_plugin.submenu_label");
sess.addSubMenu(ctx, "treemenu", SUBMENU_ID, submenu_label,
                null, "treemenuExtra", true);
sess.addMenuItem(ctx, SUBMENU_ID, MY_ITEM_ID, item_label,
                 icon_url, null, false);
The first line stores the identifier to be used for the new sub-menu in the constant SUBMENU_ID. The second line retrieves the label for the new sub-menu and stores it in the variable submenu_label. Then the sub-menu is inserted before the "Extra" entry in the context menu by calling the addSubMenu() method. The identifier of the new sub-menu (SUBMENU_ID) is then passed as parentMenuId argument in the following call to addMenuItem(). This way the new item is inserted as child entry in the previously added sub-menu. Assuming that you have added following line to locale.properties,

my_plugin.submenu_label = My Submenu

Listing 4.3.11.

the result should be as follows:

Figure 4.3.9. Sub-menu example

Enabling and disabling menu items
Sometimes you might want to enable or disable menu items, depending on which nodes are currently selected in the product-tree. The Plug-in API provides a possibility to update menu items just before a menu is opened. Every time a menu is opened, an "onOpen" event is sent to the registered UI listener (just before the menu is displayed). This allows the UI listener to update the menu items before the items are rendered on the screen.
If the context menu of the product-tree is opened, then an "onOpen" event occurs with the target identifier "treemenu". If the toolbar menu is opened, then an "onOpen" event occurs with the target identifier "contentmenu". Note that an "onOpen" event only occurs for the root menus, but not for sub-menus.
As an example, following implementation of the onEvent() method enables our context menu item, only if a single node is selected. Otherwise the menu item is disabled.
@Override
public void onEvent(UIEvent evt)
{
WebUserSession sess = evt.getSession();
String targetId = evt.getTargetId();
if (("treemenu".equals(targetId)) && evt.isOpen()) {
int cnt = sess.selectedNodesCount();
sess.setMenuDisabled(MY_ITEM_ID, cnt != 1);
} else if (MY_ITEM_ID.equals(targetId) && evt.isClick()) {
String msg = sess.getLabel("my_plugin.hello_world");
sess.showMessage(msg);
}
}
In the example above the occurance of an "onOpen" event is determined with the expression evt.isOpen(). The method isOpen() returns true if the event name is "onOpen", otherwise it returns false. Therefore isOpen() is just a shortcut for evt.getName.equals("onOpen").
The number of currently selected product-tree nodes is returned by the method selectedNodesCount() and stored in the variable cnt. Afterwards, the menu item is disabled or enabled by the expression sess.setMenuDisabled(MY_ITEM_ID, cnt != 1). The first argument (MY_ITEM_ID) defines the menu item. The boolean value that is passed as second argument determines whether the item shall be disabled (true) or enabled (false).
As you might guess, as long as a menu item is disabled, clicking on the item does not cause any "onClick" event, i.e. in the example above, no message box is shown when clicking on the disabled item.
Hiding menu items
Instead of disabling a menu item, an item could also be completely hidden. This can be done by calling the method setMenuVisible().
Updating menu items
The label and icon of a menu item can be changed after the item has been created, by calling the method setMenuLabel() or setMenuImage(), respectively.
Checkbox menu items
A menu item can also provide the functionality of a checkbox. In this case, instead of the icon a checkbox is shown next to the label. Following listing shows an example of adding a checkbox item to the context menu.
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
package myexample;
 
import org.docma.plugin.*;
import org.docma.plugin.web.*;
 
public class MyMenuPlugin extends DefaultWebPlugin
implements UIListener {
 
static final String MY_ITEM_ID = "my_plugin_menu_item";
@Override
public void onInitMainWindow(WebPluginContext ctx,
WebUserSession sess)
{
String item_label = sess.getLabel("my_plugin.show_hello");
sess.addMenuItem(ctx, "treemenu", MY_ITEM_ID, item_label,
null, "treemenuExtra", true);
sess.setMenuCheckbox(MY_ITEM_ID, true);
sess.setMenuChecked(MY_ITEM_ID, false);
sess.setUIListener(ctx, this);
}
 
@Override
public void onEvent(UIEvent evt)
{
if (MY_ITEM_ID.equals(evt.getTargetId()) && evt.isClick()) {
WebUserSession sess = evt.getSession();
boolean state = sess.isMenuChecked(MY_ITEM_ID);
sess.setMenuChecked(MY_ITEM_ID, !state);
}
}
}

Listing 4.3.12. MyMenuPlugin.java (checkbox example)

In the onInitMainWindow() method, a new item is added by calling addMenuItem() the same way as in the previous examples, except that this time the value null is passed in the parameter iconUrl. The expression sess.setMenuCheckbox(my_item_id, true) in the next line defines the item to be a checkbox-item. The second parameter of setMenuCheckbox() defines whether the item shall be rendered as normal menu item (false) or as a checkbox-item (true). Finally, the state of the checkbox is set by the expression sess.setMenuChecked(my_item_id, false), i.e. the initial state of the checkbox is unchecked.
In the onEvent() method, the state of the checkbox is then toggled. This is achieved by retrieving the current state of the checkbox via
    boolean state = sess.isMenuChecked(my_item_id);
and setting the negated value via
    sess.setMenuChecked(my_item_id, !state);
The result is that every time the user clicks on the menu item, the checkbox switches between checked and unchecked:

Figure 4.3.10. Checkbox menu item