Pretius APEX Context Menu v1.1.0 in details

At the very beginning, I wanted to release the plugin as v1.0.1 which means several issues have been fixed. But the release 1.1.0 contains not only small fixes but new features that change how the plugin can be used. In this article, I will describe all the changes. If you want to use the full potential of the plugin you should read it till the end.

At first, let me list all the changes that I have made:

  • accessibility – keyboard support
  • new Settings options
    • Display at Mouse Position
    • Narrow to Affected Elements
    • Stop Event Propagation
  • access to dynamic action attributes in Override Behaviour and Add Extra Entries
  • custom event handling
  • enhanced support for debugging
    • browser console-level
    • application-level (notifications)

Accessibility support

You might already know how much effort Oracle APEX development team put to make APEX components accessible for all users independently on their disabilities. APEX native menu widget supports the keyboard very well. Unfortunately, my previous release of the plugin (which is based on APEX native menu) wasn’t able to make triggering elements (especially elements that are not buttons) accessible via keyboard and it was because of the way the plugin was initialized.

With version 1.1.0 the plugin supports accessibility a little bit better (focused element reacts to arrow down key). Right now you can make a button or any anchor accessible using a keyboard. To do so, use Click, Page Load or After refresh event and set Fire on Initialization to Yes. As a result, a triggering element [1] can be focused and it opens a menu after a key down event occurs (enter or down arrow).

Navigate using Tab key and invoke menu using down key arrow.

menu bound with non-button element won’t focus first menu entry which requires another down key arrow to focus first menu entry. To avoid such a behavior use button not anchor or any other HTML element.

sidenote [1]

Settings attribute has new options

Display at mouse position

In the previous release, context-menu was meant to be used in 2 modes. The first mode (triggering element set to document) was dedicated to regions only and context-menu was positioned using mouse coordinates (page X and page Y). Second mode was dedicated for report cells (or any other jQuery selector) and a menu was displayed below triggering element.

The first release was invoking a context-menu below cells

I wasn’t satisfied with such an approach and I wanted to have a context-menu positioned to X and Y in any scenario – especially in case of report cells. Because of that, I have implemented the new attribute which uses available API (menu open method) to display a menu using mouse X and Y position.

Release v1.1.0 allows a context-menu to be aligned with mouse position

After I have implemented this enhacment I have faced a new problem – I wasn’t able to reference the triggering element using an entry action function.

...
"action": function( pMenuOptions, pTriggeringElement ){
  //Code to be executed when users clicks on entry
  //pTriggeringElement is null
  return void(0);
},
...

When a menu is displayed using X and Y, the native menu widget loses the context of the triggering element – pTriggering argument (line 2) is null.

In fact, it is not a big deal when the context of the triggering element is not needed – for example, action has a general context ie. reload report data. If taken action must compute something based on triggering element (row, cell, data from a table) then the context of triggering element is crucial.

Fortunately, there is a simple solution – entry action context can be easily overridden using the proxy (jQuery method) to change context of any function.

...
"action": $.proxy(function( pMenuOptions, pTriggeringElement ){
  //pTriggeringElement is null (result of using open method with X & Y)
  //this has been overridden by this.element and points to triggerin element
  return void(0);
}, this.element),
...

In line 6 you can see this.element which is part of the next enhancement described in section Enhanced attributes

Display at mouse position attribute is now available to use but keep in mind that it has the following limitations:

  • it works for any jQuery selector that is not a button – buttons always display menu aligned to it,
  • to pass a reference (of triggering element) for entry action function, jQuery proxy method has to be used
Narrow to Affected Elements

I have enhanced the plugin settings attribute with an option that allows APEX developer to change how Affected Elements should be used by the plugin. But before I explain this attribute in details let me describe the scenario that we APEX developers struggle in daily routine.

When a developer wants to bind an anchor (with attribute class set to action) in an APEX report to execute some action, then he can implement it in two [2] approaches:

developer can also make anchor execute custom event using onclick attribute and pass this as dynamic action data to reference anchor

sidenote [2]

Scenario 1

Dynamic Action definition

  • Event set to Click
  • Selection Type set to jQuery Selector
  • jQuery Selector set to .action
  • Event Scope set to Dynamic
  • Static Container (jQuery Selector) set to body

Scenario 2

Dynamic Action definition

  • Event set to After Refresh
  • Selection Type set to Region
  • Region set to Example Report

True Action: Execute JavaScript Code

  • Code attribute contains JavaScript executing the desired action in the context of this.triggeringElement
  • Affected Elements can be used to use extra APEX components needed to perform the desired action

Triggering element is computed from jQuery selector

True Action: Execute JavaScript Code

  • Code attribute contains JavaScript executing the desired action in the context of this.affectedElements
  • Affected Elements have to be used to bind the desired action with DOM elements from Example Report

Affected elements are computed from jQuery selector

$('.action')

which describes all DOM elements having a class action.

$('.action')

which describes all DOM elements having a class action.


In both scenarios, a developer ends up with elements that are not only part of Example Report but the whole page! To avoid such behaviour developers usually set Region Static ID to some value and then reuse it in Dynamic Action or Affected Elements jQuery selector. It works but it is not generic which means every change of Region Static ID has to be populated to each dynamic action.

I have decided to create a generic workaround which is part of this release. Of course, APEX developers might don’t want to use it. This option is not mandatory, developers can use it but they are not forced to change their habits. I think this approach is fine and I wonder whether you like it.

But how does it work? If developer check option Narrow to Affected Element then Affected Elements are computed according to the following rules:

  • an Affected Element must be children in DOM tree of Triggering Element or
  • be an equal Triggering Element

In the case of contextmenu event there is one additional rule:

  • this.browerEvent.target must be equal one of resulting Affected Elements or be a child of it in DOM tree

If my description is not clear enough for you, then you are welcomed to inspect it in file pretius.handleDa.js. Check function getNarrowedAffectedElements, which definition I put below.

pretius.handleDa.js, function getNarrowedAffectedElements
  ...
    "getNarrowedAffectedElements": function( pNarrowToEventTarget ){
      var 
        narrowed,
        eventTarget = this.browserEvent.target;
        triggeringElement = this.triggeringElement;

      narrowed = $.grep(this.affectedElements, function(pElement) {
        if ( $.contains(triggeringElement, pElement) || triggeringElement == pElement) {
          return true;
        }
        else {
          return false;
        }
      });

      if ( pNarrowToEventTarget ) {
        narrowed = $.grep(narrowed, function(pElement) {
          if ( $.contains(pElement, eventTarget) ) {
            return true;
          }
          else if ( pElement == eventTarget ) {
            return true;
          }
          else {
            return false;
          }
        });

      }

      return narrowed;
    },
  ...
Stop Event Propagation

In the previous version, the propagation of dynamic action event was hardcoded for the contextmenu event. I don’t like to hardcode stuff like this and now a developer can decide whether dynamic action event will be propagated in DOM tree through all ancestors.

In help text for this settings option, I have put a description which was adapted from jQuery API for event.stopImmediatePropagation().

When checked, keeps the rest of the handlers from being executed and prevents the dynamic action event from bubbling up the DOM tree.

Enhanced attributes

To unleash the full potential of the plugin I have extended attributes Override Behaviour and Add Extra Entries. In the very first release, developers were able to reference only

  • this.triggeringElement and
  • this.affectedElements.

For some reason (which is not clear for me also) I haven’t exposed other attributes that are natively available. With this version when developer wants to override entry behaviour or create menu entries dynamically he can reference also:

  • this.action – the action object containing details such as the action name and additional attribute values,
  • this.browserEvent – the event object of event that triggered the event [3],
  • this.data – optional additional data that can be passed from the event handler,
  • this.element – a jQuery reference to the DOM object of the element that invoked a menu,
  • this.id – unique identifier generated by the plugin to identify a menu container.

For event Page Load this.browserEvent equals load

sidenote [3]

Custom event handling

The plugin can be bound with a custom event. To trigger custom event developer can use available API for events.

apex.event.trigger( pSelector, pEvent, pData );

Custom events are most likely triggered as a result of different action and it might be difficult to dynamically set Affected Elements. Of course, a developer can use Affected Elements and try combining them with the plugin (but personally I don’t see the scenario that would work or would need such an approach). But if a developer needs to create a menu dynamically and he doesn’t know what DOM element will be invoking a menu (at the moment of implementing dynamic action) then he can try the approach I have already prepared.

Define dynamic action like follows:

  • Event set to Custom
  • Custom Event set to myCustomEvent
  • Selection Type set to JavaScript Expression
  • JavaScript Expression set to document

and create true action Pretius APEX Context Menu [Plug-in] with Affected Elements not set. Initialization of the plugin might look like this:

var 
  element = $('.jQuerySelector').first(),
  data = {
    "element": element
  };

if ( !element.is('button') ) {
  //if your jQuery selector return DOM element that is not button
  //then the plugin have to know you want to bind click event to invoke a menu
  data = $.extend(data, {"listenToEvent": "click"});
}

apex.event.trigger( document, 'myCustomEvent', data );

This code and use-case scenario might be a little bit mysterious at this moment but in the following week, I will post example implementation of such an approach. Right now I don’t want to spoil it – just follow me on Twitter (@bostrowsk1) and you won’t miss it!

Error handling

Debugging implemented in APEX plug-ins is a must-have. It can be done better or worse but it should be available to developers because it is the first line of support.

In my case, I’m trying to implement it better than I did for earlier plugins and this time it is no different. I have implemented checks and descriptive errors that should help a developer fix issues that might occur during implementing the plugin.

Wrong plugin implementation on dynamic action level or errors raised during executing anonymous functions (used for Override Behaviour and Add Extra Entries) are raised using native APEX API apex.message.

Additionally, in the console browser, you can expect more detailed messages on different levels: LOG, LEVEL6 and APP_TRACE.

To make it even more clear I have prepared the demo page that shows all messages that are raised when the plugin is not configured as I have anticipated.

Updating the plugin

The procedure is the same as always

  1. Go to the plugin GitHub repository and download recent (v1.1.0) release
  2. Update the plugin using APEX plug-in installation wizard
  3. Update the plugin package

Before you update the plugin at your production environment it is important to check the plugin on the copied instance of your application. Because the release contains new attributes that change the plugin implementation I advise you to check if new options should be checked manually:

  • if you have used the plugin for contextmenu event, you have to manually check option Stop Event Propagation. Even if this option is the default for a new instance of the plugin then updating the plugin won’t automatically select it for you,
  • check the relation between triggering element and affected elements – attribute Narrow to Affected Elements

And remember, if you face any bug or unexpected behaviour you can raise an issue at Github repository – just follow support guidance.

Ostrowski Bartosz

Oracle APEX Developer @PretiusSoftware

You may also like...

2 Responses

  1. Arild Sunde says:

    Hi just installed your plugin APEX_ENHANCED_LOV_ITEM today in Apex 19.1
    I am having problems getting it to work when referencing other fields from my page in the List of Values SQL.

    Here is my sql:

    Select a.KUNDEADRESSE_id r, f_GetKundeAdresse(a.KUNDEADRESSE_id) d, a.*
    from t_KUNDEADRESSE a
    where a.LAND_NR = :P30_LAND_NR and
    a.KUNDE_NR = :P30_KUNDE_NR and
    upper(a.Adressetype) = ‘L’ and
    a.aktiv = 1

    It only shows the return value (a.KUNDEADRESSE_id) in the text box and the icon to open the search table is red.

    It works fine when I removed the reference to the page items :P30_LAND_NR and :P30_KUNDE_NR but then I get more records than I want.
    Both fields P30_LAND_NR and P30_KUNDE_NR are cascading LOV parents. The source for the plugin is Database Column.
    Sincerely
    Arild Sunde

  2. @Arlid Sunde
    Your question is related to another plugin. I support my plugins at the plugin Github repository. In this case, you can go to https://github.com/bostrowski/Pretius-APEX-Enhanced-Lov-Item.

    At the moment I can say that your issue is known bug (Cascading LOV support) which will be fixed in the next release (end of November 2019).

    Regards
    Bartosz

Leave a Reply

Your email address will not be published. Required fields are marked *