7. Mai 2024

Right-click context menus in Oracle APEX #JoelKallmanDay

Learn how to improve the UX of Oracle APEX apps with custom context menus.

Many applications are used daily and are an important aspect of daily business. If you have power users, it makes sense to analyze how they interact with the app, as you might find ways to optimize the user experience.

One aspect of this is adding shortcuts so that users can achieve their goal way more quickly. Think of your email client. Most likely, it has a delete button after you open an email. But you can additionally use the „DEL“ key, drag and drop mails into the trash folder and if you right-click on a mail you can also delete it.

The right mouse button is a really useful tool we use daily, but out of the box, it is not utilized much in APEX applications. Let’s take a look at how we can change this.

Interactive Grid

John Snyders already has a section on adding context menus to interactive grids in his awesome How to Hack APEX Interactive Grid series.

We can simply put the following code in the „Initialization JavaScript Function“ section:

const cmId = 'person-ir-cm';
// create div that will serve as the popping up menu
$('body').append(`<div id="${cmId}"></div>`);
const $menu = $(`#${cmId}`);

// will be filled once a row is clicked
let clickedEmail;

Now we can right-click on any row and get the menu we normally only get behind the action button:

An interactive grid. On a row a menu is opened with the options 'copy to clipboard', 'duplicate rows', etc.

Unfortunately, this does not work in edit mode, as it will open the regular browser context menu. I think it can be confusing if it does not work all the time.

Creating our own menu

APEX offers an built-in API to create our own context menus. I will show this on an interactive report with person data. We also need a unique identifier somewhere in the row. In my case, I use the email column, which has a unique constraint. To find it easily, I gave the column a static ID.

Giving a column a static ID results in the HTML having a headers attribute with the static ID as value instead some generated ID

Now we have to define some JavaScript that runs on page load to create the menu. The first step is to create an empty div that serves as the menu element.

const cmId = 'person-ir-cm';
// create div that will serve as the popping up menu
$('body').append(`<div id="${cmId}"></div>`);
const $menu = $(`#${cmId}`);

// will be filled once a row is clicked
let clickedEmail;

Now we can define our menu items. We start with a simple copy email to clipboard item:

const menuItems = [
  {
    type: 'action',
    label: 'Copy email', // displayed text
    icon: 'fa fa-user', // displayed icon
    action: async () => {
      await navigator.clipboard.writeText(clickedEmail);
      apex.message.alert(`Copied ${clickedEmail}!`);
    },
  },
];

Next, we can initialize the menu, resulting in our div being transformed into a context menu with our actions:

$menu.menu({
  iconType: 'fa',
  items: menuItems,
});

At last, we need to listen to the contextmenu event on the table rows. Then we can extract the email from the row and open the menu at the mouse position:

$(`#person_ir`).on('contextmenu', 'tr', (e) => {
  e.preventDefault();

  const clickTarget = e.currentTarget;
  clickedEmail = $(clickTarget).find('td[headers="EMAIL"]').text();

  $menu.menu('toggle', e.pageX, e.pageY);
});

This results in the following behavior:

Right clicking on a row in an interactive report opens a context menu with the option 'Copy email'. Clicking on it copies the email to the clipboard and shows an alert with the copied email.

Data operations

Another useful thing to do in a context menu is to allow quick data operations. For example, we can add a shortcut to delete a row. To achieve this, we need to add another menu item:

const menuItems = [
  {
    // first action...
  },
  {
    type: 'action',
    label: 'Delete Person',
    icon: 'fa fa-trash',
    action: () => {
      // trigger delete event on region
      // params => 1: region static id, 2: event name, 3: data
      apex.event.trigger('#person_ir', 'delete', clickedEmail);
    },
  },
];

Subsequently, we can create a hidden page item that will hold the email of the row we want to delete. Now we create a dynamic action that listens to the event we trigger in the menu item. We use Custom as the „Event“, delete as the „Custom Event“ and select our interactive report as the region.

The first action we add to this is a „Set Value“ action to store the email address in a page item. We use the JavaScript expression this.data to get the data we passed in the event:

A dynamic action with a 'Set Value' action. The page item is set to the value of the event. The value type is JavaScript with this.data as a value.

Next, we add a PL/SQL server-code action to delete the row. We use the page item we just created set as the „Item(s) to Submit“ and use the following code:

1

2

deletefrom persons where email = :P4_CLICKED_MAIL;

At last, we need to add a refresh action for the interactive report to show the changes.

Right clicking on a row in an interactive report opens a context menu with the option 'Delete Person'. Clicking on it deletes the row and refreshes the interactive report.

Where do we use them in LCT

In our APEX test automation tool, LCT, we use context menus on the test builder page. They allow you to easily enable or disable cases or quickly open modal pages to edit their details.

Want a plug-in?

United Codes and Pretius both have plug-ins available to make it easier to create context menus in a low-code fashion. I haven’t used them yet, but they look promising.