devxlogo

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

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

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:

  • The system should handle menu item selection automatically and (when appropriate) launch specific modules, without developers having to write any code. At the same time, developers should be able to override this default behavior and be able to fire a custom event handler for any menu item.
  • The menu system should integrate with the security module to control access to menu items and related modules.
  • In addition to populating menus in a data-driven manner, developers should also be able to create menus dynamically, for example, to create Favorites, or Most Recently Used (MRU) submenus at runtime. For Content Management Systems (CMSs) the framework needs to be able to dynamically create part of the menu structure based on content added by users, and plug that into the main menu.
  • The menu system should allow developers to specify entry points where dynamic menus will get attached as submenus (such as a Favorites menu), and also define placeholders?which can get replaced at runtime by dynamic menus.
  • To support a wide diversity of user interfaces, the menu system should be visually flexible, to allow a custom look-and-feel that fits with overall application design.
  • The menu system should be defined using interfaces to prevent binding the implementation to specific existing controls and allow future enhancements or implementation changes.

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.
PropertyDescription
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 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 Roles { get; }List of roles or user groups specified for a menu entry.
List 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.
MethodDescription
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:

  • The default namespace should match the assembly name where the class is defined
  • The class name can be fully qualified with the default namespace, or can have default namespace stripped. At runtime, the framework will check if default namespace is part of class name and if not, will create the fully qualified class name by joining the assembly name with the class name. For example, the following entries are equivalent:
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.

Table 4. IWTMenu Properties: The IWTMenu interface defines these properties.
PropertyDescription
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.
PropertyDescription
IWTModule Host {get; set;}Holds a reference to the IWTModule instance into which this menu is loaded.
MethodsDescription
void InitMenu( List menuDefinition);Creates the menu based on the menu definition.
bool DetachModule(IWTModule module)Detaches the module from the Menu Item that launched the module.
EventDescription
event WTMenuEventHandler MenuItemClickThis event fires when a user clicks a menu item.
event WTMenuEventHandler MenuItemAddedThis 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.
PropertyDescription
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 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.
MethodDescription
void ActivateMenu();Call this method to activate the menu after setting a properties Menu, TargetPanel, and either a MenuDefinitionFile or a MenuDefinition.
EventDescription
event WTMenuEventHandler MenuItemClickThis 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 MiniCMSResourcesConfig 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.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist