Difference between revisions of "Riena/Navigation"

From Eclipsepedia

Jump to: navigation, search
(new: A note on typeId values)
m (breadcrumbs)
Line 1: Line 1:
[[Riena_Project | Riena Project]] > Riena Navigation
+
{{RienaBox|
 +
[[Riena Project]] ▶ [[Riena Getting started|Getting Started]] ▶ Navigation}}
  
 
{| align="right"
 
{| align="right"

Revision as of 10:30, 22 January 2010

Riena ProjectGetting Started ▶ Navigation

Contents

Navigation Concepts

The motivation for Riena's navigation scheme is that the user-interface flexibility RCP offers (all views are resizable/movable/closable), while great for "power users" like software developers, is intimidating and confusing to the average business user - the flexilibity makes the GUI overly complex.

The goal of Riena's navigation concept is therefore to make it as easy and comfortable as possible for the enterprise user to navigate between different parts of the application. Riena's navigation model consists of a hierarchy of sub-applications, module groups, modules, and sub-modules. These parts are arranged in the application's GUI in a certain pre-set layout.

For example, a Riena application's navigation tree might look like this:

  • sub-application
    • module group
      • module
        • sub-module
        • sub-module
      • module
        • sub-module
    • module group
      • module
        • sub-module
  • sub-application
    • module group
      • module
        • sub-module

A note on typeId values

  • typeIds must be unique over the whole application.
  • typeIds are never concatenated.
  • typeIds are actually keys in a hashmap.
  • You can have only one typeId for a node in the assembly, no matter which type the node has (modulegroup, module, submodule etc.).
  • typeIds must be unique in the same way in the life tree at runtime, with the exception that you can have 2 typeIds with the same value if it has different instance IDs. instanceIds are used to distinguish different nodes with the same typeId but for different data (e.g. different customers displayed using a shared view).

Adapted from a post on the riena-dev mailing list.

Subapplications

The Riena application is made up from possibly multiple subapplications identifyable by the "tabs" in the top area of the application window.

Riena Cient SubApplications.PNG

This example shows a Riena client application with 2 subapplications called 'Navigation' and 'Playground'. The 'Navigation' subapplication is currently selected.

Module Groups, Modules and Submodules

Each subapplication may contain multiple module groups, each module group may contain multiple modules and each module may contain multiple submodules. Submodules may be organized in a hierarchical way, ie a submodul may have zero (probably the most common case), one or more submodules as children.

Riena Client Navigation.PNG The picture to the left shows 3 module groups, the first one containing two modules (named 'Module 1' and 'M2 (not closable)'). The second and third module group each contain one module only (named 'Module 1.2.1' and 'Navigate', resp). The modules named 'M2 (not closable)' Module 1.2.1' cannot be closed by the user indicated by the absence of the 'X' button in the right corner of the module widget.

Note that only the first module within the first module group is expanded, ie its submodules are visible within the navigation tree. This is because that module is currently selected. We can also see that the first submodule (named 'SubModule 1.1') is the parent of another submodule (named 'SubModule 1.1.1') and has a sibling (named 'SubModule 1.2') within the same module. Selecting a subapplication, module group or module automatically activates the first submodule within that context. This implies the there is no work area associated with modules, module groups or subapplications - only submodules can have an associated work area (ie view and optionally a controller)

The Riena navigation therefore can be thought of as a tree where the root is the application node and the leafs are the submodule nodes.

Navigation Node Common Properties

There are some properties that can be defined for all navigation nodes:

  • id - The id is made up from two parts, a type and an instance part. The type part identifies the kind of dialog and is equal for all nodes representing the same kind of dialog, ie they are required to use the same view id and controller class (in case a controller is used). The instance part is used in conjunction with the type part to uniquely identify instances of the same dialog.
    If the customer overview dialog for example has type 'com.acme.customer.overview' the dialog instance for cutomer with the unique customer id '1234567' would be identified by NavigationNodeId('com.acme.customer.overview', '1234567')).
  • children - The children of the node. The type of children depends on the parent:
    • subapplications can only have module group nodes
    • module groups can only have module nodes
    • modules and submodules can only have submodule nodes

Navigation nodes except module group nodes may also have a label and/or an icon.

Creating navigation nodes

There are three different ways to create navigation nodes for Riena applications:

Programmatic Creation 
Using method calls to the Riena API.
Using Extension Points 
Providing the navigation nodes as extensions.
Navigation Node Assemblers

Programmatic Creation

All navigation structures may easily be created programmatically. If the application class configured in extension org.eclipse.core.runtime.applications extends org.eclipse.riena.navigation.ui.application.AbstractApplication the method createModel() would be sent within the start(IApplicationContext context) method. The default implementation just returns an application node without any children:

public static final String DEFAULT_APPLICATION_TYPEID = "application";
 
protected IApplicationNode createModel() {
	NavigationNodeId applicationNodeId = new NavigationNodeId(DEFAULT_APPLICATION_TYPEID);
	IApplicationNode applicationModel = new ApplicationNode(applicationNodeId);
	return applicationModel;
}

All navigation node interfaces extend a common super interface, org.eclipse.riena.navigation.INavigationNode. The implementation classes form a corresponding hierarchy where all non abstract navigation node classes inherit from org.eclipse.riena.navigation.model.NavigationNode:

  Riena INavigationNode Hierarchy.PNG   Riena NavigationNode Hierarchy.PNG


Using Custom createModel() method

Let's now try to create the 'Navigation' subapplication and the first module group from the screenshot above. We start our implementation of the createModel() method with a call to the super implemetantion to receive an IApplicationNode and configure label and application icon:

@Override
protected IApplicationNode createModel() {
	IApplicationNode applicationNode = super.createModel();
	applicationNode.setLabel("Riena Navigation Example");
	applicationNode.setIcon(getIconPath(ExampleIcons.ICON_APPLICATION));
 
// ... everthing described below goes here ...
 
	return applicationNode;

Creating a Subapplication

To be able to create a subapplication we need one more thing: As subapplications in Riena correspond to RCP perspectives we need to contribute the corresponding extension. The view class representing the subapplication perspective in Riena is org.eclipse.riena.navigation.ui.swt.views.SubApplicationView, so we will configure our subapplication as follows (of course the id is not required to be 'subapplication.1'):

<extension point="org.eclipse.ui.perspectives">
   <perspective
         class="org.eclipse.riena.navigation.ui.swt.views.SubApplicationView"
         id="subapplication.1"
         name="subapplication.1">
   </perspective>
</extension>

We can now

  • create a subapplication node with a certain node id
  • register it with the WorkareaManager to assign the perspective we configured
  • finally add it to the application node
// Navigation SubApplication
NavigationNodeId nodeId = new NavigationNodeId("org.eclipse.riena.example.navigation.subapplication");
ISubApplicationNode subApplication = new SubApplicationNode(nodeId, "Navigation");
WorkareaManager.getInstance().registerDefinition(subApplication, "subapplication.1");
applicationNode.addChild(subApplication);

This would bring up the Riena application with one subapplication called 'Navigation' that does not contain any content (please ignore menu and toolbar for now - those are there because I lazily abused the Riena example client and did not remove them): Riena EmptyNavigationSubApp.PNG

Creating a Module Group

Creating a module group is rather easy in as there is no label, icon or work area that could require configuration. As we also do not need a node id here we can simply write:

// create a module group and add it to subapplication
IModuleGroupNode moduleGroup = new ModuleGroupNode(null);
subApplication.addChild(moduleGroup);

This does not change anything in the visualization of our example client as we do not yet have any modules within our module group and empty groups are not displayed. Therefore let's proceed creating our first module...

Creating a Module

// create a module and add it module group
IModuleNode module = new ModuleNode(null, "Module 1");
module.setIcon(getIconPath(ExampleIcons.ICON_APPLICATION));
moduleGroup.addChild(module);

...and add a submodule right away.

Creating Submodules

As the submodule has a presentation in the work area the associated view needs to be configured using the eclipse 'org.eclipse.ui.views' extension point:

<extension point="org.eclipse.ui.views">
   <view allowMultiple="true"
         class="org.eclipse.riena.example.client.views.CustomerDetailSubModuleView"
         id="org.eclipse.riena.example.client.views.CustomerDetailSubModuleView"
         name="org.eclipse.riena.example.client.views.CustomerDetailSubModuleView">
   </view>
</extension>

The association from node to view is done, just like for the subapplication, via the WorkareaManager.

// create "SubModule 1.1" and add it to "Module 1"
nodeId = new NavigationNodeId("org.eclipse.riena.example.customerDetail");
ISubModuleNode subModule = new SubModuleNode(nodeId, "SubModule 1.1");
subModule.setIcon(getIconPath(ExampleIcons.ICON_FILE));
WorkareaManager.getInstance().registerDefinition(subModule, 
     CustomerDetailSubModuleController.class, CustomerDetailSubModuleView.ID);
module.addChild(subModule);

Starting the application would bring up the example appliation like this:

Riena NavigationOneModule.PNG

Note that

  • the first submodule found within the whole application (ie. first submodule within first module within first module group within first subapplication) is automatically selected by default
  • the module cannot be expanded as it has only one submodule (expanding would not make too much sense as with one submodule you do not have too many options to select another submodule)

Final Steps

It should now be easy to add some more submodules

// create "SubModule 1.1.1" and add it to "SubModule 1.1"
nodeId = new NavigationNodeId("org.eclipse.riena.example.customerDetail");
ISubModuleNode subModule2 = new SubModuleNode(nodeId, "SubModule 1.1.1");
WorkareaManager.getInstance().registerDefinition(subModule2, 
     CustomerDetailSubModuleController.class, CustomerDetailSubModuleView.ID);
subModule.addChild(subModule2);
 
// create "SubModule 1.2" and add it to "Module 1"
nodeId = new NavigationNodeId("org.eclipse.riena.example.customerDetail");
subModule = new SubModuleNode(nodeId, "SubModule 1.2");
WorkareaManager.getInstance().registerDefinition(subModule, 
     CustomerDetailSubModuleController.class, CustomerDetailSubModuleView.ID);
module.addChild(subModule);

or create a second module within the same module group. For variation we make the second module non closable.

// create "M2 (not closable)" and add it to the module group
module = new ModuleNode(null, "M2 (not closable)");
module.setIcon(getIconPath(ExampleIcons.ICON_HOMEFOLDER));
module.setClosable(false);
moduleGroup.addChild(module);
 
// create "SubModule 2.1" and add it to "M2 (not closable)"
nodeId = new NavigationNodeId("org.eclipse.riena.example.customerDetail");
subModule = new SubModuleNode(nodeId, "SubModule 2.1");
WorkareaManager.getInstance().registerDefinition(subModule, 
     CustomerDetailSubModuleController.class, CustomerDetailSubModuleView.ID);
module.addChild(subModule);

resulting in our final example application:

Riena NavigationTwoModules.PNG

That's all for the programmatic creation of the navigation tree basics. Now let's have a look how this could be achieved using the declarative way.

Using Extension Points

All navigation structures may also be created in a declarative way using extension points. Just like if using the programmatic approach the application class configured in extension org.eclipse.core.runtime.applications is required to extend org.eclipse.riena.navigation.ui.application.AbstractApplication so the method createModel() will be sent within the start(IApplicationContext context) method. The application class is (except for your view and controller implementations, of course) the only java code that is required to create exactly the same navigation structure as we did before the programmatic way.

Using Custom createModel() method

The implementation of the createModel() method is really only required to set label and icon of the application, we will not add any more code to this method:

@Override
protected IApplicationNode createModel() {
	IApplicationNode applicationNode = super.createModel();
	applicationNode.setLabel("Riena Navigation Example");
	applicationNode.setIcon(getIconPath(ExampleIcons.ICON_APPLICATION));
	return applicationNode;

Creating a Subapplication

To create a subapplication we need to contribute the corresponding perspective extension. This is exactly the same as before:

<extension point="org.eclipse.ui.perspectives">
   <perspective
         class="org.eclipse.riena.navigation.ui.swt.views.SubApplicationView"
         id="subapplication.1"
         name="subapplication.1">
   </perspective>
</extension>

The following xml code shows how to

  • define a subapplication node with a certain node id
  • assign the perspective we configured
  • assign the label for the subapplication handle

Please ignore the assembly element for now, I will explain it later:

<extension point="org.eclipse.riena.navigation.assemblies">
   <assembly
         id="org.eclipse.riena.example.navigation.assembly"
         autostartsequence="100"
         parentTypeId="application">
      <!-- create subapplication -->
      <subapplication 
            label="Navigation"
            typeId="org.eclipse.riena.example.navigation.subapplication"
            view="subapplication.1">
      </subapplication>
   </assembly>
</extension>

This would bring up the same Riena application with one subapplication called 'Navigation' not containing any content like in the first step of the programmatic example: Riena EmptyNavigationSubApp.PNG

Creating a Module Group

Creating a module group just requires adding the modulegroup element to our subapplication element. There are no attributes required but we will assign a name so we can easily identify it within the PDE:

<subapplication 
      label="Navigation"
      typeId="org.eclipse.riena.example.navigation.subapplication"
      view="subapplication.1">
   <!-- create module group and add it subapplication -->
   <modulegroup name="group">
   </modulegroup>
</subapplication>

This does not change anything in the visualization of our example client as we do not yet have any modules within or module group and empty groups are not displayed. Therefore let's again proceed creating our module...

Creating a Module

As you may have already guessed there is also a module element that can be added as a child to the modulegroup element:

<modulegroup name="group">
   <module
         label="Module 1"
         icon="org.eclipse.riena.example.client:/icons/0457_a_a00.png">
   </module>
</modulegroup>

The icon path is prefixed with the bundle name so the generic Riena navigation assembler would be able to find it.

Creating Submodules

As the submodule has a presentation in the work area the associated view needs to be configured using the eclipse 'org.eclipse.ui.views' extension point:

<extension point="org.eclipse.ui.views">
   <view allowMultiple="true"
         class="org.eclipse.riena.example.client.views.CustomerDetailSubModuleView"
         id="org.eclipse.riena.example.client.views.CustomerDetailSubModuleView"
         name="org.eclipse.riena.example.client.views.CustomerDetailSubModuleView">
   </view>
</extension>

The association from node to view and controller is donevia the view and controller attributes where the controller atribute takes the fully qualified name of the controller class.

<module
      label="Module 1"
      icon="org.eclipse.riena.example.client:/icons/0457_a_a00.png">
   <!-- create "SubModule 1.1" and add it to "Module 1" -->
   <submodule
         label="SubModule 1.1"
         icon="org.eclipse.riena.example.client:/icons/file.gif"
         typeId="org.eclipse.riena.example.customerDetail"
         controller="org.eclipse.riena.example.client.controllers.CustomerDetailSubModuleController"
         view="org.eclipse.riena.example.client.views.CustomerDetailSubModuleView">
   </submodule>
</module>

Starting the application would bring up the example appliation looking just like in the programmatic case:

Riena NavigationOneModule.PNG

Final Steps

It should now be easy to add some more submodules

<module
      label="Module 1"
      icon="org.eclipse.riena.example.client:/icons/0457_a_a00.png">
   <!-- create "SubModule 1.1" and add it to "Module 1" -->
   <submodule
         label="SubModule 1.1"
         icon="org.eclipse.riena.example.client:/icons/file.gif"
         typeId="org.eclipse.riena.example.customerDetail"
         controller="org.eclipse.riena.example.client.controllers.CustomerDetailSubModuleController"
         view="org.eclipse.riena.example.client.views.CustomerDetailSubModuleView">
      <!-- create "SubModule 1.1.1" and add it to "SubModule 1.1" -->
      <submodule
            label="SubModule 1.1.1"
            icon="org.eclipse.riena.example.client:/icons/file.gif"
            typeId="org.eclipse.riena.example.customerDetail"
            controller="org.eclipse.riena.example.client.controllers.CustomerDetailSubModuleController"
            view="org.eclipse.riena.example.client.views.CustomerDetailSubModuleView">
      </submodule>
   </submodule>
   <!-- create "SubModule 1.2" and add it to "Module 1" -->
   <submodule
         label="SubModule 1.2"
         icon="org.eclipse.riena.example.client:/icons/file.gif"
         typeId="org.eclipse.riena.example.customerDetail"
         controller="org.eclipse.riena.example.client.controllers.CustomerDetailSubModuleController"
         view="org.eclipse.riena.example.client.views.CustomerDetailSubModuleView">
   </submodule>
</module>

or create a second module within the same module group. Again the second module will not be closable.

// create "M2 (not closable)" and add it to the module group
// create "SubModule 2.1" and add it to "M2 (not closable)"
<modulegroup name="group">
   <module
         label="Module 1"
         icon="org.eclipse.riena.example.client:/icons/0457_a_a00.png">
   <!-- ... code from above... -->
   </module>
   <module
         label="M2 (not closable)"
         icon="org.eclipse.riena.example.client:/icons/HomeFolder.gif"
         unclosable="true">
      <!-- create "SubModule 2.1" and add it to "M2 (not closable)" -->
      <submodule
            label="SubModule 2.1"
            icon="org.eclipse.riena.example.client:/icons/file.gif"
            typeId="org.eclipse.riena.example.customerDetail"
            controller="org.eclipse.riena.example.client.controllers.CustomerDetailSubModuleController"
            view="org.eclipse.riena.example.client.views.CustomerDetailSubModuleView">
      </submodule>
   </module>
</modulegroup>

resulting in the same example application as the programmatic way:

Riena NavigationTwoModules.PNG

This concludes the extension point section of the navigation tree basics.

Navigation Node Assemblers

Assembler Basics

As you have seen in the previous section the extension point contains assembly elements. These elements decribe how a certain part of the Riena navigation tree is going to be 'assembled'. Basically there are two options

  • Custom Assemblers provide the user with the option to create parts of the navigation tree structure in a programmatic way without using any additional xml. A custom assembler would create navigation nodes just the way we did within the createModel() method of the programmatic example. A custom assembler MUST implement the interface org.eclipse.riena.navigation.INavigationAssembler
  • Generic Assemblers are prepared to use the element structure provided within the assembly element. Generic Assemblers MUST implement the org.eclipse.riena.navigation.IGenericNavigationAssembler interface. There is a default implementation of a generic assembler, org.eclipse.riena.navigation.model.GenericNavigationAssembler, that will be used if no assembler is specified.

Navigation Assembler Variables

When defining the content of any string attribute like eg labels, instance ids etc. variables may be used and are automatically replaced by their current value. There are some predefined variables allowing access to

  • navigation node id ${riena.navigation.nodeid}
  • navigation node parameter if supplied by the user ${riena.navigation.parameter}
  • navigation node context ${riena.navigation.context}

If you eg have a module within your application that would provide one or more submodules to edit some cutomer data and navigation nodes for different customers are distinguished by using the customer number as the navigation node instance id, the module definition could look like this:

<module
   label="Customer ${riena.navigation.nodeid:instanceId}"
   typeId="com.acme.customer.edit.module">

The paramenter after the colon is interpreted as a property of the variable ${riena.navigation.nodeid} instance at the time the module node is built. Nested and mapped properties may be accessed using the appropriate string (see the Apache Commons BeanUtils page for more information).

If your application eg would open the customer edit module only on an existing customer object (eg found in a search dialog), you might want provide the customer within the navigation argument when navigating to the customer edit module. If the customer had a name attribute, we could display the name instead of the customer number in the module label like this:

<module
   label="Customer ${riena.navigation.parameter:name}"
   typeId="com.acme.customer.edit.module">

The navigation node context is not the context accessible via NavigationNode.getContext(String) as this would not make too much sense - that context would always be empty as it can only be populated after node creation. The context in question here is the creation context of the node - simply a map that is created upon creation of the top level node created by this navigation nodes assembler. A copy of the context is inherited to the children thus modifications to the context made in different branches are independent from each other. The navigation node creation context was introduced to enable a more advanced node creation concept: loops.

Loops

Imagine the customer from the previous section has several accounts and you would like to display each account within an individual submodule. This cannot be achieved by the static configuration means we have seen so far, we need something more dynamic here:

<module
      label="Customer ${riena.navigation.parameter:name}"
      typeId="com.acme.customer.edit.module">
   <submodule 
      typeId="com.acme.customer.edit.base"
      controller="..."
      view="..."/>
   <foreach element="account" in="${riena.navigation.parameter:accounts}">
      <submodule
         label="Account ${riena.navigation.nodecontext:account.accountNo}"
         typeId="com.acme.customer.edit.account"
         instanceId="${riena.navigation.nodecontext:account.accountNo}"
         controller="..."
         view="..."/>
   </foreach>
</module>

This assumes the customer object we provided as a navigation parameter has an accounts property that returns a list or an array of account objects, each having an accountNo property. For the sake of simplicity it also assumes that the account numbers uniquely identify an account.

But what does the 'foreach' declaration above exactly do? Well, it simply iterates all accounts of the customer object provided as a navigation parameter by the user and

  1. creates a new context derived from the module context for each account and stores the current loop object under the key 'account' in it.
  2. creates a submodule for each account using the unique account number as navigation node instance id (and also showing it in the submodule title label)

Navigation Elements Reference

Consult Navigation Elements Reference for a detailed break-down of each navigation element's attributes and their meaning.