MasterPages using HTML, CSS, and JavaScript






4.98/5 (18 votes)
Describes a method whereby Web Master Pages can be developed using HTML, CSS, and JavaScript
Table of Contents
- 1. Introduction
- 2. Modifications and Additions
- 3. Creating a Master Page
- 4. Some Implementation Details
- 5. Browser Compatibility
- 6. References
- 7. Conclusion
- 8. Development Environment
- 9. History
The symbol returns the reader to the top of the Table of Contents.
1. Introduction

In 2010, I wrote an article called Master Pages using HTML and JavaScript [^] . The intent of that article was to provide a method whereby HTML master pages could be constructed using only HTML and JavaScript. I specifically did not want to depend upon any Microsoft or third party product.
Over the past eight plus years, I have been using the method to build websites. And during that time, I have made modifications and additions. It is those modifications and additions that I share in this article.
Images within this article are thumbnails. When the image is clicked, a larger version of the image will display in the default image viewing program.
2. Modifications and Additions
Three improvements of particular interest are:
- Eliminating JavaScript Global Pollution
- Using JSON to pass Parameters
- Additional Javascript Functions
2.1. Eliminating JavaScript Global Pollution
A major defect in the earlier implementation of master pages was its proliferation of objects as global JavaScript variables. At the time that the original master pages method was proposed, little thought was given to potential JavaScript name collisions. As JavaScript became ubiquitous and more and more JavaScript libraries were developed, the probability of JavaScript name conflicts rose. The original master pages implementation did not help.
The solution to the problem was to create a "namespace" and add functions to that namespace. Those functions that needed to be exported could be exported. Note only those functions required to be known outside the namespace would be exported. However, JavaScript does not contain a native implementation of namespace. To overcome that shortcoming, a JavaScript object can be pressed into service. (See JavaScript: The Definitive Guide, 5th Edition [^] for complete implemenation details.)
The following JavaScript creates a single global symbol "MasterPage" if it does not already exist:
var MasterPage; if ( !MasterPage ) { MasterPage = { }; } else if ( typeof MasterPage != "object" ) { throw new Error ( "MasterPage exists but is not an object" ); }
When successfully executed, the result is a single JavaScript global object named "MasterPage". Immediately following the MasterPage definition, an anonymous function is declared:
( function ( ) { // this anonymous function will define functions, none of which // are initially known outside the anonymous function ⁝ // **************************************************** build_page // global entry point /// <synopsis> /// MasterPage.build_page ( components ); /// /// <summary> /// modifies header and footer <div>s to produce a master page /// /// <param components> /// a JavaScript Object Notation (JSON) structure function build_page ( components ) // components - JSON structure { ⁝ } ⁝ // export the public properties (entry points) to the public namespace // leaving the private ones hidden within the anonymous function var name_space = MasterPage; name_space.build_page = build_page; ⁝ } ) ( ); // end anonymous function definition and invoke it
With build_page declared, we can invoke it with
MasterPage.build_page ( PAGE_COMPONENTS )
build_page has not been added to the global JavaScript variables; rather it is only known within MasterPage which is a global JavaScript variable. All of the public master page functions are defined in this manner. They are identified below.
2.2. Using JSON to pass Parameters
In the earlier incarnation of master pages, the JavaScript functions add_footer and add_header were invoked as event handlers for the onload event of the <body> tag. This required that a specific number of parameters be passed in a specific order. The invoked functions were required to contain logic to insure that the correct number of parameters was passed, thus attempting (although failing) to insure that the correct parameters were passed in the correct order. An example of the earlier version is
<body onload="add_header('Images/SiteLogo.png', 'Default.html', 'Journeys', 'Welcome'); add_footer('Images/ValidXHTML10.png');">
When the MasterPage module was designed, a significant modification was made. The two onload event handlers of the <body> tag were replaced by a single event handler (build_page) that accepted a single parameter. That parameter is a lightweight data-interchange format named JavaScript Object Notation [^] (JSON). An example of the resulting <body> element is
<body onload="MasterPage.build_page ( PAGE_COMPONENTS );">
where MasterPage.build_page is an entry point in the MasterPage namespace and PAGE_COMPONENTS is the JSON parameter. The JSON structure provides the flexibility to add specific visual components on a page-by-page basis. The recognized master page JSON name/value pairs are:
Name | Value | Type |
---|---|---|
header_desired | Is a header desired? | Boolean |
header_contents_url | URL of the header text | URL |
logo_url | URL of the site logo | URL |
home_url | Target URL if logo is clicked | URL |
background_image_desired | Is a background image desired? | Boolean |
background_image_url | URL of the background image | URL |
heading | Heading text | String |
heading_color | Color of Heading text (i.e., one of the HTML Color Names [^]) | String |
subheading | Subheading text | String |
subheading_color | Color of Subheading text (i.e., one of the HTML Color Names [^]) | String |
dot_desired | Is a colored dot desired? | Boolean |
dot_target_url | Target URL if the dot is clicked | URL |
dot_title | Title displayed when mouse hovers over the dot | String |
dot_image_url | Image URL to be displayed as the dot | URL |
printing_desired | Is printing the page permitted? | Boolean |
text_sizing_desired | Is text sizing to be provided? | Boolean |
text_sizing_tags | HTML tags that will participate in text sizing (e.g., p, span, td, th, etc.) | String |
constant_contents_url | URL of the JavaScript constants | URL |
menu_desired | Is a menu to be created? | Boolean |
menu_contents_url | URL of the menu text | URL |
footer_desired | Is a footer desired? | Boolean |
footer_contents_url | URL of the footer text | URL |
left_footer_desired | Is a left footer desired? | Boolean |
left_footer_contents | Contents of the left footer | String |
center_footer_desired | Is a center footer desired? | Boolean |
privacy_policy_url | URL of the privacy policy | URL |
contact_webmaster_url | URL of the contact webmaster web page | URL |
right_footer_desired | Is a right footer desired? | Boolean |
right_footer_contents | Contents of the right footer | String |
debug_json | Is debug alert displaying JSON contents desired? | Boolean |
An example PAGE_COMPONENT appears below.
2.3. Additional Javascript Functions
This master page implementation makes the following global functions available:
Name | Description | Example Invocation |
---|---|---|
build_page | modifies the header and footer <div>s to produce a master page | build_page ( components ); |
create_cookie | creates a cookie with the specified name, value, and expiration (in days) | create_cookie ( name, value, expiration_days ); |
read_cookie | reads the value of a cookie with the specified name | read_cookie ( name ); |
erase_cookie | remove the cookie with the specified name | erase_cookie ( name ); |
read_contents | retrieves the contents of the specified URL | read_contents ( url ) |
reduce_font_size | text resizing method that decrements the font size of text assigned the class "variable_font" by one point | reduce_font_size ( ); |
restore_font_size | text resizing method that restores text assigned the class "variable_font" to the current default font size | restore_font_size ( ); |
increase_font_size | text resizing method that increments the font size of text assigned the class "variable_font" by one point | increase_font_size ( ); |
add_event_handler | adds an event handler (funct) to the object (obj) for the specified event (e) | add_event_handler ( obj, e, funct ); |
remove_event_handler | removes the event handler (funct) from the object (obj) for the specified event (e) | remove_event_handler ( obj, e, funct ); |
set_keyboard_focus_to_id | sets focus to the specified element (id) | set_keyboard_focus_to_id ( id ); |
print_this_page | send current page to the printer | print_this_page ( ); |
Although not indicated in the preceding table, each invocation must be preceded by MasterPage. to indicate that the function is located in the MasterPage namespace.
3. Creating a Master Page
This section walks through the steps required to create a useful master page. These steps include:
- Using placeholders, create the basic structure of the master page.
- Replace master page placeholders with master page components.
- Add decision making.
3.1. Master Page Layout
A master page is a template of an HTML page. It consists of <head> and <body> elements, where, for our purposes, the <body> element contains three <div> elements. In code,
<html>
<head>
⁝
</head>
<body>
<div id="header" >
</div>
<div id="contents" >
⁝
</div>
<div id="footer" >
</div>
</body>
</html>
and diagrammatically.
3.2. Directory Structure
Before I begin with an example, I wish to describe the directory structure that I use to develop master pages. I cannot stress strongly enough the importance of the server directory structure used when implementing master pages. Most comments that claimed a failure of the method discussed in the original article ( Master Pages using HTML and JavaScript [^]) were symptomatic of a failure to use the directory structure defined in the article. By failing to use that directory structure, the commenter usually incurred a violation of the JavaScript same-origin policy [^].
HTML, CSS, and JavaScript source, images, and helper files must be located in specific places in the directory structure. The structure required by this implementation of master pages follows.
/Contents
footer and header INI files
menu compiler INI file
constants JSON file
/CSS
master page CSS file
/Images
Image (PNG, JPEG, etc.) files
/Scripts
master page JavaScript file
site .html files
This structure is the same as that imposed by the earlier article. However the contents of the directories have changed. The directory structure that I provide above is not the directory structure that must be used. The only requirements levied against the directory structure of your choice is that it comply with the JavaScript same-origin policy.
3.3. Step 0 - Creating the Basic Structure
The basic structure of the master page has been shown earlier. However, for practical purposes, I have found that the granularity, offered by the three division model, is not sufficient. Therefore, within the header and footer <div>s, I use <div>s to create a substructure.
3.3.1. Subdividing the Basic Structure
I split the header into two rows. I divide the upper header row into three areas: one (left) for the site logo; one (center) for the current page titling and a possible background image; and one (right) for additional tools that the reader may wish to use. I also divide the lower header row into three areas: one (left) as a spacer to align the center cell of the lower header row with the center cell of the upper header row; one (center) for a dropdown menu; and one (right) as a filler for the balance of the row. I split the footer into three areas: one each to the left, center, and right. Usually only the center area is used.
This page structure is depicted in the following figure (colors are used for illustrative purposes only).

Note that although three <div>s (header, content, and footer) have been defined, when creating a master page, only the header and footer <div>s are of interest.
3.3.2. State of Construction at the end of Step 0
To this point, only HTML and CSS have been used. The only CSS link is to the W3.CSS Framework, used throughout this article's example site.
<!DOCTYPE html > <html lang="en"> <head> ⁝ <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css" /> ⁝ </head> <body > <div id="header"> <div id="header-container" class="w3-container"...>...</div> </div> <div id="contents">...</div> <div id="footer"> <div id="footer-container" class="w3-container"...>...</div> </div> </body> </html>
Although not required in Step 0, certain HTML elements have an id attribute. Such elements will also have a style attribute with either a display or visibility value. The display form is used so that when the HTML element is not displayed, the space taken up by the element is collapsed. Examples are the header-container and the footer-container <div>s where both contain display attribute values. If, say, a header is not desired, the following JavaScript code could be executed
header-container.style.display = "none";
and the following web page would be displayed

The visibility form is used when the HTML element is not displayed and the space taken up by the element is not to be collapsed.
The header-container and the footer-container will have their contents modified in the next step.
The Step 0 download contains the HTML that generated the preceding page structure image and the CSPROJ project file.
3.4. Step 1 - Replacing Master Page Placeholders
Having defined the overall structure of the master page, the placeholders (e.g., "Left Header", "Center Header", "Right Header", etc.) must now be replaced with the components that will be in the master page.
The master page header components for this article's master page are depicted in the following figure.

The logo is the site logo. It is an image link that, when clicked, will cause the browser to return the reader to the site's home page.
The background image is a cosmetic component that is pure eye candy. For the image I will use a Graphics Interchange Format (gif) animated image. The heading will be the name of the site; the subheading will be the name of the page.
The redirection image (a green dot, in this case) allows a developer to redirect a reader to another page when the reader clicks on the image (green dot). The print icon allows printing the page. The text sizing tool (the three letters "A") allow resizing of text on the page. Unlike the usual browser zoom controls, this control only changes the size of specified text elements (e.g., <p>, <span>, <th>, <td>, etc.).
The menu allows the reader to navigate to other pages of the website.
When these components are implemented, the web page becomes as depicted in the following figure.

Only the center footer is used.
To this point, only HTML and CSS have been used. The only link to CSS other than to W3.CSS is to master_pages.css that currently contains:
body
{
background:#FFFFF0; /* Ivory */
}
The Step 1 download contains the HTML, CSS, and images that generated the preceding image and the CSPROJ project file. The directory structure is
CSS master_page.css Images green_dot.png ocean2.gif printer.png printer_hover.png site_logo.png index.html
3.5. Step 2 - Adding Decision Making
We incorporate decision making through the JavaScript function MasterPage.build_page. The function is invoked when the page loads (as the event handler of the <body> onload event).

In this implementation of master pages, I wanted the flexibility to add visual components to the header on a page-by-page basis. As a result, the selectable components of the header became as depicted to the left.
Although selectable components could be used for the footer, I chose not to have variable footer components.
Note that the names of these selectable components mirror the names of the JSON object, described above.
For the example site, this JSON structure, named PAGE_COMPONENTS and defined in the <head>, is
<script> var PAGE_COMPONENTS = { "header_desired":true, "header_contents_url":"./Contents/header.ini", "logo_url":"./Images/site_logo.png", "home_url":"./index.html", "background_image_desired":true, "background_image_url":"./Images/ocean2.gif", "heading":"Journeys", "heading_color":"White", "subheading":"Home", "subheading_color":"White", "dot_desired":true, "dot_image_url":"./Images/green_dot.png", "dot_title":"Home", "dot_target_url":"index.html", "printing_desired":true, "text_sizing_desired":true, "text_sizing_tags":"p,span,td,th,textarea", "constant_contents_url":"./Contents/constants.json", "menu_desired":true, "menu_contents_url":"./Contents/menu.ini", "footer_desired":true, "footer_contents_url":"./Contents/footer.ini", "left_footer_desired":true, "left_footer_contents":"", "center_footer_desired":true, "privacy_policy_url":"./privacy_policy.html", "contact_webmaster_url":"./contact_webmaster.html", "right_footer_desired":true, "right_footer_contents":"", "debug_json":false }; </script>
and is passed to the master pages build_page function by
<body onload="MasterPage.build_page ( PAGE_COMPONENTS );">
3.5.1. Dividing the HTML
In the table of recognized JSON names, four names appear with "contents_URL" in their names. The contents of these four files, to which these names point, are:
Name | Contents |
---|---|
constant_contents_url | Constants (in JSON) that are used during the execution of certain JavaScript functions |
footer_contents_url | HTML that comprises the footer (obtained from footer-container) |
header_contents_url | HTML that comprises the header (obtained from header-container) |
menu_contents_url | Instructions for the display of a menu |
At this pont in developing the master page, we are interested in moving the contents of header-container and footer-container to their respective text files (pointed to by header_contents_url and footer_contents_url, respectively). This is done to make the contents of the header and the contents of the footer available to all HTML pages on the site.

However, before we move the contents of header-container and footer-container, we need to replace selectable components with metadata.
An example may help. In the following HTML fragments, the one to the left is a part of the original header-container and the one to the right has metadata (bolded and bracketed by "{|}") replacing four of the attribute values.
<div id="header-container" <div id="header-container" class="w3-container" class="w3-container" style="display:block;"> style="display:{|}header_desired_display{|};"> <div class="w3-cell-row" <div class="w3-cell-row" style="width:100%; style="width:100%; height:100px; height:100px; min-height:100px;"> min-height:100px;"> <div id="site-logo" <div id="site-logo" class="w3-cell w3-col w3-center" class="w3-cell w3-col w3-center" style="width:15%; style="width:15%; height:100px; height:100px; min-height:100px; min-height:100px; visibility:visible;"> visibility:{|}logo_visibility{|};"> <a href="#" > <a href="{|}home_url{|}" > <img alt="Site Logo" <img alt="Site Logo" src="./Images/site_logo.png" src="{|}logo_url{|}" width="88" width="88" height="98" /> height="98" /> </a> </a> </div> </div> ⁝ ⁝
After all of the selectable components have been replaced by metadata, the header-container and footer-container can be placed in their text files. Using the directory structure described earlier, the files would be placed in the Contents directory.
The metadata is operated on by various JavaScript functions that are in master_page.js. For example, if a header is desired and the header_contents file is successfully retrieved, the metadata {|}header_desired_display{|} will be replaced by the JavaScript statement
header_contents = header_contents.replace ( '{|}header_desired_display{|}', 'block' ) ;
This replacement process continues for the rest of the metadata. The relationship between the JSON PAGE_COMPONENTS and metadata is as follows.
Page Component Metadata/Variables Effected header_desired header_desired_display header_contents_url header_contents logo_url logo_visibility, logo_url home_url home_url background_image_desired background_heading-subheading_visibility background_image_url background_image_url heading heading_visibility, heading heading_color heading_color subheading subheading_visibility subheading subheading_color subheading_color; dot_desired dot_visibility dot_image_url dot_target_url dot_title dot_title dot_target_url dot_image_url printing_desired print_visibility text_sizing_desired text_sizer_visibility text_sizing_tags text_sizing_tags constant_contents_url constants menu_desired menu_desired menu_contents_url menu_contents footer_desired footer_desired_display footer_contents_url footer_contents left_footer_desired left_footer_visibility left_footer_contents left_footer_contents center_footer_desired center_footer_visibility privacy_policy_url privacy_policy_url contact_webmaster_url contact_webmaster_url right_footer_desired right_footer_visibility right_footer_contents right_footer_contents debug_json
At this point, this master page implementation is complete. The Step 2 download contains the currrent project. The directory structure is:
Contents constants.json footer.ini header.ini menu.ini CSS master_page.css Images green_dot.png ocean2.gif printer.png printer_hover.png site_logo.png under_construction.png Scripts master_page.js contact_webmaster.html index.html link_1.html link_2.html link_3.html link_4.html master_pages_template.html privacy_policy.html
3.5.2. MasterPages Template
As I suggested in the earlier article, creating new webpages can be more easily accomplished if a template is used. The following code is a template based upon the example website.
<!DOCTYPE html > <html lang="en"> <head> <title></title> <meta http-equiv="Content-type" content="text/html; charset=UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css" /> <link rel="stylesheet" href="./CSS/master_page.css" /> <!-- place additional scripts here (e.g., Google Analytics, etc.) --> <script> var PAGE_COMPONENTS= { "header_desired":true, "header_contents_url":"./Contents/header.ini", "logo_url":"WEBSITE_LOGO_IMAGE", "home_url":"WEBSITE_HOME_URL", "background_image_desired":TRUE_OR_FALSE, "background_image_url":"BACKGROUND_IMAGE", "heading":"WEBSITE_NAME", "heading_color":"HTML_COLOR_NAME", "subheading":"WEBSITE_PAGE_NAME", "subheading_color":"HTML_COLOR_NAME", "dot_desired":TRUE_OR_FALSE, "dot_image_url":"DOT_IMAGE_URL", "dot_title":"DOT_TITLE", "dot_target_url":"DOT_REDIRECT_URL", "printing_desired":TRUE_OR_FALSE, "text_sizing_desired":TRUE_OR_FALSE, "text_sizing_tags":"HTML_TAGS_FOR_TEXT_RESIZING", "constant_contents_url":"./Contents/constants.json", "menu_desired":TRUE_OR_FALSE, "menu_contents_url":"./Contents/menu.ini", "footer_desired":TRUE_OR_FALSE, "footer_contents_url":"./Contents/footer.ini", "left_footer_desired":TRUE_OR_FALSE, "left_footer_contents":"", "center_footer_desired":TRUE_OR_FALSE, , "privacy_policy_url":"./privacy_policy.html", "contact_webmaster_url":"./contact_webmaster.html", "right_footer_desired":TRUE_OR_FALSE, "right_footer_contents":"", "debug_json":TRUE_OR_FALSE, }; </script> </head> <body onload="MasterPage.build_page ( PAGE_COMPONENTS );"> <div id="header"> </div> <div id="contents"> </div> <div id="footer"> </div> <script src="./Scripts/master_page.js"></script> </body> </html>
The capitalized text should be replaced by desired values.
Note that if, say, dot_desired is set false, none of the other PAGE_COMPONENTS dot associated components (i.e., dot_image_url, dot_title, or dot_target_url) need be supplied. All of those components are ignored (whether present or not).
4. Some Implementation Details
4.1. W3.CSS Framework
While researching this article, I encountered the W3C Schools W3.CSS Framework [^]. From the website:
W3.CSS is a modern CSS framework with built-in responsiveness:
Smaller and faster than any other CSS frameworks.
Easier to learn, and easier to use than any other CSS frameworks.
Better cross-browser compatibility than any other CSS frameworks.
Uses standard CSS only (No jQuery or JavaScript library).
Supports modern responsive mobile first design by default.
Provides CSS equality for all browsers: Chrome, Firefox, Edge, IE, Safari, Opera, ....
Provides CSS equality for all devices: desktop, laptop, tablet, and mobile.
Speeds up and simplifies web development:
A nice thing about W3.CSS is its extensive documentation. By leaving a browser window open to the W3 Schools website, the details of the framework can be easily referenced. I am using W3.CSS as the CSS framework for all of the examples in this article.
4.2. Header Menu
The original article did not support a menu. This version provides for a page-by-page menu, compiled from the contents of the "menu.ini" file. Note that there may be more than one "menu.ini" file in the Contents directory (obviously with different names). Each "menu.ini" file contains instructions that generate a page-specific menu. However for the purposes of this article, only a single menu will be used across the site.
There are three forms for the contents of "menu.ini". All three may appear in a single "menu.ini" file.
1. <menu_item_name>,0,<menu_item_target_URL> 2. <menu_item_name>,<count> <submenu_item_name>,<submenu_item_target_URL> : <submenu_item_name>,<submenu_item_target_URL> 3. / <comment>
The first form produces a menu item that has no submenu items. An example is the menu item "Home" that usually has no submenu items beneath it.
The second form produces a dropdown menu item that has <count> submenu items beneath it. In this form, it is required that <count> entries follow the initial line. An example is a menu item "Calendars" that would have two or more monthly calendars as submenu items beneath it.
The last form is a comment. Any line that starts with a slash is ignored. An empty line is also ignored.
4.2.1. build_menu ( components )
build_menu ( components ) is invoked if the components member menu_desired is true.
function build_menu ( components ) { var i = 0; var line = ''; var lines = [ ]; var menu_contents = null; var menu_contents_url = null; var menu_item = ''; var menu_item_separator = ','; var menu_line_height = 0.9; var menu_metadata = null; var pieces = [ ]; var start_menu_line_comment = '/'; if ( components.constant_contents_url ) { var constants_metadata = read_contents ( components.constant_contents_url ); if ( constants_metadata ) { var constants = JSON.parse ( constants_metadata ); if ( constants ) { if ( constants.menu_line_height ) { menu_line_height = parseFloat ( constants.menu_line_height ); } if ( constants.start_menu_line_comment ) { start_menu_line_comment = constants.start_menu_line_comment; } if ( constants.menu_item_separator ) { menu_item_separator = constants.menu_item_separator; } } } } if ( !components.menu_contents_url ) { return ( null ); } menu_metadata = read_contents ( components.menu_contents_url ); if ( !menu_metadata ) { return ( null ); } lines = menu_metadata.split ( '\n' ); menu_contents = " <div class='w3-bar'\n" + " style='vertical-align:top; \n" + " line-height:" + menu_line_height.toString ( ) + ";'>\n"; while ( i < lines.length ) { line = lines [ i ].replace ( '\r', '' ); // ignore empty lines if ( line.length <= 0 ) { i++; continue; } // ignore commentary lines if ( line.startsWith ( start_menu_line_comment ) ) { i++; continue; } pieces = line.split ( menu_item_separator ); if ( pieces.length == 3 ) // Home,0,index.html { menu_item = " <a href='" + pieces [ 2 ] + "' \n" + " class='w3-bar-item w3-button'>" + pieces [ 0 ] + "</a>\n"; menu_contents += menu_item; i++; } else // Events,3 { var count = 0; pieces = line.split ( ',' ); menu_item = " <div class='w3-dropdown-hover'>\n" + " <button class='w3-button'>" + pieces [ 0 ] + "▼</button>\n" + " <div class='w3-dropdown-content w3-bar-block w3-card-4'>\n"; menu_contents += menu_item; count = parseInt ( pieces [ 1 ], 10 ); // item_1,item_1.html // item_2,item_2.html // item_3,item_3.html for ( var j = 0; ( j < count ); j++ ) { i++; line = lines [ i ].replace ( '\r', '' ); pieces = line.split ( "," ); menu_item = " <a href='" + pieces [ 1 ] + "'\n" + " class='w3-bar-item w3-button'>" + pieces [ 0 ] + "</a>\n"; menu_contents += menu_item; } menu_contents += " </div>\n"; i++; } } menu_contents += " </div>"; return ( menu_contents ); }
The contents of the menu.ini file for this article's example site is:
Home,0,index.html Link1,0,link_1.html Other Links,3 Link2,link_2.html Link3,link_3.html Link4,link_4.html
The result of the compilation of this example, is:
<div class='w3-bar' style='vertical-align:top; line-height:0.9;'> <a href='index.html' class='w3-bar-item w3-button'>Home</a> <a href='#' class='w3-bar-item w3-button'>Link1</a> <div class='w3-dropdown-hover'> <button class='w3-button'>Other Links▼</button> <div class='w3-dropdown-content w3-bar-block w3-card-4'> <a href='#' class='w3-bar-item w3-button'>Link2</a> <a href='#' class='w3-bar-item w3-button'>Link3</a> <a href='#' class='w3-bar-item w3-button'>Link4</a> </div> </div> </div>
When this HTML is placed into the header center menu, and the cursor hovers over the menu item dropdown (Other Links), the following is displayed.

The reader is cautioned that the JavaScript compiler (found in build_menu in master_page.js) performs well with well-behaved input. However it fails when it encounters unexpected or unrecognized input. There is no error reporting nor is there any embedded recovery.
4.2.2. JavaScript String startsWith() Method
During development of this article, I found that some browsers (notably IE) do not recognize the startsWith() method of the JavaScript String object. During menu compilation, startsWith() is required to test for a comment (i.e., a line starting with '/'). There were a number of solutions available. I chose to incorporate the functionality through a String prototype. As a result of that decision, the following code appears in master_page.js.
if ( !String.prototype.startsWith ) { String.prototype.startsWith = function ( search, pos ) { return this.substr ( ( !pos || pos < 0 ) ? 0 : +pos, search.length ) === search; }; }
4.3. JavaScript Constants
The file constants.json contains some constants that affect JavaScript functions. All values contained therein have default values. So unless there are unusual circumstances, the file need not exist. For this article, constants.json contains the following.
{ "menu_line_height" : 0.9, "start_menu_line_comment" : "/", "menu_item_separator" : ",", "cookie_name" : "SAVED_VARIABLE_FONT_SIZE", "default_font_size" : 13, "maximum_font_size" : 24, "minimum_font_size" : 8 }
Note that the contents of this file is a JSON object. Furthermore, the contents may be modified to suit your own master page needs.
4.4. Text Sizing
Although most browsers provide some form of text sizing (zooming), the effect is usually applied to the whole of the web page. This is not often the effect desired. For master pages, the effect is limited to specific elements whose tags are specified by the programmer.
Text sizing is established by the following procedure:
- Retrieve the text sizing constants defined in the JSON file specified by the constant_contents_url component passed to MasterPages.build_page. If none were supplied, default values will be used.
- Add the class .variable_font style to the CSS of the current document:
// ************************************************** add_css_text // local entry point // see Timo Huovinen answer at https://stackoverflow.com/ // questions/3922139/ // add-css-to-head-with-javascript function add_css_text ( css_text ) { var head = document.getElementsByTagName ( 'head' ) [ 0 ]; var style = document.createElement ( 'style' ); style.setAttribute ( 'type', 'text/css' ); if ( style.styleSheet ) // IE { style.styleSheet.cssText = css_text; } else // W3C { style.appendChild ( document.createTextNode ( css_text ) ); } head.appendChild ( style ); }
where the css_text passed to add_css_text() is:
.variable_font { font-size:DEFAULT_FONT_SIZE + 'pt'; }
and DEFAULT_FONT_SIZE is either a default value of 16 or the value obtained from constants.default_font_size from the JSON file constants.json.
Note that head.appendChild places the new stylesheet after all existing stylesheets. This will be important later.
- If the components.text_sizing_tags was provided, each tag is extracted from the string and placed in the string array text_sizing_tags. If no text sizing tags were supplied, default values will be used.
- For each tag in the string array text_sizing_tags, for each document element with the same tag name, add the class variable_font to the element.
- Searching from the last sytlesheet in the document to the first, locate the style .variable font, and record the rule in the variable variable_font_rule. Because the last stylesheet contains the style .variable font, the search will terminate successfully.
- Create a session cookie containing the current default font size.
There are three onclick event handlers that implement text sizing: reduce_font_size(), increase_font_size(), and restore_font_size(). In the first two, the font sizing cookie is read and the font size is decremented or incremented, respectively. In restore_font_size(), the font size is restored to the value of DEFAULT_FONT_SIZE. At the end of all three functions, the following code is executed:
variable_font_rule.style.fontSize = ( font_size ) + "px"; create_cookie ( COOKIE_NAME, font_size.toString ( ), 0 );
Because the font size is stored in a session cookie, the font size for each page is maintained from page to page. However, if the browser is closed, the cookie is deleted, and the next time the website is visited, there will be no memory of the font size (it will be set to the value in DEFAULT_FONT_SIZE).
The text_sizing_tags component of the JSON PAGE_COMPONENTS object contains a comma separated list of tags that are to be effected by text sizing. Tags that accept text are listed below:
Text Accepting Tags <a> <abbr> <acronym> <address> <article> <bdo> <button> <caption> <cite> <code> <dd> <dfn> <div> <dt> <fieldset> <figcaption> <header> <kbd> <label> <legend> <li> <object> <option> <p> <pre> <q> <samp> <script> <span> <td> <textarea> <th> <tt> <var>
When supplied in text_sizing_tags, tags should be supplied without enclosing brackets (i.e., < and >).
In the preceding list of Text Accepting Tags, the following Text Modifying Tags do not appear:
Text Modifying Tags <b> <del> <em> <i> <ins> <mark> <small> <strong> <sub> <sup> <u>
These tags are omitted because their parent element is normally found in the list of text accepting tags and are therefore covered through inheritance. The Text Modifying Tags can be supplied if desired.
5. Browser Compatibility
![]() | ![]() | ![]() | ![]() | ![]() |
Chrome | Firefox | Internet Explorer | Opera | Safari Partial |
Safari does not appear to properly support the w3-display-right class. Output appears below the centerline of the w3-display-container.
Edge does not provide a download for Windows 7, my development environment. As a result, master pages has not been tested in that browser.
The testing procedure is:
- Open the Step 2 project in Visual Studio.
- Open index.html in the Visual Studio HTML editor.
- Click on File -> View in Browser in Visual Studio. When clicked, the ASP.NET Development Server opens using a specific port. In the address bar of the default browser, a line, like the following, appears:
http://localhost:50775/Step_2/index.html
- Copy the contents of the address line to the clipboard.
- Open the browser for which testing is to occur.
- Past the contents of the clipboard into the browser's address bar.
Using this procedure, each of the browsers whose image is depicted above, were tested.
7. Conclusion
I hope that I have presented a better method of developing master pages than was presented in an earlier article. Some significant improvements were made that I hope make the process more useful.