Aranea—Java Web Framework Construction and Integration Kit

Reference Manual

Jevgeni Kabanov

Rein Raudjärv

Toomas Römer

Taimo Peelo

Martti Tamm

Version 1.2 (Work In Progress)


Table of Contents

1. Introduction
1.1. Overview
1.2. Organization
2. Components, Widgets and Services
2.1. Introduction
2.2. Coding Conventions
2.2.1. Checked versus Unchecked Exceptions
2.2.2. Public versus Framework Interfaces
2.2.3. Components and Their Orthogonal Properties
2.3. Components and Environment
2.3.1. Composite Pattern and Paths
2.3.2. Environment
2.3.3. Messaging Components
2.3.4. State and Synchronization
2.4. InputData and OutputData
2.4.1. Extensions
2.4.2. HttpInputData and HttpOutputData
2.5. Services
2.5.1. Filter Services
2.6. Widgets
2.6.1. ViewModel and Rendering
2.7. Application Widgets
2.7.1. Children Management
2.7.2. Event Listeners
2.7.3. Action Listeners
2.7.4. Environment
2.7.5. Overridable Methods
2.7.6. InputData and OuputData
2.7.7. View Model and Rendering
2.7.8. Putting It All Together
2.8. Standard Contexts
2.8.1. MessageContext
2.8.2. LocalizationContext
2.8.3. FlowContext
2.8.4. PopupWindowContext
2.8.5. OverlayContext
2.8.6. MenuContext
2.8.7. ConfirmationContext
2.8.8. ManagedServiceContext, ThreadContext, and TopServiceContext
3. Framework and Configuration
3.1. Overview
3.2. Application Configuration
3.2.1. web.xml
3.2.2. aranea-conf.xml
3.2.3. aranea-conf.properties
3.2.4. AraneaSpringDispatcherServlet
3.2.5. Extending Dispatcher
3.2.6. ConfigurationContext
3.2.7. StateVersioningContext
3.3. Framework Assembly
3.4. Framework Configuration
3.5. Framework Components
3.5.1. Localization Filter
3.5.2. AJAX Update Regions Filter
3.5.3. Environment Configuration Filter
3.5.4. Critical Exception Handler
3.5.5. File Uploading Filter
3.5.6. HTTP Response Headers Filter
3.5.7. JSP Configuration Filter
3.5.8. User Messages Filter
3.5.9. Popup Windows Filter
3.5.10. Component Serialization Auditing Filter
3.5.11. Statistics Logging Filter
3.5.12. Browser Window Cloning Filter
3.5.13. Multi-submit Protection Filter
3.5.14. Class Reloading Filter
3.5.15. Client State Serialization Filter
3.5.16. Extension File Import Filter
3.5.17. Bookmarking/URL Mounting Filter
3.5.18. Root Flow Container
3.5.19. Overlay Container
3.5.20. System Form Field Storage Filter
3.5.21. Window Scroll Position Filter
3.6. Other
3.6.1. Extension Resources
4. JSP and Custom Tags
4.1. Aranea Standard Tag Library
4.2. System Tags
4.2.1. <ui:importScripts>
4.2.2. <ui:importStyles>
4.2.3. <ui:body>
4.2.4. <ui:systemForm>
4.2.5. <ui:messages>
4.3. Basic Tags
4.3.1. <ui:attribute>
4.3.2. <ui:elementContent>
4.3.3. <ui:element>
4.3.4. <ui:keyboardHandler>
4.3.5. <ui:eventKeyboardHandler>
4.4. Widget Tags
4.4.1. <ui:widgetContext>
4.4.2. <ui:widget>
4.4.3. <ui:widgetInclude>
4.5. Event-producing Tags
4.5.1. <ui:eventButton> and <ui:eventLinkButton>
4.5.2. <ui:onLoadEvent>
4.5.3. <ui:registerPopups>
4.6. HTML entity Tags
4.6.1. Predefined entity tags
4.7. Putting Widgets to Work with JSP
4.8. Layout Tags
4.8.1. <ui:layout>
4.8.2. <ui:row>
4.8.3. <ui:cell>
4.8.4. <ui:updateRegion>, <ui:updateRegionRow>, and <ui:updateRegionRows>
4.9. Presentation Tags
4.9.1. <ui:bold>
4.9.2. <ui:italic>
4.9.3. <ui:font>
4.9.4. <ui:style>
4.9.5. <ui:newline>
4.9.6. <ui:tooltip>
4.9.7. <ui:basicButton>
4.9.8. <ui:basicLinkButton>
4.9.9. <ui:link>
4.10. Programming JSPs without HTML
4.11. Customizing Tag Styles
4.12. Making New JSP Tags
4.12.1. Utilities and base classes
4.12.2. Inheriting tag attributes from base tags.
4.12.3. Widgets and events
4.12.4. Layouts
5. Forms and Data Binding
5.1. Forms
5.1.1. FormWidget
5.1.2. Controls
5.1.3. Constraints
5.1.4. Data
5.1.5. Converters
5.1.6. Form validation
5.2. Forms JSP Tags
5.2.1. Common attributes for all form element rendering tags.
5.2.2. <ui:form>
5.2.3. <ui:formElement>
5.2.4. <ui:label>
5.2.5. <ui:simpleLabel>
5.2.6. <ui:button>
5.2.7. <ui:linkButton>
5.2.8. <ui:formKeyboardHandler>
5.2.9. <ui:formEnterKeyboardHandler>
5.2.10. <ui:formEscapeKeyboardHandler>
5.2.11. <ui:textInput>
5.2.12. <ui:autoCompleteTextInput>
5.2.13. <ui:comboTextInput>
5.2.14. <ui:textInputDisplay>
5.2.15. <ui:numberInput>
5.2.16. <ui:numberInputDisplay>
5.2.17. <ui:floatInput>
5.2.18. <ui:floatInputDisplay>
5.2.19. <ui:passwordInput>
5.2.20. <ui:textDisplay>
5.2.21. <ui:valueDisplay>
5.2.22. <ui:textarea>
5.2.23. <ui:richtextarea>
5.2.24. <ui:richTextAreaInit>
5.2.25. <ui:textareaDisplay>
5.2.26. <ui:hiddenInput>
5.2.27. <ui:checkbox>
5.2.28. <ui:checkboxDisplay>
5.2.29. <ui:fileUpload>
5.2.30. <ui:dateInput>
5.2.31. <ui:dateInputDisplay>
5.2.32. <ui:timeInput>
5.2.33. <ui:timeInputDisplay>
5.2.34. <ui:dateTimeInput>
5.2.35. <ui:dateTimeInputDisplay>
5.2.36. <ui:select>
5.2.37. <ui:selectDisplay>
5.2.38. <ui:multiSelect>
5.2.39. <ui:multiSelectDisplay>
5.2.40. <ui:radioSelect>
5.2.41. <ui:radioSelectItem>
5.2.42. <ui:radioSelectItemLabel>
5.2.43. <ui:checkboxMultiSelect>
5.2.44. <ui:checkboxMultiSelectItem>
5.2.45. <ui:checkboxMultiSelectItemLabel>
5.2.46. <ui:conditionalDisplay>
5.2.47. <ui:conditionFalse>
5.2.48. <ui:conditionTrue>
5.2.49. <ui:listDisplay>
5.2.50. <ui:automaticFormElement>
5.3. Form Lists
5.3.1. FormListWidget
5.3.2. FormListUtil
5.3.3. Form Row Handlers
5.3.4. Models
5.3.5. In Memory Form List
5.4. Form Lists JSP Tags
5.4.1. <ui:formList>
5.4.2. <ui:formListRows>
5.4.3. <ui:formListAddForm>
6. Lists and Query Browsing
6.1. Introduction
6.2. Lists API
6.2.1. A Typical List
6.2.2. Fields
6.2.3. Ordering
6.2.4. Filtering
6.2.5. Backend Data Provider
6.2.6. Quick Overview on How to Use
6.3. Selecting List Rows
6.4. List JSP Tags
6.4.1. <ui:list>
6.4.2. <ui:listFilter>
6.4.3. <ui:listFilterButton> and <ui:listFilterClearButton>
6.4.4. <ui:listRows>
6.4.5. <ui:listRowButton>
6.4.6. <ui:listRowLinkButton>
6.4.7. <ui:listRowCheckBox> And <ui:listRowRadioButton>
6.5. Editable Lists
7. Other Uilib Widgets
7.1. Trees
7.1.1. TreeWidget & TreeNodeWidget
7.1.2. Tree JSP tags
7.2. Tabs
7.2.1. TabContainerWidget
7.2.2. Tab JSP tags
7.3. Context Menu
7.3.1. ContextMenuWidget & ContextMenuItem
7.3.2. Rendering context menus with JSP template
7.4. Partial Rendering
7.4.1. The Two Steps
7.4.2. Partial Rendering Example
8. Third-party Integration
8.1. Spring Application Framework
8.1.1. BeanFactory, ApplicationContext, WebApplicationContext
8.1.2. Spring Localization Filter
8.1.3. Widget Dependency Injection
9. Javascript Libraries
9.1. Third-party Javascript Libraries
9.1.1. The DHTML Calendar (http://www.dynarch.com/projects/calendar/)
9.1.2. Prototype (http://www.prototypejs.org/)
9.1.3. script.aculo.us (http://script.aculo.us/)
9.1.4. TinyMCE (http://tinymce.moxiecode.com/)
9.1.5. Prototip (http://www.nickstakenburg.com/projects/prototip/)
9.1.6. ModalBox (http://www.wildbit.com/labs/modalbox/)
9.1.7. log4javascript (http://log4javascript.org/)
9.2. Aranea Clientside Javascript

Chapter 1. Introduction

1.1. Overview

Aranea is a Java Hierarchical Model-View-Controller Web Framework that provides a common simple approach to building the web application components, reusing custom or general GUI logic and extending the framework. The framework is assembled from a number of independent modules with well-defined responsibilities and thus can be easily reconfigured to perform new and unexpected tasks. The controller is separated into a hierarchy of components that can react to user or system events. The framework is completely view agnostic, but provides a thorough library of JSP custom tags that target building GUIs without writing a line of HTML. All components and modules are simple Plain Old Java classes without any XML mappings and thus usual Object-Oriented design techniques can be applied. Aranea manages the component field persistence automatically and inherently supports nested state.

Aranea is logically separated in the following modules:

Aranea Core
Contains the core interfaces and base implementations that define Aranea base abstractions and their contracts. Includes packages org.araneaframework and org.araneaframework.core and is packaged into aranea-core.jar.
Aranea Framework
Framework module sits on top of the Core module and contains the implementation of the Aranea Web Framework that does not directly depend on any container. Framework module includes package org.araneaframework.framework and its subpackages and is packaged into aranea-framework.jar.
Aranea HTTP
HTTP module extends the Framework module with services that use a Servlet container. Servlet module includes package org.araneaframework.http and its subpackages and is packaged into aranea-servlet.jar.
Aranea Integration
Spring module integrates Aranea with the Spring IoC container. Spring module includes package org.araneaframework.integration.spring and its subpackages and is packaged into aranea-spring.jar.
Aranea UiLib
UiLib module contains reusable GUI widgets and supporting API. UiLib module includes package org.araneaframework.uilib and its subpackages and is packaged into aranea-uilib.jar.
Aranea JSP
JSP module contains a custom tag library, including tags that render UiLib widgets. JSP module includes package org.araneaframework.jsp and its subpackages and is packaged into aranea-jsp.jar.
Aranea Backend
Backend module contains supporting classes that are to be used in the application service layer (e.g. backend list data provider helper classes). Backend module includes package org.araneaframework.backend and its subpackages and is packaged into aranea-backend.jar.

These modules depend on each other as follows:

1.2. Organization

The rest of this manual is organized as follows:

Components, Widgets and Services

This chapter describes the core Aranea abstractions in detail generally not necessary to just develop application code so it can be skipped during the first reading. It is quite dry on the examples, but its understanding is crucial to develop Aranea extensions. To get a quick understanding of how to program with widgets read Section 2.7, “Application Widgets”.

Framework and Configuration

This chapter describes how to assemble and configure both applications and the Aranea framework itself. It also describes in detail main components of the Aranea framework. The most interesting part for a beginner would be Section 3.2, “Application Configuration”.

JSP and Custom Tags
This chapter describes how to render Aranea widgets and services with custom JSP tag library supplied in the Aranea distribution.
Forms and Data Binding

This chapter describes how Aranea manages reading data from request, validating and converting it to the model objects.

Lists and Query Browsing

This chapter describes how to make pageable, filterable and orderable tables in Aranea.

Uilib widgets

This chapter looks at various other Uilib widgets and explains their use.

Third-party Integration

This chapter describes Aranea integration hooks for third-party toolkits and frameworks. At the moment it includes Spring.

Javascript Libraries

This chapter describes the Javascript libraries that Aranea uses and the Javascript API that Aranea provides.

Chapter 2. Components, Widgets and Services

2.1. Introduction

Aranea framework and component model are very simple and implemented purely in Plain Old Java. There are no XML mappings, code generation or bytecode enhancement. The whole component model consists mainly of five interfaces: org.araneaframework.Component, org.araneaframework.Service, org.araneaframework.Widget, org.araneaframework.Environment, org.araneaframework.Message and some conventions regarding their usage and implementation.

This chapter describes the core Aranea abstractions in detail generally not necessary to just develop application code so it can be skipped during the first reading. It is quite dry on the examples, but its understanding is crucial to develop Aranea extensions. To get a quick understanding of how to program applications with widgets read Section 2.7, “Application Widgets”.

2.2. Coding Conventions

2.2.1. Checked versus Unchecked Exceptions

It is our firm belief that checked exceptions are unnecessary in Controller and therefore Aranea will in most cases allow to just declare your overriding method as throws Exception. On the other hand no framework interfaces throw checked exceptions so the exception-handling boilerplate can be delegated to a single error-handling component.

2.2.2. Public versus Framework Interfaces

Since the application programmer implements the same components that are used for framework extension, it is important to discourage the access to public framework interfaces (which are necessarily visible in the overridden classes). Thus a simple convention is applied for core framework interfaces, which is best illustrated with the following example.

public interface Service extends Component, Serializable {
  public Interface _getService();

  public interface Interface extends Serializable {
    public void action(Path path, InputData input, OutputData output) throws Exception;
  }
}

As one can see, the real interface methods are relocated to an inner interface named Interface, that can be accessed using a method _get<InterfaceName>(), which starts with an underscore to discourage its use. As a rule of a thumb, in Aranea the methods starting with an underscore should only be used, when one really knows what one is doing.

2.2.3. Components and Their Orthogonal Properties

Aranea has three main types of components: org.araneaframework.Component, org.araneaframework.Service and org.araneaframework.Widget. These components also have a number of orthogonal properties (like Viewable, Composite), which are represented by interfaces that need to be implemented. Since some particular API methods expect a particular type of component with a particular property (e.g. ViewableWidget) one would either have to abandon static type safety or define a lot of meaningless interfaces that would clutter the Javadoc index and confuse the source code readers. The approach chosen in Aranea is to make such interfaces internal to the property, like in the following example.

public interface Viewable extends Serializable {
  public Interface _getViewable();

  interface Interface extends Serializable {
    public Object getViewModel() throws Exception;
  }

  public interface ViewableComponent extends Viewable, Component, Serializable {}
  public interface ViewableService extends ViewableComponent, Service, Serializable {}
  public interface ViewableWidget extends ViewableService, Widget, Serializable {}
}

2.3. Components and Environment

org.araneaframework.Component represents the unit of encapsulation and reuse in Aranea. Components are used to both provide plug-ins and extensions to the framework and to implement the actual application-specific code. A component has (possibly persistent) state, life cycle, environment and a messaging mechanism.

public interface Component extends Serializable {
  public Scope getScope();             /** @since 1.1 */
  public Environment getEnvironment(); /** @since 1.1 */
  public boolean isAlive();            /** @since 1.1 */

  public Component.Interface _getComponent();

  public interface Interface extends Serializable {
    public void init(Environment env) throws Exception;
    public void destroy() throws Exception;
    public void propagate(Message message) throws Exception;
    public void enable() throws Exception;
    public void disable() throws Exception;
  }
}

The component life cycle goes as follows:

  1. init() —notifies the component that it should initialize itself passing it the Environment . A component can be initialized only once and the environment it is given stays with it until it is destroyed.
  2. All other calls (like propagate() ) should be done when a component is alive, initialized and enabled.
  3. disable() —notifies the component that it will be disabled and will not receive any calls until it is enabled again. A component is enabled by default.
  4. enable() —notifies the component that it has been enabled again. This call may only be done after a disable() call.
  5. destroy() —notifies the component that it has been destroyed and should release any acquired resources and such. A component can be destroyed only once and should be initialized before that.

Further in the text we will refer to an initialized and not destroyed component instance that has a parent as live and one that has not been disabled or has been re-enabled as enabled.

Aranea provides a base implementation of the Componentorg.araneaframework.core.BaseComponent. This implementation mainly enforces contracts (including life cycle and some basic synchronization). A base class for application development org.araneaframework.core.BaseApplicationComponent is also available.

Component methods not dealing with lifecycle or messaging are accessible without getting at Component.Interface. Component.getScope() returns scoped identifier that uniquely identifies that Component in component hierarchy. Component.getEnvironment() returns information of Environment in which Component lives and Component.isAlive() allows component to check whether it is living at all :).

2.3.1. Composite Pattern and Paths

Composite pattern refers to a design approach prominent (specifically) in the GUI modeling when objects implementing the same interface are arranged in a hierarchy by containment, where the nodes of the tree propagate calls in some way to the leafs of the tree. It is shown on Figure 2.1, “Composite design pattern”.

Composite design pattern

Figure 2.1. Composite design pattern

Composite is one of the main patterns used in Aranea. It is mainly used to create a Hierarchical Controller using Component containment. In terms of Component interface Composite is used to propagate life cycle events and route messages (see Section 2.3.3, “Messaging Components”).

The flavor of the Composite pattern as used in Aranea typically means that every contained component has some kind of an identifier or name that distinguishes it from other children of the same parent (note that the child is not typically aware of its identifier). This identifiers are used to route messages and events and can be combined to form a full identifier which describes a "path" from the root component to the child in question. This paths are represented by a Iterator-like interface org.araneaframework.Path.

public interface Path extends Cloneable, Serializable {
  public Object getNext();
  public Object next();
  public boolean hasNext();
}

Each next() call will return the identifier of the next child in the path to the descendant in question. Default implementation (org.araneaframework.core.StandardPath) uses simple string identifiers like "a" or "b" and combines them using dots forming full paths like "a.b.c".

A Composite component may want to make its children visible to the outside world by implementing the org.araneaframework.Composite interface:

public interface Composite extends Serializable {
  public Composite.Interface _getComposite();
  public interface Interface extends Serializable {
    public Map getChildren();
    public void attach(Object key, Component comp);
    public Component detach(Object key); 
  }
}

This interface allows to both inspect and manipulate component children by attaching and detaching them from the parent component.

As most of the Aranea abstractions are built to be used with the Composite concept we will illustrate it in greater detail when examining other abstractions and their implementation. Further on we will assume that any Component has a parent that contains it and every child has some kind of name in relation to the parent unless noted otherwise (obviously there is at least one Composite that does not have a parent, but we don't really care about that at the moment).

2.3.2. Environment

org.araneaframework.Environment is another important concept that represents the way Components interact with the framework. Environment interface is rather simple:

public interface Environment extends Serializable {
  public Object getEntry(Object key);
  public Object requireEntry(Object key) throws NoSuchEnvironmentEntryException;
}

It basically provides means of looking up entry objects by their key. A typical usage of the Environment can be illustrated with an example.

...
MessageContext msgCtx = (MessageContext) getEnvironment().getEntry(MessageContext.class);
msgContext.showInfoMessage("Hello world!");
...

As one can see from the example Environment will typically allow to look up implementations of the interfaces using their Class as the key (this is in fact an Aranea convention in using and extending the Environment). The interfaces serving as keys for Environment entries are referred to as contexts. It is thus not unlike JNDI or some other directory lookups that allow to hold objects, however unlike them Environment is very specific to the Component it is given to, and can be influenced by its parents. In fact, all contexts available in the Environment will be provided to the Component by its parents or ancestors (in the sense of containment rather than inheritance). Thus, two different Components may have completely different Environments.

A default implementation of Environment is org.araneaframework.core.StandardEnvironment. It provides for creating an Environment from a java.util.Map, or extending an existing environment with map entries.

A component can provide an environment entry to its descendant, by providing it to the initializer of its direct child. For instance the MessageContext could be provided by the following message component:

public class MessageFilterService implements MessageContext, Component, Service {
  protected Service childService;
  public void setChildService(Service childService) {
    this.childService = childService;
  }

  public void init(Environment env) {
    childService.init(
      new StandardEnvironment(env, MessageContext.class, this);
  }

  //MessageContext implementation...
  public String showInfoMessage(String message) {
    //Show message to user...
  }  

  //...
}

After that the childService, its children and so on will be able to use the MessageContext provided by MessageFilterService. Of course this can be done simpler as shown in examples in this chapter, but this is how most of the components in Aranea provide new contexts to the Environment.

Sometimes, however, one may want to make his or her component or widget independent from the specific Environment. This can be achieved by using org.araneaframework.core.RelocatableDecorator:

  Service child = new RelocatableDecorator(new MyWidget());
  addWidget("c", child);

For example, this technique is used when a user clones a thread (middle mouse button click on a link), and it is necessary to clone the state. Then each widget is cloned, and a new Environmnet is provided to them.

2.3.3. Messaging Components

So far, we have looked at the component management and environment. However what makes the component hierarchy such a powerful concept is messaging. Basically, messaging allows us to send any events to any component in the hierarchy (including all components or a specific one). The messaging is incorporated using the org.araneaframework.Message interface

public interface Message extends Serializable {
  public void send(Object id, Component component) throws Exception;
}

and Component.propagate(Message message) method. The default behavior of the propagate() method should be to send the message to all component children, passing the send() method the identifier of the child and the child itself. It is up to the message what to do with the child further, but typically Message just calls the propagate() method of the child passing itself as the argument after possibly doing some custom processing (the double-dispatch OO idiom).

A standard Message implementation that uses double-dispatch to visit all the components in hierarchy is org.araneaframework.core.BroadcastMessage. It usage can be illustrated with the following example:

...
Message myEvent = new BroadcastMessage() {
  public void execute(Component component) throws Exception {
  if (component instanceof MyEventListener)
    ((MyEventListener) component).onMyEvent(data);
  }
}
myEvent.send(null, rootComponent);
...

This code will call all the components in the hierarchy that subscribed to the event and pass them a certain data parameter. As one can see, when calling Message.send() we will typically pass null as the first parameter, since it is needed only when propagating messages further down the hierarchy. Note that messages can be used to gather data from the components just as well as for passing data to them. For example one could construct message that gathers all FormWidgets from the widget hierarchy:

public static class FormWidgetFinderMessage extends BroadcastMessage {
  List formList = new ArrayList();

  protected void execute(Component component) throws Exception {
    if (component instanceof org.araneaframework.uilib.form.FormWidget) {
      formList.add(component);
    }
  }

  public List getAllForms() { return formList; }
}

Another standard Message implementation is org.araneaframework.core.RoutedMessage, which allows us to send a message to one specific component in the hierarchy as in the following example:

...
Message myEvent = new RoutedMessage("a.b.c") {
  public void execute(Component component) throws Exception {
    ((MyPersonalComponent) component).myMethod(...);
  }
}
myEvent.send(null, rootComponent);
...

This code will send the message to the specific component with path "a.b.c" and call myMethod() on it.

2.3.4. State and Synchronization

The handling of persistent state in Aranea is very simple. There are no scopes and every component state is saved until it is explicitly removed by its parent. This does not mean that all of the components are bound to the session, but rather that most components will live a period of time appropriate for them (e.g. framework components will live as long as the application lives, GUI components will live until user leaves them, and so on). This provides for a very flexible approach to persistence allowing not to clutter memory with unused components.

The end result is that typically one needs not worry about persistence at all, unless one is programming some framework plug-ins. All class fields (except in some cases transient fields) can be assumed to persist while the host object is live.

However such handling does not guarantee that the component state is anyhow synchronized. As a matter of fact most of the framework components outside the user session should be able to process concurrent calls and should take care of the synchronization themselves. However application components are typically synchronized by the framework. More information on the matter will follow in Section 2.7, “Application Widgets”.

2.4. InputData and OutputData

InputData is Aranea abstraction for a request, which hides away the Servlet API and allows us to run Aranea on different containers (e.g. in a portlet or behind a web service).

InputData provides access to the data sent to the component. This data comes in two flavours:

  • getScopedData(Path scope) returns a java.util.Map with the data sent specially to component, which unique identifier in the component hierarchy is scope. To get at this data, one can use construction inputData.getScopedData(getScope().toPath()) from a component.
  • getGlobalData() returns a java.util.Map with the data sent to the application generally.

In case Aranea is running on top of a servlet both these maps will contain only Strings. If one wants to access multi-valued parameters in servlet environment StandardServletInputData.getParameterValues(String name) method should be used (returns String array). In case of the usual path and scope implementation (as dot-separated strings) global data will contain the submitted parameters with no dots in them and scoped data will contain the parameters prefixed with the current component scope string.

Analogically OutputData is Aranea abstraction for response. HttpOutputData being the subinterface and StandardServletOutputData implementation for servlet environments.

As InputData and OutputData are typically connected, they can be retrieved from the other *Data structure using correspondingly getOutputData() and getInputData() methods.

2.4.1. Extensions

Both InputData and OutputData implement a way to extend their functionality without wrapping or extending the objects themselves. This is achieved by providing the following two methods:

void extend(Class interfaceClass, Object extension)
Object narrow(Class interfaceClass);

The following example should give an idea of applying these methods:

input.extend(FileUploadExtension.class, new FileUploadExtension(input));

...

FileUploadExtension fileUploadExt = 
  (FileUploadExtension) input.narrow(FileUploadExtension.class);
if (fileUploadExt.uploadSucceeded()) {
  //...
}

Note

Both HttpServletRequest and HttpServletResponse are available as InputData and OutputData extensions respectively.

2.4.2. HttpInputData and HttpOutputData

Although all of the core Aranea abstractions are independent of the Servlet API and web in general, we also provide a way to manipulate low-level HTTP constructs. To that goal we provide two interfaces, HttpInputData and HttpOutputData, which extend respectively InputData and OutputData.

Let's examine the HttpInputData. First of all it provides methods that are similar to the ones found in the HttpServletRequest:

MethodDescription
Iterator getParameterNames()Returns an iterator over names of the parameters submitted with the current request.
String[] getParameterValues(String name)Returns the array of values of the particular parameter submitted with the current request.
String getCharacterEncoding()Returns the character encoding that is used to decode the request parameters.
setCharacterEncoding(String encoding)Sets the character encoding that is used to decode the request parameters. Note that this must be called before any parameters are read according to the Servlet specification.
String getContentType()Returns the MIME content type of the request body or null if the body is lacking.
Locale getLocale()Returns the preferred Locale that the client will accept content in, based on the Accept-Language header. If the client request doesn't provide an Accept-Language header, this method returns the default locale for the server.

Note

Unlike InputData methods the parameters are presented as is and include both global and scoped parameters (the scoped ones are prefixed by the full name of the enclosing widget).

However next methods are a bit different from the HttpServletRequest alternatives:

MethodDescription
String getRequestURL()Returns the target URL of the current request.
String getContainerURL()Returns an URL pointing to the Aranea container (in most cases the dispatcher servlet).
String getContextURL()Returns an URL pointing to the Aranea container context (in most cases the web application root).
String getPath()Returns the path on the server starting from the dispatcher servlet that has been submitted as the part of the request target URL.
pushPathPrefix(String pathPrefix)Consumes the path prefix allowing children to be mapped to a relative path.
popPathPrefix()Restores the previously consumed path prefix.

The interesting part here are the methods that deal with the path. The problem is that unlike most common cases Aranea components form a hierarchy. Therefore if a parent is mapped to path prefix "myPath/*" and its child is mapped to a path prefix "myChildPath/*" if the path handling were absolute the child would never get the mapped calls. This is due to the child being really mapped to the path "myPath/myChildPath". Therefore the parent must consume the prefix "myPath/" using method pushPathPrefix() and then the child will be correctly matched to the relative path "myChildPath".

HttpOutputData contains methods that are comparable to the ones found in HttpServletResponse:

MethodDescription
String encodeURL(String url)Encodes the URL to include some additional information (e.g. HTTP session identifier). Note that Aranea may include some information not present in the servlet spec.
sendRedirect(String location)Sends an HTTP redirect to a specified location URL.
OutputStream getOutputStream()Returns an OutputStream that can be used to write to response. Note that unlike the Servlet specification, Aranea permits to use stream and writer interchangeably.
PrintWriter getWriter()Returns a PrintWriter that can be used to write to response. Note that unlike the Servlet specification, Aranea permits to use stream and writer interchangeably.
setContentType(String type)Sets the MIME content type of the output. May include the charset, e.g. "text/html; charset=UTF-8".
Locale getLocale()Returns the locale associated with the response.
String getCharacterEncoding()Returns the character encoding used to write out the response.
void setCharacterEncoding(String encoding)Sets the character encoding used to write out the response.

2.5. Services

org.araneaframework.Service is a basic abstraction over an event-driven Controller pattern that inherits life cycle, environment and messaging from the Component. The difference from the Component is as follows:

public interface Service extends Component, Serializable {
  public Interface _getService();

  public interface Interface extends Serializable {
    public void action(Path path, InputData input, OutputData output) throws Exception;
  }
}

The method action() is similar to the service() method in the Servlet API, InputData being an abstraction over a request and OutputData being an abstraction over a response (see Section 2.4, “InputData and OutputData”). Thus a service will both process the request parameters and render itself during this method call. However unlike servlets services can be Composite and may be defined both statically (on application startup) or dynamically (adding/removing new services on the fly).

Services are the basic working horses of the Aranea framework. They can generally be both synchronized and unsynchronized depending on the context. Services may also have persistent state and their lifetime is explicitly managed by their parent (see Section 2.3.4, “State and Synchronization”). The service life cycle is very simple—as long as the service is live and enabled it can receive action() calls, possibly several at a time.

Aranea provides a base implementation of the Serviceorg.araneaframework.core.BaseService and a base class for application development org.araneaframework.core.BaseApplicationService.

2.5.1. Filter Services

One of the most common ways to use the services is to create a filter service, that wraps a child service and provides some additional functionality and/or environment entries. To that purpose Aranea provides a filter base class — org.araneaframework.framework.core.BaseFilterService. This class implements all of the Service methods, by default just delegating them to the corresponding child methods. A common thing to do is override the action() method to add functionality and getChildEnvironment() to add environment entries, as shown in the following example:

public class StandardSynchronizingFilterService 
  extends BaseFilterService {
  
  protected Environment getChildEnvironment() {
    return new StandardEnvironment(
        getEnvironment(), 
        SynchronizingContext.class, 
        new SynchronizingContext() {});
  }
  
  protected synchronized void action(
      Path path, 
      InputData input, 
      OutputData output) throws Exception {
    super.action(path, input, output);
  }
}

More information on services and other components that make up the framework can be found in Chapter 3, Framework and Configuration.

2.6. Widgets

Widget is the main abstraction used to program applications in Aranea. Widget is specifically any class extending the org.araneaframework.Widget interface and adhering to a number of conventions. More generally, widgets are components that function both as controllers and GUI elements, and that have the following properties:

Synchronized
The widget is almost always accessed by a single thread, therefore there is rarely any need to think about synchronization. Usually one can assume that there is only one user using the widget at any time and program to service this user without any concern for concurrency. There is only one exception to this: one of the default Widget implementations is BaseApplicationWidget which allows registration of action listeners (see Section 2.7.3, “Action Listeners”) which can be invoked asynchronously when so desired.
Stateful
When programming widgets there is no need to concern oneself with juggling the HttpSession attributes or similar low-level mechanics. Widget state (meaning the class fields) is guaranteed to be preserved as long as the widget is alive. One can just use these fields to save the necessary data without any external state management, thus adhering to the rules of object-oriented encapsulation.

The latter two properties make widgets ideal for programming custom application components.

Widgets extend services with a request-response cycle:

public interface Widget extends Service, Serializable {
  public Interface _getWidget();
  
  public interface Interface extends Serializable {
    public void update(InputData data) throws Exception;
    public void event(Path path, InputData input) throws Exception;
    public void render(OutputData output) throws Exception;
  } 
}

Although widgets extend services, a widget will function during one request either as a widget or as a service—that is if a widget receives an action() call then no other request-response cycle calls can occur.

The widget request-response cycle proceeds as follows:

  1. update() —this method is called for all the widgets in the hierarchy. It allows widgets to read the data from request and possibly store some conversation state or at least temporary information to render the next view.
  2. event() —this method is called on only one widget in the hierarchy. It allows to send events from the user to widgets. The path is used to route the event to the correct widget and is empty when the event is delivered to its endpoint. This method is optional in the widget request-response cycle.
  3. render() —the way this method is called depends on how widgets are rendered (see Section 2.6.1, “ViewModel and Rendering” ). It may be called more than once (or not at all) during one request-response cycle. Typically it is called once for each widget that has a rendering template defined.

Aranea provides a base implementation of the Widgetorg.araneaframework.core.BaseWidget and a base class for application development org.araneaframework.core.BaseApplicationWidget. More on the last one can be found in Section 2.7, “Application Widgets”.

2.6.1. ViewModel and Rendering

The default model of both widget and service rendering is that they render themselves. However, in most cases the widget might want to delegate the rendering to some templating language. In some other cases the widget might be rendered externally, without calling render() at all. Further on, we will describe these three cases in detail.

Self-rendering
In the most basic situation the widget will just use OutputData for rendering by casting it into e.g. HttpOutputData . In such a case the widget will just write out markup and return from the render() method optionally rendering children as well. The data for rendering will be drawn from the widget fields, children and widget Environment.
Using templates for rendering
The most common case in application widgets is to delegate rendering to a templating language. A widget may basically choose to render itself in arbitrary templating language as Aranea does not impose any restrictions. In fact, one widget may be rendered with one templating language, while another one with a completely different language. The template can gain access to the widget using the knowledge of the widget's full name (which is gathered in the OutputData scope). It is then possible to acquire the widget View Model, which is a read-only representation of the widget state. For that the widget should implement org.araneaframework.Viewable :
public interface Viewable extends Serializable {
  public Interface _getViewable();

  interface Interface extends Serializable {
    public Object getViewModel() throws Exception;
  }  
}
View model is put together by the widget being rendered and should contain all the data necessary to render the widget.
External rendering

Finally, a widget render() method may not be called altogether and a Viewable widget may be rendered externally using the available View Model. This is the case with some reusable widgets which are rendered using e.g. JSP tags.

2.7. Application Widgets

This section explains how to program applications using widgets as the main abstraction.

A typical application widget class will extend org.araneaframework.uilib.core.BaseUIWidget. This widget represents the usual custom application component that is rendered using Aranea custom JSP tags. BaseUIWidget inherits most of its functionality from org.araneaframework.core.BaseApplicationWidget the difference between the two being only that BaseUIWidget assumes to be connected with a JSP page (or another templating toolkit).

2.7.1. Children Management

BaseApplicationWidget provides a number of methods for managing child widgets:

public abstract class BaseApplicationWidget ... {
  ...
  public void addWidget(Object key, Widget child);
  public void removeWidget(Object key);
  public void enableWidget(Object key);
  public void disableWidget(Object key);
  ...
}

As one can see, every added child has an identifier which should be unique among its siblings. This identifier is used when rendering and sending events to the widget in question, to identify it among its peers. Together with widget's parents identifiers this forms a unique identifier (scope) of widget in the component hierarchy.

Typically, children are added when created:

addWidget("myChildWidget", new MyChildWidget("String parameter", 1));

An added child will be initialized, will receive updates and events and may be rendered. A widget can be active only if added to a parent. It will live as long as the parent, unless the parent explicitly removes it:

removeWidget("myChildWidget");

Removing a child widget will destroy it and one should also dispose of any references that may be pointing to it, to allow the child to be garbage collected.

A usual idiom is to save a reference to the newly created and added child using a parent widget field:

public class MyWidget extends BaseUIWidget {
  private MyChildWidget myChildWidget;
  
  protected void init() {
    myChildWidget = new MyWidget("String parameter", 1);
    addWidget("myChildWidget", myChildWidget);
  }
}

This allows to call directly child widget methods and does not anyhow significantly increase memory usage, so this technique may be used everywhere when needed.

Disabling a child (disableWidget("myChildWidget")) will stop it from receiving any events or rendering, but will not destroy it. It can later be reenabled by calling enableWidget("myChildWidget").

2.7.2. Event Listeners

Registering event listeners allows widgets to subscribe to some specific user events (widget will receive only events specially sent to it). The distinction comes by the "event identifier" that is assigned to an event when sending it. The events are handled by the classes extending org.araneaframework.core.EventListener:

public interface EventListener extends Serializable {
  public void processEvent(Object eventId, InputData input) throws Exception;
}

The event listeners are registered as following:

addEventListener("myEvent", new EventListener() {
  public void processEvent(Object eventId, InputData input) throws Exception {
    log.debug("Received event: " + eventId);
  }
}

Of course, the event listener does not have to be an anonymous class and can just as well be an inner or even a usual public class. A standard base implementation org.araneaframework.core.StandardEventListener is provided that receives an optional String event parameter:

addEventListener("myEvent", new StandardEventListener() {
  public void processEvent(Object eventId, String eventParam, InputData input) throws Exception;
    log.debug("Received event " + eventId + " with parameter " + parameter);
  }
}

Another useful way to process events is to register a proxy event listener (org.araneaframework.core.ProxyEventListener) that will proxy the event to a method call, e.g.:

protected void init() {
  addEventListener("myEvent", new ProxyEventListener(this));
}

// This method handles the event that was registered in init().
public void handleEventMyEvent(String parameter) {
    log.debug("Received event myEvent with parameter " + parameter);
}

The convention is that the proxy event listener translates an event "<event>" into a method call handleEvent<event> making the first letter of <event> uppercase. The "String parameter" is optional and can be omitted.

A useful feature is the method setGlobalEventListener(EventListener listener) that allows to register a listener that will receive all events sent to the widget. In fact BaseUIWidget does that by default, and typically you will use the individual event listeners only when you want to override this default behaviour. This allows to just define correct method names (handleEvent<event>) and all events will be translated to the calls to these methods. Certainly this can also be cancelled by calling clearGlobalEventListener(), or overridden by adding your own global event listener.

2.7.3. Action Listeners

Registering action listeners allows widgets to subscribe to some specific user generated actions. Actions differ from events in that widget lifecycle execution for whole component tree is not triggered upon request—actions are just sent to the receiving widget's ActionListener, which is SOLELY responsible for generating the whole response. For rich UI components this allows a quick conversations with server, without requiring full form submits and generating whole view.

Actions are handled by the classes extending org.araneaframework.core.ActionListener

public interface ActionListener extends Serializable {
  public void processAction(Object actionId, InputData input, OutputData output) throws Exception;
}

and their registration is analogous to event listeners:

addActionListener("actionId", new SomeActionListener());

2.7.4. Environment

Every initialized widget has a reference to org.araneaframework.Environment available through the getEnvironment() method. Environment allows to look up framework services (called contexts):

MessageContext msgCtx = (MessageContext) getEnvironment().getEntry(MessageContext.class);
msgCtx.showInfoMessage("Hello world!");

As one can see from the examples, contexts are looked up using their interface Class object as key. All framework services in Aranea are accessible only using the environment.

To find out more about Environment see Section 2.3.2, “Environment”

2.7.5. Overridable Methods

The main method that is typically overridden in a widget is init(). As widget does not get an environment before it is added and initialized it is impossible to access framework services in the constructor, therefore most of the initialization logic moves to the custom init() method. A dual overridable method is destroy(), though it is used much less.

In addition to event processing it is sometimes useful to do some kind of preprocessing. The BaseApplicationWidget has the following method that may be overridden to allow this processing:

protected void handleUpdate(InputData input) throws Exception {}

handleUpdate() is called before event listeners are notified and allows to read and save request data preparing it for the event. More importantly, this method is called even when no event is sent to the current widget allowing one to submit some data to any widget.

2.7.6. InputData and OuputData

In Aranea one usually does not need to handle request manually in custom application widgets. Even more, the request is not accessible by default. The usual way to submit custom data to a widget and read it is using Aranea Forms (see Chapter 5, Forms and Data Binding). However, when one needs to access the submitted data, one can do that using the org.araneaframework.InputData. This class can be used as follows:

...
String myData1 = 
  (String) getInputData().getScopedData().get("myData1");
String globalSubmittedParameter = 
  (String) getInputData().getGlobalData().get("globalSubmittedParameter");
...

getInputData() is a BaseApplicationWidget method that returns the input data for the current request (one can also use the input parameter given to event listener directly).

org.araneaframework.OutputData is accessible through the getOutputData() method of BaseWidget or directly as the output parameter passed to render() method.

To find out more about InputData and OutputData see Section 2.4, “InputData and OutputData”

2.7.7. View Model and Rendering

BaseApplicationWidget also contains methods that facilitate transferring data to the presentation layer. This is achieved using a View model—an object containing a snapshot of the widget current state. The most typical way to use the view model it to add data to it:

...
putViewData("today", new Date());
putViewData("currentUser", userBean);
...

View data is typically accessible in the presentation layer as some kind of a variable (e.g. a JSP EL variable) for the current widget. If the data becomes outdated one can override it using putViewData() call or remove it using the removeViewData() call. In case one needs to put view data that would last one request only there is an alternative method:

...
putViewDataOnce("now", new Date());
...

Finally widget instance is also visible to the view, so one of the ways to make some data accessible is just to define a JavaBean style getter:

...
public Date getNow() {
  return new Date();
}
...

BaseUIWidget allows to render the current widget using a JSP page. To do that one needs to select a view as follows:

...
setViewSelector("myWidget/form");
...

This code makes the widget render itself using the JSP situated in WEB-INF/jsp/myWidget/form.jsp (of course the exact place is configurable). It is also possible to render the widget using other template technologies with the same view selector by overriding the render() method in the base project widget.

2.7.8. Putting It All Together

A typical application custom widget will look like that:

public class TestWidget extends BaseUIWidget {

  private static final Logger log = Logger.getLogger(TestWidget.class);
  
  private Data data;
  
  protected void init() throws Exception {
    //Sets the JSP for this widget to "/WEB-INF/jsp/home.jsp"
    setViewSelector("home");
    
    //Get data from the business layer
    data = ((TestService) lookupService("testService")).getData("test parameter");
    
    //Make the data accessible to the JSP for rendering
    putViewData("myData", data);    
  }
  
  /*
   * Event listener method that will process "test" event.
   */
  public void handleEventTest() throws Exception {
    getMessageCtx().showInfoMessage("Test event received successfully");       
  }
}

2.8. Standard Contexts

Contexts are the Aranea way to access framework services. They can be looked up from the environment as shown in Section 2.7.4, “Environment”. This section describes the most common Aranea contexts that should be available in any typical configuration. All these contexts are also available directly through BaseUIWidget methods as shown further on.

2.8.1. MessageContext

org.araneaframework.framework.MessageContext allows to show messages to the user. The messages can be of several types, including predefined error and informative types. Typically messages will be shown somewhere in the application (exact way is application-specific). MessageContext is available through a BaseUIWidget method getMessageCtx() and is typically used as follows:

getMessageCtx().showInfoMessage("Hello world!");

MessageContext divides messages by type (with predefined "info", "warning" and "error" types available) and life span (usual or permanent). Usual messages are shown to user once and then cleared, while permanent messages will be shown to user until explicitly cleared by the programmer:

MethodDescription
showMessage(String type, String message)Shows a message message of type type to the user.
showMessages(String type, Set<String> messages)Shows messages of type type to the user.
showInfoMessage(String message)Shows an error message to the user.
hideInfoMessage(String message)Hides an info message from user.
showWarningMessage(String message)Shows a warning message to the user.
hideWarningMessage(String message)Hides a warning message from user.
showErrorMessage(String message)Shows an informative message to the user.
hideErrorMessage(String message)Hides an error message from user.
clearMessages()Clears all non-permanent messages.
showPermanentMessage(String type, String message)Shows a permanent message message of type type to the user. The message will be shown until hidden explicitly.
hideMessage(String type, String message);Removes a message message of type type.
hideMessages(String type, Set<String> messages);Removes messages of type type.
hidePermanentMessage(String message)Clears the specific permanent message, under all message types where it might be present.
clearPermanentMessages()Clears all of the permanent messages.
clearAllMessages()Clears all messages (both permanent and usual).
Map<String, Collection> getMessages()Returns all present messages as a Map. Keys of the Map are the different message types encountered so far and under the keys are the messages in a Collection.

Note

Messages should already be localized when passed to the MessageContext, it does not do any further processing. Use LocalizationContext described in Section 2.8.2, “LocalizationContext” to do the actual localization of the added message.

For information on implementation of the MessageContext see Section 3.5.8, “User Messages Filter”. For standard JSP tag which renders MessageContext messages to response, see <ui:messages>.

2.8.2. LocalizationContext

org.araneaframework.framework.LocalizationContext allows to get and set current session locale, localize strings and messages, and lookup resource bundles. The context is available through the BaseUIWidget method getL10nCtx(). Typically it is used as follows:


...
String message = getL10nCtx().localize("my.message.key");
getMessageCtx().showInfoMessage(message);
...

LocalizationContext provides the following methods:

MethodDescription
Locale getLocale()Returns the current session locale.
setLocale(Locale locale)Sets the current session locale.
String localize(String key)Localizes a string returning one that corresponds to the current locale.
ResourceBundle getResourceBundle()Returns a resource bundle corresponding to the current locale.
ResourceBundle getResourceBundle(Locale locale)Returns a resource bundle corresponding to arbitrary locale.
String getMessage(String code, Object[] args)Localizes the code and uses it to format the message with the passed arguments. The format of the localized message should be acceptable by java.text.MessageFormat.
String getMessage(String code, Object[] args, String defaultMessage)Localizes the code and uses it to format the message with the passed arguments. The format of the localized message should be acceptable by java.text.MessageFormat. If the localized message cannot be resolved uses defaultMessage instead.
void addLocaleChangeListener(LocaleChangeListener listener);Registers a listener (Component) that will be notified when locale is changed.
boolean removeLocaleChangeListener(LocaleChangeListener listener)Unregisters listener (Component) so that it will not be notified of locale changes anymore. Returns whether the listener was found to be present and actually removed.

For information on implementation of the LocalizationContext see Section 8.1.2, “Spring Localization Filter”.

2.8.3. FlowContext

A common need in a web programming is to support navigation style known as flows—interactive stateful processes that can navigate to each other passing arguments when needed. A more complex case is when we also have flow nesting—a flow can call a subflow, and wait for it to finish, then reactivate again. In this case we can have at any given moment a stack of flows, where the top one is active, and the next one will reactivate when the top one finishes. It is also useful if nested flows can return resulting values when they finish.

Flow diagram

Figure 2.2. Flow diagram

org.araneaframework.framework.FlowContext is the Aranea context that provides support for nested flow navigation. Aranea flow is a widget that is running in the flow container (using the FlowContext.start() method. Aranea abstraction for the nested state is that of a function—the nested flow takes in some parameters and when finished may return some value or signal that no value can be returned. The context is available as getFlowCtx() method of BaseUIWidget and allows to start flows, finish flows and return the resulting value.

To start a new flow one needs to create a widget as usual. The widget may take some parameters in the constructor—they are considered to be the incoming parameters of the flow:

...
getFlowCtx().start(new TestFlow(new Long(5)));
...

This call will start a new nested flow for the widget TestFlow making the current flow inactive. TestFlow will render and receive event until it explicitly returns control to the starting flow. Note that this code will start the flow and then return the control, so it is important not to do anything in the same method after starting a new flow.

To end the flow successfully one needs to do as follows:

...
getFlowCtx().finish(new Long(8));
...

This call will finish the current flow (in our case TestFlow) and return the control to the starting flow and its widget.

Often one needs to handle the return from the flow, processing the returned result. This corresponds to our abstraction of a method, however since Java does not support continuations we chose to allow the caller to register a handler when starting the flow by passing a FlowContext.Handler:

...
getFlowCtx().start(new TestFlow(new Long(5)),
  new FlowContext.Handler() {
    public void onFinish(Object result) {
      getMessageCtx().showInfoMessage("TestFlow returned value " + result);
    }
    public void onCancel() {
      //Ignore cancelled flow
    }
  });
...

A less common but nevertheless useful feature is to configure the starting flow after it has been initialized. For that the caller needs to pass a FlowContext.Configurator:

...
getFlowCtx().start(new TestFlow(new Long(5)), 
  new FlowContext.Configurator() {
     public void configure(Component comp) {
      ((TestFlow) comp).setStrategy(TestFlow.ATTACK);
     }
  }, null);
...

FlowContext also allows to replace the current flow instead of deactivating it by using the replace() method and to cancel the current flow by using the cancel() method.

Transitions between the flows are performed by FlowContext.TransitionHandlers.

interface TransitionHandler extends Serializable {
   /**
    * @param eventType FlowContext.START .. FlowContext.RESET
    * @param activeFlow active flow at the moment of transition request
    * @param transition Serializable closure that needs to be executed for transition to happen
    */
   void doTransition(int eventType, Widget activeFlow, Closure transition);
}

After initialization, each flow may set the TransitionHandler which will handle navigation events performed while flow which set the TransitionHandler is active. This can be used to customize navigation logic—i.e. ask for confirmations when navigating away from flow containing unsaved data, restore window scroll position when returning to caller flow or checking for privileges before starting the next flow.

For standard implementation, please see Section 3.5.18, “Root Flow Container”

2.8.4. PopupWindowContext

Popup windows in Aranea are separate threads that are started using org.araneaframework.http.PopupWindowContext. Popups can be used, for example, to open new widgets or to upload files (using org.araneaframework.http.service.FileDownloaderService). To open a new widget in a popup, the widget must handle the entire page, and its subwidgets may handle certain specific parts of a page. This is similar to how a root widget handles the components in the main thread.

One can access the PopupWindowContext by getting it from the Environment. If it is accessed from a widget that extends BaseUIWidget, the getPopupCtx() method can be used.

Here is an example on how to the server enables the user to download a file:

  PopupWindowContext popupContext = (PopupWindowContext) getEnvironment().getEntry(PopupWindowContext.class);
  popupContext.open(new FileDownloaderService(selectedFile), new PopupWindowProperties(), null);

In the example above, the first parameter is the service that downloads the file to the user's computer, and the second one is the popup window properties. Sometimes one may want to also specify the widget that caused the popup to open. Therefore, the last parameter in the example is the opener, which usually is null, but may be provided as this (the caller widget). (The popup widget can access the opener by PopupWindowContext.getOpener().)

The following is an example from Aranea sample application (SamplePopupWidget) on how to open a popup widget (from a widget that extends BaseUIWidget):

  getPopupCtx().open(
    new LoginAndMenuSelectMessage("Demos.Simple.Simple_Form"), 
    new PopupWindowProperties(), this);

Here it must send a Message to the components that starts new widgets to produce the desired effect. The LoginAndMenuSelectMessage is a SeriesMessage that first uses the flow context from the child environment of the login widget to start a new root context. Then the menu select widget searches the menu widget to select the given menu item. Below are the codes for messages.

The code for the LoginAndMenuSelectMessage:

public class LoginAndMenuSelectMessage extends SeriesMessage {

  public LoginAndMenuSelectMessage(String menuPath) {
    super(new Message[] {
        new LoginMessage(), 
        new MenuSelectMessage(menuPath)});
  }

}

The code for the LoginMessage:

public class LoginMessage extends BroadcastMessage {

  protected void execute(Component component) throws Exception {
    if (component instanceof LoginWidget) {
      LoginWidget loginWidget = (LoginWidget) component;

      Environment childEnv = loginWidget.getChildEnvironment();

      FlowContext flow = (FlowContext) childEnv.getEntry(FlowContext.class);
      flow.replace(new RootWidget(), null);
    }
  }

}

The code for the MenuSelectMessage:

public class MenuSelectMessage extends BroadcastMessage {

  private String menuPath;

  public MenuSelectMessage(String menuPath) {
    this.menuPath = menuPath;
  }

  protected void execute(Component component) throws Exception {
    if (component instanceof MenuWidget) {
      MenuWidget w = (MenuWidget) component;
      w.selectMenuItem(menuPath);
    }
  }

}

MethodDescription
String open(Message startMessage, PopupWindowProperties properties, Widget opener)Uses a message that opens a widget inside a new popup.
String open(Service service, PopupWindowProperties properties, Widget opener)Uses a service that serves the data for a new popup.
String openMounted(String url, PopupWindowProperties properties)Opens the mount URL in a popup.
open(String url, PopupWindowProperties properties)Opens the given URL in a popup.
boolean close(String id) throws ExceptionCloses the popup with given ID (the ID is returend when the popup is created).
Widget getOpener()Provides the popup opener.
Map getPopups()Returns a map with popups (the key is the ID of the popup, and the value is an instance of PopupServiceInfo.

To enable popups at JSP layer, one must also register it inside the system form as the following code snippet does from root.jsp of the Aranea Demo Application:

...
  <ui:body>

    <div id="cont1">
      <ui:systemForm method="POST">
        <ui:register.../>
        <ui:registerPopups/>
...

Standard implementation of PopupWindowContext is described in Section 3.5.9, “Popup Windows Filter”.

2.8.5. OverlayContext

Supports running processes in "overlay" (in parallel FlowContext of the same session thread). It is used to allow construction of modal dialogs and modal processes. To start a process inside overlay, a widget calls one of the getOverlayCtx().start(...) methods.

The getOverlayCtx() method is defined in BaseUIWidget so all sub-classes should be able to access it. Others can retrieve it from the Environment like following: (OverlayContext) getEnvironment().getEntry(OverlayContext.class).

The first time the overlay mode is started, the start(...) must take a container (root) widget as its argument because everything that happens in overlay mode, is happening like in a separate window. For example, the code in Aranea Demo Application creates the overlay root widget and its content(s) like this:

  getOverlayCtx().start(
      new OverlayRootWidget(new ModalDialogDemoWidget(true)));

Notice that the start(...) method is used to start two widgets. The custom-made OverlayRootWidget acts like a root widget, which behind the scenes also specifies a flow container for the child widget (i.e. ModalDialogDemoWidget). Here is the sample code for the OverlayRootWidget:

public class OverlayRootWidget extends BaseUIWidget {

  private Widget child;

  public OverlayRootWidget(Widget child) {
    this.child = child;
  }

  protected void init() throws Exception {
    Assert.notNull(child);
    addWidget("c", new OverlayFlowContainer(child));
    setViewSelector("overlayRoot");
  }

  private class OverlayFlowContainer extends ExceptionHandlingFlowContainerWidget {

    public OverlayFlowContainer(Widget topWidget) {
        super(topWidget);
    }

    protected void renderExceptionHandler(OutputData output, Exception e) throws Exception {
      if (ExceptionUtils.getRootCause(e) != null) {
        putViewDataOnce("rootStackTrace", ExceptionUtils.getFullStackTrace(
            ExceptionUtils.getRootCause(e)));
      }        
      putViewDataOnce("fullStackTrace", ExceptionUtils.getFullStackTrace(e)); 
      ServletUtil.include("/WEB-INF/jsp/menuError.jsp", this, output);
    }

  }

}

OverlayContext provides the replace*, start* and reset* methods that act analogously to FlowContext corresponding methods, but affect only overlayed process. Additionally, following methods are available:

MethodDescription
boolean isOverlayActive()Returns whether some overlayed process is active.
setOverlayOptions(Map options)Sets the presentation options for overlayed processes.
Map getOverlayOptions()Returns the map with current presentation options for overlayed processes.
finish(Object result)Similar to FlowContext.finish(Object obj) but closes the entire OverlayContext not just the last flow widget.
cancel()Similar to FlowContext.cancel() but closes the entire OverlayContext not just the last flow widget.

To make overlay possible on the client-side, one must register it inside the system form as the following code snippet does from root.jsp of the Aranea Demo Application. In addition, the modalbox.css file must also be incorporated to enable the visual part of the overlay mode. In the example below, the file is explicitly defined (it refers to the modalbox.css provided by Aranea), although the necessary styles are also included if there are no attributes specified on the tag.

  ...
  <head>
        ...
        <ui:importStyles file="css/modalbox/modalbox.css" media="screen"/>
        ...
  </head>

  <ui:body>

    <div id="cont1">
      <ui:systemForm method="POST">
        <ui:register.../>
        <ui:registerOverlay/>
...

Notice the <ui:registerOverlay/> tag!

Standard implementation of OverlayContext is described in Section 3.5.19, “Overlay Container”.

2.8.6. MenuContext

Defines the standard methods for menu handlers (contexts). Most custom implementations can extend the org.araneaframework.uilib.core.BaseMenuWidget and its buildMenu() method.

MethodDescription
void selectMenuItem(String menuItemPath)Marks the menu item (identified by given path) as active.
MenuItem getMenu()Provides access to the entire menu.
void setMenu(MenuItem menu)Specifies the menu to use.

All menu items are represented as a tree of org.araneaframework.uilib.core.MenuItem objects that have its own label and a flow (a widget or a flow creator). An entire menu is also a MenuItem and its menus are declared with addSubMenuItem(MenuItem item). A MenuItem may not have a flow, if it represents a sub menu. An example menu might look like this:

  MenuItem menu = new MenuItem();
  demoMenu = menu.addMenuItem(null, new MenuItem("Demo_Menu", DemoWidget.class));
  demoMenu.addMenuItem(new MenuItem("Context_Menus", DemoContextMenuWidget.class));
  demoMenu.addMenuItem(new MenuItem("Easy_AJAX_Update_Regions", EasyAJAXUpdateRegionsWidget.class));
  demoMenu.addMenuItem(new MenuItem("Cooperative_Form", FriendlyUpdateDemoWidget.class));
  ...

To enable the menu widget, the root widget may initialize it. Then the menu can be accessed by view data. A simplified example for JSP (without style information) is below:

<ui:widgetContext id="menu">
  <c:forEach items="${viewData.menu.subMenu}" var="item">
    <c:if test="${item.value.selected}">
      <ui:eventLinkButton eventId="menuSelect" eventParam="${item.value.label}" labelId="${item.value.label}" styleClass="active"/>
    </c:if>

    <c:if test="${not item.value.selected}">
      <ui:eventLinkButton eventId="menuSelect" eventParam="${item.value.label}" labelId="${item.value.label}"/>
    </c:if>
  </c:forEach>
</ui:widgetContext>

2.8.7. ConfirmationContext

Aranea standard component chain enriches Environment with a context called ConfirmationContext. This can be used for executing some code conditionally, depending on user actions. Context interface is simple and consists of following methods:

public interface ConfirmationContext extends Serializable {

  void confirm(Closure onConfirmClosure, String message);

  String getConfirmationMessage();

}

There the org.apache.commons.collections.Closure is a simple interface to encapsulate business logic:

public interface Closure {

  public void execute(java.lang.Object input);

}

The input param is null for transitions handlers.

When confirmation is registered (with the confirm(...) method), rendering mechanism will present end-user with the browser standard message box (on page load) and ask for confirmation of requested action. Depending on users choice, action encapsulated in the onConfirmClosure param either will get executed or not.

Combined with FlowContext.TransitionHandler, confirmation could be asked whenever the user performs navigation that would make active flow unreachable and flow contains data that has not yet been saved.

getFlowCtx().setTransitionHandler(
  new CancelConfirmingTransitionHandler(
      new ShouldConfirmOnUnsavedData(),
      "Some data not saved yet. Continue anyway?"));

Here CancelConfirmingTransitionHandler (provided by Aranea) registers the confirmation whenever FlowContext.cancel() is called from active flow and org.apache.commons.collections.Predicate (that is used to check the custom condition before executing the event) ShouldConfirmOnUnsavedData (not provided by Aranea) evaluates to true. Only after the user confirms the navigation, the event will allow flow transition to be actually be performed.

ConfirmationContext and TransitionHandlers together are a reliable and convenient way of preventing end-users shooting themselves in the foot.

2.8.8. ManagedServiceContext, ThreadContext, and TopServiceContext

This section describes the contexts that are at the core of request handling.

org.araneaframework.framework.ManagedServiceContext represents a context that handles the requests of different threads (windows) in one session. The basic idea is that it routes requests to the right services. Here's an overview of the interface:

package org.araneaframework.framework;

public interface ManagedServiceContext extends Serializable {

  public Object getCurrentId();

  public Service addService(Object id, Service service);

  public Service addService(Object id, Service service, Long timeToLive);

  public Service getService(Object id);

  public void close(Object id);

}

org.araneaframework.framework.ThreadContext represents a context that makes popups possible. Without it the user would have the same session in both windows, because Aranea application has a state. ThreadContext, however, provides means to create a new (distinct) thread in the session. A proof that a ThreadContext is running in a web application is the fact that you can find something like this in the source code of a page:

<input name="araThreadServiceId" type="hidden" value="mainThread"/>

It means that the next request (submit) made will be bound to the mainThread.

org.araneaframework.framework.TopServiceContext further specifies the ManagedServiceContext by being the top-most (and thus accessible by all users), though it works like ThreadContext. The difference, however, lies in the fact that TopServiceContext is not session based. Therefore, it would handle threads when, for example, the user has not logged in. And one may see it in their Aranea application page as following:

<input name="araTopServiceId" type="hidden" value="application"/>

ThreadContext and TopServiceContext do not introduce new methods compared to ManagedServiceContext.

Chapter 3. Framework and Configuration

3.1. Overview

Aranea framework consists of a number of independent components each performing a single well-defined function. Aranea uses Spring to wire these components into a working framework. Though other IoC containers and configuration frameworks would also work we support Spring by default since it provides a very comfortable and versatile syntax for configuring beans. The dispatcher servlet that uses Spring is called org.araneaframework.integration.spring.AraneaSpringDispatcherServlet. Note that Aranea itself does not depend on Spring except the classes in the org.araneaframework.integration.spring package.

3.2. Application Configuration

3.2.1. web.xml

The simplest way to configure Aranea for a web application is to set the araneaApplicationStart init parameter of the dispatcher servlet to the starting widget or flow of the application:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC 
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
  "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <listener>
    <listener-class>
      org.araneaframework.http.core.StandardSessionListener
    </listener-class>
  </listener>
   
   <servlet>
      <servlet-name>araneaServlet</servlet-name>
      <servlet-class>
        org.araneaframework.integration.spring.AraneaSpringDispatcherServlet
      </servlet-class>
      <init-param>
        <param-name>araneaApplicationStart</param-name>
        <param-value>example.StartWidget</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
   </servlet>
   
   <servlet-mapping>
      <servlet-name>araneaServlet</servlet-name>
      <url-pattern>/main/*</url-pattern>
   </servlet-mapping>  
</web-app>

This configuration will load Aranea using example.StartWidget as the application starting point.

Note

The servlet must be mapped to a all subpathes starting from some prefix (in our case /main/*) so that Aranea could do some path-dependent operations like extension file importing.

Note

org.araneaframework.http.core.StandardSessionListener is required to allow Aranea to process events like session invalidation.

3.2.2. aranea-conf.xml

Aranea can also be configured using a Spring configuration file located in /WEB-INF/aranea-conf.xml. Particularly it may be used to set the starting widget instead of the init-parameter:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
  "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>   
    <bean id="araneaApplicationStart" 
      class="example.StartWidget"
      singleton="false"/>
</beans>

This seems to be more verbose, but it also allows to configure the framework components as described in Section 3.4, “Framework Configuration”.

3.2.3. aranea-conf.properties

Aranea also takes into account a property file located in /WEB-INF/aranea-conf.properties. The following properties are recognized:

PropertyDescription
l10n.resourceBundleThe base name of the resource bundle used for localization. This value isn't used if default araneaLocalizationFilter is overidden (e.g. by the SpringLocalizationFilterService)

Default value: org.araneaframework.http.support.DefaultResourceBundle

l10n.defaultLocaleThe default locale to be used in the application.

Default value: en

l10n.encodingThe default character encoding to be used throughout the application (e.g. for request and response).

Default value: UTF-8

jsp.pathThe path from the webapp root to the directory that will act as JSP root. The JSPs put there can be selected using widget view selectors (see Section 2.7.7, “View Model and Rendering”).

Default value: /WEB-INF/jsp

3.2.4. AraneaSpringDispatcherServlet

AraneaSpringDispatcherServlet provides a number of init-params that allow to further customize Aranea configuration:

init-paramDescription
araneaCustomConfXMLThe custom location of a Spring XML file used to configure Aranea.

Default value: /WEB-INF/aranea-conf.xml

araneaCustomConfPropertiesThe custom location of a property file used to configure Aranea.

Default value: /WEB-INF/aranea-conf.properties

araneaApplicationStartThe class name of an Aranea widget that will serve as the starting point of an Aranea application. If omitted the Spring bean araneaApplicationStart will be used.
araneaApplicationRootThe class name of an Spring bean describing an Aranea component that will serve as the framework root. If omitted the Spring bean araneaApplicationRoot will be used. Can be used to override the default configuration altogether.

3.2.5. Extending Dispatcher

Currently, the most common way to put Aranea to work is to host it in a Servlet 2.3 or compatible container. The most generic way to do that is to extend the org.araneaframework.http.core.BaseAraneaDispatcherServlet and build the root component of type org.araneaframework.http.ServletServiceAdapterComponent in the overrided method buildRootComponent():

package com.foobar.myapp;

class MyServlet extends BaseAraneaDispatcherServlet {
   protected ServletServiceAdapterComponent buildRootComponent() {
     StandardServletServiceAdapterComponent root = new StandardServletServiceAdapterComponent();

     //Configure the child components, service widgets using setter injection
     //...

     return root;
   }
}

One can then use such a servlet to configure Aranea in a web application as by replacing the standard dispatcher servlet with the custom one in WEB-INF/web.xml.

3.2.6. ConfigurationContext

Aranea also provides a central way to configure some settings that affect the way some components work or display data. These settings are stored in a Map, where the key names are defined in the org.araneaframework.uilib.ConfigurationContext interface.

The ConfigurationContext is accessible from the Environment or by the getConfiguration() method of BaseUIWidget.

Every application may provide their own values for settings by implementing their version of the ConfigurationContext like following:

public class CustomConfiguration implements ConfigurationContext {

  private Map configuration = new HashMap();

  public CustomConfiguration() {
    // Note that these constants are defined by the ConfigurationContext.
    configuration.put(CUSTOM_DATE_FORMAT, "dd.MM.yyyy|d.M.yyyy");
    configuration.put(CUSTOM_TIME_FORMAT, "HH:mm");
    configuration.put(FULL_LIST_ITEMS_ON_PAGE, new Long(20));
  }

  public Object getEntry(String entryName) {
    return configuration.get(entryName);
  }

}

For more information on the settings that can be changed, please see the ConfigurationContext interface in the Aranea API.

To make Aranea use the custom-created configuration, the class must be defined in aranea-conf.xml file like this:

<bean id="araneaConfiguration"
    class="com.company.conf.CustomConfiguration" singleton="false" />

Note that it is defined as not being a singleton. This means that the configuration is created for every context (user). Therefore, the settings can be further customized to be more user specific.

3.2.7. StateVersioningContext

To compensate the lack of support for the back button of the web browser, a custom solution for this has been added to Aranea since release 1.2. This solution depends on rsh (Really Simple History), so you also need to import JavaScripts (js/aranea/aranea-rsh.js, js/rsh/rsh.js, see also System tags).

The main interface of this context is org.araneaframework.http.StateVersioningContext, and once enabled, it is accessible form the Environment, though one generally does not need to access it. Every application may provide their own values for costumizing the number of pages (default is 20) of the StateVersioningContext. To enable state versioning, one must add following XML code to aranea-conf.xml:


<bean id="araneaStateVersioningFilter"
  class="org.araneaframework.http.filter.StandardStateVersioningFilterWidget"
  singleton="false">
    <property name="maxVersionedStates" value="10"/>
</bean>

Note that it is defined as not being a singleton. This means that the configuration is created for every context (user). Therefore, the settings can be further customized to be more user specific.

Warning

Since the visited pages are serialized on the client side, do not set high values for maxVersionedStates as it puts more stress on the client's browser. Therefore, 10 is normal, 20 should be maximum value for this attribute.

In addition, an update region must be defined in your root.jsp that wraps system form and has a fixed name: araneaGlobalClientHistoryNavigationUpdateRegion. This is the region that is updated when the user moves between pages using the back button of the browser. Here's an example:

<ui:body>
        ...
        <ui:updateRegion globalId="araneaGlobalClientHistoryNavigationUpdateRegion">
                ...
                <ui:systemForm method="post">
                        ...
                </ui:systemForm>
                ...
        </ui:updateRegion>
        ...
</ui:body>

Finally, you also need to copy etc/js/rsh/blank.html from the Aranea 1.2 release package and place it in the root directory of your WAR bundle (the same directory where WEB-INF is located). This HTML file is needed for compatibility with Internet Explorer.

3.3. Framework Assembly

Aranea framework is made up of the same Components, Services and Widgets that are also used to develop Aranea applications. Each component performs a single well-defined function and depends on its parents only through the Environment where component lives. The framework components mostly fall in one of the three following categories:

Filter

Filter components are the simplest. The component (typically Service, see Section 2.5, “Services”) contains a single unnamed child and implements the Filter pattern by either passing each call to the child or not. However in addition it may enrich the child's environment with contexts and provide more functionality like exception handling or synchronization. Typical examples of filters are localization filter (provides a localization context), synchronization filter (synchronizes on action() method) and transactional filter that does not let through double submits.

Router

Router typically contains many named children, and chooses only one to propagate the calls to according to some InputData parameter. Router may have the children either statically preconfigured or created dynamically when the request comes (the latter is the case with session service router). It may also allow us to add/remove children while the application is running. A typical application of a router is to distinguish among major application parts by some attribute (like component corresponding to a user session, or one of the popup window of current user).

Container

Container can have one or many children, but it typically will do more with them than just passing the calls to one of them. A typical example is the widget container service which translates action() calls into widget update()/event()/render() cycle.

The frameworks itself is assembled using a hierarchy of components (this hierarchy is mostly flat, except for the routers and application components). The hierarchy is arranged simply by containment, with each component containing its chidren as fields as illustrated on Figure 3.1, “Framework assembly example”.

Framework assembly example

Figure 3.1. Framework assembly example

Of course this illustration is simplified, omitting most of the components described in Section 3.5, “Framework Components”. If you want to find out more about the way framework is built and assembled, see the Aranea Technical Paper.

3.4. Framework Configuration

Aranea framework is assembled into a mostly-flat hierarchy using Spring beans. The default Aranea configuration is loaded by the AraneaSpringDispatcherServlet, but it can be overriden with the custom configuration in aranea-conf.xml. The dispatcher servlet loads the configuration in such a way that same named beans in aranea-conf.xml override the ones specified in the default configuration. However, not all beans can be safely or comfortably overriden, since many of them will also refer to their child beans.

It is always safe to override filters, as they should never refer directly to their children. To override a filter just make a bean definition with the same name as in default configuration (filters and their default configuration names among other components are described in Section 3.5, “Framework Components”). For instance to override the default localization context with a custom-made one, one would need to add the following lines:

<bean class="example.LocalizationFilterService"
  id="araneaLocalizationFilter" singleton="false">
  <property name="languageName" value="ee"/>
</bean>

There is no good way in Spring to undefine a bean, so instead we use a "No OPeration" filter to nullify a filter from the default configuration:

<bean class="org.araneaframework.framework.core.NopFilterWidget"
  id="araneaTransactionFilter" singleton="false"/>

Warning

Since filters can be both services and widgets, you have to be careful to use the appropriate one for the current context. In current case you have override service filters with NopFilterService and widget filters with NopFilterWidget.

There is no generic way to insert filters into an arbitrary place in the framework component hierarchy. However there are several predefined places left for optional bean insertion at various levels of the hierarchy, which should cover most of customization needs. To allow inserting more than one filter at a time a filter chain bean is provided that allows putting together an arbitrary long chain of filters:

<bean id="araneaCustomSessionFilters" singleton="false"
  class="org.araneaframework.framework.filter.StandardFilterChainService">
  <property name="filterChain">     
    <list>
      <ref bean="araneaSerializingAudit"/>
      <ref bean="myCustomFilter1"/>
      <ref bean="myCustomFilter2"/>
    </list>
  </property>
</bean>

Note

Use StandardFilterChainService for hosting service filters and StandardFilterChainWidget for hosting widget filters.

Follows a description of the insertion point beans and their scope:

Bean nameScope and Description
araneaCustomApplicationFilters

These filters are created only once and live as long as the application does. They are not synchronized and should be use to add features generic to the whole application, not specific users. The exceptions thrown by this filters are intepreted as critical and are handled by the critical exception handler.

Examples: araneaFileUploadFilter, araneaStatisticFilter.

araneaCustomSessionFilters

These filters are created for every HTTP user session and live as long as the session does. They are generally synchronized and should be used to add features specific to the current user session.

Examples: araneaLocalizationFilter.

araneaCustomThreadFilters

These filters are created for every user browser window and live as long as the window does. They are synchronized and should be used to add features specific to the individual browser window (e.g. most rendering filters will fall into this category).

Examples: araneaThreadCloningFilter .

araneaCustomWidgetFilters

These filters are created for every user browser window and live as long as the window does. They are synchronized and should be used to add features specific to the individual browser window. Unlike the rest of the filters this can be widgets and thus can take advantage of the widget update/event/process/render cycle.

Examples: araneaTransactionFilter, araneaMessagingFilter.

3.5. Framework Components

Aranea configuration is determined by request-processing components that can be assembled in many different ways. Following sections are a brief reference for pre-existing standard components, most of which are also used in Aranea framework default configuration.

3.5.1. Localization Filter

Java class:StandardLocalizationFilterService
Default configuration name:araneaLocalizationFilter
Provides:LocalizationContext
Depends on:-

Provides localization services to children. See Section 2.8.2, “LocalizationContext”.

Injectable propertiesDescription
languageName

java.lang.String

A valid ISO Language Code. Sets Locale according to given language.
resourceBundleName

java.lang.String

Name of the used resource bundle used to localize the application.
locale

java.util.Locale

Locale to use. Either that or languageName should be specified, but not both.

3.5.2. AJAX Update Regions Filter

Java class:StandardUpdateRegionFilterWidget
Default configuration name:araneaUpdateRegionFilter
Provides:UpdateRegionContext
Depends on:-

When framework receives an event(request) that has update region parameters defined, this filter is activated and takes care that only the smallest renderable unit that defines named update region is actually rendered. Generated response also contains only the rendered content of particular component(s) that needed to be rendered for updating the regions.

Injectable propertiesDescription
characterEncoding

java.lang.String

The character encoding for responses served by this filter, default being "UTF-8".
Notes: In Aranea 1.1 this filter has changed from Service to Widget. Also, the configuration parameter existingRegions only exists in 1.0 branch (TODO: elaborate why? (imho it should remain anyway)).

3.5.3. Environment Configuration Filter

Java class:StandardContextMapFilterWidget
Default configuration name:araneaEnvContextFilter
Provides:-
Depends on:-

Filter widget that enriches children environment with specified context entries.

Injectable propertiesDescription
contexts

java.util.Map

A map of contexts that will be added to environment. The keys can contains strings of kind "package.ClassName.class", which will use a Class object of the specified classname as the context key. The context value should be an object instance of the context interface. By convention a context should be registered under a key that is an interface it implements.

3.5.4. Critical Exception Handler

Java class:StandardCriticalExceptionHandlingFilterService
Default configuration name:araneaCriticalErrorHandler
Provides:-
Depends on:-

Catches the exceptions (if any) occuring while executing children methods; passes the exceptions on to Service that deals with exception handling (obtained from ExceptionHandlerFactory).

Injectable propertiesDescription
exceptionHandlerFactory

ExceptionHandlerFactory

A factory for creating exception handlers. An exception handler is a service, which handles the user notification and recovery.

3.5.5. File Uploading Filter

Java class:StandardFileUploadFilterService
Default configuration name:araneaFileUploadFilter
Provides:FileUploadContext, FileUploadInputExtension
Depends on:-

Enriches child environment with FileUploadContext (which is just a marker interface). When incoming request is multi-part request, children's InputData is extended with FileUploadInputExtension that allows children easy access to uploaded files.

Injectable propertiesDescription
multipartEncoding

java.lang.String

Character encoding that will be used to decode the multipart/form-data encoded strings. The default encoding is determined by Apache Commons FileUpload class.
useRequestEncoding

boolean

When set to "true" request character encoding will be used to parse the multipart/form-data encoded strings.
maximumCachedSize

java.lang.Integer

Maximum size of file that may be cached in memory.
maximumSize

java.lang.Long

Maximum size of file that may be uploaded to server.
maximumRequestSize

java.lang.Long

Maximum size of the request that server will parse to the end.
tempDirectory

java.lang.String

Temporary directory to use when uploading files.

3.5.6. HTTP Response Headers Filter

Java class:StandardHttpResponseFilterService
Default configuration name:araneaResponseHeaderFilter
Provides:-
Depends on:-

Filter that sets necessay headers of the response.

Injectable propertiesDescription
cacheable

boolean

Whether the response is cacheable or not. By default it is not cacheable.
contentType

java.lang.String

Sets the content type of the response. Default is "text/html; charset=UTF-8".
cookies

java.util.Map

Constructs cookies from the <cookieName, cookieValue> pairs in the map and sets them in response.
headers

java.util.Map

Sets the headers of the response from the map of <headerName, headerValue>.
cacheHoldingTime

long

Sets the cache-control's max-age parameter, value is in milliseconds. Response must be cacheable for this to have any effect.

3.5.7. JSP Configuration Filter

Java class:StandardJspFilterService
Default configuration name:araneaJspConfigFilter
Provides:JspContext
Depends on:LocalizationContext

Provides JSP specific information to children.

Injectable propertiesDescription
submitCharset

java.lang.String

Sets the "accept-charset" attribute value that will be used for rendering Aranea JSP specific systemForm.
jspPath

java.lang.String

Path where widgets rendering themselves with jsp templates should search for them. Default is "/WEB-INF/jsp".
jspExtension

java.lang.String

File name extension jsp templates are assumed to have. Default is ".jsp".

3.5.8. User Messages Filter

Java class:StandardMessagingFilterWidget
Default configuration name:araneaMessagingFilter
Provides:MessageContext
Depends on:-

See Section 2.8.1, “MessageContext”.

3.5.9. Popup Windows Filter

Java class:StandardPopupFilterWidget
Default configuration name:araneaPopupFilter
Provides:PopupWindowContext
Depends on:ThreadContext, TopServiceContext, TransactionContext

Provides methods for opening new session-threads and renders these in different browser windows at client-side.

Injectable propertiesDescription
threadServiceFactory

ServiceFactory

Factory that should build the component chain according to effective Aranea configuration, beginning with sessionthread-level filters.

3.5.10. Component Serialization Auditing Filter

Java class:StandardSerializingAuditFilterService
Default configuration name:araneaSerializingAudit (not included in default filter chain)
Provides:-
Depends on:-

Always serializes the the session during the request routing. This filter helps to be aware of serializing issues during development as when the session does not serialize, exception is always thrown. In production configuration, this filter should never be enabled, thus it is disabled by default.

Injectable propertiesDescription
testXmlSessionPath

java.lang.String

The path where the serialized sessions should be logged in XML format. If not specified, serialization tests are performed in-memory.

3.5.11. Statistics Logging Filter

Java class:StandardStatisticFilterService
Default configuration name:araneaStatisticFilter
Provides:-
Depends on:-

Filter that logs the time it takes for the child service to serve the request (complete its action method).

Injectable propertiesDescription
message

java.lang.String

The prefix of the statistics log statement.

3.5.12. Browser Window Cloning Filter

Java class:StandardThreadCloningFilterService
Default configuration name:araneaThreadCloningFilter
Provides:ThreadCloningContext
Depends on:ThreadContext, TopServiceContext

Implementation of a service that clones currently running session thread upon request and sends a response that redirects to cloned session thread. It can be used to support "open link in new window" feature in browsers. Cloning is generic and resource demanding, as whole tree of session thread components is recreated. Custom applications may find that they can implement some application specific cloning strategy that demands less memory and processing power.

Injectable propertiesDescription
timeToLive

java.lang.Long

Inactivity time for cloned thread after which thread router may kill the thread service. This is specified in milliseconds. If unset, threads created by cloning service usually live until HTTP session in which they were spawned expires.

3.5.13. Multi-submit Protection Filter

Java class:StandardTransactionFilterWidget
Default configuration name:araneaTransactionFilter
Provides:TransactionContext
Depends on:SystemFormContext

TransactionContext implementation that filters routing of duplicate requests. The detection of duplicate requests is achieved through defining new transaction ID in each response and checking that next request submits the consistent transaction ID. Missing (null) transaction ID is always considered inconsistent. For purposes of asynchronous requests, override transaction ID is always considered consistent.

Transactions work in Aranea application by default. You may notice it in a web page as a hidden field, for example:

<input name="araTransactionId" type="hidden" value="-8629560801569274688"/>

The value is random, and a TransactionContext checks every request whether it is the same as expected (it remembers the previous transactionId value it gave to the page). If it is not the same, the request will be ignored. Therefore, one may notice when transactions are inconsistent: the pages won't update itself (on first click).

Sometimes, however, a transactionId may become inconsistent (for various reasons, such as due to a background request). Then the solution would be to change the transactionId value to "override" (for example, by using JavaScript). (In the next response, the transactionId will still have a new random numeric value.)

Request parameter nameDescription
transactionIdTransaction id must be equal to the last one generated for the transaction to be consistent.

3.5.14. Class Reloading Filter

Java class:StandardClassReloadingFilterWidget
Default configuration name:-
Provides:-
Depends on:-

This filter allows to reload the underlying object classes dynamically. This means that you can just change the widget source file, compile it (e.g. with IDE built-in compiler) and it will be reloaded seamlessly in Aranea. This will apply only to Aranea widget classes under this filter and the classes they contain (but not e.g. Spring beans). This filter must be registered instead of the araneaApplicationStart to function.

Warning

None of the classes under this filter may be configured by Spring or anything else using its own classloader!

Injectable propertiesDescription
childClass

java.lang.String

The full names of the child widget class.

3.5.15. Client State Serialization Filter

Java class:StandardClientStateFilterWidget
Default configuration name:araneaClientStateFilter (not included in default filter chain)
Provides:-
Depends on:SystemFormContext

This filter will serialize the state of underlying widgets onto client-side. This significantly decreases the server-side session size and thus memory use. It is especially useful in intranet applications with lots of spare bandwidth. The filter should be positioned as the first custom widget filter for most gain.

Note

The filter will protect against tampering with the serialized state and will throw an exception if modified state is submitted from the client-side. As a bonus this filter will also allow a user to make up to 10 steps back and forward in browser history, restoring the correct state.

Injectable propertiesDescription
compress

boolean

If true the serialized state will also be GZIP'ed, trading processor time for bandwidth. False by default.

3.5.16. Extension File Import Filter

Java class:StandardFileImportFilterService
Default configuration name:araneaFileImportFilter
Provides:-
Depends on:-

This filter is responsible for providing a virtual file system so that extensions could make use of the resources included in .JAR files. See Section 3.6.1, “Extension Resources”

When the file importer is used to provide aranea resources (styles/JavaScripts) it also defines cache time, after which the browser reloads the resources. You can configure this value through web.xml configuration parameter (the default time is 1 hour):

<context-param>
        <param-name>fileImporterCacheInMillis</param-name>
        <param-value>10800000</param-value>
</context-param>

3.5.17. Bookmarking/URL Mounting Filter

Java class:StandardMountingFilterService
Default configuration name:araneaMountingFilter
Provides:MountContext
Depends on:-

Implementation of a service that allows to "mount" flow components to a publicly accessible URL. It is used when it is needed that some (read-only) parts of application are accessible to users who are not able to enter the session-based conversation with application.

Injectable propertiesDescription
mounts

java.util.Map<String, MountContext.MessageFactory>

Keys in the map are URL prefixes under which the flow component is mapped. Values are org.araneaframework.Message factories of type MountContext.MessageFactory—producing messages that generate component hierarchy for serving wanted content.

3.5.18. Root Flow Container

Java class:RootFlowContainerWidget
Default configuration name:araneaRootFlowContainer
Provides:RootFlowContext, FlowContext
Depends on:-

See Section 2.8.3, “FlowContext” for purpose and philosophy behind FlowContext. RootFlowContext is same as FlowContext, but allows acces to the root flow container at any time. Remember that RootFlowContext is the topmost flow context that everything else depends on. One can find it from the Environment.

Tip

Flow containers are not generally a part of the framework and can be used in your application as needed. In a typical Aranea application the menu will inherit from ExceptionHandlingFlowContainerWidget that besides providing the flow container functionality also allows to handle flow exceptions inside the container, preserving the menus and current state. See business application tutorial for more information.

Injectable propertiesDescription
top

org.araneaframework.Widget

First widget to be started in this container.

3.5.19. Overlay Container

Java class:StandardOverlayContainerWidget
Default configuration name:araneaOverlayContainer
Provides:OverlayContext
Depends on:-

Supports running processes in "overlay" layer (in parallel FlowContext of the same session thread). Allows construction of modal dialogs and modal processes.

Injectable propertiesDescription
main

Widget

Widget corresponding to main process running outside overlay.
overlay

FlowContextWidget

Component responsible for running processes in overlay layer.

3.5.20. System Form Field Storage Filter

Java class:StandardSystemFormFilterService
Default configuration name:araneaSystemFormFilter
Provides:SystemFormContext (for adding/examining managed form fields).
Depends on:TopServiceContext, ThreadContext

Stores system form fields that will be written out when <ui:systemForm> tag is used. Form fields that indicate service levels (topServiceId and threadServiceId) are always automatically added to every response by this implementation.

This filter does not have any special injectable properties (except the usual childService). SystemFormContext interface is accessible from the Environment when this filter is present in the hierarchy and provides addField(String key, String value); and Map getFields(); methods for managing special form fields. See also information about systemForm tag.

3.5.21. Window Scroll Position Filter

Java class:StandardWindowScrollPositionFilterWidget
Default configuration name:araneaScrollingFilter
Provides:WindowScrollPositionContext
Depends on:-

This filter provides a way to preserve the scroll position of the window so that the user would not have to scroll back to the same place on the page every time they click on something. With every submit, the page sends its scroll coordinates so that the next response would know where to scroll the page. All-in-all, you can consider it a nice feature to have.

To enable this feature, one must define it in aranea-conf.xml:

 <bean id="araneaCustomWidgetFilters" singleton="false"
    class="org.araneaframework.framework.filter.StandardFilterChainWidget">
   <property name="filterChain">
     <list>
       <ref bean="araneaScrollingFilter"/>
     </list>
   </property>
 </bean>

Note the araneaScrollingFilter, which you do not have to define yourself (just reference it).

In addition, this feature must be registered in a (root) JSP page:

 ...
  <ui:body>

    <div id="cont1">
      <ui:systemForm method="POST">
        <ui:registerScrollHandler/>
        <ui:registerPopups/>
        <ui:registerOverlay/>
...

Notice the <ui:registerScrollHandler/> tag!

3.6. Other

3.6.1. Extension Resources

External resources, such as javascript, style and image files of Aranea components are managed through different configuration files. The resources are listed in XML files and can be accessed through StandardFileImportFilterService. This approach makes it possible to package all the resources into the aranea jar archives and no manual copying of necessary files to fixed locations is needed.

Aranea comes bundled with a aranea-resources.xml file which defines all the external resources.

<?xml version="1.0" encoding="UTF-8"?>
<resources>
  <files content-type="text/css" group="defaultStyles">
    <file path="styles/_styles_global.css"/>
    ...
    <file path="styles/_styles_screen.css"/>
  </files>

  <files content-type="image/gif">
    <file path="gfx/i01.gif"/>
    ...
    <file path="gfx/i02.gif"/>
  </files>
  ...
</resources>

All the files listed in the configuration files are allowed to be loaded through the FileImportFilter. Some are grouped by name to provide an easy access for reading files in bulk.

To override specific files in the configuration file, the new file should be placed in a subdirectory override. When loading a file, Aranea first trys to open the file in the override directory and on failure trys to read the file without the prefix directory.

To add files to the defined list, construct a configuration file and name it aranea-resources.xml. All such configuration files from the classpath are parsed for the resources. If two file groups are defined with the same name, the group is formed by taking a union from the files in the group.

Groupnames defaultStyles and defaultScripts are predefined groups for managing the necessary core files that must be included for Aranea to work correctly.

For custom loading a resource, the URL to use is /fileimporter/filepath. The fileimporter is StandardFileImportFilterService.FILE_IMPORTER_NAME and filepath is the path that is defined for the file in the resource configuration file.

Extensions of the framework provide their own configuration files for configuring their resources. New extensions cannot be defined right now on the fly.

Chapter 4. JSP and Custom Tags

4.1. Aranea Standard Tag Library

Aranea supports JSP rendering by providing a JSP 1.2 custom tag library that tries to abstract away from HTML and allow programming in terms of widgets, layouts and logical GUI elements. The tag library URI is "http://araneaframework.org/tag-library/standard" and it is contained in aranea-presentation.jar, so putting this JAR in the classpath (e.g. WEB-INF/lib) is enough to put it to work. Library tags support JSP Expression Language that is used in JSTL 1.0.

Aranea examples use JSP XML form and in such form importing the library should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" 
  xmlns:ui="http://araneaframework.org/tag-library/standard" version="1.2">
  ...
</jsp:root>

In a usual JSP file it should look like this:

<%@ taglib uri="http://araneaframework.org/tag-library/standard" prefix="ui" %>
...

The suggested prefix for the tag library is "ui".

There is otherwise identical taglib that has <rtexprvalue> set to true for each tag attribute. URI for that taglib is http://araneaframework.org/tag-library/standard_rt. When using JSP version 2.0 or higher, this taglib should be used, otherwise EL in attributes is rejected by containers.

4.2. System Tags

Aranea JSP rendering should start from some root JSP (root template) that will include the root widget(s) (which typically are some kind of flowcontainers or menus). To support widgets and other custom tags one needs to make sure that the template looks something like this:

<?xml version="1.0" encoding="UTF-8"?>
<jsp:root 
  xmlns:jsp="http://java.sun.com/JSP/Page" 
  xmlns:ui="http://araneaframework.org/tag-library/standard" version="1.2">
  <ui:widgetContext>
    <html>
      <head>
        <title>Aranea Template Application</title>
         
        <ui:importScripts/>      
        <ui:importStyles/>      
   
      </head>
    
        <ui:body>
          <ui:systemForm method="POST">
            <h1>Aranea Application</h1>
              
            <ui:messages/>

            <ui:widgetInclude id="root"/>
          </ui:systemForm>
          
      </ui:body>   
    </html>
  </ui:widgetContext>
</jsp:root>

Next are described all these tags except <ui:widgetInclude>, which is described in the following section.

4.2.1. <ui:importScripts>

Aranea comes bundled with different external resources: javascript libraries, stylesheets and images. To automate the process of loading the javascript files without the manual copying of them to specific webapp locations, a special filter is used. The filter is able to read files from aranea jar files.

<ui:importScripts> depends on the filter StandardServletFileImportFilterService being set. The filter provides the functionality of reading files from the jars on the server.

If no attributes specified, the default group of javascript files are loaded.

Attributes

AttributeRequiredDescription
filenoWrites HTML <script> tag to load the specific file.
groupnoWrites HTML <script> tag to load a group of javascript files.

Here is the list of all available values for the group attribute:

  1. all - imports all of the groups described below.
  2. core-all - imports the core Aranea scripts, in addition, popup, modalbox, back-button support (rsh) scripts.
  3. core - imports only core aranea scripts that are always needed.
  4. calendar - imports only DHTML calendar scripts.
  5. calendar_et - imports only DHTML calendar scripts with an interface in estonian language.
  6. modalbox - imports only ModalBox scripts.
  7. rsh - imports only back-button support scripts.
  8. prototip - imports only Prototip scripts.
  9. ajaxupload - imports only AJAX upload functionality scripts.
  10. logger - imports only log4javascript scripts.

Note

Since 1.2.1 these Aranea JavaScript files (groups) are compressed for faster download. However, it also possible to see these scripts in more readable form by appenging "-devel" to these group names, e.g. all vs. all-devel. These groups don't have the devel version:

  • calendar
  • calendar_et
  • prototip
  • ajaxupload
  • logger (always compressed)

If you are used to including aranea*.js scripts one-by-one then your scripts will be automatically compressed. To include original scripts, insert "src/" right before the file name, e.g.

<ui:importScripts file="js/aranea/aranea.js"/>

to:

<ui:importScripts file="js/aranea/src/aranea.js"/>

To use TinyMCE editor, you need to include its scripts like this:

<ui:importScripts file="js/tiny_mce/tiny_mce.js"/>

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:importScripts/> <!-- imports files from 'all' group -->
<ui:importScripts group="logger"/> <!-- imports additional debug scripts (js logger) -->

4.2.2. <ui:importStyles>

Aranea comes bundled with CSS files to provide custom look for different predefined components (the template app, calendar, htmleditor, etc.). Just as with javascript, to use them one would have to extract them from the jars and use them just like any other css file would be used. To automate this process with aranea css files one can use the <ui:importStyles> tag to include the css files automatically.

<ui:importStyles> depends on the filter StandardServletFileImportFilterService being set. The filter provides the functionality of reading files from the jars on the server.

If no are attributes specified, the default group (i.e. all) of css files are loaded.

Attributes

AttributeRequiredDescription
filenoWrites out the HTML's CSS handling link to load the specific file.
groupnoWrites out the HTML's CSS handling link to load the group of files.
medianoMedia type to which imported styles are applied.

Here is the list of all available values for the group attribute:

  1. all - imports all of the styles (CSS) from the groups described below.
  2. aranea - imports only Aranea styles (aranea.css for the page loading message, and comboselect.css for multiselect combo box.
  3. calendar - imports only DHTML calendar styles.
  4. contextmenu - imports only Aranea context menu styles.
  5. modalbox - imports only ModalBox styles.
  6. prototip - imports only Prototip styles.

4.2.3. <ui:body>

This tag will render an HTML <body> tag with Aranea JSP specific onload and onunload events attached. It usually writes out some other page initialization scripts too, depending on the circumstances. It must be present in a JSP template, otherwise most client-side functionality will cease to function.

Attributes

AttributeRequiredDescription
onloadnoOverwrite the standard Aranea JSP HTML body onload event. Use with caution.
onunloadnoOverwrite the standard Aranea JSP HTML body onload event. Use with caution.
idnoHTML BODY id.
dirnoHTML BODY dir attribute.
langnoHTML BODY lang attribute.
titlenoHTML BODY title attribute.

4.2.4. <ui:systemForm>

This tag will render an HTML <form> tag along with some Aranea-specific hidden fields. When making custom web applications it is strongly suggested to have only one system form in the template and have it submit using POST. This will ensure that no matter what user does no data is ever lost. However Aranea does not impose this idiom and one may just as well submit using GET, define system forms in widgets and use usual HTML links instead of JavaScript. See Section 4.2, “System Tags” for usage example and Section 3.5.20, “System Form Field Storage Filter” about a filter that provides some essential hidden fields.

Attributes

AttributeRequiredDescription
idnoThe HTML "id" of the <form> tag that may be used in JavaScript. It will be autogenerated if omitted.
methodyesHTTP submit method, either GET or POST.
enctypenoSame as HTML <form> attribute enctype, defines how form data is encoded.

Variables

VariableDescriptionType
systemFormIdSystemForm FORM id.String

4.2.5. <ui:messages>

This tag will render messages of given type if they are present in current MessageContext. When type is not specified, all types of messages are rendered. As MessageContext is typically used for error messages, it is common to render these messages somewhere near top of the page, where they can easily be spotted.

Attributes

AttributeRequiredDescription
typenoMessage type.
styleClassnoCSS class applied to rendered messages, default being aranea-messages.
divIdnoSets the id of the HTML <div> inside which the messages are rendered. If left unspecified, no id is assigned.
stylenoCSS inline style applied to rendered messages. Use styleClass instead.

Examples

<?xml version="1.0" encoding="UTF-8"?>
...
<ui:messages type="info"/>
<ui:messages type="error" styleClass="custom-error-message-class"/>
<ui:messages/>
...

4.3. Basic Tags

4.3.1. <ui:attribute>

Defines an attribute of the containing element, where possible. See also Section 4.3.3, “<ui:element>”. Most form element tags accept attributes set by this tag too, see the section called “Examples”.

AttributeRequiredDescription
nameyesAttribute name.
valueyesAttribute value.

Examples

<?xml version="1.0" encoding="UTF-8"?>
...
  <!-- set the onkeypress attribute for HTML input produced by ui:textInput-->
  <ui:textInput>
    <ui:attribute name="onkeypress" value="upperCase(this);"/>
  </ui:textInput>
...

4.3.2. <ui:elementContent>

Defines an HTML element content, meaning the body of the HTML element where text and other tags go.

4.3.3. <ui:element>

Defines HTML node, can be used together with <ui:attribute> and <ui:elementContent> to define a full HTML node.

AttributeRequiredDescription
namenoHTML element name.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:element name="span">
  <ui:attribute name="class" value="fancy"/>
  <ui:elementContent>Contents of fancy span.</ui:elementContent>
</ui:element>

4.3.4. <ui:keyboardHandler>

Registers a simple javascript keyboard handler.

AttributeRequiredDescription
scopenoWhen a keyboard event happens, it is usually associated with a certain form element / form / widget / etc. The object with which an event is associated is identified by a hierarchical id (e.g. there may be widget 'somelist', containing form 'somelist.form', containing textbox 'somelist.form.textbox'. The scope is a prefix of that id that must match in order for the handler to be triggered. For example, the handler with scope='somelist.form.textbox' will be triggered only when the event in the textbox occurs, but the handler with scope="somelist" will be triggered when any event in any of the elements inside any of the forms of "somelist" occurs. I.e. for any element with ID beginning with 'somelist'. When scope is not specified, a global handler is registered, that reacts to an event in any form/widget.
handleryesA javascript handler function that takes two parameters - the event object and the element id for which the event was fired. Example:
function(event, elementId) { alert(elementId); }
keyCodenoKeycode to which the event must be triggered. 13 means enter. Either keyCode or key must be specified, but not both.
keynoKey, to which the event must be triggered. Key is specified as a certain 'alias'. The alias may be an ASCII character or a digit (this will denote the corresponding key on a US keyboard), a space (' '), or one of the following: 'return', 'escape', 'backspace', 'tab', 'shift', 'control', 'space', 'f1', 'f2', ..., 'f12'.
keyCombonoKey combination, which should trigger the event. It can is specified with key aliases separated with "+" signs. For example "ctrl+alt+f1", "alt+r" etc.

Examples

<!-- Globally-scoped F2 listener -->
<ui:keyboardHandler 
  scope="" 
  key="f2" 
  handler="function() { alert('You pressed F2. Do it again if you dare!');}"/>

4.3.5. <ui:eventKeyboardHandler>

Registers a 'server-side' keyboard handler that sends an event to the specified widget.

AttributeRequiredDescription
scopenoSection 4.3.4, “<ui:keyboardHandler>”
widgetIdnoId of Widget that is target of event produced by keyboard handler.
eventIdnoId of event that should be sent to target widget.
eventParamnoEvent parameters
updateRegionsnoEnumerates the regions of markup to be updated in this widget scope. Please see <ui:updateRegion> for details.
globalUpdateRegionsnoEnumerates the regions of markup to be updated globally. Please see <ui:updateRegion> for details.
keyCodenoKeycode to which the event must be triggered. 13 means enter. Either keyCode or key must be specified, but not both.
keynoKey, to which the event must be triggered. Key is specified as a certain 'alias'. The alias may be an ASCII character or a digit (this will denote the corresponding key on a US keyboard), a space (' '), or one of the following: 'return', 'escape', 'backspace', 'tab', 'shift', 'control', 'space', 'f1', 'f2', ..., 'f12'.
keyCombonoKey combination, which should trigger the event. It can is specified with key aliases separated with "+" signs. For example "ctrl+alt+f1", "alt+r" etc.

Examples

<!-- F2 listener that sends event 'add' to context widget upon activation -->
<ui:eventKeyboardHandler eventId="add" key="f2" widgetId="${widgetId}"/>

4.4. Widget Tags

4.4.1. <ui:widgetContext>

This tag should generally be the root of every widget JSP. It makes the widget view model accessible as an EL variable. It can also be used to render a descendant widget in the same JSP with the current widget. In the latter case you should set the id attribute to the identifier path of the descendant widget in question. Note that all widget-related tags inside of this tag will assume that the widget in question is their parent or ancestor (that is all the identifier paths will start from it).

Attributes

AttributeRequiredDescription
idnoA dot-separated widget identifier path leading from the current context widget to the new one.

Variables

VariableDescription
widgetThe context widget instance. Can be used to access JavaBean property data from the widget (e.g. ${widget.foo} will translate to a getFoo() widget call}.
widgetIdThe full dot-separated identifier of the context widget.
viewDataThe view data of the context widget (see BaseApplicationWidget.putViewData()).
viewModelThe view model of the context widget.
scopedWidgetIdThe scoped id of the context widget.

Examples

The most common usage of <ui:widgetContext> is as root tag for widget JSPs:
<?xml version="1.0" encoding="UTF-8"?>
...
  <ui:widgetContext>
    ...
    <c:out value="${viewData.myMessage}"/>
    ...
  </ui:widgetContext>
...
The other use case is to render a descendant widget:
<?xml version="1.0" encoding="UTF-8"?>
...
  <ui:widgetContext>
    ...
    <ui:widgetContext id="child.ofMyChild">
      <c:out value="${viewData.messageFromChildOfMyChild}"
    </ui:widgetContext>
    ...
  </ui:widgetContext>
...

4.4.2. <ui:widget>

This tag is used when one needs to render a child or descendant widget while still retaining in both current widget context and JSP. It publishes the widget view model and full identifier as EL variables, but does little else and does not setup a widget context (e.g. <ui:widgetInclude> tag will not take it into account).

Attributes

AttributeRequiredDescription
idyesA dot-separated widget identifier path leading from the current context widget to the target widget.

Variables

VariableDescription
widgetThe widget instance. Can be used to access JavaBean property data from the widget (e.g. ${widget.foo} will translate to a getFoo() widget call}.
widgetIdThe full dot-separated identifier of the widget.
viewDataThe view data of the widget (see BaseApplicationWidget.putViewData()).
viewModelThe view model of the widget.
scopedWidgetIdThe scoped id of the context widget.

Examples

<?xml version="1.0" encoding="UTF-8"?>
...
  <ui:widgetContext>
    ...
    <ui:widget id="child.ofMyChild">
      <c:out value="${viewData.messageFromChildOfMyChild}"
          <ui:widgetInclude id="child"/>
    </ui:widget>
    ...
  </ui:widgetContext>
...

4.4.3. <ui:widgetInclude>

This tag is used to render some child or descendant widget. It will call the widget's render() method, which will allow the target widget to choose how to render itself.

Attributes

AttributeRequiredDescription
idyesA dot-separated widget identifier path leading from the current context widget to the target widget.
pathnoPath to JSP, relative to jspPath of StandardJspFilterService.

Examples

<?xml version="1.0" encoding="UTF-8"?>
...
  <ui:widgetContext>
    ...
    <ui:widgetInclude id="child.ofMyChild"/>
    ...
  </ui:widgetContext>
...

4.5. Event-producing Tags

4.5.1. <ui:eventButton> and <ui:eventLinkButton>

These tags will render a button (or a link) that when clicked will send a specified event to the target widget with an optional String parameter.

Attributes

AttributeRequiredDescription
idnoHTML "id" of the element that can be used to access it via DOM.
labelIdnoThe key of the localizable label that will be displayed on the button.
eventIdnoThe identifier of the event that will be sent to the target widget.
eventParamnoString event parameter that will accompany the event.
eventTargetnoID of receiving widget. Almost never set directly. Defaults to current context widget.
disablednoIf set to a not null value will show the button disabled.
renderModenoAllowed values are (button | input) - the corresponding HTML tag will be used for rendering. Default is button. This attribute only applies to <ui:eventButton>, <ui:eventLinkButton> is always rendered with HTML link.
styleClassnoThe CSS class that will override the default one.
updateRegionsnoComma separated list of update regions that should be updated upon button receiving event. This attribute is only needed when using AJAX features—ordinary HTTP requests always update whole page.
globalUpdateRegionsnoComma separated list of global update regions that should be updated upon button receiving event. This attribute is only needed when using AJAX features—ordinary HTTP requests always update whole page.
onClickPreconditionnoPrecondition for deciding whether onclick event should go server side or not. If left unspecified, this is considered to be true.
tabindexnoThis attribute specifies the position of the current element in the tabbing order for the current document. This value must be a number between 0 and 32767.

HTML, Styles and JavaScript

The eventButton tag writes out an HTML <button> closed tag with a default CSS class of "aranea-button".

The eventLinkButton tag writes out an HTML <a> open tag with a default CSS class of "aranea-link-button".

Examples

<?xml version="1.0" encoding="UTF-8"?>
...
  <ui:widgetContext>
    ...
    <ui:eventButton eventId="test" eventParam="${bean.id}"/>
    <ui:eventLinkButton eventId="edit" eventParam="${bean.id}">
      <img src="editButton.png"/>
    </ui:eventLinkButton>
    ...
  </ui:widgetContext>
...

4.5.2. <ui:onLoadEvent>

This tag will register events that are executed when HTML page body has completely loaded. This tag can be used multiple times, all specified events will be added to event queue and executed in order of addition.

Attributes

AttributeRequiredDescription
eventyesEvent to register.

Examples

<?xml version="1.0" encoding="UTF-8"?>
...
    <ui:onLoadEvent event="activateFlashLights();"/>
    <ui:onLoadEvent event="changeMenuBackGroundColor();"/>
...

4.5.3. <ui:registerPopups>

This tag checks presence of server-side session-threads that represent popups and adds system loadevent for opening them in new browser window at client-side. For tag to have an effect, HTML page BODY tag must have attribute onload event set to AraneaPage (See Aranea Clientside Javascript) onload event. Also, this tag only works inside <ui:systemForm> tag.

Attributes

This tag has no attributes.

Examples

<?xml version="1.0" encoding="UTF-8"?>
...
<ui:body>
  <ui:systemForm method="POST">
    <ui:registerPopups/>
  </ui:systemForm>
</ui:body>
...

4.6. HTML entity Tags

HTML entities can be inserted by using the predefined entity tags or using the <ui:entity> for entities that have not been defined by Aranea JSP library.

The entity tag accepts a attribute code which is used as &code; to get the HTML entity.

AttributeRequiredDescription
codenoHTML entity code, e.g. nbsp or #012.
countnoNumber of times to repeat the entity.

4.6.1. Predefined entity tags

The following predefined entities also accept the count attribute. It defines the number of times to repeat the entity.

TagDescription
<ui:acute>HTML &acute; entity.
<ui:copyright>HTML &copyright; entity.
<ui:gt>HTML &gt; entity.
<ui:laquo>HTML &laquo; entity.
<ui:lt>HTML &lt; entity.
<ui:nbsp>HTML &nbsp; entity.
<ui:raquo>HTML &raquo; entity.
<ui:acute>HTML &acute; entity.

4.7. Putting Widgets to Work with JSP

Now we have defined enough JSP tags to render our example widget (see Section 2.7.8, “Putting It All Together”):

<?xml version="1.0" encoding="UTF-8"?>
<jsp:root 
  xmlns:jsp="http://java.sun.com/JSP/Page" 
  xmlns:c="http://java.sun.com/jstl/core"
  xmlns:ui="http://araneaframework.org/tag-library/standard" version="1.2">
  <ui:widgetContext>
    <h3>Test widget</h3>
    
    Data field: <c:out value="${viewData.myData.field}"/>
    <ui:eventButton labelId="#Test" eventId="test"/>      
  </ui:widgetContext>
</jsp:root>

We can use just usual JSTL Core library tags to access the widget view data, as long as the <ui:widgetContext> is present via the viewData EL variable.

4.8. Layout Tags

4.8.1. <ui:layout>

Represents a layout. Layouts allow to describe the way content will be placed on the page.

AttributeRequiredApplicable to:
widthnoLayout width.
rowClassesnoDefault style of rows in this layout.
cellClassesnoDefault style of cells in this layout.
styleClassnoCSS class for tag.

Variables

VariableDescriptionType
rowClassProviderProvides row class, usually should not be used from JSP.RowClassProvider
cellClassProviderProvides cell class, usually should not be used from JSP.CellClassProvider

4.8.2. <ui:row>

Represents a row in layout.

AttributeRequiredApplicable to:
heightnoRow height.
cellClassesnoDefault style of cells in this row..
styleClassnoCell css class, defines the way the cell will be rendered.
overrideLayoutnoBoolean that determines whether row's own styleClass completely overrides styleClass provided by surrounding layout (default behaviour), or is appended to layout's styleClass.

Variables

VariableDescriptionType
cellClassProviderProvides cell class, usually should not be used from JSP.CellClassProvider

4.8.3. <ui:cell>

Represents a cell in layout.

AttributeRequiredApplicable to:
heightnoRow height.
widthnoRow width.
colSpannoCell colspan, same as in HTML.
rowSpannoCell rowspan, same as in HTML.
styleClassnoCell css class, defines the way the cell will be rendered.
overrideLayoutnoBoolean that determines whether cells's own styleClass completely overrides styleClass provided by surrounding layout or row (default behaviour), or is appended to layout's or row's styleClass.

Examples

Layouts, rows and cells are used together like this:
<?xml version="1.0" encoding="UTF-8"?>
...
  <ui:layout rowClasses="even,odd" cellClasses="one,two,three,four">
    <ui:row>
      <ui:cell>
         <!-- cell content -->
      </ui:cell>
    </ui:row>
  </ui:layout>
...

4.8.4. <ui:updateRegion>, <ui:updateRegionRow>, and <ui:updateRegionRows>

These three tags define the update regions in the output that can be updated via AJAX requests. The update regions chosen to be updated when some event occurs is decided by tags that take the updateRegion attribute (See Section 5.2.1, “Common attributes for all form element rendering tags.”).

The <ui:updateRegion> should be used when defining updateregion when the region is not contained in HTML table (layout). The <ui:updateRegionRow> is basically a table row (td) and is for updating a table row. The <ui:updateRegionRows> is for defining a region which is an HTML table body, and contains table rows itself. Updating only single cells is not possible due to browser incompatibilities.

AttributeRequiredDescription
id no The id of the region. Will be used to reference the region when POST'ing a form.
globalId no When not using the globalId, the full id will be formed by concatenating the context widget id with the specified id. If for a reason you would want to avoid that, then you specify the id with the globalId attribute.

Either id or globalId attribute is required.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<!-- First update region, placed outside HTML table -->
<ui:updateRegion id="outsideTable">
</ui:updateRegion>

<ui:layout>
  <!-- Second update region, placed inside HTML table -->
  <ui:updateRegionRows id="insideTable">
    <ui:row>
      ...
    </ui:row>
    <!-- Third update region for updating a row, placed inside HTML table -->
    <ui:updateRegionRow id="aRow">
      <ui:cell>...</ui:cell>
      <ui:cell>...</ui:cell>
    </ui:updateRegionRow/>
  </ui:updateRegionRows>
</ui:layout>

<!-- Button that makes a background submit of specified event. 
     When response arrives specified updateregions are updated -->
<ui:eventButton id="test" updateRegions="outsideTable,insideTable"/>

4.9. Presentation Tags

Aranea JSP library contains synonyms for some (deprecated) HTML presentation tags.

4.9.1. <ui:bold>

Acts as the HTML <b> tag.

4.9.2. <ui:italic>

Acts as <i> HTML tag.

4.9.3. <ui:font>

Acts as <font> HTML tag.

AttributeRequiredApplicable to:
face no The font face of the font.
color no The color of the font.

4.9.4. <ui:style>

Sets a CSS class for the tag content, acts as a <span> HTML tag with the class atribute set.

AttributeRequiredApplicable to:
styleClass no CSS class for tag.

4.9.5. <ui:newline>

Puts a visual new line (<br/>).

4.9.6. <ui:tooltip>

Defines tooltip that is shown when web application user hovers mouse over element to which the tooltip is attached.

AttributeRequiredApplicable to:
element yes HTML id of DOM element that is target of the tooltip.
text yes Tooltip content.
options no Options for tooltip (including tooltip classname, title, etc -- see prototip.js for details).

4.9.7. <ui:basicButton>

Represents an HTML form button.

AttributeRequiredApplicable to:
renderMode no Allowed values are (button | input) - the corresponding HTML tag will be used for rendering. Default is button.
id no Button id, allows to access button from JavaScript.
labelId no Id of button label.
onclick no onClick Javascript action.
styleClass no CSS class for button.
style no Inline CSS style for button.

4.9.8. <ui:basicLinkButton>

Represents a link with an onClick JavaScript action.

AttributeRequiredApplicable to:
id no Button id, allows to access button from JavaScript.
styleClass no CSS class for tag.
style no Inline CSS style for tag.
onclick no onClick Javascript action.
labelId no Id of button label.

4.9.9. <ui:link>

Usual HTML link, acts as a <a> HTML tag.

AttributeRequiredApplicable to:
disabledStyleClass no CSS class for disabled link.
id no Link id, allows to access link from JavaScript.
href no Link target URL.
target no Link target, same as <a> HTML tag target attribute.
disabled no Controls whether the link is disabled, disabled link doesn't link anywhere.
styleClass no CSS class for tag.
style no Inline CSS style for tag.

4.10. Programming JSPs without HTML

Aranea standard tag library should mostly be enough to shelter end-users from the need to write HTML inside JSPs. Snippets of HTML are alright but using it too often tends to lead to inflexible UI; instead of embedding HTML in JSPs custom tags should be written if the need arises.

When writing JSPs without embedded HTML, programmers best friends are styleClass attributes of presentation tags, allowing tuning of tag appearances and layout tags.

Layout tags are tags extending BaseLayoutTag. Layout tags allow placing of rows inside them (and rows allow using of cells inside). Standard layout tag (<ui:layout>) outputs HTML table, and standard row and cell tags output HTML tr and td tags, respectively. This is by no means a requirement for layout tags—there are probably ways to achieve the same behaviour with correctly styled HTML div tags; but the tables should do just fine for majority of needs.

4.11. Customizing Tag Styles

Presentation tags (tags extending PresentationTag or implementing StyledTagInterface) have attribute styleClass that specifies the CSS style class used for rendering the tag. When styleClass attribute for tag is not specified, some default style bundled with Aranea is used; or in some cases no HTML class attribute is output at all—allowing cascading styles from some parent (HTML) tag to take over the presentation.

Presentation tags also have style attribute for specifying inline style for tag. Using it is discouraged—tweaking style classes to fit ones specific needs is highly recommended.

Some tags may have more than one attributes for defining tag style. For example <ui:layout> tag and other layout tags that extend LayoutHtmlTag or BaseLayoutTag have attributes rowClasses and cellClasses that specify the default styles for <ui:row> and <ui:cell> tags used within the layout. These can be overriden with row and cell own styleClass attribute.

To actually use new style(s) for some tag one often can just write a new CSS style (i.e. "somestyle { background: #ffc; color: #900; text-decoration: none; }")—apply that and be done with it. For more complicated tags, one may need to take a quick peek at tag source code to see what HTML tags are output and design their styles accordingly. Most of the time that should not be necessary.

Changing default tag styles can be done in two ways—modifying CSS files or extending the tag one wants to customize with dynamic initializer like this:

{
    styleClass = "some-wanted-style";
}

needless to say, first method is very much preferred because creating custom tags just for changing tag styles is quite pointless.

There is also a renderMode attribute; in current tag library there are very few tags supporting this attribute. One of those is ButtonHtmlTag (<ui:basicButton>)—its renderMode should have value "input" or "button" (default) and it specifies whether the button should be rendered in HTML with <input type=button ... > or <button ... > tag. In the future, number of JSP tags having renderMode attribute will probably increase (this can be used to get rid of multiple JSP tags for rendering different types of (multi)selects, inputs and displays).

Attributes defining tag styles

AttributeRequiredApplicable to:
styleInline CSS style applied to tag. Avoid.Presentation tags.
styleClassCSS class applied to tag.Presentation tags.
rowClassCSS class applied to rows inside the tag.Layout tags.
cellClassCSS class applied to cells inside the tag.Layout tags, row tags.
renderModeDefines the renderMode used for rendering the tag.<ui:basicButton>, <ui:eventButton>, <ui:button>.

4.12. Making New JSP Tags

JSP tags are very application specific, need for additional or modified JSP tags arises quite often. Due to presentational nature of HTML and Javascript, extending the tags that really output HTML instead of providing some information to subtags is messy. We look here at some more general tags and contracts that should be followed when writing Aranea JSP tags.

4.12.1. Utilities and base classes

Custom tags should extend at least org.araneaframework.jsp.tag.BaseTag that provides methods for registering subtags, manipulation of pagecontext and attribute evaluation.

import java.io.Writer;
import org.araneaframework.jsp.tag.entity.NbspEntityHtmlTag;
import org.araneaframework.jsp.util.JspUtil;

public class DummyTag extends BaseTag {
  public static String KEY = "org.araneaframework.jsp.tag.DummyTag";
  
  BaseTag subTag;
  
  @Override
  protected int doStartTag(Writer out) throws Exception {
    int result = super.doStartTag(out);

    // make this tag implementation accessible to subtags which 
    // is quite pointless since this tag does not implement any useful interface.
    // it demonstrates Aranea JSP convention for providing info to subtags
    addContextEntry(KEY, this);
    
    // write some real output that ends up at the served web page
    JspUtil.writeOpenStartTag(out, "div");
    JspUtil.writeAttribute(out, "id", "dummyDivId");
    JspUtil.writeCloseStartTag(out);
    
    // it is possible to register in JAVA code too, this one just writes out nbsp entity.
    subTag = new NbspEntityHtmlTag();
    registerSubtag(subTag);
    executeStartSubtag(subTag);
    
    return result;
  }

  @Override
  protected int doEndTag(Writer out) throws Exception {
    executeEndTagAndUnregister(subTag);
    
    JspUtil.writeEndTag(out, "div");
    
    return super.doEndTag(out);
    // Now everything about this tag ceases to exist, 
    // context entries are removed, souls are purged.
  }
}

org.araneaframework.jsp.util.JspUtil that was used here is an utility class containing some functions for writing out (XML) tags with somewhat less room for errors than just out.write(). Other notable methods provided by BaseTag are getOutputData() that returns response data, getConfiguration() and getLocalizationContext(). For tags with attributes, attribute evaluation functions that support Expression Language (EL) expressions are provided in BaseTag. Typical usage of these functions is following:

public void setWidth(String width) throws JspException {
  this.width = (String)evaluate("width", width, String.class);
}

Another common base tag for tags that output real HTML is org.araneaframework.jsp.PresentationTag. The DummyTag should really extend it too, since it outputs some HTML. PresentationTag defines style and styleClass attributes that can be applied to most HTML tags.

Important tag cleanup method is doFinally() that is called after rendering. It should be used to clear references to objects that should no longer be referenced after rendering. As in containers tag instances can live very long time, they can leak quite a lot of memory unless resources are deallocated.

4.12.2. Inheriting tag attributes from base tags.

Custom tags extending Aranea tags are able to accept all supertag attributes, but these must be also defined in TLD, otherwise the JSP containers will complain. As some base tags may be abstract, information about their attributes cannot be deduced from Aranea JSP standard TLD. To address this problem, Aranea distribution does the following: aranea.jar and aranea-jsp.jar include the file META-INF/aranea-standard.tcd (TCD stands for Tag Class Descriptor) which includes the attribute information for all Aranea Standard JSP classes. To make use of this information, one first generates TLD for custom tag classes and then merges the TCD information into it. It is done with org.araneaframework.buildutil.TcdAndTldMerger utility included in aranea.jar (since 1.0.10, previously it had to be compiled separately after downloading distribution). All custom compiled tag classes as well as Aranea JSP tag classes must be available on classpath when using this utility.

Example of using the TcdAndTldMerger utility:

<target name="tld">
  <!-- generate TLD without parent attribute information -->
  <webdoclet destdir="somedir" force="false" >
    <fileset dir="${src.dir}" includes="**/*Tag.java"/>

    <jsptaglib validatexml="true"
      shortName="shortName"
      filename="filename.tld"
      uri="customuri"
      description="description"
    />
  </webdoclet>

  <!-- invoke the TcdAndTldMerger utility -->
  <java classname="org.araneaframework.buildutil.TcdAndTldMerger" fork="true">
    <arg value="META-INF/aranea-standard.tcd"/>    <!-- Tag class descriptor to merge with -->
    <arg value="somedir/filename.tld"/>            <!-- Source TLD -->
    <arg value="somedir/filename.tld"/>            <!-- Destination TLD -->
    <classpath>
    <path refid="araneaclasspath"/>
      <path refid="compiledcustomtagclasses"/>
      <path refid="varia">
    </classpath>
  </java>

</target>

When running given target, one should see messages similar to following:

8 attributes for 'custom.RandomTag' found from 'org.araneaframework.jsp.tag.presentation.PresentationTag'.

4.12.3. Widgets and events

Sending events to widgets is accomplished with javascript submit functions, helpful utility being org.araneaframework.jsp.util.JspUtil and org.araneaframework.jsp.util.JspWidgetCallUtil. First one would construct org.araneaframework.jsp.UiEvent and (in case of HTML element which receives only one event) calls JspUtil.writeEventAttributes(Writer out, UiEvent event) and afterwards writeSubmitScriptForEvent(Writer out, String attributeName).

//public UiEvent(String eventId, String eventTargetWidget, String eventParameter)
UiEvent event = new UiEvent("hello", "contextWidgetId", "name");
// long way to ouput custom attributes version
JspUtil.writeEventAttributes(out, event);
JspWidgetCallUtil.writeSubmitScriptForEvent(out, attributeName);
// short version
JspWidgetCallUtil.writeSubmitScriptForEvent(out, "onclick", event);

// both will output something like this:
    // arn-evntId="hello" 
    // arn-trgtwdgt="contextWidgetId" 
    // arn-evntPar="name" 
    // onclick="return _ap.event(this);"

4.12.4. Layouts

New layouts are mostly concerned with styles or render layouts with some additional tags instead plain table, tr, td. As simple example, we define a layout that applies a class "error" to cells which contain invalid FormElement. Note that approach we use only works when cell tag is aware of the surrounding FormElement at the moment of rendering, meaning that FormElement is rendered in JSP something like this:

<?xml version="1.0" encoding="UTF-8"?>
...
<ui:formElement id="someId">
    <ui:cell>
        <ui:label/>
    </ui:cell>

    <ui:cell>
        <ui:textInput/>
    </ui:cell>
</ui:formElement>
...

What is needed foremost is a decorator for cells that are used inside invalid FormElement.

public class ErrorMarkingCellClassProviderDecorator implements CellClassProvider {
  protected CellClassProvider superProvider;
  protected PageContext pageContext;

  // constructs a decorator for superProvider, makes pageContext accessible
  public ErrorMarkingCellClassProviderDecorator(CellClassProvider superProvider, PageContext pageContext) {
    this.superProvider = superProvider;
    this.pageContext = pageContext;
  }

  public String getCellClass() throws JspException {
    FormElement.ViewModel formElementViewModel = (FormElement.ViewModel)
      pageContext.getAttribute(FormElementTag.VIEW_MODEL_KEY, PageContext.REQUEST_SCOPE);
    // superProvider.getCellClass() may only be called once, otherwise moves on to next cell's style
    String superClass = superProvider.getCellClass();

    if (formElementViewModel != null && !formElementViewModel.isValid()) {
      if (superClass != null)
        return superClass + " error";
      else
        return "error";
    }

    return superClass;
  }
}

Actual layout tag that decorates its cells according to described logic:

public class CustomLayoutTag extends LayoutHtmlTag {
  protected int doStartTag(Writer out) throws Exception {
    int result = super.doStartTag(out);
    addContextEntry(CellClassProvider.KEY, new ErrorMarkingCellClassProviderDecorator(this, pageContext));

    return result;
  }
}

Chapter 5. Forms and Data Binding

One of the most common tasks in web applications is gathering user input, converting it to model objects and then validating it. This is typically referred to as data binding and every major web framework has support for this activity. In this chapter we will introduce the widgets and supporting API that implement this tasks.

5.1. Forms

Unlike many other frameworks, in Aranea request processing, validating and data binding is not a separate part of the framework, but just another component. Specially it is widget org.araneaframework.uilib.form.FormWidget and some support widgets. A typical form is shown on Figure 5.1, “Form example”.

Form example

Figure 5.1. Form example

5.1.1. FormWidget

Let's say we have a Person model JavaBean that looks like this:

public class Person {
  private Long id;
  private String name;
  private String surname;   
  private String phone;

  public Long getId() {return id;}
  public void setId(Long id) {this.id = id;}
  
  public String getName() {return name;}
  public void setName(String name) {this.name = name;}
  
  public String getSurname() {return surname;}
  public void setSurname(String surname) {this.surname = surname;}
  
  public String getPhone() {return phone;}
  public void setPhone(String phone) {this.phone = phone;}
}

A typical form will be created and used like this:

...
private BeanFormWidget personForm;
private Person person;
...
protected void init() {
  ...
  personForm = new BeanFormWidget(Person.class);
  addWidget("personForm", personForm);

  personForm.addBeanElement("name", "#Name", new TextControl(new Long(3), null), true);
  personForm.addBeanElement("surname", "#Last name", new TextControl(), true);
  personForm.addBeanElement("phone", "#Phone no", new TextControl(), true);
  ...
  person = lookupPersonService().getSomePerson();
  personForm.readFromBean(person);
  ...
}
...

Note that here we basically do three things:

Create and register the form
The line new BeanFormWidget(Person.class) creates a new form widget that is associated with the JavaBean model class Person . The line addWidget("personForm", personForm) initializes and registers the form allowing it to function.
Add form fields
The line personForm.addBeanElement("name", "#Name", new TextControl(new Long(3), null), true) adds an element associated with the JavaBean property "name" (this is also the identifier of the field), with a label "Name" (labels in Aranea are localizable by default and "#" escapes a non-localizable string), a text box control with a minimal length of 3 and that is mandatory.
Write JavaBean
The line personForm.readFromBean(person) reads the data from JavaBean properties to the corresponding form fields.

Now that we have created the form we show how to process events, validate and read the request data. The following example code should be in the same widget as the previous:

...
private void handleEventSave() {
  if (personForm.convertAndValidate()) {
   personForm.writeToBean(person);
   ...
   lookupPersonService()().savePerson(person);
  }
}
...

This code will execute if an event "save" comes and will do the following:

  • Convert the request data to the JavaBean types and validate it according to the rules specified in controls (e.g. minimal length). Wrapping event body in if (personForm.convertAndValidate()) {...} is a generic idiom in Aranea as we believe that explicitly leads to flexibility. By default the values will be just read from request without any parsing, conversion or validation and the latter will be done only after the convertAndValidate() call. This allows for example to validate only a subform or even one element, by calling only their convertAndValidate() method.
  • Read the person object from the form, filling it in with the user data. Note that the same object that was originally read from the business layer is used here and forms take care of merging the new data and preserving the old.

Note the use of the getValueByFullName() method. Form API contains several such methods (named *ByFullName()), which allow to access fields, controls and values using full dot-separated element names.

If you have a composite JavaBean (containing other JavaBeans) you may want to create a form with a similar structure. Let's say that our Person bean contains an Address under "address" JavaBean property:

...
personForm = new BeanFormWidget(Person.class);
addWidget("personForm", personForm);
...
BeanFormWidget addrForm = personForm.addBeanSubForm("address");
addrForm.addBeanElement("postalCode", "#Postal code", new TextControl(), true);
addrForm.addBeanElement("street", "#Street", new TextControl(), true);
...

Note that the fields will be available from the main form using a dot-separated name, e.g. String street = (String) personForm.getValueByFullName("address.street").

5.1.2. Controls

At the core of the data binding API lies the notion of controls (org.araneaframework.uilib.form.Control). Controls are the widgets that do the actual parsing of the request parameters and correspond to the controls found in HTML forms, like textbox, textarea, selectbox, button, ... Additionally controls also do a bit of validating the submitted data. For example textbox control validates the minimum and maximum string length, since the HTML tag can do the same. Programmer usually does not read values from Control directly, but from FormElement that takes care of converting value of Control to FormElement Data.

The following example shows how to create a control:

...
TextControl textBox = new TextControl(new Long(10), null);
...

This code will create a textbox with a minimal length of 10. Note that this code does not yet put the control to work, as controls are never used without forms, which are reviewed in the next section.

Follows a table of standard controls all found in org.araneaframework.uilib.form.control package:

ControlDescription
ButtonControlA control that represents a HTML form button.
CheckboxControlA control that represents a binary choice and is usually rendered as a checkbox.
DateControlA date selection control that allows to choose a date. Supports custom formats of date input and output.
DateTimeControlA date and time selection control that allows to choose a date with a corresponding time. Supports custom formats of date and time input and output.
DisplayControlA control that can be used to render a read-only value that will not be submitted with an HTML form.
FileUploadControlA control that can be used to upload files to the server.
FloatControlA textbox control that constrains the text to be floating-point numbers. Can also check the allowed minimum and maximum limits.
HiddenControlA control that can be used to render an invisible value that will be submitted with an HTML form.
NumberControlA textbox control that constrains the text to be integer numbers. Can also check the allowed minimum and maximum limits.
TimeControlA time selection control that allows to choose a time of day. Supports custom formats of time input and output.
TextareaControlA multirow textbox control that can constrain the inserted text minimal and maximal length.
TextControlA simple textbox control with one row of text that can constrain the inserted text minimal and maximal length.
AutoCompleteTextControlTextControl with autocompletion capability.
TimestampControlSimilar to DateControl but works with java.sql.TimeStamp.
SelectControlA control that allows to select one of many choices (may be rendered as a dropdown list or option buttons). Ensures that the submitted value was one of the choices.
MultiSelectControlA control that allows to select several from many choices (may be rendered as a multiselect list or checkbox list). Ensures that the submitted values are a subset of the choices.

SelectControl and MultiSelectControl deserve a special mention, as they need a bit more handling than the rest. The difference comes from the fact that we also need to handle the selectable options, which we refer to as DisplayItem. Each DisplayItem has a label, a string value and can be disabled. Disabled display items cannot be selected in neither select box nor multiselect box.

Both SelectControl and MultiSelectControl implement the DisplayItemContainer interface that allows to manipulate the DisplayItem:

interface DisplayItemContainer {
  void addItem(DisplayItem item);
  void addItems(Collection items);
  void clearItems();
  List getDisplayItems();
  int getValueIndex(String value);
}

In addition to this interface we also provide a DisplayItemUtil that provides some support methods on display items. These include the method addItemsFromBeanCollection that allows to add the items to a (multi)select control from a business method returning a collection of model JavaBean objects (which is one of the most common use cases). So a typical select control will be filled as follows:

SelectControl control = new SelectControl();
control.addItem(new DisplayItem(null, "- choice -"));
DisplayItemUtil.addItemsFromBeanCollection(
  control,
  lookupMyService().getMyItemCollection(),
  "value",
  "label");

Controls can also listen to user events. For example ButtonControl can react to an onClick event, while most others can react to an onChange event. The only thing needed to receive the control events is to register an appropriate event listener:

...
SelectControl selControl = new SelectControl();
FormElement selEl = form.addBeanElement("clientId", "#Client id", selControl, true);
selControl.addOnChangeEventListener(new OnChangeEventListener() {
  public void onChange() {
    //We convert and validate one element only as the rest of the form
    //might be invalid
    if (selEl.convertAndValidate()) {
      Long clientId = (Long) selEl.getValue();
      //Now we can use the client id to do whatever we want
      //E.g. update another select control
    }
  }
});
...

onChange events are also produced by text boxes and similar, so the user input can processed right after the user has finished it.

5.1.3. Constraints

Though controls provide some amount of validation they are limited only to the rules that can be controlled on the client-side. To support more diverse rules Aranea has org.araneaframework.uilib.form.Constraint, that allows to put any logical and/or business validation rules. Typically constraints are used as follows:

...
myForm.addBeanElement("registration", "#Registration", new DateControl(), true);
myForm.getElement("registration").setConstraint(new AfterTodayConstraint(false));
...

The org.araneaframework.uilib.form.constraint.AfterTodayConstraint makes sure that the date is today or later, with the boolean parameter indicating whether today is allowed. The constraint will validate if the form or the element in question is validated (e.g. convertAndValidate() is called) and will show an error message to the user, if the constraint was not satisfied. The error message is customizable using localization and involves the label of the field being validated.

The following is a more complex example that shows how to use constraints that apply to more than one field, and how to combine constraints using logical expressions:

...
searchForm = new FormWidget();

//Adding form controls
searchForm.addElement("clientFirstName", "#Client first name", 
  new TextControl(), new StringData(), false);
searchForm.addElement("clientLastName", "#Client last name", 
  new TextControl(), new StringData(), false);

searchForm.addElement("clientAddressTown", "#Town", 
  new TextControl(), new StringData(), false);
searchForm.addElement("clientAddressStreet", "#Street", 
  new TextControl(), new StringData(), false);

//First searching scenario
AndConstraint clientNameConstraint = new AndConstraint();
clientNameConstraint.addConstraint(
  new NotEmptyConstraint(searchForm.getElementByFullName("clientFirstName")));
clientNameConstraint.addConstraint(
  new NotEmptyConstraint(searchForm.getElementByFullName("clientLastName")));

//Second searching scenario
AndConstraint clientAddressConstraint = new AndConstraint();
clientAddressConstraint.addConstraint(
  new NotEmptyConstraint(searchForm.getElementByFullName("clientAddressTown")));
clientAddressConstraint.addConstraint(
  new NotEmptyConstraint(searchForm.getElementByFullName("clientAddressStreet")));

//Combining scenarios
OrConstraint searchConstraint = new OrConstraint();    
searchConstraint.addConstraint(clientNameConstraint);
searchConstraint.addConstraint(clientAddressConstraint);

//Setting custom error message
searchConstraint.setCustomErrorMessage("Not enough data for search!");

//Setting constraint
searchForm.setConstraint(searchConstraint);

//Putting the widget
addWidget("searchForm", searchForm);    
...

The example use case is a two scenario search—either both client first name and client last name fields are filled in or both town and street address fields are filled in, otherwise an error message "Not enough data for search!" is shown. The constraints will be validated when convertAndValidate() method is called on searchForm. Note that the constraint is added to the form itself, rather than to its elements—this is a typical idiom, when the constraint involves several elements.

Table of standard Constraints.

ConstraintPurpose
AfterTodayConstraintField constraint, checks that field contains Date later than current date.
NotEmptyConstraintField constraint, checks that field contains non-empty value.
NumberInRangeConstraintField constraint, checks that number in a field belongs on given range (integer only).
StringLengthInRangeConstraintField constraint, checks that length of a string in a field falls within given boundaries.
RangeConstraintMultiple field constraint, checks that value of one field is lower than value of other field. Field values must Comparable.
AndConstraintComposite constraint, checks that all subconstraints are satisfied.
OrConstraintComposite constraint, checks that at least one subconstraint is satisfied.

There are two constraints that deserve a special mention. One of them is OptionalConstraint that will only let its subconstraint to validate the field, if the field has been submitted by user (it is very useful for instance when non-mandatory fields must nevertheless follow some pattern, whereas empty input should still be allowed).

The other constraint is called GroupedConstraint. It is useful in cases when different constraints should be activated depending on the particular state of the component (a typical use case is that some groups of fields are made mandatory in different states of document approval). The constraint is created using the ConstraintGroupHelper as follows:

...
ConstraintGroupHelper groupHelper = new ConstraintGroupHelper();
AndConstraint andCon = new AndConstraint();
andCon.addConstraint(
  groupHelper.createGroupedConstraint(new NotEmptyConstraint(field1), "group1"));
andCon.addConstraint(
  groupHelper.createGroupedConstraint(new NotEmptyConstraint(field2), "group1"));
andCon.addConstraint(
  groupHelper.createGroupedConstraint(new NotEmptyConstraint(field3), "group2"));
andCon.addConstraint(
  groupHelper.createGroupedConstraint(new NotEmptyConstraint(field4), "group2"));
form.setConstraint(andCon);

//Now only field1 and field2 will be required from user!
groupHelper.setActiveGroup("group1");
...

Custom Constraints

It is a very common need to validate some additional logic for a particular field (e.g. a field must follow some particular pattern). In this case it is comfortable to create a custom constraint. Most often the constraint is associated with one field only, so we will extend the BaseFieldConstraint, which supports this particular idiom:

...
public class PersonIdentifierConstraint extends BaseFieldConstraint {
  public void validateConstraint() {
    if (!PersonUtil.validateIdentifier(getValue()) {
      addError("Field '" + getLabel() + "' is not a valid personal identifier");
    }
  }
}
...

Note that we can use getValue() that contains the converted value of the field. We can also use the fields label via getLabel(). We might also want to localize the message and in such a case you will find MessageUtil to contain some helpful methods.

If we need to validate more than one field we should extend the BaseConstraint and take those fields into the constructor. In this case the developer will have to provide this fields to the constraint and the constraint should be added to the enclosing form.

5.1.4. Data

The typical use of forms includes associating the form fields with JavaBean properties. However this is not always possible, since it is not feasible to make a JavaBean property for each and every form field. In such cases one may still want to use type conversion and data validation. To do that forms allow the org.araneaframework.uilib.form.Data and its subclasses (subclasses correspond to specific types) to be associated with the field:

...
personForm = new BeanFormWidget(Person.class);
addWidget("personForm", personForm);
...
personForm.addElement("numberOfChildren", "#No. of chidren",
  new NumberControl(), new LongData(), true);
...

In such a case one can retrieve the data directly from the field:

...
private void handleEventSave() {
  if (myForm.convertAndValidate()) {
    ...
    Long numberOfChildren = (Long) personForm.getValueByFullName("numberOfChildren");
    //Alternative:
    //FormElement nocEl = (FormElement) personForm.getElement("numberOfChildren");
    //Long numberOfChildren = (Long) nocEl.getValue();
    ...
  }
}
...

If there is no JavaBean to associate the form with org.araneaframework.uilib.form.FormWidget may be used instead of BeanFormWidget.

Note that the reason for existence of Data objects is that Java types correspond poorly to some restricted types—for instance enumerations, type encodings and collections container types (this problem is somewhat solved in Java 5, but Aranea is compatible with Java 1.3).

Table of Data types.

DataValue Type
BigDecimalDatajava.math.BigDecimal
BigDecimalListDataList <java.math.BigDecimal>
BooleanDatajava.lang.Boolean
BooleanListDataList <java.lang.Boolean>
DateDatajava.util.Date
DisplayItemListDataList <org.araneaframework.uilib.support.DisplayItemDisplayItem>
FileInfoDataorg.araneaframework.uilib.support.FileInfo
IntegerDatajava.lang.Integer
IntegerListDataList <java.lang.Integer>
LongDatajava.lang.Long
LongListDataList <java.lang.Long>
StringDatajava.lang.String
StringListDataList <java.lang.String>
TimestampDatajava.sql.Timestamp
YNDatajava.lang.String

Finally Data constructor also accepts both a Class instance and a simple string. So if you have a custom datatype with an appropriate converter (see next section) you can just assign the data with the same type (in fact if you have your own converter the type doesn't matter that much, it will just allow some checks to be done on the programmer).

5.1.5. Converters

Converter sole purpose is conversion of values with one type to values of another type. Conventionally converter which convert() method accepts object of type A and returns object of type B is named AToBConverter. Converter from type B to type A is obtained with new ReverseConverter(new AToBConverter()).

public interface Converter extends Serializable, FormElementAware {
    public void setFormElementCtx(FormElementContext feCtx);
    public Object convert(Object data);
    public Object reverseConvert(Object data);
    public Converter newConverter();
}

Converters are used internally to convert Control values to values of FormElement Data and vice-versa. Converters are usually looked up from ConverterFactory, but each FormElement can be set explicit Converter by calling FormElement.setConverter(). Direction of Converter set this way should be from FormElement Control value type to FormElement Data type.

5.1.6. Form validation

As already mentioned, form validation is mostly explicit. By default, the values will be just read from request without any parsing, conversion or validation. Validation will be performed after call to FormWidget.convertAndValidate().

It is also possible to configure forms to be validated in the background, as end-user is filling it. Background validation is enabled by calling FormWidget.setBackgroundValidation(true). This performs XMLHttp requests (using Aranea Action API) to server each time when user moves from changed form field to another. Background validation takes place on server-side and is implicit.

Produced form validation error messages are rendered by active FormElementValidationErrorRenderer implementation, which adheres to these methods:

public interface FormElementValidationErrorRenderer extends Serializable {

  void addError(FormElement element, String error);

  void clearErrors(FormElement element);

  String getClientRenderText(FormElement element);

}

The last method is used to provide the client-side script (together with <script>...</script> tags) that binds its validator with the given form element and updates error messages inside the <span> element of the given form element.

It is possible to choose between two bundled implementations — StandardFormElementValidationErrorRenderer, which is enabled by default, and LocalFormElementValidationErrorRenderer. The first one renders FormElement validation errors to standard MessageContext. The second bundled implementation renders the validation messages into the same HTML span element as input field (using the script returned by the getClientRenderText(FormElement) method).

FormElementValidationErrorRenderer default implementation can be switched by configuring the bean representing ConfigurationContext (named 'araneaConfiguration') to have entry with key 'uilib.widgets.forms.formelement.error.renderer' value set to desired FormElementValidationErrorRenderer instance:

<bean id="araneaConfiguration" singleton="false"
    class="org.araneaframework.uilib.core.StandardConfiguration">
  <property name="confEntries">
    <map>
        <entry key="uilib.widgets.forms.formelement.error.renderer">
          <bean class="org.araneaframework.uilib.form.LocalFormElementValidationErrorRenderer" singleton="false"/>
        </entry>
    </map>
  </property>
</bean>

For the cases where validation errors should be rendered differently for just few elements, FormElement.setFormElementValidationErrorRenderer() method should be used.

5.2. Forms JSP Tags

Form JSP tags can be divided into two categories—tags providing contexts (<ui:form>, <ui:formElement>) and tags for rendering form elements containing different controls. We will first describe the attributes that are common to all form element rendering tags; then proceed to explain context tags and different form element rendering tags with their unique attributes.

5.2.1. Common attributes for all form element rendering tags.

AttributeRequiredDescription
idno/yesId of form element to be rendered. If not specified, it is usually taken from current form element context (Section 5.2.3, “<ui:formElement>”). For few tags, it is required.
eventsnoWhether element will send events that are registered by server-side, true by default.
validateOnEventnoWhether the form should be validated on the client-side (or by making AJAX request to server) when element generates an event (this is false by default and is not supported by any default Aranea JSP tags).
tabindexnoThis attribute specifies the position of the current element in the tabbing order for the current document. This value must be a number between 0 and 32767.
updateRegionsnoComma separated list of update regions that should be updated upon button receiving event. This attribute is only needed when using AJAX features— ordinary HTTP requests always update whole page.
globalUpdateRegionsnoComma separated list of global update regions that should be updated upon button receiving event. This attribute is only needed when using AJAX features— ordinary HTTP requests always update whole page.
styleClassnoCSS class applied HTML tag(s) that are used for rendering element.
stylenoInline CSS style applied to HTML tag(s) that are used for rendering element.

5.2.2. <ui:form>

Specifies form context for inner tags. Form view model and id are made accessible to inner tags as EL variables.

Attributes

AttributeRequiredDescription
idnoId of context form. When not specified, current form context is preserved (if it exists).

Variables

VariableDescriptionType
formView model of form.FormWidget.ViewModel
formIdId of form.String
formFullIdFull id of form.String
formScopedFullIdFull scoped id of form.String

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:form id="loginForm">
    ...  <!-- formElements, formElementLabels, ... --> ...
</ui:form>

5.2.3. <ui:formElement>

Specifies form element context for inner tags. Must be surrounded by <ui:form> tag. Form element view model, id and value are made accessible to inner tags as EL variables.

Attributes

AttributeRequiredDescription
idyesId of context form element.

Variables

VariableDescriptionType
formElementView model of form element.FormElement.ViewModel
formElementIdId of form element.String
formElementValueValue currently kept inside form element.Object

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:form id="loginForm">
    <ui:formElement id="username">
        ...
    </ui:formElement>
</ui:form>

5.2.4. <ui:label>

Renders localizable label bound to form element. Rendered with HTML <span> and <label> tags.

Attributes

AttributeRequiredDescription
idnoId of form element which label should be rendered. If left unspecified, form element id from form element context is used.
showMandatorynoIndicates whether mandatory input fields label is marked with asterisk. Value should be true or false, default is true
showColonnoIndicates whether colon is shown after the label. Default is true.
Also has standard style and styleClass attributes.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:form id="loginForm">
    <ui:row>
        <ui:formElement id="username">
            <ui:cell>
                <ui:label/>
            </ui:cell>
        </ui:formElement>
    </ui:row>
</ui:form>

5.2.5. <ui:simpleLabel>

Renders localizable label (with HTML <span> and <label> tags).

Attributes

AttributeRequiredDescription
labelIdyesID of label to render.
showMandatorynoIndicates whether label is marked with asterisk. Value should be true or false, default is false
showColonnoIndicates whether colon is shown after the label. Default is true.
fornoID of the form element for which the label is created.
Also has standard style and styleClass attributes.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:form id="loginForm">
    <ui:row>
        <ui:cell>
            <ui:simpleLabel labelId="username.input.label" showMandatory="true" for="username"/>
        </ui:cell>
    </ui:row>
</ui:form>

5.2.6. <ui:button>

Renders form buttons that represent ButtonControls. Either HTML <button> or <input type="button" ... > will be used for rendering.

Attributes

AttributeRequiredDescription
showLabel no Indicates whether button label is shown.Value should be true or false, default is true
onClickPrecondition no Precondition for deciding whether onclick event should go server side or not. If left unspecified, this is considered to be true.
renderMode no Allowed values are button and input—the corresponding HTML tag will be used to render the button. Default is button.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:form id="loginForm">
    <ui:button id="loginButton"/>
</ui:form>

5.2.7. <ui:linkButton>

Renders HTML link that represents ButtonControl. HTML <a href="javascript:" ... > tag will be used for rendering. Default styleClass="aranea-link".

Attributes

AttributeRequiredDescription
showLabel no Indicates whether button label is shown.Value should be true or false, default is true
onClickPrecondition no Precondition for deciding whether registered onclick event should go server side or not. If left unspecified, this is considered to be true.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.8. <ui:formKeyboardHandler>

Registers a simple keyboard handler. Invokes a uiRegisterKeyboardHandler javascript. This is basically the same stuff as <ui:keyboardHandler> with a few modifications.

There is no scope attribute. Instead, the tag assumes that it is located inside a form, and takes the full id of that form as its scope.

As an alternative to specifying the handler attribute, you may specify a form element and a javascript event to invoke on that element. You specify the element by its id relative to the surrounding form. The event is given as a name of the javascript function to be invoked on the element. For example, if you specify the element as "someButton", and event as "click", then when the required keyboard event occurs, the following javascript will be executed:

var el = document.getElementById("<form-id>.someButton");
el.click();

Attributes

AttributeRequiredDescription
handlernoA javascript handler function that takes two parameters - the event object and the element id for which the event was fired. Example: function(event, elementId) { alert(elementId); } Either handler or elementId/event pair should be specified, not both.
subscopenoSpecifies form element which is the scope of this handler. By default the "scope" (as in <ui:keyboardHandlerTag>) of this keyboard handler is the form inside which the handler is defined. By specifying this, scope of certain element may be narrowed. For example if the handler is defined inside form "myForm", and subscope is specified as "myelement", the scope of the handler will be "myForm.myelement", not the default "myForm". The handler will therefore be active only for the element 'someElement'".
elementIdnoSets the (relative) id of the element whose javascript event should be invoked. The id is relative with respect to the surrounding form. Instead of this attribute, element's full id may be set using the fullElementId attribute, but only one of those attributes should be set at once.
fullElementIdnoSets the full id of the element whose javascript event should be invoked.
eventnoSet the javascript event that should be invoked when keypress is detected—"click" and "focus" are safe for most controls. If target element (the one given by elementId) is a selectbox "select" may be used. For more, javascript reference should be consulted. This attribute is not foolproof and invalid javascript may be produced when it is not used cautiously.
keyCodenoKeycode to which the event must be triggered. Either keyCode or key must be specified, but not both.
keynoKey to which the event must be triggered, accepts key "aliases" instead of codes. Accepted aliases include F1..F12, RETURN, ENTER, BACKSPACE, ESCAPE, TAB, SHIFT, CONTROL, SPACE.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:form id="loginForm">
    <ui:eventButton id="btnLogin" eventId="login" labelId="button.login.enter"/>
    <ui:formKeyboardHandler fullElementId="btnLogin" key="enter"/>
</ui:form>

5.2.9. <ui:formEnterKeyboardHandler>

Same as <ui:formKeyboardHandlerTag> except key is already set to enter.

5.2.10. <ui:formEscapeKeyboardHandler>

Same as <ui:formKeyboardHandlerTag> except key is already set to escape.

5.2.11. <ui:textInput>

Form text input field, represents TextControl. It is rendered in HTML with <input type="text" ...> tag. Default styleClass="aranea-text".

Attributes

AttributeRequiredDescription
size no Maximum length of accepted text (in characters).
onChangePrecondition no Precondition for deciding whether registered onchange event should go server side or not. If left unspecified, this is considered to be true. For this tag, onchange event is simulated with onblur.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:form id="someForm">
  <ui:formElement id="firstField">
     <!-- Renders input field binded to form's firstField element -->
     <ui:textInput/>
  </ui:formElement>
</ui:form>

5.2.12. <ui:autoCompleteTextInput>

Form text input field, represents AutoCompleteTextControl. It is rendered in HTML with <input type="text" ...> tag. Default styleClass="aranea-text". It is able to make background AJAX request to the server, fetching suggested completions to user input and displaying these to the user.

Attributes

AttributeRequiredDescription
size no Maximum length of accepted text (in characters).
onChangePrecondition no Precondition for deciding whether registered onchange event should go server side or not. If left unspecified, this is considered to be true. For this tag, onchange event is simulated with onblur.
divClass no CSS class attribute assigned to <DIV> inside which suggestions are presented.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.13. <ui:comboTextInput>

Form text input field, represents ComboTextControl. This is an input field combined with Select—it allows end-user to enter text into field or select some predefined value from provided list of values. It is rendered in HTML with <input type="text" ...> tag plus custom select component. Default styleClass="aranea-text".

Attributes

AttributeRequiredDescription
size no Maximum length of accepted text (in characters).
onChangePrecondition no Precondition for deciding whether registered onchange event should go server side or not. If left unspecified, this is considered to be true. For this tag, onchange event is simulated with onblur.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.14. <ui:textInputDisplay>

Form text display field, represents TextControl. It is rendered in HTML with <span ...> tag. Default styleClass="aranea-text-display".

Attributes

Has standard id and styleClass attributes.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:form id="someForm">
  <ui:formElement id="firstField">
     <!-- Renders display field for form's firstField element -->
     <ui:textInputDisplay/>
  </ui:formElement>
</ui:form>

5.2.15. <ui:numberInput>

Form number input field, represents NumberControl. It is rendered in HTML with <input type="text" ...> tag. Default styleClass="aranea-number".

Attributes

AttributeRequiredDescription
size no Maximum length of accepted text (in characters).
onChangePrecondition no Precondition for deciding whether registered onchange event should go server side or not. If left unspecified, this is considered to be true. For this tag, onchange event is simulated with onblur.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.16. <ui:numberInputDisplay>

Form number display field, represents NumberControl. It is rendered in HTML with <span ...> tag. Default styleClass="aranea-number-display".

Attributes

Has standard id and styleClass attributes.

5.2.17. <ui:floatInput>

Form floating-point number input field, represents FloatControl. It is rendered in HTML with <input type="text" ...> tag. Default styleClass="aranea-float".

Attributes

AttributeRequiredDescription
size no Maximum length of accepted floating-point number (in characters).
onChangePrecondition no Precondition for deciding whether registered onchange event should go server side or not. If left unspecified, this is considered to be true. For this tag, onchange event is simulated with onblur.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.18. <ui:floatInputDisplay>

Form floating-point number display field, represents FloatControl. It is rendered in HTML with <span ...> tag. Default styleClass="aranea-float-display".

Attributes

Has standard id and styleClass attributes.

5.2.19. <ui:passwordInput>

Form number input field, represents TextControl. It is rendered in HTML with <input type="password" ...> tag. Default styleClass="aranea-text".

Attributes

AttributeRequiredDescription
size no Maximum length of password (in characters).
onChangePrecondition no Precondition for deciding whether registered onchange event should go server side or not. If left unspecified, this is considered to be true. For this tag, onchange event is simulated with onblur.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.20. <ui:textDisplay>

Form text display field, represents DisplayControl, displays element value as String. It is rendered in HTML with <span ...> tag.

Attributes

Has standard id and styleClass attributes.

5.2.21. <ui:valueDisplay>

Puts form element value in page scope variable, represents DisplayControl. It does not output any HTML.

Attributes

AttributeRequiredDescription
var true Name of the page-scoped EL variable that will be assigned element value.
Also has standard id attribute.

5.2.22. <ui:textarea>

Form text input area, represents TextareaControl. It is rendered in HTML with <textarea ...> tag. Default styleClass="aranea-textarea".

Attributes

AttributeRequiredDescription
cols true Number of visible columns in textarea.
rows true Number of visible rows in textarea.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:form id="someForm">
  <ui:formElement id="longLongText">
    <ui:cell>
      <ui:textarea rows="15" cols="150"/>
    </ui:cell>
  </ui:formElement>
</ui:form>

5.2.23. <ui:richtextarea>

Form text input area, represents TextareaControl. It is rendered in HTML with <textarea ...> tag with styleClass="richTextEditor". The area is displayed as a rich text editor. The configuration of the editor is done via <ui:richTextAreaInit>. The tag shares all the attributes of the <ui:textarea> except the styleClass which cannot be set for this tag.

5.2.24. <ui:richTextAreaInit>

A tag for configuring the rich textareas. The tinyMCE WYSIWYG editor is attached to the textareas defined via <ui:richTextarea> . The configuration lets you choose the looks, buttons, functionality of the editor. See tinyMCE configuration reference for different configurable options.

The configuration is done via nesting key value pairs inside the <ui:richTextAreaInit>. For the key value pairs the <ui:attribute> tag is used. See the example for an overview.

The editor_selector and mode options are set by default and should not be changed. The default theme is "simple".

Important: the configuration should be done in the <head> section of the HTML document.

Example

<ui:richTextAreaInit>
  <ui:attribute name="theme" value="advanced"/>
  <ui:attribute name="theme_advanced_buttons1" value="bold,italic,underline,separator,code"/>
  <ui:attribute name="theme_advanced_toolbar_location" value="top"/>
  <ui:attribute name="theme_advanced_toolbar_align" value="left"/>
  <ui:attribute name="theme_advanced_path_location" value="bottom"/>
</ui:richTextAreaInit>

5.2.25. <ui:textareaDisplay>

Form text display area, represents TextareaControl. It is rendered in HTML with <span ...> tag. Default styleClass="aranea-textarea-display".

Attributes

AttributeRequiredDescription
escapeSingleSpaces false Boolean, specifying whether even single spaces (blanks) should be replace with &nbsp; entities in output. It affects browser performed text-wrapping. Default value is false. Attribute is available since tag-library version 1.0.6.
Also has standard id and styleClass attributes.

5.2.26. <ui:hiddenInput>

Represents a "hidden" form input element—HiddenControl. It is rendered in HTML with <input type="hidden" ...> tag.

Attributes

See Section 5.2.1, “Common attributes for all form element rendering tags.”. However, rendered tag is not visible to end-user, thus using any attributes is mostly pointless.

5.2.27. <ui:checkbox>

Form checkbox input field, represents CheckboxControl. By default styleClass="aranea-checkbox". Rendered in HTML with <input type="checkbox" ...> tag.

Attributes

AttributeRequiredDescription
onChangePrecondition no Precondition for deciding whether registered onchange event should go server side or not. If left unspecified, this is considered to be true
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.28. <ui:checkboxDisplay>

Form checkbox display field, represents CheckboxControl. By default styleClass="aranea-checkbox-display". Rendered in HTML inside <span> tag.

Attributes

Has standard id and styleClass attributes.

5.2.29. <ui:fileUpload>

Form file upload field, represents FileUploadControl. File upload can upload the file automatically once it is selected. This would enable file uploads on pages with update regions and overlay. To make a file upload submit its data without rendering the page, add a CSS class to the input named ajax-upload. See more info at the Chapter 9, Javascript Libraries part of the documentation.

Examples

<?xml version="1.0" encoding="UTF-8"?>
...
<ui:form id="uploadForm">
   <ui:row>
       <ui:cell styleClass="name">
           <ui:fileUpload id="file"/>
       </ui:cell>
   </ui:row>
</ui:form>
...

5.2.30. <ui:dateInput>

Form date input field, represents DateControl. Default styleClass="aranea-date".

Attributes

AttributeRequiredDescription
onChangePrecondition no Precondition for deciding whether registered onchange event should go server side or not. If left unspecified, this is considered to be true.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.31. <ui:dateInputDisplay>

Form date display field, represents DateControl. Default styleClass="aranea-date-display".

Attributes

Has standard id and styleClass attributes.

5.2.32. <ui:timeInput>

Form time input field, represents TimeControl. Default styleClass="aranea-time". HTML <select>s for easy hour/minute selection are rendered too, unless showTimeSelect attribute forbids it.

Attributes

AttributeRequiredDescription
onChangePrecondition no Precondition for deciding whether registered onchange event should go server side or not. If left unspecified, this is considered to be true.
showTimeSelect no Boolean, specifying whether HTML <select>'s should be rendered for easy hour/minute selection. Default is to render them (true).
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.33. <ui:timeInputDisplay>

Form time display field, represents TimeControl. Default styleClass="aranea-time-display".

Attributes

Has standard id and styleClass attributes.

5.2.34. <ui:dateTimeInput>

Form input field for both date and time, represents DateTimeControl. It is rendered as input fields for date and time + date picker and time picker (time picker can be switched off by setting showTimeSelect="false" if so desired).

Attributes

AttributeRequiredDescription
onChangePrecondition no Precondition for deciding whether registered onchange event should go server side or not. If left unspecified, this is considered to be true.
showTimeSelect no Boolean, specifying whether HTML <select>'s should be rendered for easy hour/minute selection. Default is to render them (true).
dateStyleClass no styleClass for date. Default is "aranea-date".
timeStyleClass no styleClass for time. Default is "aranea-time".
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.35. <ui:dateTimeInputDisplay>

Form display field for both date and time, represents TimeControl. Default styleClass="aranea-datetime-display".

Attributes

Has standard id and styleClass attributes.

5.2.36. <ui:select>

Form dropdown list input field, represents SelectControl. Default styleClass="aranea-select", rendered with HTML <select ...> tag.

Attributes

AttributeRequiredDescription
onChangePrecondition no Precondition for deciding whether registered onchange event should go server side or not. If left unspecified, this is considered to be true.
size no Number of select elements visible at once.
localizeDisplayItems no A boolean specifying whether to localize display items. Provides a way to override ConfigurationContext.LOCALIZE_FIXED_CONTROL_DATA.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.37. <ui:selectDisplay>

Form select display field, represents SelectControl. Default styleClass="aranea-select-display", rendered with HTML <span ...> tag.

Attributes

AttributeRequiredDescription
localizeDisplayItemsno A boolean specifying whether to localize display items. Provides a way to override ConfigurationContext.LOCALIZE_FIXED_CONTROL_DATA.
Also has standard id and styleClass attributes.

5.2.38. <ui:multiSelect>

Form list input field, represents MultiSelectControl. Default styleClass="aranea-multi-select", rendered with HTML <select multiple="true" ...> tag.

Attributes

AttributeRequiredDescription
size no Vertical size, number of options displayed at once.
localizeDisplayItems no A boolean specifying whether to localize display items. Provides a way to override ConfigurationContext.LOCALIZE_FIXED_CONTROL_DATA.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.39. <ui:multiSelectDisplay>

Form multiselect display field, represents MultiSelectControl. Default styleClass="aranea-multi-select-display", rendered with HTML <span ...> tag.

Attributes

AttributeRequiredDescription
separator no The separator between list items, can be any string or '\n' for newline. Default is ', ').
localizeDisplayItems no A boolean specifying whether to localize display items. Provides a way to override ConfigurationContext.LOCALIZE_FIXED_CONTROL_DATA.
Has standard id and styleClass attributes.

5.2.40. <ui:radioSelect>

Form radioselect buttons field, represents SelectControl. Default styleClass="aranea-radioselect". It takes care of rendering all its elements; internally using <ui:radioSelectItemLabel> and <ui:radioSelectItem> tags.

Attributes

AttributeRequiredDescription
type no The way the radio buttons will be rendered - can be either vertical or horizontal. By default "horizontal".
labelBefore no Boolean that controls whether label is before or after each radio button, false by default.
localizeDisplayItems no A boolean specifying whether to localize display items. Provides a way to override ConfigurationContext.LOCALIZE_FIXED_CONTROL_DATA.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.41. <ui:radioSelectItem>

Form radio button, represents one item from SelectControl. Default styleClass="aranea-radio". It will be rendered with HTML <input type="radio" ...> tag.

Attributes

AttributeRequiredDescription
value no The value of this radio button that will be submitted with form if this radio button is selected.
onChangePrecondition no Precondition for deciding whether registered onchange event should go server side or not. If left unspecified, this is considered to be true.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.42. <ui:radioSelectItemLabel>

Form radio button label, represents label of one item from SelectControl. It will be rendered with HTML <span ...> tag.

Attributes

AttributeRequiredDescription
value no Select item value.
showMandatory no Indicates whether label for mandatory input is marked with asterisk. Value should be true or false, default is true.
showColon no Indicates whether colon is shown between the label and value. Default is true
Also has standard id and styleClass attributes.

5.2.43. <ui:checkboxMultiSelect>

Form multiselect checkbox field, represents MultiSelectControl. It takes care of rendering all its elements; internally using <ui:checkboxMultiSelectItemLabel> and <ui:checkboxMultiSelectItem> tags.

Attributes

AttributeRequiredDescription
type no The way the checkboxes will be rendered - can be either vertical or horizontal. Default is horizontal.
labelBefore no Boolean that controls whether label is before or after each cehckbox, false by default.
localizeDisplayItems no A boolean specifying whether to localize display items. Provides a way to override ConfigurationContext.LOCALIZE_FIXED_CONTROL_DATA.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.44. <ui:checkboxMultiSelectItem>

Form radio button, represents one item from MultiSelectControl. Default styleClass="aranea-multi-checkbox". It will be rendered with HTML <input type="checkbox" ...> tag.

Attributes

AttributeRequiredDescription
value no The value of this checkbox that will be submitted with form if this checkbox is selected.
Also see Section 5.2.1, “Common attributes for all form element rendering tags.”.

5.2.45. <ui:checkboxMultiSelectItemLabel>

Form checkbox label, represents label of one item from MultiSelectControl. It will be rendered with HTML <span ...> tag.

Attributes

AttributeRequiredDescription
value no Select item value.
showMandatory no Indicates whether label for mandatory input is marked with asterisk. Value should be true or false, default is true.
showColon no Indicates whether colon is shown between the label and value. Default is true
Also has standard id and styleClass attributes.

5.2.46. <ui:conditionalDisplay>

Depending whether form element boolean value is true or false display one or other content, represents DisplayControl. <ui:conditionFalse> and <ui:conditionFalse> tags must be used inside this tag to define alternative contents. This tag itself is not rendered.

Attributes

Has standard id attribute.

5.2.47. <ui:conditionFalse>

The content of this tag will be displayed when form element of surrounding <ui:conditionalDisplay> was false. Tag has no attributes.

5.2.48. <ui:conditionTrue>

The content of this tag will be displayed when form element of surrounding <ui:conditionalDisplay> was true. Tag has no attributes.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:form id="someForm">
  <ui:conditionalDisplay id="isActive">
    <ui:conditionTrue>
      <img src="red_flag.png"/>
    </ui:conditionTrue>
    <ui:conditionFalse>
      <img src="green_flag.png"/>
    </ui:conditionFalse>
  </ui:conditionalDisplay>
</ui:form>

5.2.49. <ui:listDisplay>

Display form element value as list of strings, represents DisplayControl and requires that element value would be of type Collection.

Attributes

AttributeRequiredDescription
separator no The separator between list items, can be any string and "\n", meaning a newline (default is "\n").
Also has standard id and styleClass attributes.

5.2.50. <ui:automaticFormElement>

Sometimes the type of FormElement is not known for sure when writing JSP (it could be textInput, floatInput, select, ...). For that purpose, FormElement that has some known identifier can be dynamically associated with some JSP tag in Java code and then rendered with <ui:automaticFormElement> tag which uses associated tag to render FormElement.

Examples

In Java code, setting tag that should rendering element is done by either setting FormElement property or preferably by using AutomaticFormElementUtil utility which makes the code slightly less verbose. Following lines of code all do the same thing:

element.setProperty(FormElementViewSelector.FORM_ELEMENT_VIEW_SELECTOR_PROPERTY, new FormElementViewSelector(tag, attributes));
AutomaticFormElementUtil.setFormElementViewSelector(element, new FormElementViewSelector(tag, attributes));
AutomaticFormElementUtil.setFormElementTag(element, tag, attributes);
<?xml version="1.0" encoding="UTF-8"?>
<ui:formElement id="someForm">
  <ui:cell>
    <ui:automaticFormElement/>
  </ui:cell>
</ui:formElement>

5.3. Form Lists

A common need in handling data is allowing a user to list of data, where the number of rows is not known beforehand (a typical example being user inputting one to many addresses). Aranea supports such a use case by providing a special type of FormElement that deals an arbitrary amount of subforms. This element is called FormListWidget and it can be used both on its own or as a subelement just like a FormWidget. An example of a form list is shown on Figure 5.2, “Insert your name display”.

Insert your name display

Figure 5.2. Insert your name display

5.3.1. FormListWidget

Unlike usual forms, form lists are "lazy", from the point that they are tied to a model and update themselves according to it. To create a form list widget we pass it a model and a handler:

... 
  public void init() throws Exception {
    private FormListWidget personFormList;
    ...
    Map persons = lookupMyService().getPersons();

    personFormList = new BeanFormListWidget(
        new PersonFormRowHandler(),
        new MapFormListModel(persons),
        Person.class);

    addWidget("personFormList", personFormList);
  }
...

Note here that we have tied the form list to the model that uses a Map as the underlying storage. When we update that map, the form list will also be updated. Note also that the form list widget is associated with the Person bean class, which can be used to manipulate the beans under the model.

However this code doesn't yet tell us much. The bulk of the custom logic of the form lists is hidden in the PersonFormRowHandler class. Let's inspect it step by step.

Every form row handler must implement the FormRowHandler interface. In our case we choose to extend ValidOnlyIndividualFormRowHandler, which processes only valid form rows and allows to process them one by one, not all at once:

class PersonFormRowHandler
  extends ValidOnlyIndividualFormRowHandler {
  ...
}

The first method we have to implement is getRowKey. It is used by the form list widget to identify the row among the others. Since typically the row is just a bean we can identify it using its identifier (either a natural one or artificial, as long as its unique in this context):

...
public Object getRowKey(Object rowData) {
  return ((Person) rowData).getId();
}
...

The next method is called initAddForm and it will create a form used to add new rows to the form list:

...
public void initAddForm(FormWidget addForm) throws Exception {
  addForm.addBeanElement("name", "#First name", new TextControl(), true);
  addForm.addBeanElement("surname", "#Last name", new TextControl(),  true);
  addForm.addBeanElement("phone", "#Phone no", new TextControl(), false);

  FormListUtil.addAddButtonToAddForm("#", formList, addForm);
}
...

The bulk of the logic is just adding the fields to the add form. But we also use the FormListUtil to add a button "Add" to the form, that will take care of the actual adding a new row (or at least calling the form row handler to do that). FormListUtil contains a lot of helpful methods for manipulating form lists and more on it can be found in Section 5.3.2, “FormListUtil”. The next step would be to handle the user clicking the add button and add a new row to the model. Since we process only valid rows the method will be named addValidRow:

...
public void addValidRow(FormWidget addForm) throws Exception {
  Person person = (Person) (((BeanFormWidget)addForm).writeToBean(new Person()));
  //We want to save changes immediately
  person = lookupPersonService.addPerson(person);
  data.add(person.getId(), person);
}
...

Note that although we save the changes here immediately, form lists also support deferring this until some later point as described in Section 5.3.5, “In Memory Form List”. Now that we have added a row to the model we will also have to initialize a form for that using initFormRow method:

...
public void initFormRow(FormRow formRow, Object rowData) throws Exception {
  // Set initial status of list rows to closed - they cannot be edited before opened.
  formRow.close();

  BeanFormWidget form = (BeanFormWidget)formRow.getForm();

  form.addBeanElement("name", "#First name", new TextControl(), true);
  form.addBeanElement("surname", "#Last name", new TextControl(),  true);
  form.addBeanElement("phone", "#Phone no", new TextControl(), false);

  FormListUtil.addEditSaveButtonToRowForm("#", formList, form, getRowKey(rowData));
  FormListUtil.addDeleteButtonToRowForm("#", formList, form, getRowKey(rowData));

  form.readFromBean(rowData);
}
...

Note that most of the fields are same for add form and edit forms, so in a real setup we could easily have added a method addCommonFields(FormWidget) that would add those fields to any given form (it is actually a very common idiom to do that). Finally we have to handle the saving of row form:

...
public void saveValidRow(FormRow formRow) throws Exception {
  BeanFormWidget form = (BeanFormWidget) formRow.getForm();
  Person person = (Person) form.writeToBean(data.get(formRow.getKey()));

  lookupPersonService().save(rowData);
  data.put(person.getId(), person);
}
...

And the last one left is deletion:

...
public void deleteRow(Object key) throws Exception {
  Long id = (Long) key;
  lookupPersonService().remove(id);
  data.remove(id);
}
...

5.3.2. FormListUtil

FormListUtil provides a couple of methods that help to handle form maps passed to some of the handler methods. However of main interest are the methods that add various buttons with ready logic to the add forms and row forms.

MethodDescription
addSaveButtonToRowForm()Button that will save the current row.
addDeleteButtonToRowForm()Button that will delete the current row.
addOpenCloseButtonToRowForm()Button that will open or close the current row for editing (it inverts the current state).
addEditSaveButtonToRowForm()Button that will open/close the row for editing, however will also save it after editing is finished and the row is closed.
addAddButtonToAddForm()Button that will add a new row, should be added to the addition form.

5.3.3. Form Row Handlers

Since row form handler interface supports bulk saving/adding/deleting of row forms it is comfortable to use one of the base classes that will do some of the work for you.

ClassDescription
DefaultFormRowHandlerImplements all of the menthods and default handling of opening/closing rows.
ValidOnlyFormRowHandlerChecks that all of the added/saved rows are valid.
IndividualFormRowHandlerSupports one by one processing of row saving and deleting.
ValidOnlyIndividualFormRowHandlerSupports one by one processing of row saving and deleting. Checks that all of the added/saved rows are valid.

Note that row handlers also have an openOrCloseRow method that may be overridden if one wants more than just inverting the row state on user action.

5.3.4. Models

5.3.5. In Memory Form List

Often it is the case that we do not want to save the changes in the form list to the database until the user presses the "Save" button. For such a use case we provide InMemoryFormListHelper. To use the helper we first need to initialize the form list to use the helper model:

...
private BeanFormListWidget personFormList;
private InMemoryFormListHelper inMemoryHelper;

public void init() throws Exception {
  private FormListWidget personFormList;
  ...
  Map persons = lookupMyService().getPersons();

  personFormList = new BeanFormListWidget(new PersonFormRowHandler(), Person.class);
  inMemoryHelper = new InMemoryFormListHelper(
    personFormList,
    lookupPersonService().getSomePersonList());

  addWidget("personFormList", personFormList);
}
...

Now we just have to add/save/delete the row to/from the helper:

...
public void saveValidRow(FormRow editableRow) throws Exception {
  ...
  inMemoryHelper.update(editableRow.getKey(), rowData);
}

public void deleteRow(Object key) throws Exception {
  ...
  inMemoryHelper.delete(key);
}

public void addValidRow(FormWidget addForm) throws Exception {
  ...
  inMemoryHelper.add(rowData);
}
...

And when the user presses "Save" we can just process the changes:

...
protected void handleEventSave() {
  lookupPersonService.addAll(inMemoryHelper.getAdded().values());
  lookupPersonService.saveAll(inMemoryHelper.getUpdated().values());
  lookupPersonService.deleteAll(inMemoryHelper.getDeleted());
}
...

5.4. Form Lists JSP Tags

5.4.1. <ui:formList>

Formlist is a list of forms, an editable list. This tag specifies editable list context for its inner tags.

Attributes

AttributeRequiredDescription
idnoId of editable list. When not specified, attempt is made to construct it from existing list context—it this does not succeed, tag fails.

Variables

VariableDescriptionType
formListEditable list view model.FormListWidget.ViewModel
formListIdEditable list id.String

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:list id="list">
    <ui:formList>
       ...
    </ui:formList>
</ui:list>

5.4.2. <ui:formListRows>

Iterating tag that gives access to each row and row form on the editable list current page. The editable row is accessible as "editableRow" variable.

Attributes

AttributeRequiredDescription
varnoName of variable that represents individual row (by default "row").

Variables

VariableDescriptionType
formRowCurrent editable list row view model.FormRow.ViewModel
row (unless changed with var attribute).Object held in current row.Object

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:list id="list">
    <ui:formList>
        <ui:formListRows>
           ...
        </ui:formListRows>
    </ui:formList>
</ui:list>

5.4.3. <ui:formListAddForm>

Allows for adding new forms (rows) to editable list.

Attributes

AttributeRequiredDescription
idnoEditable list id. Searched from context, if not specified.

Variables

VariableDescriptionType
formView model of form.FormWidget.ViewModel
formIdId of form.String
formFullIdFull id of form.String
formScopedFullIdFull scoped id of form.String

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:formListAddForm>
    <ui:row>
        <ui:cell>
            <ui:textInput id="name"/>
        </ui:cell>
        
        <ui:cell>
            <ui:textInput id="surname"/>
        </ui:cell>
        
        <ui:cell>
            <ui:textInput id="phone"/>
        </ui:cell>
        
        <ui:cell>
            <ui:dateInput id="birthdate"/>
        </ui:cell>                          
    </ui:row>       
</ui:formListAddForm>

Chapter 6. Lists and Query Browsing

6.1. Introduction

A common task in web applications is displaying tables. This is a simple task if the tables are small and do not contain a lot of rows. The whole table can be visible at once and there is no need to split the data into pages as well as provide an option to order the table by new column or display a search form that can be used to filter the data. In such cases where these features must be available, Aranea provides widget org.araneaframework.uilib.list.ListWidget and some support classes. In this chapter we will introduce these widgets and supporting API and show how to use and extend them.

ListWidget is used to define the list columns, the way the list can be filtered and ordered, how many items are shown per pages etc. ListWidget uses org.araneaframework.uilib.list.dataprovider.ListDataProvider to get the current list items range that also match with the current filter and order conditions. ListDataProvider can cache whole table and provide the ListWidget with the appropriate item range or fetch only the specific item range from database. Aranea provides two implementations:

org.araneaframework.uilib.list.dataprovider.MemoryBasedListDataProvider
This is the memory-based solution that must be provided with the whole data at once. It does filtering , ordering and paging memory-based. The data source is not restricted here. This is a very fast and easy-to-use solution for tables with few (typically less than 100) rows. This is a good solution when your server has enough memory to store all the rows, and quering all the rows does not take much time.
org.araneaframework.uilib.list.dataprovider.BackendListDataProvider
This is the database solution that will cache only the current item range and executes a new database query each time a filtering , ordering or paging conditions change. This is a powerful solution for tables with more than 500 rows. Especially, when the query is complex and takes time (so it would not be useful to query all the rows at once).

6.2. Lists API

6.2.1. A Typical List

A typical list will be created used like this:

...
private BeanListWidget myList;
...
protected void init() {
  ...
  myList = new BeanListWidget(MyModel.class);

  myList.setOrderableByDefault(true);
  myList.addField("name", "#Name").like();
  myList.addField("surname", "#Surname").like();
  myList.addField("phone", "#Phone no").like();
  myList.addField("birthdate", "#Birthdate").range();
  ...
  myList.setInitialOrder("name", true);
  myList.setListDataProvider(new MyListDataProvider());  
  addWidget("myList", myList);
  ...
}
...

Note that here we basically do following things:

Create the list
The line new BeanListWidget(MyModel.class) creates a new list widget that is associated with the JavaBean model class MyModel .
Make fields orderable
The line myList.setOrderableByDefault(true) configures the following fields as orderable.
Add list fields
The line myList.addField("name", "#Name").like() adds a field associated with the JavaBean property "name" (this is also the identifier of the field), with a label "Name", makes the field filterable by Like filter.
Set the initial list order
The line myList.setInitialOrder("name", true) sets the list to be ordered by field "name" by default.
Set the list data provider
The line myList.setListDataProvider(new MyListDataProvider()) sets the data provider for the list.
Register the list
The line addWidget("myList", myList) initializes and registers the list allowing it to function.

Now that we have created the list we show how to build a simple data provider. The following example code should be in the same widget as the previous:

...
private class MyMemoryBasedListDataProvider extends MemoryBasedListDataProvider {
  protected MyMemoryBasedListDataProvider() {
    super(MyModel.class);
  }
  public List loadData() throws Exception {             
    return lookupMyService().findAllMyModel();
  }
}
...

The line super(MyModel.class) associated this MemoryBasedListDataProvider with the JavaBean model class MyModel. The method List loadData() implements loading all table rows and returning it as a List object.

Later, we will also discover using org.araneaframework.uilib.list.dataprovider.BackendListDataProvider.

6.2.2. Fields

As the list may be displayed as a table, it is basically an ordered collection of items. In the previous example, we defined a list of MyModel.class typed items that have fields 'name', 'surname', 'phone' and 'birthdate'. By listing of MyModel.class, we also told ListWidget the corresponding field types, e.g String.class, String.class, Long.class and Date.class. In fact this feature of reflection is the only distinction between the ListWidget and BeanListWidget.

Each list field have its own Id, label and type. The labels are used to automatically create a corresponding title row above the displayed table. The types are used to describe how to order or filter the whole data using this field. E.g String.class is treated differently than other types, because usually one would prefer to order by this field ignoring the case. Both the labels and types are also used to build a corresponding search form - an automatically built FormWidget - for the list.

If we would like to add some list fields that are not MyModel.class fields, we can pass it's type to the ListWidget like following:

myList.addField("income", "#Income", BigDecimal.class);

Here the myList could be just a ListWidget rather than a BeanListWidget.

When adding a list field, we can also provide this field-related ordering and filtering information.

6.2.3. Ordering

Each list field can be orderable or not. We already discovered ListWidget's method setOrderableByDefault(boolean) that switch whether to configure fields that are added afterwards orderable or not. This method can be used several times in the list configuration.

Another way is to set each field individually orderable or not when they are added to the list. In such case add additional boolean argument to the addField() method such as:

myList.addField("phone", "#Phone no", false);

Notice the false as third parameter. true means that the list can be ordered by this field and false means the opposite. By not providing this parameter, simply the last value is used which has been set by setOrderableByDefault(boolean) method.

In addition, we already used method setInitialOrder(String, boolean). It sets a specified field (the first argument) to be ordered by default. true as the second argument tells the ordering should be ascending, false would mean descending. By not providing this information, the list is displayed in the original order.

6.2.4. Filtering

Filtering means that we only display a certain list items. The list can be filtered using its fields and data provided by the search form of this list.

For this, we must provide the ListWidget with the corresponding org.araneaframework.uilib.list.structure.ListFilters and FormElements. As the form elements are dummy "boxes" that hold search data, each ListFilter is related to a certain filter test, e.g. equality, greater than comparison etc. Each ListFilter also knows what information it must consider. In general, one list field is compared against a value provided by the search form. It's also assumed that a blank search field means that this particular ListWidget is currently disabled.

Fortunately, in most cases it's unnecessary to add these search fields manually. Instead, if one is adding a list field, he or she can assign both the ListFilter and FormElement for this field very simply:

myList.addField("address", "#Address").like();

Here we simply add an 'Address' field providing it with label and telling there's should be a Like filter for this field. By this, we automatically add a TextControl into the search form. By filling it with value 'Paris', we will see only rows which 'Address' field contain 'Paris', 'paris', 'PARIS' etc.

To describe, how this works, we show a longer version of the previous code:

myList.addField("address", "#Address");
myList.getFilterHelper().like("address");

So there's a special class org.araneaframework.uilib.list.structure.filter.FilterHelper that is used to add list filters. All ListWidget.addField() methods just return a little different version of this helper class, called a FieldFilterHelper. It's methods do not need a field Id and thus make one not to repeat the same field Id for each filter. In general, the shorter usage is recommended of course. However some filters are more complicated and may be related to more than one list field. For those, one must use the FilterHelper instead.

By default all filters that deal with the Strings are case insensitive. To configure some filters to be different, use the following:

myList.addField("country", "#Country").setIgnoreCase(false).like();
myList.addField("city", "#City").like();
myList.addField("address", "#Address").setIgnoreCase(true).like();

This can be explained following: Before adding a Like filter for the 'country' field, we switched to the case sensitive mode. And before adding a filter for the 'address' field, we switched to the case insensitive mode. Thus the city's filter is case sensitive as the country's but the address' filter does ignore the case.

This state is held by the FilterHelper and can be modified either by calling a method of the FilterHelper or the FieldFilterHelper. In such way, the following parameters can be set:

Case sensitivity
By using setIgnoreCase(boolean) one assigns new filters to ignore case (default) or not. This applies to filters that use String comparison.
Strict/non-strict
By using setStrict(boolean) one assigns new filters to disallow equality or not. By default equality is not allowed (strict). This applies to filters such as GreaterThan, LowerThan, Range etc.

Sometimes tables need to contain a column (or more) that is not bound to specific model object field. One can add such a column to the list structure like this:

myList.addEmptyField("choose", "#Choose");

The column still must have unique ID (e.g. "choose" in this case). The label for the column is optional. In addition, this column would not be orderable as its values are not controlled by the ListWdiget. However, this column can be used for check boxes, radio buttons, links, etc.

Now, let's show which filters we have got:

FilterHelper methodListFilter classDescription
eq()EqualFilterTests if the value of a certain list field is equal to the value of a certain search form field. The filter is disabled if the search field is blank.
eqConst()EqualFilterTests if the value of a certain list field is equal to a certain constant. This filter is always enabled.
gt(), lt()GreaterThanFilter, LowerThanFilterTests if the value of a certain list field is greater than (lower than) the value of a certain search form field. This filter is disabled if the search field is blank.
gtConst(), ltConst()GreaterThanFilter, LowerThanFilterTests if the value of a certain list field is greater than (lower than) a certain constant. This filter, if used, is always enabled.
like()LikeFilterTests if the pattern in a certain search form field matches with the value of a certain list field. This corresponds to the LIKE expression in SQL with some modifications. By default, it takes '%' and '*' symbols as any-string wildcards and '_', '.' and '?' as any-symbol wildcards. In addition, the pattern does not have to match with the whole string ('%' is automatically added before and after the pattern string). The wildcards and their automatic adding is configured by the org.araneaframework.uilib.list.util.like.LikeConfiguration which is found from the Aranea org.araneaframework.uilib.ConfigurationContext. This filter is identical in memory-based and database backend usage. This filter is disabled if the search field is blank.
likeConst()LikeFilterTests if a certain constant pattern matches with the value of a certain list field. This filter is always enabled.
startsWith(), endsWith()LikeFilterThis is very similar to the like() constraint, and tests if the list field value either starts or ends with the user-provided pattern. The filter is disabled if the search field is blank.
startsWithConst(), endsWithConst()LikeFilterTests if given constant pattern is either in the beginning or in the end of the field value. This filter, if used, is always enabled.
isNull(), notNull()NullFilterTests if the value of a certain list field is null (is not null) if the value of a certain search form field equals to a specified value.
isNullConst(), notNullConst()NullFilterTests if the value of a certain list field is null (is not null). This filter is always enabled.
range()RangeFilterTests if the value of a certain list field is between two values of certain search form fields. The filter is identical to the greater than or lower than filter in case of one of the search fields is blank. This filter is disabled if both search fields are blank.
fieldRangeInValueRange(), valueRangeInFieldRange(), overlapRange()RangeInRangeFilterTests if two values of certain list fields are between two values of certain search form fields, vice-versa or do they have a non-empty intersection. This filter is disabled if both search fields are blank.
in()InFilterTests if the value of a list field is among the values of a MultiSelectControl. It does a case-sensitive search for this.
sqlFunction()SqlFunctionFilterTests if the value returned from a certain SQL function is equal (or is greater than or is lower than) to the value of a certain list field, search form field or a constant. The arguments of the SQL function can also be chosen among the values of list fields, search form fields and constants. This filter cannot be used memory-based. This filter is always enabled.

By default the FormElements added into the search form have the same identifiers as the list fields. Therefore there can be only one search field per list field. If one would like to override the used Id for FormElement, any filter could be added like following:


        myList.addField("country", "#Country").like();
        myList.getFilterHelper().like("country", "anotherCountry");

The first line adds a list field 'country' and a Like filter associated with it as well as a new FormElement with Id of 'country'. The second line adds another Like filter associated to the list field 'country' and a new FormElement with Id of 'anotherCountry'.

By adding a filter, the corresponding FormElement is automatically created and added to the search form of the list. Now we cover the properties of the few FormElement describing their default values and showing how to customize them:

PropertyDefault valueCustomizing
IdSame as the list field Id.Call addField(...).<filter>("myCustomId");
LabelSame as the label of the associated list field.Call addField(...).useCustomLabel("myCustomLabel").xxx(...);
ControlIs selected considering the type of the associated list field:
TypeControl
java.lang.String TextControl
java.math.BigInteger, java.lang.Long, java.lang.Integer, java.lang.Short, java.lang.Byte NumberControl
java.math.BigDecimal, java.lang.Double, java.lang.Float FloatControl
Other subclasses of java.lang.Number FloatControl
java.util.Date, java.sql.Date DateControl
java.sql.Time TimeControl
java.sql.Timestamp DateTimeControl
java.lang.Boolean CheckboxControl
All others TextControl
Call addField(...).xxx(new MyCustomControl());
DataCorresponds to the type of the associated list field.Call addField(...).useFieldType(MyType.class).xxx(...);
Initial valueAlways null to disable the filter by default.After adding the field and the filter call myList.getForm().setValueByFullName("fieldId", customInitialValue); or add a custom Formelement.
MandatoryAlways false as all search conditions are optional.After adding the field and the filter call myList.getForm().getElementByFullName("fieldId").setMandatory(true); or add a custom Formelement.
FormElementSee all properties above.To use a custom Formelement, call addField(...).xxx(new MyCustomFormElement(...));. To disable adding it at all, call addField(...)._xxx(); (notice the underscore).

The xxx marks any filter adding method. As one can count, there are 6 overridden methods for each list filter: 2 versions for providing a custom Id or not and 3 versions for providing a custom FormElement, Control or neither of them. In addition there are methods that start with an _ for disabling adding a form element. Using the FilterHelper instead of FieldFilterHelper is analogous except all filter adding methods take the list field Id as the first argument in addition.

It's import to notice that xxxxConst methods do not create a form element because they are independent of the search form at all - they are constant. However they can actually take a value Id for the defined constants as well. These Ids can be used later to convert specific values when creating a database query. Of course non-constant filters have the same Ids but just use them mainly to get values from the search form. xxxxConst filters have 2 overridden add methods depending on whether the custom value Id is provided or not. By default it's the same as the field Id.

6.2.5. Backend Data Provider

Now that we have demonstrated defining lists and also creating MemoryBasedListDataProvider, we will discover using BackendListDataProvider. The following example code should be in the same widget as constructing of the related ListWidget.

...
private class MyBackendListDataProvider extends BackendListDataProvider {
  public MyBackendListDataProvider() {
    super(true);
  }
  protected ListItemsData getItemRange(ListQuery query) throws Exception {
    return lookupMyService().findMyModel(query);
  }
}
...

The line super(true) constructs BackendListDataProvider with cache enabled (only used when there are no change in query). Notice that there is no association with any JavaBean class here. The method ListItemsData getItemRange(ListQuery query) implements loading current item range according to the range indexes and filtering and ordering conditions. org.araneaframework.backend.list.mode.ListQuery and org.araneaframework.backend.list.mode.ListItemsData may be thought as being input and output of each list data query.

ListQuery is a simple JavaBean that holds the following properties:

List structure (since 1.1)
The structure of the list contains all the list fields and static information about the filtering and ordering. (It is constructed once as the ListWidget is defined.)
List item range indexes
This is 0-based start index and items count ( Long objects) that define the range. By default, lists are shown by pages. Although all items can be shown at once also. Then the start index is zero and items count is omitted.
Filter and order info (since 1.1)
These contain the current filter and ordering data as instances of Map and OrderInfo.
Filter and order expressions
These could be thought as an abstraction of SQL expressions which are constructed using the info described above (even the same instances). These expressions will be used in the WHERE and ORDER BY clauses.

Generally, this whole object is just passed to org.araneaframework.backend.list.helper.ListSqlHelper class that is used to generate SQL statements and fetching the results from database. Latter is hold in ListItemsData object which is a simple JavaBean that holds the following properties:

List items range
Model objects that are the result of the query .
Total count
Total count ( Long object) of the list. This is important information for navigating through the whole list. Notice that this depends only on filtering conditions.

Notice that BackendListDataProvider actually do not depend on using databases. It just provides a simple query object and expects a simple result to be returned. Thus, you have the power to use it as you like. At the same, Aranea provides a very useful class org.araneaframework.backend.list.helper.ListSqlHelper that generate SQL statements and fetches the results from database. We strongly recommend it together with its subclasses that support different database systems. Currently Oracle (OracleListSqlHelper), Postgre (PostgreListSqlHelper), and HSQL (HsqlListSqlHelper) databases are supported (they are used similarly because they all extend ListSqlHelper).

The following example discovers the simplest usage of ListSqlHelper. The following code should be in a service class instead of previously discovered Widget:

public class MyService {
  ...
  private DataSource dataSource;
  ...
  public ListItemsData findMyModel(ListQuery query) {
    ListSqlHelper helper = new OracleListSqlHelper(this.dataSource, query);

    helper.addMapping("name", "NAME");
    helper.addMapping("surname", "SURNAME");
    helper.addMapping("phone", "PHONE_NO");

    helper.setSimpleSqlQuery("PERSON");
    return helper.execute(MyModel.class);
  }
  ...
}

Method ListItemsData findMyModel(ListQuery query) does the following:

Constructs and initializes the helper
The line ListSqlHelper helper = new OracleListSqlHelper(this.dataSource, query) constructs OracleListSqlHelper - an Oracle specific ListSqlHelper - and passes it the DataSource and ListQuery data.
Adds column mappings
The line helper.addMapping("name", "NAME") defines that identifier of column "name" will be converted into "NAME" when used in an SQL statement. There may be lot of differnece between JavaBean properties names and database fields names. The same database identifier ( "NAME" ) is used when fetching data from ResultSet by default. This could also have another identifier set by providing it as the third argument.
Provides the helper with a simple SQL query
The line helper.setSimpleSqlQuery("PERSON") sets the whole SQL query with parameters using only the given database table name. Filtering and ordering is added automatically according to the ListQuery data.
Executes the query and retrieve the data
The line return helper.execute(MyModel.class) executes and retrieves data of both total count and items range queries. The ResultSet is read using the default BeanResultReader .

The following example discovers the custom usage of ListSqlHelper.

public class MyService {
  ...
  private DataSource dataSource;
  ...
  public ListItemsData findMyModel(ListQuery query) {
    ListSqlHelper helper = new OracleListSqlHelper(this.dataSource, query);

    helper.addMapping("name", "NAME");
    helper.addMapping("surname", "SURNAME");
    helper.addMapping("phone", "PHONE_NO");

    StringBuffer s = new StringBuffer();
    s.append("SELECT ");
    s.append(helper.getDatabaseFields());
    s.append(" FROM PERSONS");
    s.append(helper.getDatabaseFilterWith(" WHERE ", ""));
    s.append(helper.getDatabaseOrderWith(" ORDER BY ", ""));

    helper.setSqlQuery(s.toString());
    helper.addStatementParams(helper.getDatabaseFilterParams());
    helper.addStatementParams(helper.getDatabaseOrderParams());

    return helper.execute(MyModel.class);
  }
  ...
}

Method ListItemsData findMyModel(ListQuery query) does the following:

Constructs and initializes the helper
The line ListSqlHelper helper = new OracleListSqlHelper(this.dataSource, query) constructs OracleListSqlHelper - an Oracle specific ListSqlHelper - and passes it the DataSource and ListQuery data.
Adds column mappings
The line helper.addMapping("name", "NAME") defines that identifier of column "name" will be converted into "NAME" when used in an SQL statement. There may be lot of differnece between JavaBean properties names and database fields names. The same database identifier ( "NAME" ) is used when fetching data from ResultSet by default. This could also have another identifier set by providing it as the third argument.
Gets SQL substrings from the helper
The line helper.getDatabaseFields() returns just the comma-separated list of database column identifiers that were just defined in the mapping. This does not depend on the original set of list columns at all. The line helper.getDatabaseFilterWith(" WHERE ", "") returns the WHERE clause body with the provided prefix and suffix. It returns an empty string if there is no filter condition currently set (it does not mean there are no filters defined). Notice that we only deal with SQL strings here. As ListSqlHelper uses PreparedStatement objects to execute queries, there must be provided statement parameters in addition to the SQL string. This generally provides better performance of executing similar queries.
Constructs SQL query string
StringBuffer is used to construct the whole SQL query string. Notice that the helper does not construct it totally by itself. This lends user more power for complex queries. It is very important that the constructed query is for getting all rows that match with the current filter and order conditions, but not the range conditions. ListSqlHelper always executes two queries: one for getting the items count and another for getting the items range. Generally, both of these can be easily constructed from this one provided query. This implementation depends on the database system and therefore the concrete ListSqlHelper subclass.
Gets SQL parameters from the helper
The line helper.getDatabaseFilterParams() returns SQL parameters of WHERE clause or empty list if there are none.
Provides the helper with the SQL query
The line helper.setSqlQuery(...) sets the SQL string and the line helper.addStatementParams(...) adds the query parameters ( ListSqlHelper uses PreparedStatement s). Of course, the order of parameters must match with the SQL string.
Executes the query and retrieve the data
The line return helper.execute(MyModel.class) executes and retrieves data of both total count and items range queries. The ResultSet is read using the default BeanResultReader.

ListSqlHelper mappings and converters

All Aranea List filters that are propagated with values from the filter form construct an expression chain. This chain is built each time any condition is changed. E.g if one is searching for persons whose birthday is between July 6th, 1950 and Sept 2nd, 1990 then there's one value 'Birthday' and two values 'July 6th, 1950' and 'Sept 2nd, 1990' which have 'Birthday_start' and 'Birthday_end' as names. Ordering the list is done the same. When retrieving data from database all these information must be considered to build an appropriate query. Therefore all these variables must be mapped to database fields. When reading the query results Bean fields must be mapped to ResultSet columns. In general, these Bean fields match exactly with the variables. But considering more specific cases, they are not assumed to be the same.

The following list covers the terms that are used when configuring ListSqlHelper:

List field
Each list has a set of fields (or columns) that are displayed. All fields are listed up in the SELECT clause. Some of them can be used for filtering and ordering as well. Field name can be e.g "birthday" or "group.name".
Expression value
Values are the temporary information in the list filtering. '1980-08-21' is a value. 'Birthday_start' is a name of that value. In simple cases one list field matches with one value. In case of the range filter two different values (start and end of the range) are used. Also one value can be used together with two or more fields. A value identifier is used for optional converting before using it in a query. This is done by adding a Converter object to ListSqlHelper. E.g. booleans have to be converted into numeric (0 or 1) values.
Database column
Database column can be for example 'age' or 'company.name' as well as 'MAX(price)' or '(SELECT(COUNT(*) FROM document WHERE userId = user.id)' (an inner SELECT) - any expression that is part of a SQL string.
Database column alias
Database field alias is for example 'name', 'total_price' etc. It's just an identifier not a whole expression. In ListSqlHelper one can assign an alias for each database field or have it automatically generated. The result of a query is a table - a ResultSet - which columns have the same names as the aliases in the query. An alias can also be used in a custom filter condition (WHERE clause) to identify the same database field or expression that was added in the SELECT clause.

ListSqlHelper methods for configuring mappings:

MethodPurpose
addMapping(String fieldName, String columnName, String columnAlias) Adds a field name to database column name and column alias mapping. A given field is listed in the SELECT and is read from the ResultSet.
addMapping(String fieldName, String columnName) Adds a field name to database column name and column alias mapping. A given field is listed in the SELECT and is read from the ResultSet. The corresponding column alias is generated automatically.
addDatabaseFieldMapping(String fieldName, String columnName, String columnAlias) Adds a field name to database column name and column alias mapping. A given field is listed in the SELECT but is not read from the ResultSet.
addDatabaseFieldMapping(String fieldName, String columnName) Adds a field name to database column name mapping. A given field is listed in the SELECT but is not read from the ResultSet. The corresponding column alias is generated automatically.
addResultSetMapping(String fieldName, String columnAlias) Adds a field name to database column alias mapping. A given field is not listed in the SELECT but is read from the ResultSet.

ListSqlHelper methods for configuring converters:

MethodPurpose
addDatabaseFieldConverter(String value, Converter converter)Adds converter for expression value.
addResultSetDeconverterForBeanField(String beanField, Converter converter)Adds deconverter for ResultSet column by list field that is mapped with that Column.
addResultSetDeconverterForColumn(String rsColumn, Converter converter)Adds deconverter for ResultSet column.

ListSqlHelper naming strategies

Since Aranea MVC 1.1 ListSqlHelper also support naming strategies. This means that one do not need to define database column names and aliases for all list fields. Instead only list fields are listed up and they can be transformed into database column names and aliases using a strategy.

A strategy is defined by the following interface. NamingStrategy.

public interface NamingStrategy {
        String fieldToColumnName(String fieldName);
        String fieldToColumnAlias(String fieldName);
}

To set or get a strategy use methods ListSqlHelper.setNamingStrategy(NamingStrategy namingStrategy) or ListSqlHelper.getNamingStrategy() respectfully.

The standard implementation StandardNamingStrategy adds underscores to all names (e.g. "firstName" -> "first_name"). For an alias all dots are converted into underscores (e.g. "parent.friend.age" -> "parent_friend_age"). For a name all dots except the last are converted into underscores (e.g. "parent.friend.age" -> "parent_friend.age", so "parent_friend" is expected to be a table alias).

If one wishes to define table aliases for the naming strategy PrefixMapNamingStrategy (enabled by default) can be used. By using method addPrefix(String fieldNamePrefix, String columnNamePrefix) one can add a custom prefix for database columns and aliases. An instance of PrefixMapNamingStrategy can be retrieved by method ListSqlHelper.getPrefixMapNamingStrategy().

As naming strategies still expect a set of list fields to be defined there is a way to add list fields without any mappings.

A set of fields are provided by following interface.

public interface Fields {
        Collection getNames();
        Collection getResultSetNames();
}

To set or get a fields provider use methods ListSqlHelper.setFields(Fields fields) or ListSqlHelper.getFields() respectfully.

A standard implementation StandardFields enables to add fields using the following methods.

MethodPurpose
addField(String field)Adds a field by its name.
addFields(String[] fields)Adds a set of fields by their names.
addFields(Collection fields)Adds a set of fields by their names.
addFields(Class beanClass)Adds all the fields of the Bean class.
addFields(ListStructure structure)Adds all the fields defined in the list structure.

There are also corresponding methods to add fields using a prefix and methods to remove the fields (using a prefix or not).

To get the StandardFields call ListSqlHelper.getStandardFields().

The following example shows how to just list up the fields (the corresponding column names and aliases are generated by the naming strategy). Because the column "phone" has a "non-standard" column name, it is set separately.

public class MyService {
  ...
  public ListItemsData findMyModel(ListQuery query) {
    ListSqlHelper helper = new OracleListSqlHelper(this.dataSource, query);

    StandardFields fields = helper.getStandardFields();
    fields.addField("name");
    fields.addField("surname");

    helper.addMapping("phone", "PHONE_NO");

    helper.setSimpleSqlQuery("PERSON");
    return helper.execute(MyModel.class);
  }
  ...
}

If all fields described for the ListWidget should be used they can be added using the ListStructure contained in the ListQuery:

public class MyService {
  ...
  public ListItemsData findMyModel(ListQuery query) {
    ListSqlHelper helper = new OracleListSqlHelper(this.dataSource, query);

    helper.getStandardFields().addFields(query.getListStructure());
    
    helper.addMapping("phone", "PHONE_NO");

    helper.setSimpleSqlQuery("PERSON");
    return helper.execute(MyModel.class);
  }
  ...
}

6.2.6. Quick Overview on How to Use

Here is a quick summary on how one can create and use a list, based on the material described in this chapter.

  1. Create a data query. This basically means a service layer method that takes at least a ListQuery for its argument, executes the database query, and returns ListItemsData. To execute the query, one must also specify the binding between model object fields and query result set fields.
  2. Create a list widget. The (bean) list widget is used to define the columns and its labels together with sorting and filtering information. Also, one must define a data provider — either memory-based or back-end — that invokes the data query created previously. Note that the data provider implementation provides the ListQuery object (containing information about the list, the constraints, and the rows expected) for the query, and expects a ListItemsData object as a result. Finally, the created list must be added by its creator widget simply like following: this.addWidget("myList", createdList);
  3. Describe the layout in JSP. Here you can use the list tags provided by Aranea. These are described below.

6.3. Selecting List Rows

This section shows how a user can choose list rows with a check box or a radio button. The solution described here is also integrated into Aranea lists, so it is quite easy to use. Firstly, Aranea provides a check box tag (<ui:listRowCheckBox/>) and a radio button tag (<ui:listRowRadioButton/>) for list rows. These are meant for the user to check multiple rows or just one row to submit them once the user clicks on a button. Usually, these tags don't require any attributes (unless one needs to customize style or javascript), and work only with the list where they are used, even if there are multiple lists on the page.

In addition, Aranea provides a tag that selects or unselects all row check boxes in the list. It is named <ui:listSelectAllCheckBox/>. Again, it requires zero configuration.

Now, these tags are useful because the next step is just getting the model objects from the list with the ListWidget.getSelectedRows() method. Or, if a radio button was used, and, therefore, only one selected row is expected, the ListWidget.getSelectedRow() method can be used. If no rows were selected then getSelectedRows() would return an empty list, and getSelectedRow() would return null.

There is also an advanced feature in case the list row check boxes are used. One can make the list remember the selected rows in case the user switches between the (list) pages. It can be achieved by calling list.setSelectFromMultiplePages(true) (by default it is false). Once enabled, the list and the tags use the equals() method of the list row data object to know whether the row must be checked or unchecked. Therefore, when the user goes back to the list page where some rows were selected before then they would still appear selected.

Note

Enabling the selectFromMultiplePages option, that makes list remember previously selected rows, requires caution because the data model objects needs to have the equals() to correctly compare them. Otherwise, the "same" object could appear several times in the returned selected rows list. This is the reason why it is turned off by default.

6.4. List JSP Tags

6.4.1. <ui:list>

Starts a list context. List view model, list sequence view model and list id are made accessible to inner tags as EL variables.

Attributes

AttributeRequiredDescription
idnoList widget id.
varSequencenoName of variable that represents list sequence info (by default listSequence).

Variables

VariableDescriptionType
listView model of list.ListWidget.ViewModel
listSequence (unless changed with varSequence attribute).View model of list sequence info.SequenceHelper.ViewModel
listIdId of list.String
listFullIdFull id of list.String

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:list id="list">
    ...
</ui:list>

6.4.2. <ui:listFilter>

Represents list filter. Introduces an implicit form (<ui:form>), so one can place form elements under it.

This tag has no attributes.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:list id="list">
  <ui:listFilter>
    <ui:row>
        <ui:cell>
            <ui:textInput id="field1Filter"/>
        </ui:cell>
    
        <ui:cell>
            <ui:textInput id="field2Filter"/>
        </ui:cell>

        ...

    </ui:row>
  </ui:listFilter>
</ui:list>

6.4.3. <ui:listFilterButton> and <ui:listFilterClearButton>

<ui:listFilterButton> renders list's filter form filtering activation button and registers a keyboard handler, so that pressing ENTER key in any filter form field activates list filtering. <ui:listFilterClearButton> renders list's filter form clearing button, pressing it sends server-side event that clears all active list filters.

Both of these tags must be used inside <ui:listFilter> tag.

Attributes

AttributeRequiredDescription
renderModenoPossible values are button, input—filter button is rendered with corresponding HTML tags, or empty in which case JSP author must provide suitable content for this tag by themself (with an image, for example). Default rendermode is button.
onClickPreconditionnoPrecondition for deciding whether registered onclick event should go server side or not. If left unspecified, this is considered to be true.
showLabelnoIndicates whether button label is shown.Value should be true or false, default is false—using true is pointless with these particular tags, it only has some effect when specified renderMode is empty and tags body is left empty too.
Also have all common form element rendering attributes plus standard style and styleClass attributes.

Examples

<?xml version="1.0" encoding="UTF-8"?>
...
  <ui:listFilter>
    <ui:row>
      <!-- Bunch of filter fields in cells -->
      <ui:cell>
        <ui:listFilterButton/>
        <ui:listFilterClearButton/>
      </ui:cell>
    <ui:row>
  </ui:listFilter>

6.4.4. <ui:listRows>

Iterating tag that gives access to each row on the current list page. The row is by default accessible as EL variable row.

Attributes

AttributeRequiredDescription
varnoName of variable that represents individual row (by default "row").

Variables

VariableDescriptionType
row (unless changed with var attribute).Object held in current row.Object

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:list id="list">
  <ui:listFilter>
    ...
  </ui:listFilter>
  
  <ui:listRows>
    <ui:row>
        <!-- In each row, object in this list row is accessible -->
        <ui:cell>
            <c:out value="${row.field1}"/>
        </ui:cell>

        <ui:cell>
            <c:out value="${row.field2}"/>
        </ui:cell>

        ...
    </ui:row>
  </ui:listRows>
</ui:list>

6.4.5. <ui:listRowButton>

Represents an HTML form button (not tied to any Control or FormElement). Default styleClass="aranea-button", rendered with HTML <button ...> tag.

Attributes

AttributeRequiredDescription
eventId no Event triggered when button is clicked.
id no Button id, allows to access button from JavaScript.
labelId no Id of button label.
onClickPrecondition no Precondition for deciding whether onclick event should go server side or not. If left unspecified this is set to return true;.
tabindexnoThis attribute specifies the position of the current element in the tabbing order for the current document. This value must be a number between 0 and 32767.
Also has standard styleClass, updateRegions and globalUpdateRegions attributes.

6.4.6. <ui:listRowLinkButton>

Represents a HTML link with an onClick JavaScript event. Default styleClass="aranea-link-button", rendered with HTML <a href="javascript:" ...> tag.

Attributes

AttributeRequiredDescription
eventId no Event triggered when link is clicked.
id no Link id, allows to access link from JavaScript.
labelId no Id of link label.
onClickPrecondition no Precondition for deciding whether onclick event should go server side or not. If left unspecified this is set to return true;.
tabindexnoThis attribute specifies the position of the current element in the tabbing order for the current document. This value must be a number between 0 and 32767.
Also has standard styleClass, updateRegions and globalUpdateRegions attributes.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:list id="list">
  ...  
  <ui:listRows>
    <ui:row>
      ...
      <ui:cell>
        <ui:listRowLinkButton eventId="edit">
          <img src="editButton.png"/>
        </ui:listRowLinkButton>
      </ui:cell>
      ...
    </ui:row>
  </ui:listRows>
</ui:list>

6.4.7. <ui:listRowCheckBox> And <ui:listRowRadioButton>

Represents a list row check box and a list row radio button that are bound to the list row. The <ui:listRowCheckBox> accompanies with the <ui:listSelectAllCheckBox> that lets the user mark all list row check boxes (in the same list) checked or unchecked.

Both <ui:listRowCheckBox> and <ui:listRowRadioButton> usually require no configuration. However, if one needs to change something, the tags provide similar attributes.

Attributes

AttributeRequiredDescription
valueno(Check box only!) Specifies a custom value (when it is submitted). Default value is selected.
labelIdnoSpecifies a custom label for the check box or the radio button.
disablednoSpecifies whether the input should be rendered as disabled. Default is active state.
onclicknoSpecifies custom onclick event. Default is none.
accessKeynoSpecifies custom acceskey (defined by HTML). Default is none.
checkednoSpecifies the initial state of the check box or radio button. Default is unchecked.
tabindexnoThis attribute specifies the position of the current element in the tabbing order for the current document. This value must be a number between 0 and 32767.
stylenoInline (CSS) style for HTML tag.
styleClassnoCSS class for the tag.
onChangeEventIdnoThe event handler name (in the parent widget of the list) that wants to know when a row selection changes. The parameter for the event handler is the rowRequestId.
eventPreconditionnoA JavaScript event precondition on whether the onchange event should go server-side.

See also Section 6.3, “Selecting List Rows”

6.5. Editable Lists

EditableListWidget and EditableBeanListWidget are ListWidgets wrapped around FormListWidget (See Section 5.3, “Form Lists” about it) which gathers data with the help from ListWidget.

Both editable list widgets have just one constructor and one additional getter (compared to ListWidget):

public EditableListWidget(FormRowHandler rowHandler);
public EditableBeanListWidget(FormRowHandler rowHandler, Class beanClass);

// gets the wrapped form list
public BeanFormListWidget getFormList();

Most important component of editable lists is FormListWidget's RowHandler, refer to Section 5.3, “Form Lists” about implementing that interface. Other than required implementation of RowHandler, editable lists do not differ from ListWidgets.

public class SampleEditableListWidget {
  private EditableBeanListWidget list;
  
  protected void init() throws Exception {
    setViewSelector("sampleEditableListView");
    
    list = new EditableBeanListWidget(buildFormRowHandler(), SomeBean.class);
    list.setDataProvider(buildListDataProvider());
    list.setOrderableByDefault(true);
    // list has only two columns of which only one is editable
    list.addField("immutable", "#ImmutableColumnLabel", false);
    list.addField("mutable", "#MutableColumnLabel").like();

    addWidget("sampleEditableList", list);
  }
  
  private FormRowHandler buildFormRowHandler() throws Exception {
    // return formRowHandler, see the form list example
  };
  
  private private ListDataProvider buildListDataProvider() throws Exception {
    // return data provider
  }
}

JSP view for this sample widget is presented below:

...
<ui:formList id="sampleEditableList">
  <!-- List filter definition, usual -->
  <!-- Editable lists body -->
  <ui:formListRows>
    <ui:row>
      <ui:cell>
        <!-- Row object is accessible as 'row' just as in lists -->
        <c:out value="${row.immutable}"/>
      </ui:cell>
      <ui:cell>
        <!-- But the implicit form tag for current row form is also present, so... -->
        <ui:formElement id="mutable">
          <ui:textInput/>
        </ui:formElement>
      </ui:cell>
    </ui:row>
  </ui:formListRows>
</ui:formList>
...

Full editable list example is bundled with Aranea examples.

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.

Chapter 8. Third-party Integration

8.1. Spring Application Framework

8.1.1. BeanFactory, ApplicationContext, WebApplicationContext

AraneaSpringDispatcherServlet will always add a BeanFactory to the environment. It can be retrieved as follows:

BeanFactory beanFactory = 
  (BeanFactory) getEnvironment().getEntry(BeanFactory.class)

Or using the method getBeanFactory() in BaseUIWidget. By default it will contain only beans configured by Aranea, however if one also uses usual Spring configuration:

...
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value> 
    /WEB-INF/services.xml
  </param-value>
</context-param>
...
<listener>
  <listener-class>
    org.springframework.web.context.ContextLoaderListener
  </listener-class>
</listener>
...

Then the AraneaSpringDispatcherServlet will integrate with Spring and make BeanFactory provide all of the configure beans, as well as add ApplicationContext and WebApplicationContext to the environment.

Warning

AraneaSpringDispatcherServlet must be initialized after Spring context loader listener or servlet to integrate with Spring successfully.

8.1.2. Spring Localization Filter

Java class:SpringLocalizationFilterService
Default configuration name:-
Provides:LocalizationContext, SpringLocalizationContext
Depends on:WebApplicationContext

Provides localization services, see Section 2.8.2, “LocalizationContext”. The difference from the usual localization filter is that this one delegates the actual localization to a Spring MessageSource.

8.1.3. Widget Dependency Injection

Aranea does not by default support configuring widgets with Spring, as they are assumed to be created by the programmer and their life-cycle is managed by Aranea. The main problem however is that widgets are assumed to be serializable and Spring beans are often not (especially since they often are proxies with references to bean factory and so on). As a solution we provide a utility class SpringInjectionUtil that allows to inject Spring beans after a following convention:

...
injectSomeSpringBean(ISomeBean someBean) {
  this.someBean = someBean;
}
...

This method is similar to a setter method, but starts with "inject". The remainder of the method name is interpreted as the name of Spring bean to be injected, with the first letter lowercase (in the case of our example bean named "someSpringBean" would be injected). To actually inject the beans to all similarly called methods in the current widget call injectBeans() in widget init() method as follows:

...
protected init() {
  ...
  SpringInjectionUtil.injectBeans(getEnvironment(), this);
}
...

You may even put this call into the base widget of your application to ensure that all application widgets would get their dependencies injected.

Note

The injected bean must be an interface, as Aranea will construct an indirection proxy. This will ensure that the referenced object will be serializable (and small for that matter), but will also introduce a small performance penalty (we believe to be negligible next to the proxies of Spring itself).

Chapter 9. Javascript Libraries

9.1. Third-party Javascript Libraries

Aranea distribution includes some third party Javascript libraries. Most of these are not needed for using Aranea functionality, but extend the functionality of both framework and UiLib.

Note

Behaviour library was removed since 1.2.1 because it was out-of-date and because Prototype can now do its work. To reduce the scripts on the client-side, it was reasonable to avoid Behaviour.

9.1.1. The DHTML Calendar (http://www.dynarch.com/projects/calendar/)

Nice DHTML calendar, required if one wants to use Aranea JSP <ui:dateInput> or <ui:dateTimeInput> tags.

9.1.2. Prototype (http://www.prototypejs.org/)

Prototype is a JavaScript framework that aims to ease development of dynamic web applications. Aranea partial rendering model uses its XMLHttpRequest facilities for generating requests and defining update callbacks. It is also needed for using Uilib's AutoCompleteTextControl and action-enabled TreeWidget components.

9.1.3. script.aculo.us (http://script.aculo.us/)

script.aculo.us provides easy-to-use, cross-browser user interface JavaScript libraries. Only subset of script.aculo.us libraries are included— JSP tags that depends on them are <ui:autoCompleteTextInput> and <ui:tooltip>.

TinyMCE is a platform independent web based Javascript HTML WYSIWYG editor control. Required for using Aranea JSP <ui:richTextarea> tag.

Prototip allows to easily create both simple and complex tooltips using the Prototype javascript framework. If one also uses Scriptaculous some nice effects can be added. This is required when using JSP <ui:tooltip> tag.

ModalBox is a JavaScript technique for creating modern modal dialogs or even wizards (sequences of dialogs) without using conventional popups and page reloads.

9.1.7. log4javascript (http://log4javascript.org/)

Note that this is now deprecated in favour of Firebug's built in logging facilities. Logging to Firebug console is enabled with AraneaPage.setFirebugLogger().

log4javascript is a JavaScript logging framework similar to Java logging framework log4j. Include log4javascript scripts and call araneaPage().setDefaultLogger() to receive a popup window where Aranea JS debug output is logged. When Firebug is active, its logging to its console can be activated with AraneaPage.setFirebugLogger().

9.2. Aranea Clientside Javascript

Aranea uses javascript to do form submits. The code is sent to the client-side in compressed form. The script enables AJAX enabled webapps and provides more control over form submitting logic. Each page served by Aranea has associated AraneaPage object:

/**
 *  Exactly one AraneaPage object is present on each page served by Aranea and
 *  contains common functionality for setting page related variables, events and
 *  functions.
 */

// Servlet URL is set on every page load.
araneaPage().getServletURL();
araneaPage().setServletURL(url);

// If servlet URL is not enough for some purposes, encoding function should be overwritten.
araneaPage().encodeURL(url)

// Indicates whether the page is completely loaded or not. Page is considered to
// be loaded when all system onload events have completed execution.
araneaPage().isLoaded()
araneaPage().setLoaded(b)

// Dummy logger is practically no logger.
araneaPage().setDummyLogger()

// Makes Aranea scripts use log4javascript logger.
araneaPage().setDefaultLogger()

// Makes Aranea scripts use Firebug logger.
araneaPage().setFirebugLogger()

araneaPage().setLogger(theLogger)
araneaPage().getLogger()

// locale - should be used only for server-side reported locale.
araneaPage().getLocale()
araneaPage().setLocale(locale)

// Indicates whether some form on page is (being) submitted already
// by traditional HTTP request.
araneaPage().isSubmitted()
araneaPage().setSubmitted()

// Returns the active system form in this AraneaPage
araneaPage().getSystemForm()

araneaPage().setSystemForm(_systemForm)
araneaPage().setSystemFormEncoding(encoding)

// Returns the path of the component who should receive events generated by DOM element.
araneaPage().getEventTarget(element)

// Returns event id that should be sent to server when event(element) is called.
araneaPage().getEventId(element) {

// Returns event parameter that should be sent to server when event(element) is called.
araneaPage().getEventParam(element)

// Returns update regions that should be sent to server when event(element) is called.
araneaPage().getEventUpdateRegions(element)

// Returns closure that should be evaluated when event(element) is called and
// needs to decide whether server-side event invocation is needed.
araneaPage().getEventPreCondition(element)

// Adds a load event listener that is executed once when the page loaded.
araneaPage().addSystemLoadEvent(event)

// Adds a load event listener that is executed once when the page or part of it is loaded.
araneaPage().addClientLoadEvent(event)

// Adds a unload event listener that is executed once when the page unloaded.
araneaPage().addSystemUnLoadEvent(event)

araneaPage().onload()
araneaPage().onunload()

// Adds callback executed before next form submit. */
araneaPage().addSubmitCallback(callback)

// Add callback executed before form with given id is submitted next time.
araneaPage().addSystemFormSubmitCallback(systemFormId, callback) {

// Executes all callbacks that should run before submitting the form with given id.
// Executed callbacks are removed.
araneaPage().executeCallbacks(systemFormId)

// Chooses appropriate submitting method and submittable form given the HTML element
// that initiated the submit request. Applies the appropriate parameter values
// and submits the systemForm which owns the element.
araneaPage().event(element)

// Returns either form submitter, AJAX submitter, or overlay submitter.
// This function can be overwritten to support additional submit methods.
// It is called by event() to determine the appropriate form submitter.
araneaPage().findSubmitter(element, systemForm)

// Another submit function, takes all params that are possible to use with Aranea JSP currently.
araneaPage().event_6(systemForm, eventId, eventTarget, eventParam, eventPrecondition, eventUpdateRegions)

// Returns URL that can be used to invoke full HTTP request with some predefined request parameters.
araneaPage().getSubmitURL(topServiceId, threadServiceId, araTransactionId, extraParams)

// Returns URL that can be used to make server-side action-invoking
// XMLHttpRequest with some predefined request parameters.
araneaPage().getActionSubmitURL(systemForm, actionId, actionTarget, actionParam, sync, extraParams)

// Invokes server-side action listener by performing XMLHttpRequest with correct parameters.
araneaPage().action(element, actionId, actionTarget, actionParam, actionCallback, options, sync, extraParams)

// Invokes server-side action listener by performing XMLHttpRequest with correct parameters.
araneaPage().action_6(systemForm, actionId, actionTarget, actionParam, actionCallback, options, sync, extraParams)

// Method to log a message at DEBUG level.
// A shortcut for araneaPage().getLogger().debug(message).
araneaPage().debug(message)

// Adds keepalive function f that is executed periodically after time milliseconds has passed.
araneaPage().addKeepAlive: function(f, time)

// Clears/removes all registered keepalive functions.
araneaPage().clearKeepAlives()

// Returns the flag that determines whether background validation is used by
// for all forms (FormWidgets) in the application.
araneaPage().getBackgroundValidation()

// Proivdes a way to turn on/off background validation. The parameter is a boolean.
araneaPage().setBackgroundValidation(useAjax)

// ============================================================================
//                           STATIC METHODS
// ============================================================================

// Returns a default keepalive function -- to make periodical requests to
// expiring thread or top level services.
AraneaPage.getDefaultKeepAlive(topServiceId, threadServiceId, keepAliveKey)

// Searches for widget marker around the given element. If found, returns the
// marker DOM element, else returns null.
AraneaPage.findWidgetMarker(element)

// Random request ID generator. Sent only with XMLHttpRequests which apply to
// certain update regions. Currently its only purpose is easier debugging
// (identifying requests).
AraneaPage.getRandomRequestId()

// Returns the full URL for importing given file.
// The same URL that <ui:importScripts> outputs.
AraneaPage.getFileImportString(filename)

// Page initialization function, it is called upon page load.
AraneaPage.init()

// Page deinitialization function, it is called upon page unload.
AraneaPage.deinit()

// Searches for system form in HTML page and registers it in the current
// AraneaPage object as active systemForm. Also returns the found form.
AraneaPage.findSystemForm()

// RSH initialization for state versioning. Has effect only when "aranea-rsh.js"
// is also included in the page.
AraneaPage.initRSHURL()

// Properties for loading message:
AraneaPage.loadingMessageContent: 'Loading...',
AraneaPage.loadingMessageId: 'aranea-loading-message',
AraneaPage.reloadOnNoDocumentRegions: false,

// Add a handler that is invoked for custom data region in updateregions AJAX
// request. process() function will be invoked on the handler
// during processing the response. Data specific to this handler will be
// passed as the first parameter to that function (String).
AraneaPage.addRegionHandler(key, handler)

// Process response of an updateregions AJAX request. Should be called only
// on successful response. Invokes registered region handlers.
AraneaPage.processResponse(responseText)

AraneaPage.handleRequestException(request, exception)

// Create or show loading message at the top corner of the document. Called
// before initiating an updateregions Ajax.Request.
AraneaPage.showLoadingMessage()

// Hide loading message. Called after the completion of updateregions Ajax.Request.
AraneaPage.hideLoadingMessage()

// Builds the loading message that is by default shown in the right-top corner.
// The default loading message built here also depends on aranea.css.
// This method is always called during AJAX request. You are free to override it.
AraneaPage.buildLoadingMessage()

// Perform positioning of loading message (if needed in addition to CSS).
// Called before making the message element visible. This implementation
// provides workaround for IE 6, which doesn't support
// <code>position: fixed</code> CSS attribute; the element is manually
// positioned at the top of the document. If you don't need this, overwrite
// this with an empty function:
// 
Object.extend(AraneaPage, { positionLoadingMessage: Prototype.emptyFunction });

AraneaPage.positionLoadingMessage(element)


// ============================================================================
//                           FORM SUBMITTERS
// ============================================================================

// Here are three submitter classes for the standard HTTP submit, AJAX update
// region submit, and AJAX overlay submit.

// The standard HTTP submitter. Whether it's POST or GET depends on the
// "method" attribute of the "form" element. (The default is GET submit.)
var DefaultAraneaSubmitter = Class.create(

  // Local variables:

  systemForm: null,

  widgetId: null,

  eventId: null,

  eventParam: null,

  // Constructor:

  initialize(form)

  // Methods:

  // "Private" method that is called by event to store event data in local
  // variables.
  storeEventData(element)

  // Starts a submitting process. Here data is collected. Main work is done by
  // event_4().
  event(element)

  // Does a plain form submit using given parameters.
  event_4(systemForm, eventId, widgetId, eventParam)
});

// This class extends the default submitter, and overrides event() to initiate
// an AJAX request and to process result specifically for the overlay mode.
// It expects that aranea-modalbox.js is successfully loaded.
var DefaultAraneaOverlaySubmitter = Class.create(DefaultAraneaSubmitter, {

  event(element)

  event_7(systemForm, eventId, eventTarget, eventParam, eventPrecondition, eventUpdateRegions)

});

// This class extends the default submitter, and overrides event() to initiate
// an AJAX request and to process result specifically for update regions.
var DefaultAraneaAJAXSubmitter = Class.create(DefaultAraneaSubmitter, {

  // Local variable:
  updateRegions: null,

  event(element)

  event_5(systemForm, eventId, widgetId, eventParam, updateRegions)

  // Returns AJAX parameters for the request.
  getAjaxParameters(neededAraTransactionId, ajaxRequestId,
                  updateRegions, neededAraClientStateId)

  // Is called when the request completes successfully.
  onAjaxSuccess(ajaxRequestId, transport)

  // Is called when the request is completed.
  onAjaxComplete(transport)

  // Is called when the request fails.
  onAjaxFailure(transport)

  // Is called when an exception is called during request.
  onAjaxException(request, exception)

});

// The delay after which Ajax.Request onComplete expects all the DOM updates
// to have taken place, in milliseconds.
DefaultAraneaAJAXSubmitter.contentUpdateWaitDelay = 30

// An HTTP transport processor looking for state versioning info.
DefaultAraneaAJAXSubmitter.ResponseHeaderProcessor(transport)

// Region handler that updates transaction id of system form.
AraneaPage.TransactionIdRegionHandler = Class.create({
  process(content)
});

// The Region handler that updates DOM element content.
AraneaPage.DocumentRegionHandler = Class.create({
  process(content)
});

// Does DOM cleanup to avoid memory leaks when content is updated. An
// "invisible" second parameter is used to detect whether the clean-up is
// done with the element or not. If arguments.length = 1 then the input
// element is not changed, only its child-elements will be checked.
// Override this method (Object.extend()) to create your own cleanups.
AraneaPage.DocumentRegionHandler.doDOMCleanup(element)

// Handles messages that came with an AJAX request. These are the messages from
// the MessageContext. You do not need to put "messages" into your update
// regions if you have marked your messages with class "aranea-messages" and
// attribute "arn-msgs-type" with value of message type, because this handler
// knows how to update these messages.
// You may also override certain functionality to customize this class for your
// own needs.
AraneaPage.MessageRegionHandler = Class.create({

  regionClass: '.aranea-messages'

  regionTypeAttribute: 'arn-msgs-type'

  messageSeparator: '<br/>'

  process(content)

  // The input parameter is a map of messages by message type. This method goes
  // through all elements where class="aranea-messages", adds new messages to
  // them or hides messages if they were not in the response data.
  updateRegions(messagesByType)

  // This method adds messages (array) to given region (element).
  showMessageRegion(region, messages)

  // Hides given message region, and changes its content if necessary.
  hideMessageRegion(region)

  // Looks up the element that contains messages in given region.
  // Its content will be updated with new messages.
  findContentElement(region)

  // Looks up the element of the region that is intented for wrapping the messages.
  // It will be hidden, if no messages were in the response data.
  findDisplayElement(region)

  // Formats the messages (array) and returns them as String where messages are
  // separated by messageSeparator. The result is used to update the content of
  // content element (findContentElement(region)).
  buildRegionContent(messages)
});

// Region handler that opens popup windows.
AraneaPage.PopupRegionHandler = Class.create({

  process(content)

  openPopups(popups)

});

// Region handler that forces a reload of the page by submitting the system
// form.
AraneaPage.ReloadRegionHandler = Class.create({
  process(content)
});

// A region handler for form background validation.
AraneaPage.FormBackgroundValidationRegionHandler = Class.create({

  process(content)

  getParentSpan(formelement)

  getLabelSpan(formelement)

  getParentElement(el, tagName, className)

});


// Aranea page object is accessible in two ways: _ap and araneaPage().
var _ap = new AraneaPage();
function araneaPage() {
  return _ap;
}

Since Aranea 1.2.2, the JavaScript API also includes submit callback API, which can be used to provide more control over submitting. It contains methods that can be overridden to change or add some some behaviour.


/**
 * A common callback for API submitters. The callback handles common data manipulation and validation.
 * This callback should be used to add some custom features to submit data or submit response.
 */

// The only method that element-submitters should call. It takes the type of request, the form
// containing the element to be submitted, and the function that does the submit work.
AraneaPage.SubmitCallback.doRequest(type, form, element, eventFn);

// This method is called to return the result of element-submit. Here is a nice place to implement
// custom features depending on the element or request type. Feel free to override.
AraneaPage.SubmitCallback.getRequestResult(type, element, result);

// The method that is called by submitters to store submit data in the form.
AraneaPage.SubmitCallback.prepare(type, form, widgetId, eventId, eventParam)

// Processes the submit data. It calls following methods of this object:
// 1. processSubmitData - to optionally modify the submit data;
// 2. storeSubmitData - to store the submit data in the form (if submit is allowed).
// 3. Adds isSubmitAllowed, beforeSubmit, afterSubmit callbacks to data.
AraneaPage.SubmitCallback.processData(data)

// A callback to optionally modify submit data. Feel free to override.
AraneaPage.SubmitCallback.processSubmitData: function(data) {},

// A callback to store submit data in the form.
AraneaPage.SubmitCallback.storeSubmitData(data)

// A callback that is checked to enable or disable submit. Feel free to override.
isSubmitAllowed(data) {

// A callback that is called before each submit (no matter whether it is AJAX or not).
// This method includes default behaviour. Feel free to override.
AraneaPage.SubmitCallback.beforeSubmit(data)

// A callback that is called after each submit (no matter whether it is AJAX or not).
// This method includes default behaviour. Feel free to override.
AraneaPage.SubmitCallback.afterSubmit(data)
                        

The Aranea 1.2.2 release also introduced automatic file upload JavaScript API. You usually don't have to modify it except for the upload options part, especially the onComplete function. However, here's the entire API.


// This method returns the URL where to submit the file. The element parameter is not used, but
// you can modify the method to use the parameter. 
AraneaPage.getAjaxUploadURL(element);

// This method provides the data as { formField1: value1, formField2: value2, ...} that will be
// added to the form later. This data should be needed in the query.
AraneaPage.getAjaxUploadFormData(systemForm, element)

// This method is called by AraneaPage.init() to initialize file inputs with AJAX uplaod features.
// You can modify here the logic that is used for file input lookup.
AraneaPage.ajaxUploadInit()

// These are the default options that are used with the AJAX file upload. Feel free to override them.
AraneaPage.AjaxUploadOptions = {
        disabled: false,
        autoSubmit: true,
        onChange: function(file, extension, options) {},
        onSubmit: function(file, extension, options) {},
        onComplete: function(file, responseText, failMsg, options) {
                _ap.debug('File upload completed. File="' + file + '"; response="' + responseText + '".');
                if (responseText == 'OK') { // Otherwise responseText == 'FAIL'
                        // Hides the file input and shows a link instead with the file name. Once the link is
                        // clicked, the link will be removed and the file input will be shown again.
                        $(options.target).hide().insert({after:
                                '<a href="#" onclick="$(this).previous().show().next().remove(); return false;">' + file + '</a>'});
                } else {
                        if (!failMsg) {
                                failMsg = 'Uploading file "' + file + '" failed. There could have been a '
                                        + 'problem\nwith the connection or the file was too big. Please try again!';
                        }
                        alert(failMsg);
                }
        }
}