Skip to main content

Command Palette

Search for a command to run...

Refresh Oracle APEX Template Components which contain JavaScript code

Updated
4 min read
Refresh Oracle APEX Template Components which contain JavaScript code
M

With around 20 years on the job, Matt is one of the most experienced software developers at Pretius. He likes meeting new people, traveling to conferences, and working on different projects.

He’s also a big sports fan (regularly watches Leeds United, Formula 1, and boxing), and not just as a spectator – he often starts his days on a mountain bike, to tune his mind.

Welcome Oracle APEX Template Component developers... this blog post is going to get complicated real quickly.

Scenario

The Scenario is: I have a a Template Component with this Partial

{if MY_VAR/}<div id='luf'>#MY_VAR#</div>{endif/}

This Template Component has a loaded JavaScript file

My seconds.js looks like this

var tc_selector = '.luf';

function initTemplateComponent() {
    var secs = apex.date.secondsPastMidnight();    
    \((tc_selector).append(\)("<div>").text('JS: ' + secs));
}

initTemplateComponent();

Basically it grabs the number of seconds since midnight.

The region has a Static ID of ellandRoad

When it runs, I get:

  • Some numbers from the DB

  • Seconds since midnight from JavaScript

It looks like this:

The Update button you're seeing just refreshes the Template Component Region

When I click the Update button I see this this:

Whats happened is that the region has been refreshed, but the JavaScript does not run.

Solution

Rewrite the Template Component JavaScript

The solution is to rewrite the seconds.js to look like this

var tc_selector = '.luf';

function getComponentElement(parentSelector) {
    var \(parent = parentSelector ? \)(parentSelector) : null;
    return \(parent ? \)parent.find(tc_selector) : $(tc_selector);
}

apex.jQuery(window).on('theme42ready', function() {
    $(tc_selector).each(function (index) {
        var region = apex.region.findClosest(this);
        if (region) {
            $(region.element[0]).on("apexafterrefresh", function (event) {
                initTemplateComponent( event.target )
            });
        }
    });
});

function initTemplateComponent(parentSelector) {
    var secs = apex.date.secondsPastMidnight();    
    var $sel = getComponentElement(parentSelector);
    \(sel.append(\)("<div>").text('JS: ' + secs));
}

initTemplateComponent();

Points

  • I put a bit of thought into refreshing individual regions, i.e not all regions may wish to be blanket updated.

  • I tried to keep initTemplateComponent intact and resemble the original. Although now it has a parameter & calls getComponentElement. This is just a small rewrite to fetch the region affected by the refresh

  • On UT ready, a new event is added waiting for the a refresh event. This also calls initTemplateComponent so be aware that this line may need changing to whatever your function called

Next section, we have to look after the refresh

Method 1: Execute JavaScript Refresh

For this method need to change the Refresh Dynamic Action into a Execute JavaScript one

const regionID = 'ellandRoad';
apex.region(regionID).refresh().done(function () {
  apex.event.trigger('#' + regionID,'apexafterrefresh','');
})

Points:

  • In the above code, we are going to refresh the region, then wait till it refreshes.

  • After it refreshes and renders, we can trigger the event which runs the JavaScript function again.

Method 2: Monkey-Patch apex.region

🚨
This solution looks the easiest - Just use the native Refresh Dynamic Action. Although this patch will break in a future version of APEX (above 24.2) and I will not be here to help you sort it out - as I will have wrote 300+ blogs since then and I'll have no interest in returning to this. It'll be just you and your AI friend from this point on. Also - this code has not been thoroughly tested

Put this in Execute when Page Loads or similar place

(function (apex, $) {
    "use strict";

    var origRegion = apex.region;   // original function with methods attached

    function patchRegionInstance(region) {
        if (!region || region._refreshPatched) {
            return;
        }

        var origRefresh = region.refresh;
        if (typeof origRefresh !== "function") {
            return;
        }

        region.refresh = function () {
            var self = this;
            var result = origRefresh.apply(self, arguments);

            if (result && typeof result.then === "function") {
                // Native Promise
                return result.then(function () {
                    $(self.element).trigger("apexafterrefresh");
                });
            } else if (result && typeof result.always === "function") {
                // jQuery Deferred
                return result.always(function () {
                    $(self.element).trigger("apexafterrefresh");
                });
            } else {
                // No promise: fire immediately
                $(self.element).trigger("apexafterrefresh");
                return result;
            }
        };

        region._refreshPatched = true;
    }

    // Wrapper that delegates to the original region function
    function wrappedRegion(pRegionId) {
        var region = origRegion(pRegionId);
        patchRegionInstance(region);
        return region;
    }

    // Copy all static members from original apex.region to the wrapper
    Object.keys(origRegion).forEach(function (key) {
        wrappedRegion[key] = origRegion[key];
    });

    // Replace apex.region with the wrapped version, preserving namespace
    apex.region = wrappedRegion;

    // Optionally patch already-created regions (if any)
    if (apex.regions) {
        Object.keys(apex.regions).forEach(function (id) {
            patchRegionInstance(apex.regions[id]);
        });
    }

})(apex, apex.jQuery);

Now, just just use the Refresh Dynamic Action like normal.

Summary

Now the Update button is working

Yeah, apologies the solution is a bit ugly. I couldn't find a nicer solution here. I hope your AI can turn this blog into a suitable solution for your Template Component

ENJOY!

What's the picture? Mural of Luke Ayling's strike in the East Stand at Leeds United.