External HTML Loader

Related Articles

Introduction

As the web continues to progress further towards the separation of content from design and programming, it is increasingly becoming important to find practical ways to manage content. Typically, this is conducted via a server sided Content Management System (CMS), but there is way to achieve the same effect on the server side. This effect is typically referred to as External HTML Loading.

The benefit of using external loading techniques is the ability to keep initial load times down to a minimum as well as providing a way to easily manage your content. For example, in DHTML we could have 10 different layers filled with content, with each layer being 2kb in size and then toggle the visibility of the layers to achieve a system where content is switched instantly. The down side of this method is that we have to load 20k of content along with all the interface elements, which can severely impair the user experience of the page. On the other hand if we use external HTML Loading techniques, then the initial load is 2kb rather than the 20kb, because the content files are kept in separate html pages.

What we want to end up doing in our external HTML loading technique is to see our content loaded into a CSS layer; the reason being that we gain greater control over the appearance of the content than would be the case if we loaded content directly into an IFrame. For example, if we have a complex graphical background in main interface then it becomes problematic to match that background when the IFrame is scrolled. By loading the external content into a CSS Layer we circumvent this problem because we can use the transparent nature of the layer. Consequently no matter how complex the background of the interface is, we can scroll the content to our hearts content without the worry that the background images won’t align correctly. In addition, we also gain more control because we can stack layers on top of each other. This is beneficial if there is a need to have overlapping layers in your page designs.

The essence of the technique involves using an IFrame for Internet Explorer and Netscape 6, and then shifting that content via innerHTML over to a CSS Layer. In Netscape 4 this procedure is simplified because we can directly load content into a layer. To better understand this lets go through the script. We begin with a object detector;

ns4 = (document.layers);
ns6 = (!document.all && document.getElementById);
ie4 = (document.all && !document.getElementById && !window.opera);
ie5 = (document.all && !document.fireEvent && !window.opera);
op7 = (window.opera && document.createComment) ;
w3dom = (document.getElementById || op7);


The creation of the above varaibles allows us to seperate out different browsers depending upon with document objects they support. An important part of having the script run in a functional way is to use a good Document Object Switch like so;

function layerSetup(parent,id,left,top,zindex,visibility) {
if(ns4) {
this.obj = (parent != null) ? parent.document[id] : document[id];
this.obj.htm = (parent != null) ? parent.document[id].document : document[id].document;
} else if(ie4 ||ie5) {
this.obj = document.all[id].style;
this.obj.htm = document.all[id];
} else if(w3dom) {
this.obj = document.getElementById(id).style;
this.obj.htm = document.getElementById(id);
}
this.obj.left = (w3dom) ? left + "px" : left;
this.obj.top = (w3dom) ? top +"px" : top;
this.obj.zIndex = zindex;
this.obj.visibility = visibility;
return this.obj;
}}

In the above we create a DOM Switch that accounts for browser differences. We create five arguments (parent, id, left, top, zindex and visibility) that we can use later on to do the following:

* Specify whether the layer is a nested layer (the parent argument).

* Target the id attribute of the layer (the id argument)

* Positions the layers left of screen (the left argument) and also provide a switch that will correctly position standards based browsers when a DTD is used on the page.

* Positions the layers top of screen (the top argument) and also provide a switch that will correctly position standards based browsers when a DTD is used on the page.

* Specify the stacking order of the element (the z-index argument).

* Allow us to toggle the visibility of the element (the visibility argument).

Now that we created a script that will allow for the creation of JavaScript objects, we want to actually define the objects;

function defineObjects() {
page = new Object();
page.width = (ns4 || ns6 ||op7) ? innerWidth : document.body.clientWidth;
page.height = (ns4 || ns6 ||op7) ? innerHeight : document.body.clientHeight;
content = new layerSetup(null,"contentLYR",220,120,7,"visible");
sizeAnimator(content,page.width-300,page.height);
setTimeout("actionManager()",500);
}

The defineObjects() function begins by creating a new JavaScript object named page. We can then use the page object to provide a cross browser way of capturing the current browsers height and width dimensions via innerWidth and innerHeight for Netscape browsers and document.body.clientWidth and document.body.clientHeight for Internet Explorer browser.

We then create a new variable called content and point this variable to our previously created layerSetup() function. In this instance we pass the values of the arguments to the layerSetup function. To better understand this lets look at the relevant code snippet;

content = new layerSetup(null,"contentLYR",220,120,7,"visible");

In Layman's terms the above line could be read as the content variable equals a new JavaScript object, that is not nested, that has an id attribute of contentLYR, is positioned 220 pixels from left of screen and 120 pixels from top. The content variable also has a z-index attribute of 7 and its visibility is set to visible.

You will also note that immediately after the content variable line is the following code snippet;

sizeAnimator(content, page.width-300, page.height);

Let's look at the actual function it points to so we can better understand what is occurring here.

function sizeAnimator(obj,width,height) {
if(ns4) {
obj.clip.right = width;
obj.clip.bottom = height;
} else {
obj.width = (w3dom) ? width + "px" : width;
obj.height = (w3dom) ? height + "px" : height;
}
}

The function is a simple way to allow for a layer to be resized dynamically. We use three arguments; obj which is used to identify the id attribute of the element and width and height which are used to specify the width and height of the element. Consequently, we can read the following as;

sizeAnimator(content, page.width-300, page.height);

Resize the contentLYR (remember content points to contentLYR) to the browsers width minus 300 pixels and the browsers height. Therefore this line is used to control the width and height of the element.

There is a very good reason for using a function that adjusts the width and height of an element dynamically. In Netscape 4, if you dynamically load content into a layer, the OS scrollbars don't adjust automatically to cater to the new height of that layer.

As we shall see later on there is a way to circumvent this bug. For now though, it is important to understand that the sizeAnimator() function is used to dynamically adjust the width and height of an element and the initial adjustment of the layers width and height is performed at this line;

sizeAnimator(content, page.width-300, page.height);

If you wanted the layers size to be something else then all you need to do is to adjust width and height arguments to your preferences. For example if we wanted a 300*200 layer then we would use the following;

sizeAnimator(content, 300, 200);

The final line of our objectSetup() function simply sets a timer and tells the browser to load the page by pointing to the actionManager function;

setTimeout("actionManager('start')",500);

To adjust the speed of the initial page load simply adjust the 500 value. For example if we changed 500 to 1000, it would be a second before the page loads as opposed to half a second (the 500 value).

The actionManager() function looks like so;

function actionManager() {
pageManager("load_page","content/page1.html");
}

It is used as a hook into another function that utilizes a case / switch JavaScript method, which consequently triggers the load_page case and tell that function to use the value of content/page1.html. If you wanted to point to a different page then simply change the values to point to the directory the html page is in and the name of the html page.

The next phase is to create a script that will allow the external content to be loaded into the main interface. To handle this we are going to use a Case / Switch method with three cases;

Load_Page

The load_page case is used to load the external html page into an IFRAME for Internet Explorer and Netscape 6, while for Netscape 4 it loads the content directly into a CSS Layer.

A couple of things to note;

* To ensure that the external html page is displayed at the correct top position on each successive load of a page, the content variables top position is reset to the same value as when the page is first loaded. This overcomes the problem of the page loading in odd positions when it is scrolled. The relevant line is;

content.top = (w3dom) ? 120 + "px": 120;


* For Netscape 4 the width of the layer needs to be reset on each successive load of the page, so set the width of the layer to match the width as defined in the defineObjects() function. The relevant line in the load_page case is;

if(ns4) {
content.load(args[1],page.width-300);

* If the id and the name attribute of the iframe are something other than frameData, then please ensure that the script sections match. In the snippet that follows you would simply change references to frameData to match what you have used as a name and id attribute value of the IFrame in the HTML.

case "load_page" :
content.top = (w3dom) ? 120 + "px": 120;
if(ie4) {
document.frames["frameData"].document.location = args[1];}
if(w3dom) {
document.getElementById("frameData").src = args[1];
}
if(ns4) {
content.load(args[1],page.width-300);

page_is_loaded

The page is loaded case is utilized to overcome a bug with external HTML loading when the page is refreshed. A JavaScript error will occur if a page has already been loaded and then the page is refreshed, for some reason browsers seem to think two pages are being loaded at once, consequently to overcome this we set a delay timer and load subsequent pages at 2 seconds. You can modify the timer to suit.

As mentioned previously Netscape 4 does not resize the OS scrollbars correctly on loading of new content. To overcome this problem, we set reset the layers height and width, but that in itself doesn't cure the problem. We also have to force a psuedo resizing of the Web page, so that Netscape 4 can reflow the page correctly. To do this we use the window.resizeBy() method and make the browser 1*1 pixels larger than its original size and then resize it down by -1*-1 so that it matches the users original browser dimensions.

case 'page_is_loaded' :
if(ns4) {
sizeAnimator(content,page.width-300,content.htm.height);
// window.resizeBy(1,1);
// window.resizeBy(-1,-1);
}
clearTimeout(page_timer);
page_timer = setTimeout("pageManager('display_page')",2000);
break;

display_page

The display page case is an Internet Explorer and Netscape 6+ and Opera specific function that shifts the content from the IFrame to a <div> by using innerHTML. Because Netscape 4 has the ability to load content into a layer directly there is no need to shift content around.

One point that needs to be remembered is that the external page needs to have an id attribute in the body tag of the document. In this instance the id attribute used is “body”. For example;

<body id="body">

Consequently the script for browsers that support standards must also reference the body id attribute. If the id attribute is changed to something else other than body, then the script must correspond as well. For example, if we changed the body value to eddie

<body id="eddie">

Then the relevant line in the function to change would be;

content.htm.innerHTML = window.frames.frameData.document. getElementById('body').innerHTML;

And it would change to;

content.htm.innerHTML = window.frames.frameData.document. getElementById('eddie').innerHTML;

Here is the display page code snippet.

case "display_page" :
if(ie4) {
content.htm.innerHTML = document.frames['frameData']. document.body.innerHTML;
} else if(w3dom) {
content.htm.innerHTML = window.frames.frameData.document. getElementById('body').innerHTML;
}
break;


That’s it for the scripting part. Let us now take a look at the external html page that will be loaded into the main interface.

<html>
<head>
<title>DHTML Applications: Welcome Page</title>
</head>
<body id="body" onload="parent.pageManager(‘page_is_loaded’)">
<p class="main1">this is content</p>
</body>
</html>


The really important line in the external html page is the <body tag>. As mentioned previously it must contain an id attribute value. In addition it must also include the following event handler

onload="parent.pageManager('page_is_loaded')"

Noteworthy points include;

* CSS styles are linked to or embedded in the main interface page, not the external html page as it will cause older browsers to crash. The style will be correctly applied to the external page once that page has been loaded into the main interface. For example,

<p class="main1">this is content</p>

The class main1 is defined in an external CSS file which is linked from the main interface page, not the external page being loaded into the main interface. Once the external page has been loaded into the main interface the class main1 will be applied correctly.

Similarly JavaScript functions should be placed in the main interface, not the external html page. To clarify, lets assume we have a function called playMusic(). This function would be placed in the main interface or linked to as linked js file from the main interface. Then to call the function from the external html page you would use something similar to this

<a href="#" onmousedown="parent.playMusic()"> Play the Music </a>

The trick here is to always refer to the parent page (the main interface) by using the parent.functionhere() syntax. Remember we are working with IFrames here so frames based syntax needs to be utilized.

You can load any content you desire in the external HTML pages, including images, flash, applets etc...

Loading Pages Onto Themselves


To load external pages into external pages you use the following syntax;

<a href="#" onclick="parent.pageManager('load_page','content/page2.html')"> load page 2</a>

Loading Pages From The Main Interface

To load external pages from the main interface you do NOT use the parent.function here syntax, you just refer directly to the pageManager() function and use ‘load_page’ as the first argument, and specify the directory and the name of the page in the second argument. The following code snippet illustrates this;

<p><a href="#" onclick="pageManager('load_page','content/page1.html')"> Load Page 1</a></p>

The HTML Tags

In the body section of the main interface you must include an IFrame tag like so;

<iframe frameborder="0" width="100" height="100" id="frameData" name="frameData" scrolling="no" src=""></iframe>

And also include a <div> tag with contentLYR defined as the id attribute e.g.

<div id="contentLYR"></div>


The style attributes for contentLYR can be either inline or as in the provided example contained within a CSS file.

An important consideration is that when you are working with XHTML documents then Opera 7 requires you to use proper character entities in the document. For example;

<a href="#" onmousedown="pageManager(‘load_page’,’content/page1.html’)"> Load Page 1</a>


Would become

<a href="#"
onmousedown="pageManager(&#39;load_page&#39;,&#39;content/page1.html&#39;)"> Load Page 1</a>


It is a good idea to use character entities as a rule in XHTML documents as it is technically more correct.

Thats it for this tutorial!


Publication Date: Friday 5th September, 2003
Author: Eddie Traversa View profile

Related Articles