Portal Menus and Windowed Controls (WLP 8.1)
Abstract
In Internet Explorer (IE), dynamically positioning HTML elements in an overlapping fashion can lead to some elements hiding others. Unfortunately, this can occur in BEA WebLogic Portal 8.1 when multilevel menus are used on a portal page that contains specific HTML entities such as the SELECT element or ActiveX controls. The undesirable result is that the menu is partially hidden. This article shows an HTML technique that eliminates this problem by ensuring that menus are rendered above all other elements on the portal page.
Introduction
Dynamically displayed menus in HTML are often created by using DHTML effects
that selectively show or hide portions of the HTML page. The menus created by WebLogic Portal use this
technique by surrounding the menu items in standard HTML DIV
elements. These elements are
then toggled on and off by JavaScript code embedded in the page and in supporting JavaScript files
such as menu.js
. Here's how a simplified example of this would appear:
<DIV id='menu1Title' onClick='toggle("menu1")'>File</DIV> <DIV id='menu1'> <DIV id='menuItem1'>Open</DIV> <DIV id='menuItem2'>Close</DIV> <DIV id='menuItem3'>Exit</DIV> </DIV>
All of this is quite standard, and generally portal developers do not need to be exposed to this underlying implementation mechanism. But some browsers are notoriously fickle, and the technique outlined above can lead to a problem. While the solution works fine in Mozilla and Firefox, there is an issue with this technique when used with IE due to an IE implementation artifact dealing with windowed controls.
Windowed controls is a term used by Windows developers to refer to controls that have a windows
handle (HWND, for you Win32 geeks out there). These controls are managed and rendered by the
operating system, not by the browser. When Microsoft created IE, it opted to use the existing Windows
implementation of a combo box—a windowed control—as the implementation of the HTML SELECT
element. In addition to this, other embedded objects including ActiveX, Flash, and the Adobe PDF viewer
are implemented as windowed controls.
For the purposes of this article, you don't need to understand handles or Win32 but just realize that since windowed controls are managed by the operating system and not the browser, HTML elements based on windowed controls have characteristics that may be different from regular HTML elements.
The Problem
Cutting to the chase, windowed controls are the cause of a long-standing problem in
IE with dynamically positioning and showing DIV
elements in a web
page. Specifically, windowed controls show through the DIV
,
spoiling the intended effect. This tends to be most noticeable in web
pages that utilize DIV
elements to display menus, and unfortunately
the menu in WebLogic Portal is afflicted with this problem as
shown in Figure 1.
Figure 1: A drop-down menu hidden by windowed controls
In the figure, the various selection boxes in the first portlet cover the drop-down menu,
resulting in a menu that is effectively unusable. As most HTML developers know, the display of
layered HTML elements in a browser is controlled by the zIndex
property. Elements with
a higher zIndex
appear on top of elements with a lower zIndex
. The problem arises because the zIndex
of HTML elements and that of windowed controls are treated differently,
and IE always places windowed controls on top of all HTML elements. This makes it impossible to
place a DIV
over a SELECT
and not have the SELECT
showing
through the DIV
.
This issue is covered in the MSDN
documentation of the DHTML zIndex
property, where it is explicitly stated that the
zIndex
property does not support windowed controls.
A Solution
Fixing this problem for IE 5.5 and above is surprisingly simple due to a relatively new
JavaScript technique as well as a change in the behavior of the IFRAME
element. In IE 5.5 and
above, the zIndex
of the IFRAME
element is respected by both windowed
controls and HTML elements. This means that you can place an IFRAME
on top of a
SELECT
, and it will cover the SELECT
windowed control. Additionally, you
can place a DIV
on top of the IFRAME
, and neither the
IFRAME
nor the SELECT
it covers will show through.
This extra IFRAME
that is placed beneath a DIV to hide windowed controls is
called a shim since it is only present to act as a cover for the DIV
.
Numerous sources discuss using this technique, and we highlight one of them in the References
section below. The change in behavior of the IFRAME
element’s
zIndex
property also is covered in the MSDN documentation.
Applying the Shim Technique in WebLogic Portal
So now that we understand the shim technique, we need to apply it to the WebLogic Portal
menus. Fortunately for us, the menus in WebLogic Portal are generated in JavaScript, and this makes it
extremely easy for us to hook our shim into the menus. We only need to alter one file,
menu.js
, which is located in the framework/skins/default/js
directory. Documenting how menu.js
renders the menu is beyond the scope of this article, but detailed information about this process
can be found in the WebLogic Portal User Interface Framework Guide.
The first change we need to make is to add all our new shim functions for creating, displaying, and hiding the shims as needed. Note that because WebLogic Portal displays many menus simultaneously as the user drills down through various child menus, we need to have the ability to create and display multiple shims. Essentially, there is a one-to-one relationship between a shim and each menu. Each shim is created with an id that corresponds to the menu it is associated with.
Unfortunately, since menu.js
may change between WebLogic service packs, it is not
feasible to simply supply a modified menu.js
. Instead, to apply the solution, you will
need to manually modify your menu.js
file, but rest assured it is an easy task.
The first step is to add some basic JavaScript functions to the file that will be referenced later
when opening and closing menus. These functions appear below; simply append these functions at the
end of menu.js
.
//Opens a shim, if no shim exists for the menu, one is created function openShim(menu,menuItem) { if (menu==null) return; var shim = getShim(menu); if (shim==null) shim = createMenuShim(menu,getShimId(menu)); //Change menu zIndex so shim can work with it menu.style.zIndex = 100; var width = (menu.offsetWidth == 0 ? menuItem.renderedWidth : menu.offsetWidth); var height; if (menu.offsetHeight == 0) { var menus = getMenuItemCount(menu); height = menuItem.renderedHeight * menus; } else { var height = menu.offsetHeight; } shim.style.width = width; shim.style.height = height; shim.style.top = menu.style.top; shim.style.left = menu.style.left; shim.style.zIndex = menu.style.zIndex - 1; shim.style.position = "absolute"; shim.style.display = "block"; } //Closes the shim associated with the menu function closeShim(menu) { if (menu==null) return; var shim = getShim(menu); if (shim!=null) shim.style.display = "none"; } //Creates a new shim for the menu function createMenuShim(menu) { if (menu==null) return null; var shim = document.createElement("<iframe scrolling='no' frameborder='0'"+ "style='position:absolute; top:0px;"+ "left:0px; display:none'></iframe>"); shim.name = getShimId(menu); shim.id = getShimId(menu); //Unremark this line if you need your menus to be transparent for some reason //shim.style.filter="progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"; if (menu.offsetParent==null || menu.offsetParent.id=="") { window.document.body.appendChild(shim); } else { menu.offsetParent.appendChild(shim); } return shim; } //Creates an id for the shim based on the menu id function getShimId(menu) { if (menu.id==null) return "__shim"; return "__shim"+menu.id; } //Returns the shim for a specific menu function getShim(menu) { return document.getElementById(getShimId(menu)); } function getMenuItemCount(menu) { var count = 0; var child = menu.firstChild; while (child) { if (child.nodeName=="DIV") count = count + 1; child = child.nextSibling; } return count; }
After you paste
these new functions into the bottom of menu.js
, the next step is to
hook the new functions into the various menu functions. The first
function to change is called openMenu()
. If you search for it
in menu.js
you should see it takes three parameters: menuItem
,
menu
, and depth
. All you need to do here is add one new
line of code at the end of the method:
openShim(menu,menuItem);
Notice that this is a call to one of the functions we added earlier. All this does is ensure a shim gets created and displayed every time a menu is opened.
Our next modification is to ensure we close the shim when a menu is closed. To do this, we
modify the closeAllChildren()
method in menu.js
. Specifically,
you need to add one line to the method; first look for this existing line in the method:
subMenu.style.display = "none";
Right after this line, add a new line to close the shim:
closeShim(subMenu);
Again, this is a call to one of the functions that we pasted in earlier.
Congratulations! You have finished the modifications. If you did everything correctly, your WebLogic Portal menus now should work correctly in IE 5.5 and above, as shown in Figure 2.
Figure 2: Menus that correctly overlay the underlying windowed controls
Conclusion
This article
exposed the problem of HTML elements that are sometimes hidden by
other DHTML elements when the page is rendered with Internet Explorer.
We showed how using an IFRAME
shim technique can easily correct this
problem, and we demonstrated how to modify WebLogic Portal to utilize this solution.
The result is a solution that enhances the look of your
portal and increases user satisfaction.