15 Geocoding Addresses#

Geocoding an address identifies its geographic coordinates.

Having longitude and latitude information for an address makes it easy to:
  • Visualize it on a map
  • Compute its distance from another address
  • Find it when it lies within a certain radius of a point of interest

The Geocoded Address page item type lets you do geocoding in any page in the browser. If your application runs on Autonomous Database, you can also do server-side geocoding using a dedicated page process or workflow activity.

15.1 Geocoding with User Confirmation#

Use a Geocoded Address page item to show a location and get coordinates for a user-entered address.

It appears as a map on the page, and works with one or more address-related text items for data entry. If the user changes the address, they must confirm it from a list of likely matches when they submit the page. Under the covers, the address they select includes the latitude and longitude coordinates. The Geocoded Address item automatically matches addresses and coordinates using an Oracle-managed service.

Tip:

Your application's use of the Oracle eLocation service requires no additional setup or cost.

15.2 Geocoding in the Background#

When your application runs on Autonomous Database, you can geocode addresses in the background in:
  • Workflows – using a native activity,
  • Execution chain processes – using a native page process, or
  • Automations – using a native Server-side Geocoding action type.
You configure the address to match as a:
  • single, unstructured address field, or
  • set of structured address fields, each providing one part of the address.
After configuring the one or more address items, specify either or both of these options:
  • Coordinates Item to receive a GeoJSON point with coordinates of the best match, or
  • Collection Name to process the possibly multiple matching addresses.

Tip:

When using the unstructured address, remember to include the ISO 2-letter abbreviation of the country at the end of the address value to geocode.

15.1.1 Producing GeoJSON from Coordinates#

The Geocoded Address page item works with GeoJSON, the web standard format for mapping information.

If you store address coordinates in an SDO_GEOMETRY column, or as GeoJSON in a VARCHAR2 or CLOB, your Geocoded Address item can read and write address coordinates directly. Sometimes, however, you store LONGITUDE and LATITUDE in separate number columns.

Consider an EMP_ADDRESSES table that stores each employee's address, and the following EMP_AND_ADDRESS view that joins EMP and EMP_ADDRESS tables:
create or replace view emp_and_address as
select   empno,
       e.ename,
       a.address,
       a.latitude,
       a.longitude
  from emp e
  join emp_addresses a
  using (empno);

Tip:

When the join column in both tables has the same name like EMPNO here, then the USING (EMPNO) clause is an alternative to ON e.EMPNO = a.EMPNO to avoid repeating the column name. Notice in this case that the SELECT list omits the table alias before the join column name EMPNO.

Luckily, the GeoJSON format for a point is simple:
{"type":"Point","coordinates":[longitude,latitude]}

Starting with a Form region based on the EMP_AND_ADDRESS view, you can switch it to SQL Query type instead. Then, add the SQL Expression below as one additional SELECT list column. After doing this, your Geocoded Address page item can reference the GEO_JSON_POINT column. If the latitude and longitude values are both provided, then the expression returns the GeoJSON document representing a point with those coordinates. Otherwise, the CASE statement returns null.

select EMPNO,
       ENAME,
       ADDRESS,
       LATITUDE,
       LONGITUDE,
       case
         when longitude is not null and latitude is not null then
           json_object(
             'type'        value 'Point',
             'coordinates' value json_array(longitude, latitude))
       end as geo_json_point
  from EMP_AND_ADDRESS

After adding this column to the Form region's data source query, it will appear in the region as a P60_GEO_JSON_POINT page item whose type you can set to Geocoded Address.

Tip:

Using your helper functions from Simplifying Points and Distance, you could alternatively use the following expression for the GEO_JSON_POINT column:
sdo_util.to_geojson(make_point(longitude, latitude)) as geo_json_point

Caution:

Use JSON_OBJECT and JSON_ARRAY to create GeoJSON. They format decimal coordinates as JSON-compatible decimal values regardless of the end user's browser language. Using a string-based approach like the following would work fine in English and other locales that use the "." as their decimal separator. However, it would fail when running in Italian, for example, where a decimal is formatted like 33,12345 instead.

–- NOTE: This is not locale-safe
apex_string.format(
   '{"type":"Point","coordinates":[%s,%s]}',
   longitude,latitude)

A string approach can work, but would require formatting the numbers using TO_CHAR() by explicitly specifying the 'NLS_NUMERIC_CHARACTERS=.,' in its optional third parameter.

15.1.2 Avoiding SQL Expression Update Error#

When you include a SQL Expression column like GEO_JSON_POINT, remember to also enable the Query Only switch on it in the Property Editor.

This avoids the error:
ORA-01733: virtual column not allowed here

that results when the user submits a form page that attempts to update a computed column.

15.1.3 Identifying Address Text Fields#

Associate a Geocoded Address page item with one or more related fields that display the address as text.

When the Geocoded Address works with an unstructured address, then a single additional Address Item like P60_ADDRESS shown below does the job. It's where the user can type a value like:
3130 Pacific Ave, San Francisco, CA

Alternatively, use separate page items to handle each part of a structured address. By default, the Trigger Geocoding is set to Automatic. This means the item preempts the page submit to get user confirmation of a modified address. The Oracle-managed geolocation service requires the context of the country name in which to locate the address. You can select a static country value from a list, or configure a Country page item to let the end user decide.

Figure 15-1 Configuring Geocoded Address Page Item

15.1.4 Confirming an Address Interactively#

Follow the user experience for entering or editing a geocoded address.

Assume employee KING has moved recently, and a user needs to change her address. As shown below, your page can pair a Cards region on the EMP_WITH_ADDRESS view with a map region showing all their addresses using the technique explained in Using Filtered Data Primary Keys.The map tooltip uses the HTML expression below to include the address information if available.

<strong>&ENAME.</strong>{if ADDRESS/}<br>&ADDRESS.{endif/}

The figure shows a cards region on the left half of the page displaying employees ENAME on small tiles. On the right side a map displays employee's addresses as map markers. The user's mouse hovers over the map marker for employee KING, to see that she currently lives at 3130 Pacific Avenue, San Francisco, California.

Figure 15-2 Viewing KING's Existing Address in a Map Tooltip

You create a Full Card action to open the modal drawer page shown below, passing in the value of the current employee's EMPNO as the primary key page item P60_EMPNO. So, as shown below, clicking on KING's card opens the modal drawer dialog. The P60_ADDRESS text field display the unstructured address. The Geocoded Address item P60_GEO_JSON_POINT shows the address coordinates your GeoJSON SQL expression produced for the point using the LONGITUDE and LATITUDE values in KING's row in the EMP_AND_ADDRESS view.

Figure 15-3 Geocoded Address Displays Existing Address On Page Load

The user enters a new address and clicks the (Apply Changes) button to save. The Automatic setting of Trigger Geocoding on the P60_GEO_JSON_POINT Geocoded Address item causes it to preempt the page submit and validate the changed address. As shown below, matching addresses appear in a modal Geocoding Results dialog.

Figure 15-4 User Confirms Correct Address

Tip:

You can customize the title of the Geocoding Results dialog by creating a shared component Text Message with name APEX.ITEM.GEOCODE.DIALOG_TITLE that enables the Used in JavaScript option.

Once the user confirms the address, as shown below, she can click (Apply Changes) again to save the change.

Figure 15-5 Geocoded Address Map Updates to Show New Address

15.1.5 Extracting Coordinates for Storage#

To extract the geocoded address's longitude and latitude into page items, define a dynamic action event handler on the Result Selection [Geocoded Address] event.

An Execute JavaScript Code dynamic action step with the following two lines handles the task. The first line uses the shortcut syntax to set page item P60_LONGITUDE to the result of evaluating the this.data.longitude expression. The second line shows the explicit syntax for setting the value of the P60_LATITUDE to the equivalent expression for that coordinate value.

Both assignment approaches accomplish the same result. $s('item',value) is less to type, but offers no code completion. Using the explicit syntax, as you type each dot in the expression, the Code Editor lets you pick from a list of available choices. Just remember to include the .value at the end of the expression after the page item name.

$s('P60_LONGITUDE', this.data.longitude);
apex.items.P60_LATITUDE.value = this.data.latitude;

The figure shows the Set Longitude and Latitude dynamic action step in Page Designer that fires when the user selects a result in the Geocoded Address item's confirmation dialog.

Figure 15-6 Extracting Longitude and Latitude from Geocoded Address

The this.data object available to a Result Selection event's actions also contains other properties. The JSON below shows all the names your this.data.propName expressions can reference.

{
  "sequence": 0,
  "latitude": 37.77112,
  "longitude": -122.45304,
  "matchCode": 1,
  "matchVector": "??010101010??004?",
  "matchVectorScore": 100,
  "houseNumber": "2045",
  "street": "Oak St",
  "settlement": "San Francisco",
  "municipality": "SAN FRANCISCO",
  "region": "CA",
  "country": "US",
  "language": "ENG",
  "postalCode": "94117",
  "side": "R",
  "percent": 0.24,
  "edgeId": 120887751
}

Tip:

If the P60_GEO_JSON_POINT Geocoded Address item were based directly on an SDO_GEOMETRY column, or a VARCHAR2 or CLOB containing GeoJSON, then the column could be directly updatable (i.e. Query Only = OFF). Saving the coordinates would happen automatically, and no step would be necessary to extract the coordinates into separate page items. In addition, no SQL Expression would be necessary to create the point GeoJSON.

15.1.6 Unprotecting Client-Set Hidden Items#

Disable Value Protected when client-side logic sets a hidden page item's value.

Since P60_LONGITUDE and P60_LATITUDE are Hidden page items whose value gets set by a dynamic action in the page, remember to disable their Value Protected switch as shown below. If you forget this detail, your end users will see the following error when submitting the page:
Session state protection violation:
This may be caused by manual alteration of protected page item P60_LATITUDE.
Figure 15-7 Disabling Value Protected on Hidden Item Set by Dynamic Actions

15.1.7 Exploring Additional Page Features#

Explore several page techniques used by the Geocoded Address example.

The page in this Geocoded Address example uses several additional features worth exploring:
  • Clicking an employee address "pin" on the map opens the edit dialog,
  • The Map on the Cards page refreshes when the dialog closes to show the updated address,
  • Both maps stretch vertically to use the available space,
  • The dialog prevents the [Enter] key from submitting the page so it can trigger geocoding instead,
  • An app-wide JavaScript helper function sets the dialog title, and
  • The title references a translatable text message with value placeholders.

15.2.1 Selecting Items for Server-side Geocoding#

When doing server-side geocoding, select item types appropriate to the context. For example, with the:
  • native workflow activity – use Additional Data columns, parameters, or workflow variables,
  • native page process – use page items or application items, and
  • native automation action type – use query columns or application items.

Tip:

Automation actions use bind variables to reference column values of the current query row they are processing. You can assign values to the bind variables, too. These column items can not only supply address values to geocode but also be the Coordinates Item to receive the result. Subsequent actions can "see" updated row values, but they are not permanently stored anywhere.

15.2.2 Referencing Coordinates Item Values#

If your Coordinates Item is named COORDINATES, then you can access the longitude and latitude values in the following ways.

Using an SDO_GEOMETRY type, access them like this in PL/SQL:
declare
   l_coords    sdo_geometry := sdo_util.from_geojson(:COORDINATES);
   l_longitude number       := l_coords.sdo_point.x;
   l_latitude  number       := l_coords.sdo_point.y;
begin
   -- Do something with l_longitude & l_latitude here
end;
To access them using SQL instead, use:
for j in (select coords.sdo_point.x as longitude,
                 coords.sdo_point.y as latitude
            from (
               select sdo_util.from_geojson(:COORDINATES) as coords
                 from dual))
loop
   -- Do something with j.longitude & j.latitude here
end loop;
If you prefer to work with the GeoJSON document, use the following in PL/SQL:
declare
   l_longitude number;
   l_latitude  number;
begin
   l_longitude := json_value(:COORDINATES,'$.coordinates[0]');
   l_latitude  := json_value(:COORDINATES,'$.coordinates[1]');
   -- Do something with l_longitude & l_latitude here
end;
Alternatively, to access them via the GeoJSON document in SQL, use:
for j in (select longitude, latitude
            from json_table(:COORDINATES,
                    '$.coordinates'
                    columns (
                       longitude number path '$[0]',
                       latitude  number path '$[1]')))
loop
   -- Do something with j.longitude & j.latitude here
end loop;

15.2.3 Processing Collection of Matches#

If your Collection Name is GEOCODE_RESULTS then you can access the address matches using the following query:
select seq_id,
       c001 as street,
       c002 as house_number,
       c003 as postalcode,
       c004 as municipality,
       c005 as settlement,
       c006 as region,
       c007 as country,
       c011 as matchvector,
       n001 as longitude,
       n002 as latitude,
       d001 as geocoding_timestamp
from apex_collections
where collection_name = 'GEOCODE_RESULTS'
order by seq_id

Tip:

The results are added to the collection in the order "best match first" order the geocoding service provides them, so ordering by the collection's SEQ_ID column retains that order for your processing.

15.2.4 Configuring Match Mode#

When doing server-side geocoding with a structure address, your choice of match mode influences how strict to be.

As shown below, you configure it in the Shared Components > Component Settings page for the Server Side Geocoding component. This setting has no effect when geocoding an unstructured address.

Figure 15-27 Configuring Match Mode for Server-side Geocoding

15.1.7.1 Clicking Map Point to Open Edit Dialog#

Configure a drill down on a map layer item by setting up its link target.

In the example shown below, notice the map region shows employee addresses on the calling page. While exploring the map by hovering over its points, a user might click a point to edit that employee's address. Rather than expecting them to click on a card, you can anticipate this user instinct to improve their experience.

Figure 15-8 Clicking Refreshes on Return from Dialog to Show KING's Updated Address

As shown below, after selecting the Addresses map layer, you can directly configure the Link > Target using the Link Builder dialog you're familiar with for configuring buttons and links. By choosing page 60 and configuring the value of P60_EMPNO to come from the EMPNO column, you let the user open the modal Employee Address edit dialog using whichever way feels right to them.

Figure 15-9 Linking to Another Page from a Map Point

15.1.7.2 Refreshing Region on Dialog Close#

Use a dynamic action event handler to refresh a calling page's region when the user closes an edit dialog.

The user clicks a card or a map point. Page 60 opens as a modal drawer to edit that employee's address. When the user cancels the dialog by clicking the "x" icon or pressing the [Esc] key, the address remains unchanged. However, if they close the dialog by clicking the (Apply Changes) button, they might have updated the employee's address.

End users expect their change to immediately reflect on the map in the calling page. Just use a Refresh action for the map region in a Dialog Closed event handler. This ensures that if you update KING's address again, as shown below, the map instantly shows her new location.

Figure 15-10 Map Refreshes on Return from Dialog to Show KING's Updated Address

To define a Dynamic Action event handler on the Dialog Closed event, select the Dynamic Actions tab in Page Designer, right-click on the Dialog Closed event heading in the tree, and choose Create Dynamic Action.

In the When section of the Property Editor, specify which dialog's closing should trigger this action using Selection Type. For example, you can target a dialog opened by a particular button, by a component in a particular region, or by a particular element using a jQuery Selector.

To react to the closing of a dialog opened by any element in the page, target the page's <body> element that contains all other elements. As shown below, choose Selection Type of jQuery Selector with a Selector of body to do this. This ensures the map refresh occurs whether the user opened the dialog by clicking on a card or a map point.

Figure 15-11 Configuring a Dialog Close Dynamic Action Event Handler

15.1.7.3 Stretching to Fill Vertical Space#

The 12-column grid system simplifies filling the available horizontal space. However, sometimes you want a component to stretch vertically to use available height.

15.1.7.4 Preventing Enter Key from Submitting#

Prevent the Enter key from submitting a single-field dialog by adding a visually hidden text field.

When a user presses the [Enter] key in a web page, for historical reasons, all browsers submit the page when it contains just a single input field. In contrast, if the page has two or more input fields, then the [Enter] key does not do this.

The Employee Address modal drawer is a "single input field" page. Technically, its only input field is P60_ADDRESS since the Geocoded Address page item P60_GEO_JSON_POINT renders as a map. In this situation, the simplest solution to avoid [Enter]'s submitting the page is to add a second input field. By hiding this second field as described, the end user does not even know it's there.

When adding this hidden field, give it a name that clarifies its special purpose. As shown below, leave its page item type as Text Field instead of Hidden. Then add the Universal Theme CSS class u‑hidden to its Advanced > CSS Classes property to hide it instead. Notice the label is blank and the Appearance > Template is set to Hidden as well. Despite its not being visible to the user, the browser counts this second field which avoids the [Enter] key from submitting the page.

Figure 15-19 Adding CSS-Hidden Field to Stop Enter Key from Submitting Page

15.1.7.5 Triggering Geocoding with Enter Key#

Use a native Trigger Geocoding dynamic action step to do address confirmation on-demand.

For example, as shown below, you can use it from a dynamic action event handler on the Key Press event of the P60_ADDRESS page item. Use the following JavaScript expression in the event handler's Client-side Condition to target the pressing of the [Enter] key:
this.browserEvent.key === "Enter"

In the Client-side Condition of the Trigger Geocoding action step, use this JavaScript expression to ensure it only happens if the user has changed the address before pressing [Enter].

apex.items.P60_ADDRESS.isChanged()

With these configurations in place, now the user gets immediate geocoding feedback if they press this [Enter]. When they later click (Apply Changes), the Automatic geocode trigger is smart enough to avoid repeating the process if it was already done on-demand.

Figure 15-20 Triggering Address Geocoding Using the Enter Key

15.1.7.6 Including Context Info in Dialog Title#

Use JavaScript to set a modal dialog's title to include context information from the calling page.

As shown below, when opening a modal dialog, it can be useful to include some context information in the dialog title like the name of the employee whose address the user is editing.

Figure 15-21 Showing ENAME in Modal Dialog Provides Useful Context

The Form region in the modal drawer page 60 includes the P60_ENAME page item, and the Form Initialization page process automatically retrieves the EMP_AND_ADDRESS view row based on the P60_EMPNO primary key value you pass in. So you already have the ENAME value on hand. Your instinctive first attempt shown below is to include &P60_ENAME. in the Title.

Figure 15-22 First Unsuccessful Attempt to Include Context Info in Dialog Title

However, when you run the page, the &P60_ENAME. comes up puzzlingly empty as shown below.

Figure 15-23 The &P60_ENAME. in Dialog Title Comes Up Empty
In short, due to the timing of when the dialog title is determined at runtime, to include context information you need to set the dialog title dynamically in the Page Load event using code like the following:
// JavaScript in Page Load event action to set dialog title
apex.util.getTopApex()
         .jQuery(".ui-dialog-content")
         .dialog("option", "title",
                 "Address for Employee " + $v('P60_ENAME'));

Tip:

To avoid having a static title display before your Page Load event sets the dynamic title, reference a non-existent substitution string like &INITIALLY_BLANK. in the page Title in Page Designer.

15.1.7.7 Providing App-Wide Helper Functions#

Put JavaScript helper functions used on multiple pages in an application-wide library.

If you foresee setting a dynamic dialog title in many pages in your app, then it's a great candidate to include in an application-wide helper function library. For maintenance purposes, this idea beats the alternative of copying and pasting the same code over and over. In each usage, only the title text would be different. Creating an application-wide JavaScript function library involves two steps:
  • Adding a Static Application File to contain the helper code
  • Referencing the file in your application's User Interface JavaScript File URLs list.

15.1.7.8 Using Translatable Text in JavaScript#

Consider using a translatable Text Message when setting a dynamic dialog title from JavaScript.

Once your app helper function "namespace" is available application-wide, your Employee Address modal drawer page's Page Load dynamic action can now set its dynamic title using the one-liner below:
app.setDialogTitle('Address for Employee ' + $v('P60_ENAME'));

However, if your application may ever need to be translated, rather than hard-coding the dialog title text, use a shared component Text Message instead. For the dialog title in this example, navigate to Shared Components > Text Messages to create a translatable text message as shown below with name ADDRESS_FOR_EMPLOYEE. Notice its corresponding Text for the English (en) language is the following. It includes the numbered parameter %0 as a placeholder.

Address for Employee %0

Importantly, you enable the Used in JavaScript switch since your call to set the dynamic dialog title happens in JavaScript. The figure shows the Create/Edit Text Message dialog for the ADDRESS_FOR_EMPLOYEE text message with this switch enabled.

Figure 15-26 Configuring a Translatable Text Message for Use in JavaScript
After defining this text message, you can reference it in your Page Load dynamic action's call to set the dynamic dialog title as follows:
app.setDialogTitle(
    apex.lang.formatMessage(
        'ADDRESS_FOR_EMPLOYEE',
        apex.items.P60_ENAME.value));

15.1.7.3.1 Stretching Vertically Using CSS#

In Cascading Style Sheets (CSS), the viewport is the visible browser area. One vertical stretching strategy sizes a component to the remaining vertical space after accounting for other page elements.

As shown below, the CSS expression 100vh represents 100% of the viewport height. One rem unit is the height of a character in the current page's root font. It's a relative unit so it works correctly even when the user zooms or shrinks the font size at runtime. For example, you might estimate the height of the navigation bar to be about five (5) lines of text in the root font size (5rem). So the expression below represents the vertical height remaining after accounting for the fixed navigation bar.

100vh - 5rem

The figure illustrates the height of the viewport, the height of the navigation bar, and the difference between the two representing the remaining vertical space.

Figure 15-12 Subtracting Height of Fixed Page Elements from Viewport Height

15.1.7.3.2 Inspecting Potential Stretch Targets#

Vertical stretching requires inspecting the web page to determine an appropriate container element to target in your CSS rules.

Right click the page and choose the Inspect context menu option to activate your browser's Developer Tools. As shown below, use the Elements tab to identify which element sets the component's height. For example, here the map region's <div> element with class a-MapRegion-container explicitly sets the height to 400 pixels (400px).

Figure 15-13 Determining the Element or Class to Target with a CSS Rule

15.1.7.3.3 Calculating Height in Inline CSS#

CSS rules can use calculated expressions.

The following CSS rule targets the a-MapRegion-container class and uses the calc() function to set the height of elements with that class to expression 100vh - 5rem. Notice the class name has a dot before it in the rule. The additional !important flag causes this rule to take precedence over any default height specification in the page. Like a spreadsheet formula, as the user resizes the browser the CSS height updates instantly based on the new viewport size.

.a-MapRegion-container {
   height : calc(100vh - 5rem) !important;
}

After selecting the page in the component tree, you can add this rule to the page-level CSS > Inline section as shown below.

Figure 15-14 Adding Inline CSS Rule to a Page

This produces the outcome below. Notice in the figure that map uses all the remaining vertical height available to give users a better view.

Figure 15-15 Vertical Stretch in Action on a Map Region in a Normal Page

Since you use the relative CSS rem units, as shown below the calculated expression works correctly whether the user zooms the font smaller or larger. The figure shows two versions of the same cards and map page of employees. The first is zoomed to 67% and the map uses all the remaining vertical space as expected. The second is zoomed to 175% and the map has adjusted appropriately in that case as well.

Figure 15-16 Relative CSS rem Unit Works Correctly at Any Zoom Percent

15.1.7.3.4 Applying Vertical Stretch in a Dialog#

Use CSS to stretch a component vertically in a modal dialog.

As shown below, to stretch the Geocoded Address map in the modal drawer page, the fixed amount of page contents includes the title bar and Address field at the top as well as the dialog footer containing the buttons at the bottom. After some experimentation you settle on 100vh - 14rem as the expression that best approximates the remaining vertical space. This example highlights the approach: the actual amount to subtract for a specific dialog you build depends on your use case. The figure illustrates the empirical heights in CSS rem units of the dialog footer (5rem), the viewport (100vh), and the header and other contents above the map (9rem), along with the calculated height of the map component to stretch vertically.

Figure 15-17 Vertical Stretching May Need to Account for Fixed Content Above and Below
Using the browser Developer Tools, you identify the a-GeoCoder-map class as the one to target, so you add the following rule to the modal drawer page's CSS > Inline section:
.a-GeoCoder-map  {
   height : calc(100vh - 14rem) !important;
}

In no time, as shown below, your modal dialog's Geocoded Address map is now also efficiently using the remaining vertical space. The figure shows the Geocoded Address map occupying the entire space below the Address text field and above the dialog footer.

Figure 15-18 Vertical Stretch in Action in a Model Drawer for a Geocoded Address Map

15.1.7.7.1 Adding Static Application JavaScript File#

Use a Static Application File shared component to include files of any kind with your app.

From the Shared Components > Static Application Files page in the App Builder, add a file named app.js to include application-wide helper functions.

Tip:

The suggested file name app.js can be any name you like, but keep the .js extension.

15.1.7.7.2 Including Helper Functions in All Pages#

Once you add JavaScript file to Static Application Files, cite the script's reference URL in application settings to load it on all pages.

In the Shared Components > Static Application Files list page, as shown below, you can find the Reference URL for any file. When useful, since the list is an Interactive Report, use its filtering features to show only JavaScript files. Notice the Reference column value for JavaScript files includes both the #APP_FILES# and #MIN# substitutions. Copy the Reference value for the app.js file to the clipboard to use in the next step.

Figure 15-24 List Page Shows Reference Path for a Static Application Files

Tip:

When running with debug tracing, the original JavaScript source file is used instead.When you save a JavaScript file like app.js, App Builder automatically creates a minified version named app.min.js. Minification removes whitespace and shortens names without changing the code’s logic, making the file smaller. APEX uses the minified file at runtime to improve page load performance, except when Debug tracing is enabled. With Debug tracing, APEX uses the original JavaScript source file.

To include a static application JavaScript file in every page, start by copying its Reference path from the page above. Then, as shown below, paste that value into the File URLs list field on the JavaScript tab of the Shared Component > User Interface Attributes page. If you include multiple files, list one per line.

Figure 15-25 Including Static App File Reference in Application JavaScript File URLs

15.1.7.7.1.1 Using JavaScript Namespace Template#

Use an app.js file to define a single global namespace for reusable JavaScript helper functions.

Use the following app.js code as a template for a JavaScript helper function library. This one includes two "public" functions named setDialogTitle() and areDifferent(). This block of JavaScript code defines a reusable helper function "namespace" called app. You can think of it like a PL/SQL package:
  • It has public functions (returned in the return {…} block).
  • It may also have private helper functions defined inside the block but not returned.
  • Only the returned functions are visible from outside.

This approach creates a single global symbol app instead of adding many separate function names into the global JavaScript scope. That keeps things clean and avoids naming conflicts.

// Export single global symbol "app" to keep code clean
const app = (function($) {

    // --- Private Functions ---

    // function myPrivateFunction(itemName) {
    //   ⋮
    // }

    // --- Public Functions (returned below!) ---

    //----------------------------------------------------
    // Set the title of the current dialog window
    //----------------------------------------------------
    function setDialogTitle(title) {
        apex.util.getTopApex()
                 .jQuery(".ui-dialog-content")
                 .dialog("option", "title", title);
    }

    //----------------------------------------------------
    // Return true if x and y are different, treating null
    // and empty string as equivalent in the comparison
    //----------------------------------------------------
    function areDifferent(x, y) {
        return (x || '') !== (y || '');
    }

    // Only these functions are exposed to JavaScript code
    // in APEX pages; everything else stays private.
    return {
        setDialogTitle,
        areDifferent
    };
    // Ensure $ in app namespace resolves to correct jQuery
})(apex.jQuery);

15.1.7.7.1.2 Comparing Namespace to PL/SQL Package#

Use an immediately invoked function expression (IIFE) to create an app namespace for reusable JavaScript helper functions.

JavaScript doesn't have packages like PL/SQL, so developers use the pattern in app.js to simulate a modular structure. The high-level pattern looks like this:
// Define an "app" namespace with exported functions
const app = (function($) {

    // --- Private Functions ---
    function myPrivateFunction(itemName) {…}

    // --- Public Functions (returned below!) ---
    function setDialogTitle(title) {…}
    function areDifferent(x, y) {…}

    return {
        setDialogTitle,
        areDifferent
    };
})(apex.jQuery);
Known as an immediately invoked function expression (IIFE) the pattern breaks down as follows:
  • The function (function($) {…})(apex.jQuery); is defined and then immediately called.
  • Its single parameter named $ is passed the correct version of jQuery APEX uses.
  • It returns any functions to expose publicly as properties of an object.
  • The {setDialogTitle, areDifferent} in the return statement is legal shorthand for:
    {
       setDialogTitle: setDialogTitle,
       areDifferent: areDifferent
    }
  • That object becomes the value of the app variable.

So when you later write the following line of code in an APEX page, it calls the setDialogTitle function inside the app function library.

app.setDialogTitle("Hello");

Since app defines a unique name to unambiguously qualify its exported function names, JavaScript developers call it a namespace.

By attaching your helper functions to a single namespace object (app), you:
  • Keep your global "footprint" of additional top-level names small.
  • Group related logic in one place.
  • Make your code easier to organize and maintain.
  • Define a public specification of exported functions.
  • Maintain your ability to change private functions in the namespace body.

This is very similar to how you use packages in PL/SQL.

Caution:

Unlike PL/SQL, JavaScript does not support defining multiple versions of the same function name with different parameter lists. If you inadvertently do this, the function defined last in the file "wins". To achieve a similar effect, define your function with all possible parameters, then test whether a particular parameter was not supplied by checking an expression like the following:
if (typeof yourParam === "undefined") {
  // yourParam was not passed
}