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;
}
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', '' );
if ( line.length <= 0 )
{
i++;
continue;
}
if ( line.startsWith ( start_menu_line_comment ) )
{
i++;
continue;
}
pieces = line.split ( menu_item_separator );
if ( pieces.length == 3 )
{
menu_item =
" <a href='" + pieces [ 2 ] + "' \n" +
" class='w3-bar-item w3-button'>" + pieces [ 0 ] + "</a>\n";
menu_contents += menu_item;
i++;
}
else
{
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 );
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:
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 )
{
style.styleSheet.cssText = css_text;
}
else
{
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.