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
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
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.

References