HierMenus CENTRAL: HierMenus In Progress. HierMenus 5.2 Release Notes (4/6)
[previous] [next] |
Cross-Frame Event Timing in Opera 7
While testing the new code additions described on the previous page, we uncovered an unusual behavior specific to Opera 7 cross-frames implementations. Specifically, while all top level menus displayed properly, child menus of those top-level menus either displayed sporadically or refused to display at all. The problem seemed to strike somewhat randomly, as a single menu could have both working and non-working child menus attached to it.
Naturally, our first instinct was to check our newly added menu creation and/or Keep In Window logic for discrepancies that may cause such a problem. However, we soon found that even with the new logic "turned off" (i.e., leaving the actual code in place but setting the menu parameters to create all menus on load and allow the Keep In Window behavior to be applied to all top level menus) we still saw the problem--even though the same test pages in previous HM versions did not display the error. The existence of the additional logic itself seemed to be enough to trigger the error; which often means there's some type of timing problem involved. The additional logic "shifted" the processing within the code just enough to trigger problems that we hadn't seen before.
After further diagnosis we were able to conclude that the problem was due to the unique handling of events in Opera 7 cross frames scenarios. Unlike the other major browsers, it appears as though Opera 7 will fire events that originate in two separate frames simultaneously, rather then ensuring that the events are processed sequentially. If you're skeptical of this point, have a look at the following example page, which opens in a new window. (Note that this example page is crafted specifically for Opera 7, other browsers will not get the same results and may produce confusing and/or problematic displays):
In this page, we present a simple two frame page, with all of the JavaScript functions defined in the top frame. Four text input boxes in the top frame serve as message windows, allowing us to see the processing of our target events.
Once loaded, rolling over a link in the top frame will fire a
function called mouseOver which is also defined in the top frame.
mouseOver does little more than execute a counting loop, displaying
its progress in the designated text input box of the top frame:
function mouseOver(desc,mdString,loopMax) { var theMC=document.getElementById('mc'); theMC.value=desc; var j=0; var k=0; for(var i=1;i<=loopMax;i++) { var theMD=document.getElementById(mdString); theMD.value=desc+": "+i; j=i; k=j-1; } return true; }
Within the lower frame is an additional link that will execute the same mouseOver function above when rolled over. This lower link tells mouseOver to direct its counting display to the right hand input box. It also reduces the number of iterations for the main loop, so the lower mouseOver will typically finish before the top mouseOver. In Opera 7, the result is that both counters continue to increment simultaneously--the same function is executed twice from two different event triggers. Local variables for the function are properly separated/isolated between the two calls; but any global variables accessed by the function (such as the theMC value setting) are shared by both.
This simultaneous processing has a major effect on HierMenus processing, since HM in several instances assumes that certain events will follow each other sequentially. For example, We assume that the MenuOut and ItemOver (or MenuOver, depending on the browser) handlers will be fired in succession as a user rolls over individual items of a menu. When we set timers from within these events (such has hover timers for the display of child menus in ItemOver, or hide timers in MenuOut), we can assume that those timers won't fire until after the current event queue--specifically the new MenuOver and ItemOver calls--have fired. As you can tell from the above example, this assumption is invalid in Opera cross-frames scenarios, since the events (the actual mouse activity, in this example) and the timers (for hover or hide times) are triggered from two different windows and can thus occur simultaneously, or in a different order than we expected. In the case of the child menus that wouldn't appear, we found that the displayChild function, which is called from a timeOut in the navigation frame, was, in some cases, executing between (or in tandem) with the ItemOver and MenuOver calls generated by the menu item itself. The result is that no menu is displayed--since the displayChild function first checks to make sure that a menu is actually being rolled over at the time of the display, and the MenuOver call that would tell it precisely that has not yet been processed.
A related problem occured in that it was possible to pop up the wrong menu from a particular navigation page link. Again, this is due to the events of the navigation page (the mouseover of the link that would spawn the menu) occurring simultaneously with the events of the content page (in this case a queued MenuOver call from rapidly rolling the mouse over multiple items in a displayed menu). The MenuOver call resets the global HM_CurrentMenu parameter--the same parameter used by the PopUp function--while the function is in the process of displaying the selected menu.
Though not particularly pretty, our solution is to force all of the relevant HierMenus events--including timeOuts--to occur from within the content frame of the document. This will ensure that each of the events is processed in the right order, the order that we expect based on the mouse activity within the document.
To accomplish this, each of our timer events is re-routed to be set (and if necessary, cleared) using the content frame window as a base. Additionally, each of the event handlers that is called as the result of an event that is triggered in the navigation frame is also re-routed to be called as a timer that is fired from the content frame window. In this latter case, be careful to grab whatever you need from the event object that is passed to the event in the first place, and pass those pieces of information to the code that will be executed from the timer in the content page. Our new HM_f_PopUp routine for Opera, for example, now looks like this (line-wrapped for clarity):
// 5.2 function HM_f_PopUp(menuname,e){ if(!HM_AreLoaded) return true; if(HM_IsReloading||!HM_f_DocumentCheck()) return true; HM_MenusTarget.HM_NavWindow=window; var eSrcId=e.srcElement.id; if(!eSrcId) eSrcId=e.srcElement.id=menuname+"Clicker"; var eType=e.type; var eClientX=e.clientX; var eClientY=e.clientY; var TimeoutCommand = "if (window.HM_NavWindow) "+ "HM_NavWindow.HM_f_PopUpReal('"+ menuname+"','"+ eType+"',"+ eClientX+","+ eClientY+",'"+ eSrcId+"')"; HM_MenusTarget.setTimeout(TimeoutCommand,1); return true; }
After setting a variable in the content frame that will point back to our current window (HM_NavWindow), we then build a timeOut call that will pass the necessary event parameters to the actual code that will display the selected menu. And since that function call originates from the event queue of the content page and not the navigation page, it honors the event processing order that exists within the content page event structure itself.
Sometimes it pays to try unique experiments with your code. While working through potential work arounds for the issue described above, we discovered an interesting Opera trick that may help us solve the Opera 7 unsynchronized menu problem for good...
Created: August 28, 2003
Revised: August 28, 2003
URL: https://www.webreference.com/dhtml/hiermenus/inprogress/7/4.html