27 may 2013

MouseOut close of ADF Popup with dialog inside.

A couple of months ago I was helping development of ADF 11g application. One of the most important parts of that app was the menu bar.

This menu was designed to support 4 levels, and almost 900 different options items. One of the problems that I faced was that some options of the menu opens a af:popup with an af:dialog inside. That options needs to be opened when user clicks, and self-closed when user moves out of dialog.

In summary, I need the mix of "mouseHover" and "action" types on my <af:showPopupBehavior> tag.

Check the following video to see the menu in action.



To modify the expected behavior of <af:showPopupBehavior> type "action" I've created this simple JavaScript function to overwrite ADF JS library.

AdfShowPopupBehavior.prototype.fire = function (a)
{
    a.cancel();
    var b = AdfPage.PAGE, c = this._type, d = a.getSource(), e = d.getClientId(), f = d.findComponent(this._popupId);
    if (AdfPage.PAGE.isScreenReaderMode() && (c == "mouseHover" || c == "mouseMove" || c == "mouseOver" || c == "mouseOut"))
    AdfLogger.LOGGER.fine("showPopupBehavior trigger type " + c + " suppressed in screen reader mode for launch source id: " + e);
    else if (f)
    {
        var g = f._delayedActivationState;
        if (g)
        if (g.launchSourceId == e)
            return;
        else 
        b.cancelTimer(g.timerId), 
        delete f._delayedActivationState;
        if (f.isPopupVisible())
        {
            var h = this._getPopupWindow(f);
            if (h == null)
            return;
            g = this._isInlinePopupSelector(h);
            h = this._wasOpendedFromSameSource(h, e);
            if (g)
            {
                if (f.hide(), h)
                    return 
            }
            else 
            h || f.hide()
        }
        var k = this._align, h = this._alignId, g = 
        {
        };
        g[AdfRichPopup.HINT_LAUNCH_ID] = e;
        var l = this._type == AdfComponentEvent.CONTEXT_MENU_EVENT_TYPE;
        if (h || k || l)
        {
            k && (g[AdfRichPopup.HINT_ALIGN] = k, h || (h = d.getClientId(), k = h.lastIndexOf(":"), k !=  - 1 && (h = h.substring(k + 1))));
            h && ((k = d.findComponent(h)) ? g[AdfRichPopup.HINT_ALIGN_ID] = k.getClientId() : AdfLogger.LOGGER.warning("Unable to find align component: ", h));
            if (l)
            g[AdfDhtmlPopupWindow.HINT_TYPE] = AdfDhtmlPopupWindow.HINT_TYPE_MENU, g[AdfDhtmlPopupWindow.HINT_AUTODISMISS] = AdfDhtmlPopupWindow.HINT_AUTODISMISS_MENU, h || (h = AdfAgent.AGENT.getMousePosition(a.getNativeEvent()), g[AdfDhtmlPopupWindow.HINT_MOUSEPOSITION] = h);
            if (c == "mouseHover" || c == "action")
            g[AdfDhtmlPopupWindow.HINT_AUTODISMISS] = AdfDhtmlPopupWindow.HINT_AUTODISMISS_MOUSEOUT, g[AdfDhtmlPopupWindow.HINT_AUTODISMISS_MOUSEOUT_ID] = g[AdfRichPopup.HINT_ALIGN_ID] ? g[AdfRichPopup.HINT_ALIGN_ID] : g[AdfRichPopup.HINT_LAUNCH_ID]
        }
        a.getType() == AdfUIInputEvent.MOUSE_IN_EVENT_TYPE ? (a = b.scheduleTimer(this, this._onMouseOverTimeout, 
        {
            hints : g, popup : f, source : d
        },
500), f._delayedActivationState = 
        {
            timerId : a, launchSourceId : e
        },
        d.addEventListener(AdfUIInputEvent.MOUSE_OUT_EVENT_TYPE, this._fireCancel, this)) : f.show(g)
    }
    else 
    AdfLogger.LOGGER.severe("Could not find popup ", this._popupId, " from component ", d)
};

The line that changes the expected behavior is this one:

if (c == "mouseHover" || c == "action")

Include that JS fragment in a JS file and later import to your pageTemplate or JSPX page:

<af:resource type="javascript" source="../js/popbehavior.js"/>

Also, you can modify the delay time of mouse-out event modifiying the "500ms" value in the JS code.

 Useful links