DevX HomePage

Build an AJAX Content Management System with Visual WebGUI: Adding Menu Components

Menus are basic components of any application. A well-designed menuing framework should be flexible, customizable for both look-and-feel and functionality, and easy to integrate into applications.

he MiniCMS system begun in the first article in this series needs to have flexible menu components. The menu system should be data driven—in this example, menus are created from a XML-formatted configuration file that defines each menu entry, specifies a name and optional image for each menu, and supports submenus to any number of levels.

A flexible menuing framework should have the following characteristics:

This article shows how to implement a menu framework that has these desirable characteristics using the Model-View-Controller (MVC) pattern. That pattern lets you separate the actual menu definition structure (representing the Model), from the concrete visual implementation containing user interface elements that represent the View, and from the menu Controller, which creates the connection between the Model and the View. Using this approach, developers can implement whatever View is required to accommodate a custom look-and-feel, without changing the menu definition or the controller.

This article walks you through the menuing framework, describing the interfaces for the Model, View, and Controller. A follow-up article will delve deeply into defining and programming a complete custom working menu system.

Implementing the Model: Menu Definition

The various properties and a single method of a menu item are specified through the IWTMenuEntryDefinition interface . This interface defines all the elements needed to create a menu item, but does not define the menu item itself.

Table 1 shows the properties and methods:

Table 1. IWTmenuEntryDefinition Interface: The table shows the properties and methods exposed by the IWTmenuEntryDefinition interface.
Property Description
string Name { get; } The name or label displayed in the menu.
string IconName {get;} File name or resource name for the menu icon. This can be the name of a JPG, GIF, or PNG file located in Icons or Images resource folders as configured in your Visual WebGUI project (see Create and Configure the Project: Directories), or in the assembly that contains the module the menu item launches.
ResourceHandle Icon {get;} Returns the actual icon for menu as a ResourceHandle, which can be assigned to any Visual WebGUI object that exposes an Image property.
ExecutionMode ExecutionMode {get;} Specifies the execution mode where the menu item is available. Can be Web (for native Visual WebGUI applications) or Client (for desktop Visual WebGUI applications).
string ID {get;} Lets you specify an optional ID for the menu entry.
bool HasProperties {get;} Determines whether the menu entry definition contains additional properties. See Properties below for more information.
Dictionary<string, string> Properties {get;} Properties are key/value pairs of type string that can store additional data for a menu item. For example, a menu entry that starts a Web Browser module, might have a property namedURL, associated with the URL the browser should navigate to when loaded.
bool HasRoles {get;} Returns true if user Groups or Roles are specified for the menu. This lets the menu system integrate easily into applications that work with user groups or roles-based authentication, such as ASP.NET membership. Note, however, that the menu system is not bound to any particular security framework, and does not enforce any security restrictions. Instead, it simply provides hooks that developers can use to integrate the menus with any security or authentication framework
List<string> Roles { get; } List of roles or user groups specified for a menu entry.
List<IWTMenuEntryDefinition> ChildMenus { get; } Contains a list of menu entry definitions for child menus.
bool HasChildMenus { get; } This value is true if the menu entry contains child submenu definitions.
string ModuleClassName { get; set; } Specifies the class name of the module launched by the menu.
Type ModuleClassType { get; } Returns a type used to create the module launched by the menu. This is created from the ModuleClassName using Reflection.
Method Description
bool PropertyExists(string propName); Returns true when the specified property exists in the menu entry properties list.

To associate menu items with specific modules, the framework uses the class name specified in ModuleClassName to instantiate the corresponding object at runtime, through Reflection. The supplied ModuleClassName must comply with the following naming conventions:

Type= "SampleModules.TestTreeHostModule, MiniCMS"
Type= "MiniCMS.SampleModules.TestTreeHostModule, MiniCMS"

The first form is preferred for simplicity.

The framework contains a Webtools.Base.Helpers.WTHelper class that provides static methods to create types and objects through reflection either from the class name or directly from the AppSettings keys

 
Figure 1. 1 Menu Definition Interface and Classes: Together, these interfaces and classes form the basic functionality of the menuing system.

Figure 1 shows the interface and classes used by the menuing system. The WTMenuEntryDefinitionBase class implements the base functionality for defining menu entries, and WTMenuEntryDefinitionXml extends that by supporting menu definitions based on entries specified in an XML-formatted menu configuration file.

WTMenuEntryDefinitionBase is an abstract class that contains concrete implementations for those members of the IWTMenuEntryDefinition interface that are unlikely to change in derived classes. One such member is the Icon property, which must return a ResourceHandle for the icon, either by searching for an image embedded as a resource in the assembly that contains the module launched by the menu, or in the Icons or Images resource folders as configured in the Visual WebGui project (see the earlier article Create and Configure the Project: Directories). Here's the Icon property implementation:

public virtual ResourceHandle Icon {
get {
ResourceHandle icon = null;
string iconName = IconName;
// if there is icon name
if (!string.IsNullOrEmpty(iconName)) {
// configure it as resource name
// (e.g from path\file name format to dot format)
// filename Images\Users.png will became Images.Users.png
string iconResourceName =
WTHelper.FileNameAsEmbeddedResourceResource(iconName);
// try to find the type of module launched by menu
// if no module is specified, look for resource icon
// in current assembly
Type moduleClass = ModuleClassType;
if (moduleClass == null)
moduleClass = this.GetType();
// if image exists as embeded resource, return handle to it
if (WTHelper.FileExistsAsEmbeddedResource(moduleClass, iconName))
icon = new AssemblyResourceHandle(moduleClass,
iconResourceName);
// otherwise try to look for it either in Icons resource filder
else if (WTHelper.IconExists(iconName))
icon = new IconResourceHandle(iconResourceName);
// or in Images resource filder
else if (WTHelper.ImageExists(iconName))
icon = new ImageResourceHandle(iconResourceName);
}
// if the icon was not found, use default menu icon
if (icon == null)
icon = WTMenuEntryDefinitionBase.DefaultMenuIcon;
return icon;
}
}

The DefaultMenuIcon method (see Listing 1) called toward the end of the preceding method returns a default Icon as a resource handle. This strategy provides a standard default icon for all menu items without a custom icon specification.

The rest of the properties defined in WTMenuEntryDefinitionBase are basic field accessors.




Menu Configuration File

You specify menu definitions in an XML file that may be either embedded into assembly as a resource or located in a subfolder of the application.

Here's a sample menu definition file:

<MainMenu DefaultAccesMode="Allow" SecurityHandlerClass="" >
<MenuItem Name="Main Dashboard"
Icon="file.png"
Type= "SampleModules.TestTreeHostModule, MiniCMS">
<MenuItem Name="Graphic Dashboard"
Icon="User_48.png"
RunMode="Web"
Type= "SampleModules.GraphicDashboard, MiniCMS" Tag=""/>
<MenuItem Name="Large Graphic Dashboard"
Icon="User_48.png"
ID="4195E1D3-CA05-4cb0-9F46-7EA0B09FE410"
Type= "SampleModules.SampleLargeGraphicDashboard, MiniCMS"/>
</MenuItem>
<MenuItem Name="Placeholder"
IsPlaceholder="1"
CustomMenuCreator="DocumentsMenu, MiniCMS.DocMenuCreator"
ID="4195E1D3-CA05-4cb0-9F46-7EA0B09FE410"
Tag=""/>
<MenuItem Name="Favorites"
Icon="flash.png"
CustomMenuCreator="FavoritesMenu, MiniCMS.DocMenuCreator"
ID="4195E1D3-CA05-4cb0-9F46-7EA0B09FE410"
AccessCode="10"
Tag=""/>
</MainMenu>

Note that the menu definition file has a root element named MainMenu, and that the menu items are sub-elements of type MenuItem. Most MenuItem attributes map to properties of IWTmenuEntryDefinition. Table 2 lists all the possible MenuItem element attributes:

Table 2. MenuItem Element Attributes: The table shows all the possible MenuItem attributes.
Attribute Description
Name The name of the menu, displayed in the menu system. This is the only mandatory attribute.
ID Sets an ID for the menu. If omitted, the system generates a random ID (a new GUID) when the menu item is instantiated.
Icon The icon used for this menu entry. The system first searches for icons embedded as resources in the assembly of the class to be started by module, then in the Icons or Images resource folders as configured in the Visual WebGUI project.
Type Specify the class that defines the module to be launched by menu selection. The class must implement either the IWTModule interface (see this earlier article), or the IWTRunModule interface.
Roles Specifies a comma-delimited list of roles or groups with sufficient rights to view/use this menu item, such as ASP.NET membership roles, or any other custom security/authentication mechanism.
RunModePossible values: Web Desktop Specifies whether a menu item is available in Web mode or Desktop mode. Visual WebGui application offers dual mode (Web and Desktop) functionality from a single code base. Currently, Desktop mode is in early development phases, but this setting aims to offer the possibility of having specific modules available only in a specific run mode. For example, you might implement a module to launch external desktop applications installed on local computer in Desktop mode.
IsPlaceholder Specifies that this MenuItem acts as placeholder. Such MenuItems must include the CustomMenuCreator attribute, which specifies a class that will create menu definition entries dynamically at runtime to replace the MenuItem specified with IsPlaceholder.
CustomMenuCreator This class must implement the interface IWTCustomMenuCreator. It creates a menu definition structure dynamically, which will either replace a menu item specified as IsPlaceholder, or if it does not include IsPlaceholder, will be attached as one or more child menus.

Table 3 shows the attributes of the MainMenu element:

Table 3. MainMenu Element Attributes: The MainMenu element may define these attributes.
Attribute Description
DefaultAccessMode Specifies the access mode for menu items, if no other security rules apply. Possible values are: Allow, and Deny. If missing, the system defaults to Allow.
SecurityHandlerClass This attribute's value is the name of a class that implements the ISecurityHander interface. If present, this component is used to determine the access rights for each menu entry when that menu entry definition is created.

The helper class WTMenuRootDefinitionXml handles the MainMenu element, extracting the values for the DefaultAccessMode and SecurityHandlerClass attributes.

MenuItem elements are handled by WTMenuEntryDefinitionXml, which extends WTMenuEntryDefinitionBase. WTMenuEntryDefinitionXml defines a constructor that accepts an XmlElement parameter containing a MenuItem definition. That sets the internal XmlElement field, loads roles and properties in the corresponding collection, and initializes the ModuleClassName property, as shown below:

public WTMenuEntryDefinitionXml(XmlElement nodeSetting){
m_nodeSetting = nodeSetting;
InitRoles();
InitProperties();
ModuleClassName = m_nodeSetting.GetAttribute("Type");
}

The methods InitRoles and InitProperties parse the corresponding attributes and load the appropriate collections. InitProperties creates a property for every attribute.

private void InitRoles() {
string roles = m_nodeSetting.GetAttribute(“Roles");
if (!string.IsNullOrEmpty(roles)){
// extract all roles in an array
string[] rolesAry = roles.Split(',');
for (int i = 0; i < rolesAry.Length; i++)
// trim spaces and add to Roles collection
rolesAry[i] = rolesAry[i].Trim();
// add all trimmed roles
Roles.AddRange(rolesAry);
}
}
private void InitProperties(){
// get reference to all attributes of XmlElement
XmlAttributeCollection attributes = m_nodeSetting.Attributes;
m_properties = new Dictionary<string,string>();
// and add them to a Dictionary<,>
foreach (XmlAttribute attrib in attributes)
m_properties.Add(attrib.Name, attrib.Value.ToString());
}

The properties code simply returns the value from the corresponding attribute:

public override string Name {
get { return m_nodeSetting.GetAttribute(“Name"); }
}
public override string ID{
get {
string id = m_nodeSetting.GetAttribute(“ID");
if (string.IsNullOrEmpty(id))
id = base.ID;
return id;
}
}

The code that creates the CustomMenuCreator component is shown below. It uses Reflection and helper methods from class WTHelper to create the MenuCreator class instance.

private string m_customCreatorClassname = string.Empty;
private IWTCustomMenuCreator m_customCreator = null;
//...
public IWTCustomMenuCreator CustomMenuCreator {
get {
// if menucreator is null and menucreator class name is empty
if (m_customCreator == null &&
m_customCreatorClassname == string.Empty) {
// read CustomMenuCreator attribute
IWTCustomMenuCreator custCreator = null;
m_customCreatorClassname =
m_nodeSetting.GetAttribute(“CustomMenuCreator");
if (m_customCreatorClassname!=string.Empty)
// try to create the instance of CustomMenuCreator
// if class name is not specified correctly
// or not class definition not available
// this can fail, so we wrap in a try ... catch statement
try {
custCreator = (IWTCustomMenuCreator)WTHelper.
CreateObjectByReflectionFromClassName(
m_customCreatorClassname);
}
catch (Exception ex) {
// if fail, log the error
ErrorHandler.LogError(string.Format(
"Error creating custom menu creator for class {0}",
m_customCreatorClassname), ex);
custCreator = null;
}
m_customCreator = custCreator;
}
return m_customCreator;
}
}

Now, to put things together, you need to create a complete data structure for the menu definition, which will then create the actual menu. Creating the menu is the job of the menu controller, implemented by the WTMenuDefinitionController class. WTMenuDefinitionController exposes the static method InitMenuStructure, which receives the name of a menu configuration file as a parameter, and attempts to build the menu structure from the information in that file.

The configuration file gets loaded into an XmlDocument. It passes the root node (the DocumentElement), to the LoadMenuConfiguration method that builds the menu definition structure. The returned value is a generic List<IWTMenuEntryDefinition>, which contains the actual menu definition structure used to build the usercontrol menu component. LoadMenuConfiguration receives two more parameters, extracted from the attributes of the MainMenu root node, by the helper class WTMenuRootDefinitionXml: the default access mode for menu entries if no other security restriction applies, and the name of the module implementing security access rights for menu items.

Listing 2 shows the LoadMenuConfiguration method.




Defining the View: Interfaces for the Visual Portion of Menus

The visual parts of the menu system also use interfaces, which lets developers implement custom user interfaces for the user control representing the menu. The two interfaces that define the menu are IWTMenu and IWTMenuItem, as shown in Figure 2.

 
Figure 2. Visual Menu Interfaces: These interfaces define the visual part of the menu. IWTMenu defines the interface for a menu object, while IWTMenuItem defines the interface implemented by menu items.

IWTMenuItem exposes properties that define a menu item, described in Table 4.

Table 4. IWTMenu Properties: The IWTMenu interface defines these properties.
Property Description
bool Enabled {get; set;} If enabled, this menu item can be selected.
ResourceHandle Icon {get; set;} Holds the ResourceHandle of the menu icon to display.
string ID {get;} Returns the ID for the menu item.
IWTMenuEntryDefinition MenuEntryDefinition {get; set;} Reference to the IWTMenuEntryDefinition from which this menu item was created.
IWTMenu Parent {get;} Reference to parent menu (IWTMenu).
IWTModule LinkedModule {get; set;} Reference to the module launched by this menu item (IWTModule).

IWTMenu defines the interface to implement the menu. Its members are described in Table 5.

Table 5. IWTMenu Interface Members: The interface defines one property, two methods, and two events.
Property Description
IWTModule Host {get; set;} Holds a reference to the IWTModule instance into which this menu is loaded.
Methods Description
void InitMenu( List<IWTMenuEntryDefinition> menuDefinition); Creates the menu based on the menu definition.
bool DetachModule(IWTModule module) Detaches the module from the Menu Item that launched the module.
Event Description
event WTMenuEventHandler MenuItemClick This event fires when a user clicks a menu item.
event WTMenuEventHandler MenuItemAdded This event fires when a new item gets added to the menu.

Defining the Controller

At this point, you've seen the menu item definition classes representing the menu Model, and the interfaces for visual part of the menu system—Menus and Menu Items—that represent the View. The final component that joins everything together into a functional module is the Controller.

The controller is defined through the interface IWTMenuController, as shown in Figure 3.

 
Figure 3. Menu Controller Interface: The IWTMenuController interface defines a controller, which joins the Model and the View. You can see a list and description of the IWTMenuController interface members in Table 6.

Table 6. IWTMenu Interface Members: The table lists and describes the properties, methods, and events for the IWTMenu interface.
Property Description
IWTMenu Menu {get; set;} Holds a reference to the IWTMenu handled by the controller.
string MenuDefinitionFile {get; set;} Name of the menu configuration file used to build the menu. If this is specified, the controller builds the MenuEntryDefinition data structure and passes it to IWTMenu to create the menu's visual components.
List<IWTMenuEntryDefinition> MenuDefinition {get; set;} The controller can receive the menu definition data structure directly (which was created by some external component). In this case it uses this structure directly, and does not need the MenuDefinitionFile property.
IWTWorkplace TargetPanel {get; set;} The workplace (see this this earlier article) into which to load the module launched by a menu item.
Method Description
void ActivateMenu(); Call this method to activate the menu after setting a properties Menu, TargetPanel, and either a MenuDefinitionFile or a MenuDefinition.
Event Description
event WTMenuEventHandler MenuItemClick This event fires when a user clicks a menu item.
event WTMenuCancelEventHandler BeforeLaunchModule; This event fires just before launching the module associated with a menu item. You can use it to cancel the module launch.

Here's the ActivateMenu code:

public void ActivateMenu()
{
// ...
// if there is no MenuDefinition property set,
// and if menu configuration file exists
// create menu definition structure
if (m_menuDef==null && WTHelper.ConfigFileExists(
m_configFile))
m_menuDef = WTMenuDefinitionController.
InitMenuConfiguration(m_configFile);
// and ask menu to build itself
if (m_menuDef!=null)
m_menu.InitMenu(m_menuDef);
}

The set accessor for the Menu property unbinds the controller from the MenuItemClick event of a previous menu object (if one was set), and then binds to the MenuItemClick event, as shown below:

public IWTMenu Menu
{
get { return m_menu; }
set {
// if there was a previous menu assigned to controller
// unbind from its MenuItemClick event
if (m_menu!=value && m_menu!=null)
m_menu.MenuItemClick -= new
WTMenuEventHandler(HandlerMenuItemClick);
// and bind to MenuItemClick event fired by the menu
m_menu = value;
if (m_menu!=null)
m_menu.MenuItemClick += new
WTMenuEventHandler(HandlerMenuItemClick);
}
}

The MenuItemClick handler code checks whether the selected MenuItem is associated with a linked module (if it is, then the module has already been loaded, and just needs to be reactivated. If there is an associated module, the event fires the BeforeLaunchModule, which gives developers an opportunity to write code to cancel the module load, and then launches the module (unless canceled).

If the module was already launched, it is just reactivated, and menuItem.LinkedModule.Refresh is called, which allows the module to refresh its status.

private void HandlerMenuItemClick(object sender, WTMenuEventArgs e)
{
WTMenuCancelEventArgs ce = new WTMenuCancelEventArgs(e.MenuItem);
// if no linked module
if (e.MenuItem.LinkedModule == null){
OnBeforeLaunchModule(sender, ce);
if (!ce.Cancel)
LaunchModule(e.MenuItem);
}
else
ShowModule(e.MenuItem);
OnMenuItemClick(sender, e);
}
private void ShowModule(IWTMenuItem menuItem)
{
m_host.ShowModule(menuItem.LinkedModule);
// if linked module exists, just refresh it
menuItem.LinkedModule.Refresh(
new Webtools.Base.Events.WTCancelGenericEventArgs());
}

The LaunchModule method is the workhorse of the controller. It instantiates a module object and checks whether the module needs be launched in the same workplace where the menu itself is loaded. If so, it assigns the menu host as the parent module for the launched module; otherwise, the parent is set to null. Finally the controller calls the appropriate StartModule method. The module can implement either IWTModule, which provides a StartModule() method with no parameters, or IWTRunModule, which defines a StartModule method to receive a IWTMenuEntryDefinition parameter. The latter allows modules to get access to all the attributes defined in the menu configuration file. The important parts of the code are shown below (some code was removed for clarity):

private void LaunchModule(IWTMenuItem menuItem)
{
if (menuItem != null && menuItem.MenuEntryDefinition != null)
{
IWTMenu parentMenu = menuItem.Parent;
// create module to be launched
Type type = menuItem.MenuEntryDefinition.ModuleClassType;
if (type != null)
{
object objModule =
WTHelper.CreateObjectByReflectionFromType(type);
IWTModule module = objModule as IWTModule;
if (module != null) {
// check if menu is hosted on a IWTModule,
// which is loaded into the same Workplace where
// the menu module should be loaded
// if so, set the parent module of the menu module
// as the host IWTModule for the menu
// This letS the module pass the control back to
// the menu when it is closed
// If menu is loaded into different workplace,
// pass null as parentModule
IWTModule parentModule = menuItem.Parent.Host;
if (!m_targetPanel.CheckModuleExists(parentModule))
parentModule = null;
module = m_host.LoadModule(module, parentModule);
// attach the started module to menu item and bind
// to its Close event
menuItem.LinkedModule = module;
module.Close += new Webtools.Base.Events.
WTCancelGenericEventHandler(Module_Close);
}
// if the module to be started implements IWTRunModule
// call StartModule method with MenuEntryDefinition parameter,
// which lets the module receive all extra properties defined
// in the MenuItem definition entry from the configuration file
IWTRunModule runModule = module as IWTRunModule;
if (runModule != null)
runModule.StartModule(menuItem.MenuEntryDefinition);
else
module.StartModule();
}
else
// if no type, hide all visible modules
m_targetPanel.HideAllModules();
}
}

The handler for Module_close simply detaches the module from the related menu item:

private void Module_Close(object sender,
Webtools.Base.Events.WTCancelGenericEventArgs e)
{
IWTModule module = sender as IWTModule;
if (module != null)
m_menu.DetachModule(module);
}




Linking View to Model through the Controller

With the menu definition data structure (the model), the MenuController (the controller), and the interfaces defining the menu objects (the view) all the components are in place to implement the Model-View-Controller pattern on the menu system. You just need to create a concrete implementation of the user control by implementing the IWTMenu and IWTMenuItem interfaces.

Although there's not room to build an actual implementation in this article, to whet your appetite, the next article creates a sample set of implementations for a menu control, using the interfaces, including a WTTreeMenu (a menu based on a TreeView), a WTNavTreeMenu (a combination of navigation tabs on first menu level and tree menus starting at the second level) and a Control Panel-like menu. To switch from one menu to another in the sample application you can simply replace one menu component with another one and bind it to the controller’s Menu property.

The downloadable sample application shown in Figure 4 uses the WTNavTreeMenu component, while the one in Figure 5 shows the same application using a WTTreeMenu component.

 
Figure 4. Tab-and-Tree Menu: Here's a screenshot of the sample application using a WTNavTreeMenu component.
 
Figure 5. Tree Menu: Here's the sample application using a simple TreeView-based menu.

 
Figure 6. Increase Private Version Setting to Invalidate Cache: After any change to content files that might be cached, increase the Private Version setting to invalidate the cache and force a re-read of content objects from the server.
Author's Note: You can switch the sample application from one menu type to another. To use the menu shown in Figure 4, set SampleNavTreeMenu.wgx as the redirect page in Index.htm or as the start page in Project Properties → Web → Start Action → Specific Page. To use the menu shown in Figure 5, set SampleTreeMenu.wgx as the redirect page in Index.htm or as the start page in the Project properties.

If you edit the menu configuration file, located in MiniCMS\Resources\Config\ MiniCMSMenu.xml, or add or change images or icons, you will need to increase the Private Version setting in web.config or in the Project properties → General tab, as shown in Figure 6. This is necessary to invalidate the cache and reload any content files from the server (such as new configurations, images, icons, reports, etc.)

Feel free to experiment with the sample menuing system. The next article approaches menus from the standpoint of building custom menus that work with the menuing framework you've seen described here.

Bogdan Zamfir has a Master's degree in Computer Science and has been working as an independent consultant and software developer since 1993. He develops both web and desktop applications, using various languages and platforms including C#, VB.NET, Visual FoxPro, Microsoft Access, Visual Basic, and C/C++. Visit his web site for more information.


DevX is a division of Internet.com.
© Copyright 2010 Internet.com. All Rights Reserved. Legal Notices