Chapter 7. Other Uilib Widgets

7.1. Trees

7.1.1. TreeWidget & TreeNodeWidget

org.araneaframework.uilib.tree.TreeWidget allows representation of hierarchical data in a manner that has become traditional in GUIs, as an expandable/collapsable tree. TreeWidget represents trees' root node, which is special in that it is not usually really rendered on-screen but serves as point where child nodes are attached. Child nodes of TreeWidget are TreeNodeWidgets acquired from associated TreeDataProvider or could be attached by the developer. The TreeWidget supports expanding and collapsing of all those nodes.

TreeDataProvider is a simple interface with ability to return data belonging to any given node of the tree.

public interface TreeDataProvider extends Serializable {
  /**
   * Returns a list of child nodes for specified parent node.
   */
  List<TreeNodeWidget> getChildren(TreeNodeContext parent);

  /**
   * Returns whether the specified tree node has any children.
   */
  boolean hasChildren(TreeNodeContext parent);
}

As is apparent from the definition of TreeDataProvider, descendants of the TreeWidget that are to be presented in a tree, must be of type TreeNodeWidget. TreeNodeWidget is the superclass of TreeWidget that also has child nodes and will be rendered too. Node rendering is done with display widget that is passed to TreeNodeWidget in its constructor.

/** Childless collapsed node, rendered by display widget. */
public TreeNodeWidget(Widget display);
/** Node with children. Expanded by default. */
public TreeNodeWidget(Widget display, List nodes);
/** Node with children, expand/collapse state can be set with corresponding flag. */
public TreeNodeWidget(Widget display, List nodes, boolean collapsed);

Display widget can be any widget that can render itself, it is rendered in the place of tree node instead of TreeNodeWidget, which is just a data holder. Very often, display widget is BaseUIWidget which renders itself according to some JSP template. TreeNodeWidget does not accept independent TreeDataProvider, its children are acquired from TreeWidget's TreeDataProvider.

TreeWidget enriches the Environment with TreeContext. TreeNodeWidget enriches the Environment of its display widget with TreeNodeContext. Through these contexts display widgets have access to owning tree node and root of the tree.

7.1.2. Tree JSP tags

<ui:tree>

Renders tree with given id.

Attributes
AttributeRequiredDescription
idyesID of the tree widget.
Examples
<?xml version="1.0" encoding="UTF-8"?>
<ui:tree id="simpleTree"/>
<!-- nothing more required, nodes' display widgets will take care of rendering the tree nodes.>

7.2. Tabs

Tabs provide the tabbed interface for switching between different content.

7.2.1. TabContainerWidget

TabContainerWidget manages widgets that are to be displayed and manipulated in separate tabs. It provides basic operations like adding, removing, disabling, enabling and switching between tabs. Its main operation mode is stateful, where switching between tabs preserves state in inactive tabs. It can be made to operate statelessly (or with custom state management) by constructing new tab with WidgetFactory instead of Widget.

Following methods are available for adding tabs:

void addTab(String id, String labelId, Widget contentWidget);
void addTab(String id, Widget labelWidget, Widget contentWidget);
void addTab(String id, String labelId, WidgetFactory contentWidgetFactory);
void addTab(String id, Widget labelWidget, WidgetFactory contentWidgetFactory);

And for common tab operations:

boolean removeTab(String id);
boolean disableTab(String id);
boolean enableTab(String id);
boolean selectTab(String id);

For its children, TabContainerWidget is accessible from Environment as TabContainerContext.

Since Aranea 1.2.2, one can also add a tab switch listener. The listener is invoked right before the current tab is about to be replaced with a new tab. Here note that the current tab may be null (when the selectTab() method was not called before). The listener is defined as a sub-interface of TabContainerContext:


  /**
   * An interface for tab switch listeners. Tab switch occurs when the currently
   * selected tab changes.
   * @since 1.2.2
   */
  interface TabSwitchListener extends Serializable {

    /**
     * A listener for tab switching. Before the selected tab will be replaced
     * with a new one, this method is called to check whether the switch is
     * allowed. Note that the <code>selectedTab</code> parameter may be
     * <code>null</code> if no tab is currently selected.
     * <p>
     * The last parameter is a tab switch closure that is executed only when the
     * listener returns <code>true</code> or when the listener executes it
     * itself. Therefore, this closure can also be used with
     * {@link ConfirmationContext#confirm(Closure, String)}.
     * 
     * @param selectedTab The currently selected tab. May be <code>null</code>.
     * @param newTab The tab that will replace the current one.
     * @param switchClosure A closure that handles tab switch.
     * @return <code>true</code>, if the switch is allowed.
     */
    boolean onSwitch(TabWidget selectedTab, TabWidget newTab, Closure switchClosure);

  }     

The default implementation is DefaultTabSwitchListener, which basically corresponds to the default behaviour. However, one can easily write and set their own handler for a tab container. In addition, the switchClosure parameter helps integrating this solution with the ConfirmationContext.

7.2.2. Tab JSP tags

<ui:tabContainer>

Opens the tab container context and renders the labels for all tabs inside this container.

Attributes
AttributeRequiredDescription
idyesID of the tab container widget.

<ui:tabBody>

Renders the body of currently active (selected) tab. Must be used inside tab container context.

<ui:tabs>

Renders specified tab container fully—writes out tab labels and active tab's content.

Attributes
AttributeRequiredDescription
idyesID of the tab container widget.

Usage of tab tags in JSP templates.

<ui:tabs id="tabContainer"/>

<!-- equivalent to previous, but one could add custom content before and after tab body -->
<ui:tabContainer id="tabContainer">
  <ui:tabBody/>
</ui:tabContainer>

7.3. Context Menu

Context menu is the menu that pops up when mouse right-click is made on some item (widget) in an UI.

7.3.1. ContextMenuWidget & ContextMenuItem

Widget that represents context menu content is called ContextMenuWidget. By convention, it is usually added to component hierarchy as a child of the widget for which it provides context menu.

widgetWithContextMenu.addWidget("contextmenu", new ContextMenuWidget(...));

ContextMenuWidget sole constructor has a single ContextMenuItem parameter. ContextMenuItem is a hierarchical container for menu items, consisting of menu entries and entry labels. There are two types of menu entries: ContextMenuEventEntry and ContextMenuActionEntry —which respectively produce events (see Section 2.7.2, “Event Listeners”) or actions (see Section 2.7.3, “Action Listeners”) upon selection of context menu item. Except for produced event type, these entries are constructed identically. Creating context menu entry which tries to invoke widget event listener of someWidget without supplying any event parameters is done as follows:

ContextMenuEntry entry = new ContextMenuEventEntry("someEvent", someWidget);

When menu entry produced event requires some parameters, javascript function must be defined that returns desired parameters. When left undefined, function() { return null; } is used. Sample javascript function which always returns value of some fixed DOM element as event parameter looks like this:

var contextMenuEventParameterSupplier = function() {
  // make sure that function call was really triggered by menu selection
  if (araneaContextMenu.getTriggeringElement()) {
    // supply value of DOM element 'someElement' as event parameter
    return $('someElement').value;
  }
  return null;
};

Corresponding menu entry which detects and submits event parameters is created similarly to previous:

ContextMenuEntry entry = new ContextMenuEventEntry("someEvent", someWidget, "contextMenuEventParameterSupplier");

Whole construction of single multi-element and multi-level ContextMenuWidget will look similar to this:

ContextMenuItem root = new ContextMenuItem();
// entry that produces event when clicked on
ContextMenuItem firstEntry = 
  new ContextMenuItem(
    getL10nCtx().localize("someLabel"), // label 
    new ContextMenuEventEntry("someEvent", this));
// entry that just functions as submenu
ContextMenuItem secondEntry = new ContextMenuItem(getL10nCtx().localize("submenu"));
// action producing entry in a submenu
ContextMenuItem thirdEntry = 
  new ContextMenuItem(
    getL10nCtx().localize("someOtherLabel"), 
    new ContextMenuActionEntry("someAction", this, "contextMenuActionParameterSupplier"));
secondEntry.addMenuItem(thirdEntry);
root.addMenuItem(firstEntry);
root.addMenuItem(secondEntry);

7.3.2. Rendering context menus with JSP template

To get functional context menus on client-side, template must define the sections belonging to a widget which has the context menu and register the context menu. Context menus are known to work in Internet Explorer and Mozilla Firefox browsers.

<ui:contextMenu>

Registers the context menu in current template for widget with id.

Attributes
AttributeRequiredDescription
idyesID of the ContextMenuWidget
updateRegionsnoRegions which should be updated when context menu event has been processed.
globalUpdateRegionsnoGlobal regions which should be updated when context menu event has been processed.

As one widget might be rendered in separate sections in a template, all these sections need to be identified so that correct context menu can be detected at all times. This is done with <ui:widgetMarker> tag surrounding the widget sections.

<ui:widgetMarker>

Defines the surrounded section as belonging to a widget with id. It writes out some HTML tag with class attribute value set to widgetMarker.

Attributes
AttributeRequiredDescription
idyesID of the widget which section is surrounded by this marker tag.
tagnoHTML tag to render the marker with. Default is HTML div.

Example: JSP template containing context menu.

<!-- Defines context menu for a ListWidget "list" -->
<ui:list id="list">
  <ui:listFilter> ... </ui:listFilter>
  <ui:listRows>
    <!-- marker surrounding widget with identifier "list" -->  
    <ui:widgetMarker id="list" tag="tbody">
      <ui:row id="${listFullId}.row${rowRequestId}">
                <!-- cells -->
      </ui:row>
        </ui:widgetMarker>
  </ui:listRows>

  <!-- Context menu widget with conventional id -->
  <ui:contextMenu id="list.contextmenu"/>
</ui:list>

7.4. Partial Rendering

Imagine that you have a big web page with input form, and you want certain input controls to update something on that page as user changes the value of the control. Now, would it be efficient if the value changes, its onchange event submits the data so that an OnChangeEventListener could read it and return the same page with slight changes? The main problem here is that a small change should not force the user wait until the page reloads. Here is the part where partial rendering comes in.

Note

Partial rendering is more thoroughly described by Alar Kvell's bachelor thesis Aranea Ajax. This section concentrates mostly on how a programmer can make partial rendering work.

7.4.1. The Two Steps

First of all, a page must have a part (parts) that needs to be updated when an event occurs. These regions are marked with the <ui:updateRegion> tag by also indicating its ID to reference it later.

Note

It is not possible to update an HTML table cell. One needs to update the entire row by using the <ui:updateRegionRows> tag.

Next, one needs to specify the updateRegions attribute of the input control that has an event registered. The attribute value should contain a comma-separated list of update region IDs that need to be update due to the event. It is important for this value to be specified, because otherwise the entire page would be posted to the server.

When the input control has the updateRegions attribute defined, Aranea will use Ajax to send the form data to the server, invoke the OnEventListener associated with the event, and return the parts of the pages defined as update regions. Finally, the script on the client side will replace the update regions on the page with the received ones. For everything else on the same page, it will remain the same.

Note that these two steps described above are all that need to be taken to make partial rendering possible with Aranea.

7.4.2. Partial Rendering Example

Now let's take a look at a short example where partial rendering is used. The following is the code snippet from Aranea demo application component Easy AJAX w/ 'update regions'.

<ui:row>
  <ui:formElement id="beastSelection">
    <ui:cell styleClass="name">
      <ui:label />
    </ui:cell>
    <ui:cell>
      <ui:select updateRegions="ajaxBeasts"/>
    </ui:cell>
  </ui:formElement>
</ui:row>

<ui:updateRegionRows id="ajaxBeasts">
  <c:if test="${not empty form.elements['concreteBeastControl']}">
    <ui:row>
      <ui:formElement id="concreteBeastControl">
        <ui:cell styleClass="centered-name">
          <ui:label />
        </ui:cell>
        <ui:cell>
          <ui:checkboxMultiSelect type="vertical" />
        </ui:cell>
      </ui:formElement>
    </ui:row>
  </c:if>
</ui:updateRegionRows>

In the example, you can see that it does not matter in which order the update region is declared and referenced. Also, because data (form elements) is displayed using table rows, we must use <ui:updateRegionRows> tag here to make it work. However, the most important part of this example is that the <ui:select> control defines the update region it wishes to update.

Note

Currently file upload inputs don't work with update regions because the JavaScript cannot read the unsubmitted file and serialize it to send it to the server. Therefore, if you provide the updateRegions attribute for a file upload input, the file won't reach the server. We hope to find a solution to this limitation in near future.