Introduction
Most web site pages are divided into three parts: header, page contents, and footer. Usually, headers and footers are the same across all pages on the web site, leaving only the page content to vary from page to page. A "master page" allows the page header and footer to be created once and then reused.
In the ASP.NET environment, a master page is defined with place holders for the content that varies from page to page. In a PHP environment, the header and footer are "brought into" a web page by using the PHP include facility. There are third-party products that provide a master page facility in ways akin to ASP.NET. It has also been suggested that a global search and replace operation would suffice.
I have problems with each of these methods. PHP is not an option. The IDE that I use is Visual Studio 2008 Express. There is no built-in PHP support. Global search and replace is, at best, dangerous since unanticipated results can occur. Third party technologies require that I reach into my pocket and make a payment. And although I use an ASP.NET supporting IDE, I want the web pages to be independent of Microsoft, that is, written only in HTML and JavaScript.
This article describes a method to build web site "master pages" using HTML and JavaScript.
Background
The pages that I want to create have the form:
The header and the footer must be constant across the site. Not constant in the sense that they cannot be changed, but rather constant in that they are the same from web page to web page. For example:
For my particular purpose, I want the header to contain:
- A site logo and a target page if the user clicks on the logo
- A page heading
- A page subheading
- A horizontal line that separates the header from the content
I want to be able to modify the page heading and page subheading on page load.
I want the footer to contain:
- Links to other site pages
- A copyright notice
In the event that I validate a page using the W3C Markup Validation Service, I want to place the W3C validation icon in the footer.
I also want to be able to completely revise the contents of both the header and the footer, independently of the page contents.
Using the Code
The header and footer text reside in text files located in the website directory tree. The pertinent directory structure of my website is:
GGGustafson
CSS
GGGustafson.css
HeaderFooterContents
footer_contents.txt
header_contents.txt
Images
favicon.ico
SiteLogo.png
ValidXHTML10.png
Scripts
add_footer.js
add_header.js
IO.js
place_in_outerHTML.js
ContactMe.html
Default.html
PrivacyPolicy.html
Note that I am hosting my site on Microsoft Office Live (MSOL). MSOL expects web pages to be ASPX or HTML pages. For this article, I will be using HTML pages in the MasterPage downloadable source. However, if the reader uses Visual Studio to build web site pages, chose ASPX pages. Once a new page is defined, you may, if desired, delete the aspx.cs and designer.cs pages that are automatically generated by Visual Studio. You may also change the web page extension from .aspx to .html (replying "Yes" in the following dialog box).
Note, however, that the action
attribute of a <form>
tag may not have a target with an extension of .html. That is why the ContactMeSuccessful web page in the MasterPage downloadable source has the extension .aspx.
The header is encapsulated in the header_contents.txt file, accessed by the add_header
script; the footer is in the footer_contents.txt file, accessed by the add_footer
script. On document load, these two JavaScript scripts are executed:
<body onload="add_header('PAGE LOGO',
'PAGE LOGO TARGET',
'PAGE HEADER',
'PAGE SUBHEADER');
add_footer('PAGE VALIDATION LOGO');">
In the preceding example, pseudo arguments are used. In an actual page, they would be replaced by actual arguments. For example, on my site Default.html page, I use:
<body onload="add_header('Images/SiteLogo.png',
'Default.html',
'Journeys',
'Welcome');
add_footer('Images/ValidXHTML10.png');">
For the add_header
script to execute to completion, four arguments must be passed, and the web page must contain:
<div id="header">
</div>
This "header" <div>
is placed wherever the header is to appear in the web page (usually, immediately following the <body>
tag).
For the add_footer
script to execute to completion, the web page must contain:
<div id="footer">
</div>
The "footer" <div>
is placed wherever the footer is to appear in the web page (usually, immediately before the required <script>
include tags). The add_footer
script accepts one optional argument.
I place the <script></script>
blocks following the footer <div>
but before the </body>
tag. I add the attribute defer="defer"
to the <script>
include blocks to ensure that the HTML loads completely before the scripts are loaded. Note that the "defer
" attribute of the <script>
tag is currently implemented only by Internet Explorer. I expect other browsers to implement this standard attribute in the future.
The <script></script>
blocks that I include to implement master pages are:
<script type="text/javascript"
defer="defer"
src="Scripts/place_in_outerHTML.js"></script>
<script type="text/javascript"
defer="defer"
src="Scripts/IO.js"></script>
<script type="text/javascript"
defer="defer"
src="Scripts/add_footer.js"></script>
<script type="text/javascript"
defer="defer"
src="Scripts/add_header.js"></script>
The add_header
script and the contents of the header contents file are dependent upon what the header is to contain. I mentioned earlier what I wanted my header to contain. For that, I need four arguments to add_header
. The revised version of the add_header
script is now:
function add_header ( site_logo,
site_logo_target,
page_header,
page_subheader )
{
if ( arguments.length == 4 )
{
var document_title = "";
document_title = page_header;
document_title += ' - ' + page_subheader;
document.title = document_title;
if ( document.getElementById )
{
var header = document.getElementById ( 'header' );
if ( header )
{
var header_contents = read_contents (
"HeaderFooterContents/header_contents.txt" );
if ( header_contents )
{
header_contents = header_contents.replace (
'{{SiteLogoTarget}}',
site_logo_target );
header_contents = header_contents.replace (
'{{SiteLogo}}',
site_logo );
header_contents = header_contents.replace (
'{{PageHeader}}',
page_header );
header_contents = header_contents.replace (
'{{PageSubHeader}}',
page_subheader );
place_in_outerHTML ( header, header_contents );
}
}
}
}
}
Note that, at a reader's suggestion, the four substitutable fields (i.e., SiteLogoTarget
, SiteLogo
, PageHeader
, and PageSubHeader
) are now enclosed within the delimiters "{{" and "}}", eliminating an earlier ambiguity that arose during the replacement operation.
If four arguments were not supplied, the script exits. A document title is created. If the <div>
with the ID of "header" is not found, the script exits. The content of the header_contents text file is then read. If the file was read successfully, various substrings are replaced by their new values. Finally, the revised header contents replace the header <div>
's outerHTML
. For the header, I use a header contents file that contains:
<table class="header"
cellpadding="0"
cellspacing="0" >
<tr>
<td class="left_column left_header">
<a href="{{SiteLogoTarget}}" >
<img alt="Site Logo"
src="{{SiteLogo}}"
style="height:100px;
width:89px;"/>
</a>
</td>
<td class="center_column center_header">
<table>
<tr class="header_title_subtitle">
<td>
<span id="header_title">{{PageHeader}}</span>
</td>
</tr>
<tr class="header_title_subtitle">
<td>
<span id="header_subtitle">{{PageSubHeader}}</span>
</td>
</tr>
</table>
</td>
<td class="right_column right_header">
</td>
</tr>
<tr>
<td colspan="3">
<hr class="colored_spacer" />
</td>
</tr>
</table>
The add_footer
script and the footer contents file contents are dependent upon what the footer is to contain. I mentioned earlier what I wanted my footer to contain. For that, I need one optional argument to add_footer
. The revised version of the add_footer
script is then:
function add_footer ( footer_image )
{
if ( document.getElementById )
{
var footer = document.getElementById ( 'footer' );
if ( footer )
{
var footer_contents = read_contents (
"HeaderFooterContents/footer_contents.txt" );
if ( footer_contents )
{
if ( footer_image )
{
footer_contents = footer_contents.replace (
'{{FooterImage}}',
footer_image );
footer_contents = footer_contents.replace (
'{{DisplayFooterImage}}',
'block' );
}
else
{
footer_contents = footer_contents.replace (
'{{FooterImage}}',
'' );
footer_contents = footer_contents.replace (
'{{DisplayFooterImage}}',
'none' );
}
place_in_outerHTML ( footer, footer_contents );
}
}
}
}
Note that here too, at a reader's suggestion, the two substitutable fields (i.e., FooterImage
and DisplayFooterImage
) are now enclosed within the delimiters "{{" and "}}", eliminating an earlier ambiguity that arose during the replacement operation.
If the <div>
with the ID of "footer" is not found, the script exits. The contents of the footer_contents text file is then read. If the file was successfully read, various substrings are replaced by their new values. Finally, the revised footer contents replace the <div>
's outerHTML
. For the footer, I use a footer contents file that contains:
<table class="footer"
cellpadding="0"
cellspacing="4">
<tr>
<td class="left_column"
rowspan="2">
<img alt=""
src="{{FooterImage}}"
style="display:{{DisplayFooterImage}};" />
</td>
<td class="center_column footer_first_line" >
<a href="ContactMe.html">
Contact Me
</a>
|
<a href="PrivacyPolicy.html">
Privacy Policy
</a>
</td>
<td class="right_column">
</td>
</tr>
<tr>
<td class="center_column footer_second_line" >
© 2010 G. G. Gustafson, All Rights Reserved
</td>
<td class="right_column">
</td>
</tr>
</table>
The add_footer
and add_header
scripts access the footer_contents and header_contents files, respectively, through the IO script.
var XMLHttpFactories = [
function ( )
{
return ( new XMLHttpRequest ( ) );
},
function ( )
{
return ( new ActiveXObject ( "Msxml2.XMLHTTP" ) );
},
function ( )
{
return ( new ActiveXObject ( "Msxml3.XMLHTTP" ) );
},
function ( )
{
return ( new ActiveXObject ( "Microsoft.XMLHTTP" ) );
}
];
function createXMLHTTPObject()
{
var xmlhttp = false;
for ( var i = 0; ( i < XMLHttpFactories.length ); i++ )
{
try
{
xmlhttp = XMLHttpFactories [ i ] ( );
}
catch ( e )
{
continue;
}
break;
}
return ( xmlhttp );
}
function read_contents ( url )
{
var request = createXMLHTTPObject ( );
request.open ( 'GET', url, false );
request.setRequestHeader ( 'Content-Type', 'text/html' );
request.send ( '' );
return ( request.responseText );
}
The JavaScript IO function is the reason that this "master page" paradigm works. Without it, it would have been impossible to read in the web page header and footer text.
Lastly, the place_in_outerHTML
script should be mentioned.
function place_in_outerHTML ( element,
contents )
{
if ( element.outerHTML )
{
element.outerHTML = contents;
}
else
{
element.innerHTML = contents;
}
}
Firefox does not support outerHTML
as an element property (it is currently only available in Internet Explorer). To keep the browser differences out of add_header
and add_footer
, I invoke place_in_outerHTML
. This script is not quite what I want, but again, it works. By placing the JavaScript in a separate file, I can revisit the code, and possibly make changes, without disturbing the rest of the site.
Web Page Template
Whenever I build a new web page, I use the following template as a starting point:
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<link rel="shortcut icon"
media="screen,print"
href="Images/favicon.ico" />
<link type="text/css"
rel="Stylesheet"
media="screen,print"
href="CSS/GGGustafson.css" />
</head>
<body onload="add_header('PAGE LOGO',
'PAGE LOGO TARGET',
'PAGE HEADER',
'PAGE SUBHEADER');
add_footer('PAGE VALIDATION LOGO');">
<div id="header">
</div>
<table class="content"
cellpadding="0"
cellspacing="0">
<tr>
<td class="left_column left_content">
</td>
<td class="center_column center_content">
</td>
<td class="right_column right_content">
</td>
</tr>
</table>
<div id="footer">
</div>
<script type="text/javascript"
defer="defer"
src="Scripts/place_in_outerHTML.js"></script>
<script type="text/javascript"
defer="defer"
src="Scripts/IO.js"></script>
<script type="text/javascript"
defer="defer"
src="Scripts/add_footer.js"></script>
<script type="text/javascript"
defer="defer"
src="Scripts/add_header.js"></script>
</body>
</html>
Points of Interest
Most importantly, the header, footer, and accessing functions presented in this article are what I use in my website; they should be modified to meet your needs.
I encourage you to use the web page template (or something like it). I have found that it saves me a lot of time building new pages.
I use <table>
s to organize my web pages (others may argue for the use of <div>
s for that purpose). But to obtain the effects of a percentage based three column document, I found that <table>
s worked better than <div>
s. Of course, then again, maybe I'm not well enough versed to make <div>
s work.
Lastly, I strongly suggest that the contents of the header and the footer be initially built in the web page template, in place of the header and footer <div>
s, respectively. When the template looks the way you want it to look, then move the header and footer contents to their respective text files and reinsert the header and footer <div>
s.
References
Browsers Tested Successfully
History
- 10/06/2010 - Revised IO.js, add_header.js, and add_footer.js to improve security.
- 11/15/2010 - Revised article to address readers' comments and correct typographic and logic errors; ensured that paradigm would work on all common browsers.