Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

MasterPages using HTML, CSS, and JavaScript

4.98/5 (21 votes)
4 Jul 2018CPOL19 min read 52.5K   2.4K  
Describes a method whereby Web Master Pages can be developed using HTML, CSS, and JavaScript

1. Introduction Table of Contents

Master Page

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 Table of Contents

Three improvements of particular interest are:

  • Eliminating JavaScript Global Pollution
  • Using JSON to pass Parameters
  • Additional Javascript Functions

2.1. Eliminating JavaScript Global Pollution Table of Contents

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 Table of Contents

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 Table of Contents

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 Table of Contents

This section walks through the steps required to create a useful master page. These steps include:

  1. Using placeholders, create the basic structure of the master page.
  2. Replace master page placeholders with master page components.
  3. Add decision making.

3.1. Master Page Layout Table of Contents

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,

en-us
<html>
  <head>
    ⁝
  </head>
  <body>
    <div id="header" >
    </div>

    <div id="contents" >
      ⁝
    </div>

    <div id="footer" >
    </div>
  </body>
 </html>

and diagrammatically.

3.2. Directory Structure Table of Contents

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.

en-us
/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 Table of Contents

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 Table of Contents

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

Image 13

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 Table of Contents

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

Image 15

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 Table of Contents

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.

Header Components

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.

Image 18

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:

CSS
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 Table of Contents

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

Desired Header Footer Components

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 Table of Contents

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.

HTML Javascript Text Files

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 Table of Contents

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 Table of Contents

4.1. W3.CSS Framework Table of Contents

While researching this article, I encountered the W3C Schools W3.CSS Framework [^]. From the website:

en-us
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 Table of Contents

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 ) Table of Contents

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&#9660;</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.

Image 28

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 Table of Contents

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 Table of Contents

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 Table of Contents

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 Table of Contents

Chrome Firefox Internet Explorer Opera Safari
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 Table of Contents

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.

8. Development Environment Table of Contents

MasterPages was developed in the following environment:

Microsoft Windows 7 Professional SP 1
Microsoft Visual Studio 2008 Professional SP1
Microsoft .Net Framework Version 3.5 SP1

9. History Table of Contents

06/28/2018       Original article
07/04/2018       Revised formatting and typos
07/04/2018       Repaired error that caused Chrome and Safari to fail

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)