|
home / bulletins / 2 / page 6
HierMenus 5.0: Developmental Considerations
For developers following along with the ongoing HM Development,
a number of issues were raised in the creation of HM5 that may be of interest.
Primary among these are the cross-frames specific issues of building the menus
in the content frame (and especially the timing of this process),
and the trapping and handling of access errors resulting from the Same-Origin
policy. Some additional cross-browser implementation issues will round out our
release notes for this version.
Loading the Menus
As noted earlier in this article, a key design goal of HM5 was
to allow the script to be downloaded once and only once per frameset. Specifically,
the script should be downloaded and implemented as part of the navigation page
of the frameset, and not repeatedly downloaded with each new content page, in
order to preserve this key advantage to using a frames-based design in the first
place. For the initial load of the menus, we simply trap the onload
handler of the frameset itself, which is supported in all HM-capable
browsers. But what about when the content page changes? In order to
display menus within the content frame, the menus themselves must be created
and attached (using appendChild or some similar method) to the actual
document in the content frame, so once the content page is changed the menus
we've so carefully created are gone. So how do we know when to reload them?
It turns out there are three different methods utilized by HM
to determine when to rebuild the menus in the content frame; four when you
consider the differing methods used for different browsers.
1.) Monitor the load event of the content frame.
This ends up being the simplest and most efficient of all of
our methods, as it allows us to know conclusively when a new
page has been loaded into the content frame each time
a new page is loaded. We can then use
the firing of that event to tell us when it's time to rebuild
the menus themselves in the content frame (assuming, of course,
security checks on the content frame's document pass).
Netscape 4.x supports this event via its event registration
methods:
HM_LoadElement.captureEvents(Event.LOAD);
HM_LoadElement.onload = HM_f_CheckFrameLoad;
And then:
function HM_f_CheckFrameLoad(e){
if (e.target.name == HM_FramesMainFrameName){
HM_f_FrameLoad();
routeEvent(e);
}
}
And later versions of Mozilla (1.1+), Netscape (7+) and Konqueror (3.0+)
support the assignment of the frame onload via the standardized
method:
var FrameEl=parent.document.getElementsByName(HM_FramesMainFrameName)[0];
FrameEl.addEventListener('load',HM_f_FrameLoad,false);
HM_FrameHasLoadHandler = true;
Internet Explorer, however, provides a very interesting case. Though we can retrieve
the frame element itself via the same getElementsByName call
above, setting its onload handler, either directly via FrameEl.onload= or
via the Microsoft specific FrameEl.attachEvent() results not in
the assignment of the frame load handler, but rather in the assignment of the
frameset load handler. Yet, attaching a simple handler to the
onload attribute of a frame tag verifies that both IE5.5 and
IE6 will fire the load event with each swap of the content page!
For IE, therefore, we've resorted to the admittedly kludgey method of looking
for something specifically triggered from a user supplied onload handler to tell
us if such a handler is actually in use. If we see this "something," (in our
case, the setting of a specific variable in the parent) then we know the handler
actually exists and can latch on to it for further page swaps. The code we ask
users to insert into their frame onload handlers is as follows:
onLoad="HM_UseFrameLoad=1;if(window.HM_f_LoadMenus)HM_f_LoadMenus();"
And the (condensed) version of the HM code used to "look" for this onload is:
if(!HM_LoadCheckDone) {
if(parent.HM_UseFrameLoad) {
HM_FrameHasLoadHandler = true;
parent.HM_f_LoadMenus = HM_f_FrameLoad;
}
HM_LoadCheckDone = true;
}
Clearly not the prettiest thing in the world; but it works, and has the added
advantage of working for any browser that supports frame loads regardless of their
event handling methods.
2.) Check document.readyState.
For browsers that do not support frame loads but do support the
document.readyState property (that's Internet Explorer 4.0/5.0,
IE5.5+ without the onload kludge described above, and Opera 7+) this
property is interrogated for the value complete before attempting to
build menus in the content page.
3.) Hook timers to the page unload.
All other browsers use the clumsy method of setting timers that begin firing when
the content page is unloaded and check for the presence (and accessibility) of the
content page's document. As soon as the content document can be recovered (without
any accessibility errors) and it appears to be a legitimate object, we begin
the process of rebuilding the menus on the page. We also search the content page
document for the existence of the last menu created on the previous page. If this
menu exists, then the content page document we are looking at is not the
newly loaded page; but instead is the existing page, which has not fully unloaded
yet. In this case, we reset the timer and keep waiting.
Clearly this
is the least-desirable option and is therefore saved for last, after the other two
options have failed.
Handling Access Errors
When attempting to access or manipulate properties in another
frame, there is always the possibility of triggering access errors as the result
of the browser's Same-Origin policy (described more fully on an earlier
page). Thus, error handlers must be put in place to examine these errors and
return from them gracefully in the event they are encountered. Again, two methods
were employed, depending on the browser version being used.
For later version browsers (all for which basic DOM processing is
supported) the elegant try..catch structure is the way to go, as it allows
us to localize the "security checking" of the target document via simple statements
such as (line wrapped here for clarity):
function HM_f_DocumentCheck() {
var ErrorFound = false;
var theDocument = null;
try{
var TypeOfDocument = typeof(HM_MenusTarget.document);
if((HM_IE||HM_Opera)&&(typeof(HM_MenusTarget.document) == "unknown"))
return false;
if((HM_IE||HM_Opera)&&(HM_MenusTarget.document.readyState != "complete"))
return false;
theDocument = ((HM_MenusTarget)&&(HM_MenusTarget.document)) ?
HM_MenusTarget.document : null;
var DummyMenu = HM_MenusTarget.document.getElementById('HM_dummy_menu');
if(theDocument) ErrorFound = false;
}
catch(e){
ErrorFound = HM_f_PermissionDenied(e);
}
return (!ErrorFound);
}
The above contains a couple browser-specific workarounds to make sure that,
if an error is indeed triggered, that it will be the access error (and not some
other type of error report). For example, in Opera 7 it is enough to test for the typeof
the target document to trigger an access error, hence that statement comes first.
Additional checks are then made specifically for the document.readyState
variable (for those browsers that support it) and the existence of the target document itself
(for other browsers). Finally, since some Mozilla versions won't trigger an access error with
any of the previous statements, a getElementById call is processed for a dummy menu
item. We don't need the actual dummy menu itself (indeed, it doesn't exist), we only need to
know that we can use the method. Once an error is found, it's passed to a
specific function that will interrogate the error message and return true if it appears to
be one related to permissions.
For earlier browsers, we're limited to hooking the onerror handler
directly, interrogating the message provided to it, and returning true if a permission
error is found and passing on the error handler to the original error handler (if there
was one) if not. We are careful to return the value that is returned to us from
the original error handler, so as to preserve its intentions.
On our final page of this release article, we take a quick look at some
of the other specific cross-browser issues that surfaced during our HM5 development.
      
[previous] [next]
|