The above menu bar and the one at the bottom of the page are both dynamically generated using a short html++ program. No pages need recoding as menu options are added or changed.



Magazine Ads

html++ CGI Class Library
The menugen.cpp demo application



This document contains the actual C++ source code to the html++ application used to dynamically generate the menus and JavaScript used on this web site. For convenience, comments appear in green, and html++ objects and functions appear in bold print.

The purpose of this program is to dynamically construct menus at runtime, moving the details of menu options and changes away from the documents, dramatically simplifying their design and maintenance.

Though the amount of the code may seem larger than expected (just over 500 lines), it's actually quite short -- less than 260 lines excluding comments and the embedded JavaScript. In fact, just over 100 lines are all that's needed for the core logic, which is replicated in the GenerateTextMenu() and GenerateJavaCode() functions. The size of the executable is a miserly 143k (BSDI 3.0 Unix, Intel platform). The small size of html++ applications is in stark contrast to Perl scripts, for which the Perl interpreter alone requires over 500k.

This application is designed to be executed by a web server using regular server-side include processing (in this case, Apache on BSDI Unix). Since the output of this program will be included in another page, the main() routine uses the generic htmlContainer object to store and output the contents rather than the usual htmlPage object, so as not to include extra <HTML>, <HEAD> and <BODY> tags.

The goal of the web site is to have a consistent look and feel for the menus across all pages. We wanted to include the use of JavaScript to enhance the appearance of the menu buttons, so that they change state whenever the user moves the mouse over them. We also wanted a text version of the menu to appear at the bottom of each document.

Accomplishing these goals entails three pieces:

  • JavaScript code to define the menu button bitmaps, and to handle the onMouseOver and onMouseOut events for drawing the "selected" and "unselected" menu buttons.
  • HTML code to place the inital unselected menu buttons on the page, referencing the image file array defined in the JavaScript code and naming the HREF's so that the JavaScript code will know where to place it's images.
  • HTML code to place the text version of the menu at the bottom of pages.
Doing all this without the aid of html++ would require duplicating the JavaScript and HTML code in each of the documents, while requiring that each page be manually customized to reflect the menu buttons to be displayed. This would prove extermely cumbersome as the web site grows -- every single document would have to be modified if the menus changed in the slightest.

By using html++ to centrally define all the menus and options, only three statements need to be used in each document, and they won't be affected if the menu structure changes:

In the page header, to generate the JavaScript:

<!--exec cmd="/cgi-bin/menugen java" -->
In the page body, to generate the HTML code for the menu images and hyperlinks:
<!--exec cmd="/cgi-bin/menugen" -->
At the bottom of the page, to generate the text version of the menu:
<!--exec cmd="/cgi-bin/menugen text_menu" -->
Whenver changes to the menus are required, such as when documents are added or removed, or when the style of the buttons is changed, the entire web site can be updated at once by simply updating Menu_Array with the new documents and recompiling this application.



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <htmlpp.h>     // the html++ include file


#define SUBDIRECTORY_SEPARATOR	'/'         //  Change for Unix or Windows
#define IMAGES_DIRECTORY	"/images"   //  Location of image files
#define INDEX_FILE		"index.html"


//  This image is placed to the right
//  of each button for a shadow effect.
#define RIGHT_SHADOW		"shadow_rt.gif"
#define RIGHT_SHADOW_HILITE	"shadow_rt_arrow.gif"


//  There are two states for two categories for each menu button:
//  group in (highlighted) and out (normal), and non-group in and out.
//  Documents in the same directory are considered to be in a group.
#define _IN			"_in.gif"
#define _OUT			"_out.gif"
#define _IN_GROUP		"_in_group.gif"
#define _OUT_GROUP		"_out_group.gif"


typedef struct
{
   char * image_base_name ;  //  Filename less the in/out suffix
   char * description ;      //  The alt text for the image
   char * url ;              //  The URL to load when the menu button is selected
} MenuItem, * PMenuItem ;


//  An array of all the menu buttons for all the pages in the site.
//  When a page is requested by a browser, this array will be searched
//  to match the subdirectory and document filename to determine
//  what buttons to display, and what group (if any) should be highlighted.
MenuItem Menu_Array[] =
{
   // filename base	alt text		url
   { "m_home",		"Home",			"/index.html" },
   { "m_company",	"The Company",		"/company/index.html" },
   { "m_crusher",	"Crusher",		"/crusher/index.html" },
   { "m_overview",	"Overview",		"/crusher/overview.html" },
   { "m_features",	"Features",		"/crusher/features.html" },
   { "m_performance",	"Performance",		"/crusher/performance.html" },
   { "m_faq",		"FAQ's",		"/crusher/faq.html" },
   { "m_download",	"Download",		"/crusher/download.html" },
   { "m_pricing",	"Pricing",		"/crusher/pricing.html" },
   { "m_htmlpp",	"html++",		"/htmlpp/index.html" },
   { "m_overview",	"Overview",		"/htmlpp/overview.html" },
   { "m_features",	"Features",		"/htmlpp/features.html" },
   { "m_faq",		"FAQ's",		"/htmlpp/faq.html" },
   { "m_download",	"Download",		"/htmlpp/download.html" },
   { "m_pricing",	"Pricing",		"/htmlpp/pricing.html" },
   { "m_support",	"Support",		"/support/index.html" },
   { "m_overview",	"Overview",		"/support/overview.html" },
   { "m_techdocs",	"Technical Documents",	"/support/techdocs.html" },
   { "m_faq",		"FAQ's",		"/support/faq.html" },
   { "m_archives",	"Message Archives",	"/support/archives.html" },
   { "m_updates",	"Updates",		"/support/updates.html" },
   { "m_news",		"What's New",		"/news/index.html" },
   { "m_partners",	"Strategic Partners",	"/partners/index.html" },
   { "m_demos",		"Demos",		"/demos/index.html" },
   { "m_purchase",	"Purchase",		"/purchase/index.html" },
   { "m_online",	"Online Store",		"/purchase/online.html" },
   { "m_international",	"International Distributors",	"/purchase/international.html" }
} ;


//  Local function prototypes
String BaseName( const String& path_and_file ) ;
void   Separate( const String& path_and_file, String& path, String& file ) ;
void   GenerateJavaCode( htmlContainer& page ) ;
void   GenerateTextMenu( htmlContainer& page ) ;
void   GenerateMenu( htmlContainer& page ) ;


//
//  Standard main() entry point. Command-line arguments are used to
//  control what output is generated:
//     no arguments    HTML code for the menus images and hyperlinks
//     "java"          JavaScript for handling button state changes
//     "menu_text"     HTML for text version of menu for page footers
//
int main( int argc, char ** argv )
{
   Boolean        generate_java = FALSE ;
   Boolean        generate_text_menu = FALSE ;
   htmlContainer  page ;

   //  Scan command-line arguments, using the html++ String
   //  class for easy lowercase comparison.
   for ( int i = 1 ; i < argc ; i++ )
   {
      if ( String(argv[i]).ToLower() == "java"  )
         generate_java = TRUE ;

      if ( String(argv[i]).ToLower() == "text_menu"  )
         generate_text_menu = TRUE ;
   }

   page << htmlComment("//  Auto-generated using html++  "
                       "See http://www.dcmicro.com for info.\n")
        << "\n" ;

   //  Build the appropriate content based on the command-line args
   if ( generate_java )
      GenerateJavaCode( page ) ;
   else if ( generate_text_menu )
      GenerateTextMenu( page ) ;
   else
      GenerateMenu( page ) ;
   
   //  Output the dynamically-constructed HTML code to standard out,
   //  which the web server will pass along to the user's browser.
   cout << page ;

   return 0 ;
}


//  Return the filename portion without the path. This is
//  a good example of how easy it is to use the html++ String class.
String BaseName( const String& path_and_file )
{
   String  filename ;
   int     i ;

   //  Start at the right side minus one byte, in case
   //  the string ends in a subdirectory character
   for ( i = path_and_file.GetLength()-2 ; i >= 0 ; i-- )
   {
      //  Break loop when subdirectory character located
      if ( path_and_file[i] == SUBDIRECTORY_SEPARATOR )
      {
         break ;
      }
   }

   filename = path_and_file.Right( path_and_file.GetLength() - i - 1 ) ;

   //  If path_and_file terminated with a subdiretory
   //  character, remove it from the result string now.
   filename.RTrim( SUBDIRECTORY_SEPARATOR ) ;

   return filename ;
}


//  Separate a full path into a path and filename.
void Separate( const String& path_and_file, String& path, String& file )
{
   file = BaseName( path_and_file ) ;
   path = path_and_file - file ;
}


//  Generate the JavaScript code that defines the bitmaps
//  used to draw the menu. This routine scans Menu_Array, selecting the
//  button images (highlighted and unhighlighted) to use and adding them
//  to two comma-separated strings. Once the strings are assembled, they
//  are inserted into the JavaScript as elements of two arrays of image
//  filenames.
//
//  The logic of the GenerateTextMenu() and GenerateMenu() functions is
//  identical to this function, except that they output HTML code instead
//  of JavaScript.
//
//  This routine appears to be long, but in fact most of it is the text
//  of the JavaScript code.
void GenerateJavaCode( htmlContainer& page )
{
   PMenuItem   ptr ;
   String      doc_path ;
   String      doc_file ;

   //  The htmlCgi class automatically loads all CGI data. We will
   //  use it to determine the name of the URL requested by the browser.
   htmlCgi     cgi( cin ) ;

   //  Get the name of the requested URL, and break it into
   //  it's path and file components.
   Separate( cgi.Script_Name, doc_path, doc_file ) ;

   //  These two strings will be inserted into the JavaScript code
   String menu_img ;     // unhighlighted image list
   String menu_img_h ;   // highlighted image list

   //  Loop for all elements in Menu_Array
   for ( int i = 0 ; i < NUMBER_OF_ELEMENTS(Menu_Array) ; i++ )
   {
      //  Create a pointer to array element for easier access
      ptr = &Menu_Array[i] ;

      //  Construct the base filename of the menu button image
      String  gif = IMAGES_DIRECTORY + String(SUBDIRECTORY_SEPARATOR)
                         + ptr->image_base_name ;

      //  Break the URL associated with the menu button into
      //  it's filename and path components.
      String  path, file ;
      Separate( ptr->url, path, file ) ;

      //  If the path of the requested URL is the same as the one
      //  in Menu_Array, then this menu button should appear as part
      //  of a group (i.e., all documents in a subdirectory are grouped).
      if ( path == doc_path )
      {
         menu_img   += "   '" + gif + _OUT_GROUP + "',\n" ;
         menu_img_h += "   '" + gif + _IN_GROUP + "',\n" ;
      }
      else if ( file == "" || file == INDEX_FILE )
      {
         //  Otherwise, the menu button is stand-alone
         menu_img   += "   '" + gif + _OUT + "',\n" ;
         menu_img_h += "   '" + gif + _IN + "',\n" ;
      }
   }

   //  Remove the trailing CR and comma from both strings
   menu_img.RTrim( '\n' ) ;
   menu_img.RTrim( ',' ) ;
   menu_img_h.RTrim( '\n' ) ;
   menu_img_h.RTrim( ',' ) ;

   //  Construct the actual JavaScript using the htmlScript object
   htmlScript   script ;
   script
   << ""
   << "var preloadImage ;  // for pre downloading images into cache"
   << ""
   << "//  Unhighlighted images. The function calls within the"
   << "//  HREFs in the document reference the elements by number."
   << "var menu_img = new Array("

   //  Insert the string containing the unhighlighted button filenames
   << menu_img

   << ") ;"
   << ""
   << "//  Highlighted images."
   << "var menu_img_h = new Array("

   //  Insert the string containing the highlighted button filenames
   << menu_img_h

   << ") ;"
   << ""
   << "//  The mouseOver event - display the highlighted image."
   << "function Mover(n)"
   << "{"
   << "  if ( (navigator.appName == 'Netscape' && navigator.appVersion.substring(0,1) >= 3)"
   << "       || (navigator.userAgent.indexOf('MSIE') != -1 "
   << "       && navigator.appVersion.substring(0,1) >= 4) )"
   << "  {"
   << "    document.images['p' + n].src = menu_img_h[n] ;"
   << "  }"
   << "}"
   << ""
   << "//  The mouseOut event - display the normal (unhighlighted) image."
   << "function Mout(n)"
   << "{"
   << "  if ( (navigator.appName == 'Netscape' && navigator.appVersion.substring(0,1) >= 3)"
   << "       || (navigator.userAgent.indexOf('MSIE') != -1 "
   << "       && navigator.appVersion.substring(0,1) >= 4) )"
   << "  {"
   << "    document.images['p' + n].src = menu_img[n] ;"
   << "  }"
   << "}"
   << ""
   << "//  Pre-download images in the background"
   << "function preloader(theArray)"
   << "{"
   << "  if( (navigator.appName == 'Netscape' && navigator.appVersion.substring(0,1) >= 3)"
   << "      || (navigator.userAgent.indexOf('MSIE') != -1 
   << "      && navigator.appVersion.substring(0,1) >= 4) )"
   << "  {"
   << "    preloadImage = new Array() ;"
   << "    for( var i=0; i < theArray.length; i++ )"
   << "    {"
   << "      preloadImage[i] = new Image() ;"
   << "      preloadImage[i].src = image_directory + '/' + theArray[i] + '.gif' ;"
   << "    } "
   << "  }"
   << "}"
   << ""
   << "//  Download the page first, then the images in the background."
   << "function init()"
   << "{"
   << "  preloader( menu_img_h ) ;"
   << "}"
   << "" ;

   //  Insert the script object into the page object passed to this routine.
   page << script ;
}



//  This routine generates a text version of the menu for
//  display as a line across the bottom of the page.
void GenerateTextMenu( htmlContainer& page )
{
   int            i ;
   Boolean        first = TRUE ;
   htmlCgi        cgi( cin ) ;
   htmlContainer  menu ;

   PMenuItem   ptr ;
   String doc_path ;
   String doc_file ;

   Separate( cgi.Script_Name, doc_path, doc_file ) ;

   for ( i = 0 ; i < NUMBER_OF_ELEMENTS(Menu_Array) ; i++ )
   {
      ptr = &Menu_Array[i] ;

      htmlHyperLink hlink( ptr->url, ptr->description ) ;

      String  path, file ;
      Separate( ptr->url, path, file ) ;

      if ( file == "" || file == INDEX_FILE )
      {
         if ( first )
            first = FALSE ;
         else
            menu << " | " ;

         if ( path == doc_path )
            menu << ptr->description ; // don't use a link on the current page
         else
            menu << hlink ;
      }
   }

   //  Center the menu in a smaller font.
   page << htmlCenter( htmlFont( "helvetica,arial", -2 ) << menu ) ;
}



//  This routine generates the HTML code for the document body. It
//  is responsible for placing the initial images and hyperlinks 
//  on the page, with the attributes to invoke the JavaScript mouse
//  events using the proper array index.
void GenerateMenu( htmlContainer& page )
{
   htmlCgi  cgi( cin ) ;
   int      i ;
   int      count ;

   PMenuItem   ptr ;
   String doc_path ;
   String doc_file ;

   Separate( cgi.Script_Name, doc_path, doc_file ) ;

   for ( i = 0, count = 0 ; i < NUMBER_OF_ELEMENTS(Menu_Array) ; i++ )
   {
      ptr = &Menu_Array[i] ;

      String  gif = IMAGES_DIRECTORY + String(SUBDIRECTORY_SEPARATOR)
                        + ptr->image_base_name ;

      //  Create an object to represent the buttom image
      htmlImage  img( "", ptr->description ) ;

      //  The image has a JavaScript-accessible name of "p" + the ordinal
      //  number of the image filename as it appears in the JavaScript
      //  menu_img and menu_img_h arrays.
      img.Name( "p" + String(count) ) ;
      img.Border( 0 ) ;

      //  The hyperlink for the menu button. The text parameter is an
      //  empty string since the button image will be inserted into it.
      htmlHyperLink hlink( ptr->url, "" ) ;

      //  The SetAttribute() method lets us extend html++ with
      //  new or unsupported attributes as the HTML spec evolves;
      //  in this case, the JavaScript events for mouse movements.
      hlink.SetAttribute( "onMouseOver", "Mover('"+String(count)+"');" ) ;
      hlink.SetAttribute( "onMouseOut", "Mout('"+String(count)+"');" ) ;

      String  path, file ;
      Separate( ptr->url, path, file ) ;

      //  Create an object for the right shadow
      htmlImage   shadow_right( IMAGES_DIRECTORY
                       + String(SUBDIRECTORY_SEPARATOR) + RIGHT_SHADOW ) ;
      shadow_right.Border(0) ;

      //  If this is the specifc url requested, change the shadow
      //  image to one that includes an arrow to indicate the selection.
      if ( String(ptr->url) == cgi.Script_Name )
      {
         shadow_right.Image( IMAGES_DIRECTORY
               + String(SUBDIRECTORY_SEPARATOR) + RIGHT_SHADOW_HILITE ) ;
      }

      if ( path == doc_path )
      {
         gif += _OUT_GROUP ;
         img.Image( gif ) ;

         //  Insert the image into the hyperlink, then insert it followed
         //  by the shadow image and a line break into the page.
         page << ( hlink << img ) << shadow_right << htmlBreak() ;
         ++count ;
      }
      else if ( file == "" || file == INDEX_FILE )
      {
         gif += _OUT ;
         img.Image( gif ) ;
         page << ( hlink << img ) << shadow_right << htmlBreak() ;
         ++count ;
      }
      else
      {
         //  The menu button is a member of another group
         //  and is not displayed on the menu. This is how
         //  submenus are effectively "collapsed".
      }
   }
}

//  end of program







©Copyright 1995-2007 DC Micro Development, Inc., All rights reserved.
Crusher, html++, NetKit and TopDog are trademarks of DC Micro Development, Inc.
1890 Star Shoot Pkwy, Suite 170-309
Lexington, KY 40509
Phone: 859-317-2352