|
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.
|




|
|
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
|