Tuesday, March 17, 2020

Oracle APEX Global Notification

In the APEX application properties there is a property called Global Notification where you can enter some text that will be displayed on every page - the help text says:
You can use a global notification to communicate system status. If your page template contains a #GLOBAL_NOTIFICATION# substitution string then the text entered here displays on each page. For example, suppose you entered the message "Team picnic this Friday" in this attribute. Assuming your page templates support the global notification substitution string then this message would display on each page.
To create a global notification:
1. Include the #GLOBAL_NOTIFICATION# substitution string in your page template.
2. Navigate to the Edit Application page and enter a message in the Global Notifications attribute.
3. Click Apply Changes.
So let's see what we get if we put our message here:
It works, but it isn't exactly eye-catching is it? I was hoping the page template might have some mark-up around this to liven it up, but it doesn't. I could change the page template, but I'd rather not do that as I'd prefer to use standard Universal Theme page templates that can be refreshed after upgrades. Or I could use HTML in the global notification text:
<div class="u-info padding-md">
  <span class="fa fa-info-circle"></span>
  The system will be down for essential maintenance over the weekend.
</div>
That looks like this:
That is a lot better, I think. And obviously I can vary the colour and icon according to the type of message. But it still has some drawbacks for me:
  • I need to remember that HTML mark-up each time I want to set up a global notification, or remove one.
  • I need to amend the application to do it, rather than change some data outside the application.
So I quickly devised an alternative global notification system using a table and a report region with a bespoke report template. Here is the table:
The ID is just a surrogate key. TEXT contains the plain text of the message, e.g. "The system will be down for essential maintenance over the weekend." FROM_DATETIME and TO_DATETIME allow set-up of a message that will show for a defined period only (these are optional in case I want one shown indefinitely.) Finally, STATE is a string such as 'danger', 'info', 'success', 'warning' to determine the appropriate styling and icon to use. Here is the report SQL:

select id, text
, case state
       when 'info' then 'fa-info-circle'
       when 'danger' then 'fa-hand-stop-o'
       when 'warning' then 'fa-warning'
       when 'success' then 'fa-check-circle'
       end as iconclass
, 'u-' || state as divclass       
from notices
where sysdate between nvl(from_datetime,sysdate) and nvl(to_datetime,sysdate)
and id not in (select column_value from apex_string.split(:APP_NOTICES_CLOSED))
order by case state when 'danger' then 1 when 'warning' then 2 end, state

The report SQL derives the icon type and the class to style the message based on the state. An enhancement would be to put these in a table of notification state attributes and look them up. The report region is defined on page 0 so that notifications appear on all pages, in the Before Content Body position. The region template is Blank with Attributes. I have created a bespoke report (row) template - I could have used the Universal Theme's "Alert" report template, but it isn't quite what I want. The template HTML is:

<div class="#DIVCLASS# padding-md">
  <span class="fa #ICONCLASS#"></span>
  #TEXT#
  <a href="#" id="closeNotice-#ID#" class="closeNotice" title="Hide this notice">
    <span class="fa fa-times-circle-o u-pullRight #DIVCLASS#"></span>
  </a>
</div>

OK, let's set up a few notices and see what we get...
Nice(ish). But the users might get fed up with all those messages clogging up every page. What they need is a way to dismiss them. We've already done some of the work - note the X icon at the right-hand end of each message, and this line in the report SQL:
and id not in (select column_value from apex_string.split(:APP_NOTICES_CLOSED))
We need to create an application item called APP_NOTICES_CLOSED. This will be a list of the notice IDs that the user has dismissed. Then we need the logic to maintain this item - a dynamic action defined on page 0 as follows:
  • Event: Click on Javascript Selector ".closeNotice"
  • Action: Execute Javascript code:
  • $s ('P0_CLOSE_NOTICE_ID', 
        $(this.triggeringElement).attr('id').substr(12));
    (So we also need a hidden page 0 item called P0_CLOSE_NOTICE_ID)
  • Action: Execute PL/SQL code:
  • declare
       l_count integer;
       l_tab apex_t_varchar2;
    begin   
       select count(*)
       into l_count
       from apex_string.split(:APP_NOTICES_CLOSED)
       where column_value = :P0_CLOSE_NOTICE_ID;
       
       if l_count = 0 then
          l_tab := apex_string.split(:APP_NOTICES_CLOSED);
          l_tab.extend;
          l_tab(l_tab.count) := :P0_CLOSE_NOTICE_ID;
          :APP_NOTICES_CLOSED := apex_string.join(l_tab);
       end if;   
    end;   
  • Action: Hide - Javascript expression:
    $(this.triggeringElement).closest('div');
Now, when the user clicks on the X on a notification message, we hide it and add its ID to a list of notifications that we will no longer show in this session. Next time the user logs on, all current messages will be displayed again. An obvious enhancement would be a way to dismiss messages forever once acknowledged.

So there it is - a fairly quick and dirty global notification mechanism with scope for various enhancements.