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>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>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('load_page','content/page1.html')"> 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
an example is located here