16 Uploading, Viewing, and Downloading Files#

Use Image Upload for images, File Upload for other file types, and native download features to serve stored files.

Use an Image Upload page item to let end users submit one or more images to store in a BLOB column or an Oracle Cloud Infrastructure (OCI) object bucket. This component provides image-specific options like cropping and client-side scaling to avoid multi-megapixel images from modern smartphones if the task doesn't require that resolution

To handle other file types – for example PDF – use the more general File Upload page item. It offers similar single and multiple file abilities for any kind of file.

Your pages can include a native Download dynamic action or page process to declaratively download images or other files, and a page can serve inline images or files using the DOWNLOAD procedure in the APEX_HTTP package.

16.1 Reviewing Break Room and Referrals Pages#

Explore two Woods HR use cases: sharing employee photos and submitting candidate CVs for open position referrals.

Your Woods HR app lets employees share photos with colleagues on the Break Room page. It also rewards staff who submit a candidate's PDF curriculum vitae (CV) on the Open Position Referrals page if their referral gets hired. The figure shows the home page of the Woods HR application where employees access an employee directory, a virtual break room, and a candidate referral function.

Figure 16-1 Break Room and Open Positions Referrals Pages in Woods HR App

16.2 Storing Employee ID in an Application Item#

Populate an application item at login to reuse a session-specific value throughout the app.

Break Room photos and Open Position Referrals both record the logged-in user’s EMPNO. Populate an application item at login to avoid repeated lookups during the session.

16.3 Uploading an Image to a BLOB Column#

Use an Image Upload page item to let users provide a photo or image file to your app.

When you use it as part of a Form region, the combination automates creating, editing, and deleting the row with the image BLOB column. Configure the Form region source with the table or view name, then set the Image Upload page item's source to the BLOB column. Other declarative settings control the features end users see while uploading the image.

16.4 Displaying BLOB Column Images#

Display images from a URL or BLOB column in common APEX regions, with a special approach for Interactive Grid.

All regions can display images using a URL, but many can display images directly from a BLOB column including: Form, Cards, Classic Report, Interactive Report, Template Component, and Search. Showing a graphic in an Interactive Grid requires an <img> HTML tag whose src attribute references the URL of the graphic to display.

16.5 Uploading a File to a BLOB Column#

Use a File Upload page item to let users provide a file to your app. When you use it as part of a Form region, the combination automates creating, editing, and deleting the row with the file BLOB column.

Configure the Form region source with the table or view name, then set the File Upload page item's source to the BLOB column. Other declarative settings control what end users see when uploading the file.

16.6 Previewing BLOB Column PDF Files Inline#

To preview a PDF file stored in a BLOB column, use an iframe.

This HTML tag lets you include one page's contents as part of another page. To simplify setup, create a template component that lets developers just set the URL of the file to preview. Combine this with a page that downloads the PDF file inline. To finish the job, adjust your application's Embed in Frames browser security setting to allow embedding a frame from the same domain.

16.7 Downloading a File for Offline Use#

Use the native Download page process or dynamic action to let the user download a file for offline use.

Both perform the same functionality, and you configure them in a similar way. Both variants offer a single-file and a multiple-file option.

16.8 Uploading Multiple Images or Files#

Your apps can use native temporary storage of uploaded files to support any business requirement.

16.9 Using an OCI Object Bucket for Files#

Use an OCI object bucket to store application files outside your transactional database.

Oracle Cloud Infrastructure (OCI) offers an Object Bucket service to efficiently store and retrieve files. While storing images, PDFs, and other files in a BLOB column the simplest approach, your app can store them in an object bucket instead. This requires some additional effort, but can potentially improve performance and require less storage in your transactional database.

16.1.1 Sharing Photos in the Break Room#

Review the Break Room page, where employees browse shared photos and add one or more images.

On the Break Room page, users browse photos shared by others, and buttons let them post a single new photo, or multiple new photos. The images appear in a Cards region using its Media settings. By applying an All Employees authorization scheme, any employee can access this page. The figure shows the virtual break room with recently posted photos from employees across the company.

Figure 16-2 Break Room Page Lets Employees Share Photos with Colleagues

16.1.2 Submitting a Referral to Earn a Bonus#

Preview the Open Position Referrals page, where employees review, submit, and download candidate CVs for open jobs..

On the Open Position Referrals page, users select an open position to review submitted candidate CVs for that job. Buttons let them submit the CV of a new referral or download one of the existing CVs to review offline. By applying an All Employees authorization scheme, any employee can access this page.

The figure shows the page employees use to refer candidates for open positions. Tiles representing open positions appear on the left in a Cards region, a select list of existing candidates lets users review existing CVs inline, and a Submit Referral button lets an employee provide a new candidate's CV for a position.

Figure 16-3 Employees Submit Candidate CVs for Open Job Positions

16.1.3 Studying Supporting Tables and Views#

Learn how the EBA_DEMO tables and views support Woods HR photo sharing and job referrals.

The tables and views supporting the Woods HR app all use the EBA_DEMO prefix. The DEPT and EMP tables shown below mimic the EMP/DEPT sample dataset. Three related tables support the break room and job referrals pages:
  • EMP_BREAKROOM_PHOTOS – stores uploaded pictures employees post.
  • JOB_POSITIONS – tracks open job positions
  • EMP_JOB_REFERRALS – contains employee-referred job candidates and respective CVs.

While all employees use Break Room and Open Job Referrals, only HR reps manage job positions.

The figure shows the structure of and relationships between the five tables involved.

Figure 16-4 Additional Tables to Support Woods HR Photos and CVs
Since you explore two approaches for storing photos in this section, the views shown below provide two different "interfaces" on the photo storage table. Both join the EMP table to use its ENAME columns as the POSTED_BY employee. Both also use the APEX_UTIL.GET_SINCE function to format the underlying CREATED date in a more useful way for UI display like "3 days ago". The views differ in the following way:
  • EMP_BREAKROOM_PHOTO_V – Has IMAGE BLOB and MIME_TYPE for local photo storage
  • EMP_BREAKROOM_PHOTO_B – Has only FILE_NAME for OCI object bucket image.

The figure depicts the structure of these two breakroom photo views and how they related to the two underlying tables EBA_DEMO_EMP and EBA_DEMO_EMP_BREAKROOM_PHOTOS.

Figure 16-5 Views Present Two "Interfaces" on Breakroom Photos Table

The EMP_BREAKROOM_PHOTO_V view includes the WHERE clause below to only return rows from the underlying table that have a valid IMAGE BLOB stored.

-- Used for photos stored locally in a BLOB
create view eba_demo_emp_breakroom_photo_v as
  select p.id,
         p.title,
         p.empno,
         p.mime_type,
         e.ename                      as posted_by,
         p.updated,
         p.image,
         apex_util.get_since(created) as posted,
         file_name
from eba_demo_emp_breakroom_photos p
join eba_demo_emp e on ( p.empno = e.empno )
where      image is not null
       and dbms_lob.getlength(image) > 0

In contrast, the EMP_BREAKROOM_PHOTO_B view's WHERE clause returns only rows with no IMAGE BLOB stored. These rows represent images stored in an Oracle Cloud Infrastructure (OCI) object bucket.

-- Used for photos stored in an OCI object bucket
create view eba_demo_emp_breakroom_photo_b as
  select p.id,
         p.title,
         p.empno,
         e.ename                      as posted_by,
         p.updated,
         apex_util.get_since(created) as posted,
         file_name
    from eba_demo_emp_breakroom_photos p
    join eba_demo_emp e on ( p.empno = e.empno )
    where    image is null
          or dbms_lob.getlength(image) = 0

16.2.1 Defining an Application Item#

Define an Application Item to hold a session-scoped value, accessible from any page.

Since it acts like a global variable for the current user, consider using a prefix like G_ to distinguish it from native substitution strings like APP_USER. For example, use the Shared Components > Application Items page shown below to create one named G_APP_USER_EMPNO to hold the numeric employee ID of the logged‑in app user.

Tip:

While an application item may contain a value that looks like a number (e.g. "1234"), it is stored as a VARCHAR2 value. Use the built-in NV('YOUR_APP_ITEM_NAME') to get the item value as a NUMBER in PL/SQL or APEX_SESSION_STATE.GET_TIMESTAMP('YOUR_APP_ITEM_NAME') to get the item as a date value.

Compute its value declaratively using an Application Computation. You can also assign a value from an Application Process using its name as a bind variable or calling SET_VALUE in the APEX_SESSION_STATE package as shown below.

Both the application computation and application process have a Processing Point. Set it to After Authentication to run immediately after login. You can also assign a new value to an application item from anywhere in your application using the same two approaches shown below.

-- Assigning an app item as a bind variable
:G_APP_USER_EMPNO := 1234;
-- Setting an app item value with a procedure
apex_session_state.set_value('G_APP_USER_EMPNO', 1234);

The figure shows the Application Items list page in Shared Components after defining the G_APP_USER_EMPNO item.

Figure 16-6 Shared Component Application Items List

16.2.2 Computing an Application Item at Login#

Use an Application Computation with After Authentication execution point to set an application item's value after the user logs in.

The one shown below computes the value of G_APP_USER_EMPNO using the SQL statement below that returns a single value.

select empno
  from eba_demo_emp
 where ename = :APP_USER

You can also use a PL/SQL expression, function body returning a result, or other methods.

Tip:

You can also assign the application item value in an Application Process with an After Authentication processing point.

The figure shows the Application Computation edit page in App Builder where the value of the G_APP_USER_EMPNO item is computed after user authentication using a one-column SELECT statement that returns one row.

Figure 16-7 Computing an Application Item's Value After Login

16.2.3 Referencing the App Item Value#

Reference an application item from any page or part of your app during the user session.

Reference the value of an application item like G_APP_USER_EMPNO anywhere you need it. Depending on the context, use :G_APP_USER_EMPNO, &G_APP_USER_EMPNO., or just the item name if declaratively specifying the name of an item whose value to use.

For example, the Add Break Room Photo page shown below configures the default value of its hidden P6_EMPNO page item to come from the G_APP_USER_EMPNO application item. This ensures the photo is correctly attributed to the employee who submits it using this page.

Figure 16-8 Using an Application Item to Default the Posting Employee ID

16.3.1 Configuring Upload Image Item Source#

After creating a form region on your table containing the BLOB image column, set the item type of the BLOB column to Image Upload.

Set the Storage > Type to:
  • BLOB column specified in Item Source attribute.
Check the Source section in the Property Editor to confirm the expected BLOB column is selected. Finally, as shown below, configure the column names for the:
  • MIME Type Column – the type of image (e.g. image/jpeg, image/png)
  • Filename Column – the uploaded file name
  • BLOB Last Updated Column – date the image was last modified.

Setting up these additional column names is important. It ensures the browser handles the image correctly when viewed or downloaded.

Tip:

Enable the Primary Key switch for form items tied to primary key columns. (e.g. P6_ID here)

The figure shows the P6_IMAGE page item selected in the Page Designer component tree. It highlights the Storage and Source group properties related to automatic BLOB file handling.

Figure 16-9 Configuring Source Properties for an Upload Image Item
Then, in the Processing tab of the Page Designer tree, ensure you have:
  • a page process of type Form - Automatic Row Processing (DML)
    • with its Form Region property set to the form region,
    • sequenced before the Close Dialog process if the page is a modal dialog or drawer, and
  • a button like the CREATE one here with its Database Action property set to SQL INSERT action.

Tip:

The Create Page Wizard creates the DML page process on a new Form page, and sets the Database Action property to SQL INSERT action on the CREATE button. However, when you add a Form region to an existing page you need to do these two steps yourself.

The figure shows the Process form Add Break Room Photo page process selected in the component tree. It highlights the process type is Form - Automatic Row Processing (DML) and the associated Form region is Add Break Room Photo.

Figure 16-10 Configuring Form DML Page Process

16.3.2 Controlling Image Upload User Experience#

Use the Image Upload page item's properties in the Display, Cropping, and Resize To sections to control your end users' experience when uploading an image.

Allow Cropping lets the end user adjust the selected portion of the image to upload. When enabled, an additional Aspect Ratio list appears to impose a standard format if appropriate.

Use the Maximum File Size, Maximum Width, and Maximum Height to constrain the size of the image. If needed, the page item resizes the file on the client before uploading. This helps avoid storing multi-megapixel images from modern smartphones when that resolution adds no value.

To let the user take a photo, set Capture Using to open a device's main or selfie camera. Users can still select an existing image file instead.

The Display As and Preview Size control how the user sees the image upload item on the page. By default, it shows as an Icon Dropzone with a preview of the selected image.

The Display Download Link controls whether the user can download an existing image when editing a row. When enabled, you can also set the Download Link Text.

The figure shows the P6_IMAGE Image Upload page item selected in the component tree and values of its properties in the Display, Cropping, and Resize To sections in the Property Editor. The mouse is showing the different values of the Capture Using property: Selfie Camera, Main Camera, or None.

Figure 16-11 Configuring Properties That Control Image Upload Behavior

16.3.3 Enlarging Image Upload Icon Preview#

Use CSS to enlarge the Image Upload icon preview if needed.

If none of the Preview Size options meets your needs, set the file drop icon size using the --a-filedrop-icon-size CSS variable. You can specify it on the item using the item name in the rule like this:
#P6_IMAGE {
  --a-filedrop-icon-size: 25rem;
}

Alternatively, specify it on the root of the page using the :root pseudo-class selector like this. CSS defines this pseudo-class to match the page's root element.

:root {
  --a-filedrop-icon-size: 25rem;
}

This rule makes the preview icon size the width of 25 characters in the size of page's base font.

Tip:

CSS rem units are tied to the page's base font size. If the user zooms in or out in their browser, units specified in rem scale automatically with the new font size.

16.3.4 Configuring Application-Wide Style Rules#

Use a page's CSS > Inline settings to define style rules that target a specific page item or ones that should affect only that page. In contrast, put rules that affect all pages in a Shared Components > Static Application Files CSS style sheet.

Rules that target the :root selector are good candidates if they should affect all pages. CSS defines this pseudo-class to match any page's root element.

For example, as shown below, the following static application app.css file defines one rule using the :root selector to specify values for three CSS variables:
  • --ut-body-content-padding-y – vertical padding for Universal Theme body content
  • --a-cv-item-width – width of each card in a Cards region
  • --a-cv-body-padding-y – vertical padding for Cards region card body section

Tip:

See CSS Variables in the Universal Theme reference app to learn about others.

:root {
    --ut-body-content-padding-y: 1rem;
    --a-cv-item-width: 16rem;
    --a-cv-body-padding-y: 0.2rem
}

The figure below shows the edit page for the app.css static application file. Notice its Reference field. It's the URL for this file. The #APP_FILES# represents base URL for your app's static files.

Figure 16-12 Defining a CSS Stylesheet in Static Application Files

To include this CSS style sheet in all pages of your application, start by clicking the Copy icon next to the style sheet's Reference URL to copy it to the clipboard. Next, go to the User Interface Attributes section of Shared Components. There, select the CSS tab as shown below. Paste the Reference value from the clipboard into the File URLs field. The result: CSS rules in your app.css now apply to all pages.

Tip:

If you need to reference multiple URLs, put each on its own line in File URLs section.

Figure 16-13 Including a CSS Style Sheet in Every Page of Your App

16.3.5 Posting a Break Room Photo#

Walk through a photo upload flow, from image selection to the refreshed Cards region on the calling page.

Now, when user SMITH adds a break room photo, she sees the modal drawer dialog shown below with a large upload image item and a field to enter a title.

Figure 16-14 Upload Image Page Item with Custom Preview Icon Size

After clicking on the empty image icon a file dialog appears. If Capture Using had been set, the device's camera would have turned on to snap a new photo. After choosing – or taking – a photo, the preview of the selected photo appears as shown below. Clicking (Upload) submits the form. The Form - Automatic DML page process inserts the new row into the form's data source: the EBA_DEMO_EMP_BREAKROOM_PHOTO_V view. It sets the title, image, and hidden P6_EMPNO value defaulted from the G_APP_USER_EMPNO application item. This results in a new row in the underlying EBA_DEMO_EMP_BREAKROOM_PHOTOS table with the IMAGE BLOB column containing the uploaded photo.

Figure 16-15 User Sees Large Image Preview Before Clicking Upload

A dynamic action on the Dialog Closed event refreshes the Cards region, so SMITH immediately sees her posted photo.

Figure 16-16 Cards Page Refreshes on Dialog Close to Show Latest Photo

16.4.1 Showing BLOB Column Images on Cards#

Configure a Cards region to display uploaded photos from a BLOB column with captions and preview links.

The Break Room page has a Cards region based on the EBA_DEMO_EMP_BREAKROOM_PHOTO_V view. Set the region's Media attributes to display the IMAGE BLOB column. As shown below, in the BLOB Attributes section it also specifies the:
  • Mime Type Column – to help the browser correctly handle the image, and
  • Last Updated Column – to let it know when to reload a cached image.
Figure 16-17 Configuring Card Media Attributes to Display BLOB Column Image

To customize the presentation of the card's information, enable Advanced Formatting in the region's Secondary Body section, and use the following HTML with substitutions. This formats the TITLE value in bold and – after a line break (<br>) – info on who posted the photo and when in an extra small text body font.

<strong>&TITLE.</strong><br>
<span class="u-text-body-xs">(&POSTED. by &POSTED_BY.)</span>

The region's cards are 16 characters wide, based on the page's base font size. The app-wide CSS rule that includes the following "Card View Item Width" variable assignment establishes this behavior for all Cards regions in the application.

    --a-cv-item-width: 16rem;

To let users see any image in a larger size, a Full Card action on the region redirects the user to a Preview Break Room Photo modal dialog page, passing the selected image ID. These few settings make the Break Room photo browsing experience below.

Figure 16-18 Configuring Card Item Media from a CLOB Image Column

16.4.2 Previewing Image in a Form Region#

To preview an image from a BLOB image column, use a Form region based on the table or view that includes it, or use a SQL query that selects it.

IF you pick a SQL query approach, ensure you indicate the primary key column. You can hide all columns other than the image and any title you want to display. For example, mark the ID column as Primary Key, and set the type of all columns except P18_TITLE and P18_IMAGE to Hidden.

select id,
       title,
       image, /* BLOB image */
       updated,
       file_name,
       mime_type,
       apex_string.format('%s (%s by %s)',
            title,
            posted,
            posted_by) as description
  from eba_demo_emp_breakroom_photo_v

Then, as shown below, set P18_IMAGE to have type Display Image, to be based on a BLOB Column, and configure additional column names for Alternative Text, Filename, MIME Type, and Last Updated.

Tip:

In addition to providing improved accessibility for screen-readers, the Alternative Text value provides the hover-over "tooltip" as well.

Figure 16-19 Configuring Display Image Form Page Item for Image BLOB
To omit a label for the P18_IMAGE item:
  • Blank out its Label property
  • Set Appearance > Template to Hidden, and
  • Set Label Column Span to zero (0).

To center the image and vertically stretch it to fill the dialog, set its Advanced > CSS Classes to vertical_stretch and add the following two CSS rules to the page's CSS > Inline setting.

.vertical_stretch {
   height: calc(100vh - 3em);
}

#P18_IMAGE {
   display : block;
   margin  : auto;
}

Tip:

The calc(100vh - 3em) computes the remaining vertical height after accounting for the height of three (3) characters in the page's base font size used by the dialog title area. For more information, see Applying Vertical Stretch in a Dialog.

If you use a modal dialog page, you can set a dynamic title on page load to show the image's title. In a Page Load dynamic action, call the setDialogTitle() JavaScript function from Providing App-Wide Helper Functions to set the dialog title dynamically to the title of the break room photo. After doing these few steps, you end up with the result shown below. A Full Card action on the Break Room Cards region in the calling page redirects to your new modal image preview dialog page, passing in the value of the P18_ID image primary key. This lets users get a better look at any photo. Hovering their mouse over the image, they can also see the image description in the tooltip.

Figure 16-20 Previewing an Image from a BLOB Column in a Form Region

16.4.3 Displaying BLOB Images in Classic Reports#

In a Classic Report region, include the size of the BLOB in your query instead of the BLOB column itself.

Use the GETLENGTH function in the DBMS_LOB package to compute it.

select  id,
        title,
        sys.dbms_lob.getlength(image) image, /* Size of BLOB Image */
        file_name,
        posted,
        posted_by,
        updated
from eba_demo_emp_breakroom_photo_v
order by updated desc

As shown below, set the Type of the IMAGE (size) column to Display Image and provide the additional image-related column names in the BLOB Attributes section. These include the BLOB image, Primary Key, Mime Type, Filename, and Last Updated date.

Figure 16-21 Configuring Display Image BLOB Column in Classic Report

To control the size of the image preview, add the following rule to the CSS > Inline section of your page. It constrains the height and keeps the image's aspect ratio. You can also just constrain the width by specifying that property instead of height.

.t-Report-report img {
    /* constrain width or height + keep aspect ratio
     *
     * width: 130px;
     */
    height: 130px;
    object-fit: contain;
}

This combination produces an image in each row as shown below.

Figure 16-22 Custom-Sized Images in a Classic Report

16.4.4 Using BLOB Images in Interactive Reports#

In an Interactive Report region, include the size of the BLOB in your query instead of the BLOB column itself.

Use the GETLENGTH function in the DBMS_LOB package to compute it.

select id,
       title,
       sys.dbms_lob.getlength(image) image, /* Size of BLOB Image */
       file_name,
       posted,
       posted_by,
       updated
from eba_demo_emp_breakroom_photo_v
order by updated desc

As shown below, set the Type of the IMAGE (size) column to Display Image and provide the additional image-related column names in the BLOB Attributes section. This includes column names for the BLOB image, Primary Key, Mime Type, Filename, and Last Updated date.

Figure 16-23 Configuring BLOB Image Column in an Interactive Report

To control the size of the image preview in each row, add the following rule to the page's CSS > Inline section. It constrains the height and keeps the image's aspect ratio. You can also just constrain the width by specifying that property instead of height.

.a-IRR-table img {
    height: 90px;
    object-fit: contain;
}

.a-IRR {
   --a-gv-cell-padding-y: 0px;
}

This combination produces the interactive report with image preview shown below.

Figure 16-24 BLOB Images in an Interactive Report

16.4.5 Including BLOB Images in Content Row#

In a Content Row Template Component region, choose a table or view that includes the BLOB image column.

Alternatively, select the BLOB image column in the query as shown below.

select id,
       title,
       image, /* BLOB image */
       file_name,
       posted,
       posted_by,
       mime_type,
       updated
from eba_demo_emp_breakroom_photo_v

Start by verifying that the primary key column has its Primary Key switch enabled.

On the region's Attributes tab, set the Avatar > Type to Image. Then, click the Image display button to open the Media dialog where you configure the BLOB image related column names.

Figure 16-25 Configuring BLOB Image in a Content Row
To control the size of the avatar image, add the following rule to the CSS > Inline section of the page:
.t-Avatar--image {
   --ut-avatar-size: 10rem;
}

This combination produces the result shown below. The figure shows a Content Row region showing three break room photos per page.

Figure 16-26 Content Row with BLOB Image in the Avatar Image Slot

16.4.6 Viewing BLOB Images in Search Results#

When creating a Search page, base your related Search Configuration on table or view including the BLOB image column.

It can also use a query that includes the image column like:
select id,
       title,
       image, /* BLOB image */
       file_name,
       posted,
       posted_by,
       mime_type,
       updated
from eba_demo_emp_breakroom_photo_v

Set the Icon Source to Image BLOB Column in the Icon and Display section. Then choose the image BLOB column and Mime Type Column as shown below.

Figure 16-27 Setting Up Search Configuration to Use a BLOB Image Column

In the Search page, size the image preview by adding the following rule to the page's CSS > Inline settings. It constrains the width and keeps the image's aspect ratio. You can also just constrain the height by specifying that property instead of width.

.a-ResultsItem-image img {
    /* constrain width or height + keep aspect ratio
     *
     * height: 50px;
     */
   width: 50px;
   object-fit: contain;
}

With that in place, your search results show the BLOB Image exactly to your specifications.

Figure 16-28 Including BLOB Column Image in Search Page Results

16.4.7 Showing BLOB Images in Interactive Grid#

An Interactive Grid has no native Display Image column type. To show an image in a row, use an HTML <img> tag with a src URL the browser can fetch and render inline.

The GET_BLOB_FILE_SRC function in the APEX_UTIL package generates the URL you need. Alternatively, you can serve the inline BLOB images from a custom page.

16.4.8 Using BLOB Column Image URLs Anywhere#

While the Interactive Grid requires it, use your preferred image URL generation approach anywhere that supports an image URL.

For example, the Static Content region has a convenient Image template with numerous declarative options for image aspect ratio, scale, shape, and filter. In a Pre-Rendering computation, compute the value of the image URL with a SQL query like the following.

select apex_page.get_url(
            p_page      => 9000,
            p_x01       => id,
            p_plain_url => true)
       end as image_url
from eba_demo_emp_breakroom_photo_v
where id = :P7_BREAKROOM_IMAGE_ID

Tip:

Pass true for p_plain_url when using GET_URL to generate image URLs for a modal dialog or drawer. It avoids including JavaScript code to close the dialog.

Then, you can reference the URL using &P7_BREAKROOM_IMAGE_URL. in the region's File URL property as shown below. Click the Template Options button to configure the declarative image formatting settings.

Figure 16-39 Using Computed Image URL in a Static Content Image Template

16.5.1 Selecting an Open Job to Add Referral#

Upload a candidate's CV from a modal form linked to the selected open job.

Each uploaded PDF file in the Woods HR app links to a particular open job position. In the Open Positions Referrals page, the user first selects the job to which to their referral pertains. Then they click a (Submit First Referral) or (Submit Referral) button to open a modal form page. They use this modal dialog or drawer page to add a new referral and upload the PDF curriculum vitae (CV). The calling page passes in the dynamic selected job ID so the new referral links to the right job when saved.

16.5.2 Passing Client-Side Data to Dialogs#

Construct appropriate dialog links to pass dynamic client-side values to a dialog, with or without server-generated checksums.

Pages can link declaratively to others when the passed parameter values don't change after rendering. But here the selected job ID is dynamic. The user changes the hidden selected job id page item by clicking on job cards.

Only the server can compute checksums, so the two options for generating a URL that passes dynamic client values to a dialog page are:
  • Use APEX_PAGE.GET_URL in a trigger action to generate it on the server, or
  • Update placeholder values in a dialog URL template, without the additional server round trip.

Avoiding server-side URL generation, however, requires disabling session state protection on the receiving dialog page items. In both cases, JavaScript routines in an app-wide app.js can simplify the job.

16.5.3 Configuring File Upload to a BLOB Column#

Configure a File Upload item to store an uploaded file in a BLOB column with related metadata.

After creating a form region on your table containing the BLOB file column, set the item type of the BLOB column to File Upload. Set the Storage > Type to BLOB column specified in Item Source attribute. Check the Source section in the Property Editor to ensure the expected BLOB column is selected. Finally, as shown below, configure the column names for the:
  • MIME Type Column – the type of file (e.g. application/pdf)
  • Filename Column – the uploaded file name
  • BLOB Last Updated Column – date the file was last modified.

Setting up these additional column names is important. It ensures the browser handles the file correctly when viewed or downloaded.

Tip:

Enable the Primary Key switch for form items tied to primary key columns. (e.g. P23_ID here)

To allow only a subset of file types – for example, only PDF files – set the File Types field to a comma-separated list of file extensions and MIME type strings. For PDF files, the extension is .pdf and the MIME Type is application/pdf. These values inform the system file picker to help users to pick the kind or kinds of file your application needs.

If you set a Maximum File Size, then the user sees a helpful validation error like the following. Then they can pick another file and try the upload again:
  • File is too large. Maximum file size is 3MB.
Figure 16-51 Configuring File Upload Page Item
Then, in the Processing tab of the Page Designer tree, ensure you have:
  • a page process of type Form - Automatic Row Processing (DML)
    • with its Form Region property set to the form region,
    • sequenced before the Close Dialog process if the page is a modal dialog or drawer, and
  • a button like the CREATE one here with its Database Action property set to SQL INSERT action.

Tip:

The Create Page Wizard creates the DML page process on a new Form page, and sets the Database Action property to SQL INSERT action on the CREATE button. However, when you add a Form region to an existing page you need to do these two steps yourself.

Figure 16-52 Ensure Automatic DML Page Process Targets the Form

16.5.4 Ensuring the Automatic DML Process#

Set up the automatic DML process and submit button with database action to insert the uploaded file row.

In the Processing tab of the Page Designer tree, ensure you have:
  • a page process of type Form - Automatic Row Processing (DML)
    • with its Form Region property set to the form region, and
    • sequenced before the Close Dialog process if the page is a modal dialog or drawer
Figure 16-53 Ensure Automatic DML Page Process Targets the Form

Importantly, also check that the button that submits the page – like CREATE below – has its Database Action property set to SQL INSERT action. This informs the Automatic Row Processing (DML) page process to insert the submitted row.

Tip:

The Create Page Wizard creates the DML page process on a new Form page, and sets the Database Action property to SQL INSERT action on the CREATE button. However, when you add a Form region to an existing page you need to do these two steps yourself.

Figure 16-54 Database Action on Submitting Button Informs Automatic DML Process

16.5.5 Controlling File Upload Item Behavior#

Use the properties in the Display section to control your end users' experience when uploading a file.

The Display As controls how the user sees the file upload item on the page. By default, it shows as an Inline File Browse but other styles featuring a drop zone are also available. The Display Download Link, Download Link Text, and Content Disposition settings define the download link the user can click to download the file when editing an existing row.

To let the user upload a "live" photo, set Capture Using to open a device's main or selfie camera. Users can still select an existing file instead. The figure shows the File Upload page item P23_CANDIDATE_CV_RESUME selected in the component tree, and highlights the Display group properties that control how user's see and interact with it.

Figure 16-55 Configuring File Upload Page Item Display Behavior

16.5.6 Submitting a Job Referral CV#

Walk through the referral CV upload flow, from selecting a PDF to refreshing the calling page.

Now, when user SMITH selects the Product Manager – Outpatient Services job card and clicks (Submit First Referral) to enter a new one, she sees the modal dialog shown below to choose the PDF curriculum vitae (CV) and supply a candidate name.

Figure 16-56 Submitting a New Job Referral Candidate's CV

When she clicks on the inline file browse area, the system file picker dialog appears, with only PDF files enabled for selection. After she picks a PDF file containing the candidate's CV and enters the candidate's name, the dialog reflects the file name to be uploaded as shown below.

Finally, after clicking on the (Upload) button, the Form – Automatic Row Processing (DML) page process inserts the new row into the EBA_DEMO_EMP_JOB_REFERRALS table, including the PDF file into the CANDIDATE_CV_RESUME BLOB column and the Close Dialog process does its job.

Figure 16-57 Uploading a File to a BLOB Column

The dialog's closing triggers a Dialog Closed dynamic action that refreshes the Referral Candidates list and the Candidate CV PDF Preview region to immediately reflect the newly added candidate and his CV.

Figure 16-58 Previewing Newly Uploaded Job Referral Candidate's CV

16.6.1 Creating PDF Viewer Template Component#

Use a Template Component to simplify viewing PDF files inline.

As shown below, the PDF Viewer component consists of simple HTML markup. It uses the {if/} Template Directive to conditionally include an <iframe> tag if the PDF URL custom attribute is set. The iframe's src attribute gets its value from this PDF URL parameter, using the substitution #PDF_URL#.

The developer can also set the Other Content Height attribute to vertically stretch the PDF-viewing frame. Its height will be the full vertical height of the viewport (100vh) minus the other content height (in rem units).

<p align="center">
    {if PDF_URL/}
    <iframe style="height: calc(100vh - #OTHER_CONTENT_HEIGHT#rem)"
              src="assets/images/oracle-apex-26.1/creating-pdf-viewer-template-component.html" data-original-src="https://docs.oracle.com/en/database/oracle/apex/26.1/apxdc/creating-pdf-viewer-template-component.html#PDF_URL#"
              width="100%" height="100%"></iframe>
    {endif/}
</p>

The Available As setting of Single (Partial) means the component expects to be used with a data source that retrieves a single row of data.

Figure 16-59 Simple Template Component to More Easily View PDFs Inline

16.6.2 Serving an Inline PDF from a BLOB Column#

Use a page to serve inline PDF data.

It calls APEX_HTTP.DOWNLOAD in a Pre‑Rendering process in the Before Header section. For convenience, it can use the native X01 parameter APEX defines. The following DOWNLOAD_REFERRAL_CV_PDF procedure retrieves the PDF file data and MIME type from the employee job referrals table using the referral ID. Then it downloads the file data. Notice it passes true to the p_is_inline parameter so the PDF is sent to the client in the way the browser can use as an inline PDF.

-- In package eba_demo_woodshr_file
procedure download_referral_cv_pdf(
    p_id in number)
is
    l_file_blob eba_demo_emp_job_referrals.candidate_cv_resume%type;
    l_mime_type eba_demo_emp_job_referrals.mime_type%type;
begin
    select candidate_cv_resume, mime_type
      into l_file_blob, l_mime_type
      from eba_demo_emp_job_referrals
     where id = p_id;

    apex_http.download(
        p_blob         => l_file_blob,
        p_content_type => l_mime_type,
        p_is_inline    => true );
end download_referral_cv_pdf;  

Call this procedure with an Invoke API process in the Before Header section as shown below. The figure shows the Download Inline CV page process and highlights its Type of Invoke API, Package name of EBA_DEMO_WOODSHR_FILE, and procedure name of DOWNLOAD_REFERRAL_CV_PDF.

Figure 16-60 Pre-Rendering Invoke API Process to Download a PDF

Configure the value of its p_id parameter using the PL/SQL expression apex_application.g_x01 as shown below.

Caution:

If any authenticated user can view any PDF, as in this Woods HR example, using the convenient X01 parameter is fine. However, to guarantee a generated URL referencing a particular PDF BLOB file cannot be manipulated to view a different one, use a checksum-protected hidden page item instead to pass the file ID to download.

Figure 16-61 Configuring Break Room Referral ID Parameter Value

16.6.3 Using PDF Viewer in Job Referrals Page#

Use a PDF Viewer template component to display a PDF file inline in a page.

The Open Position Referrals page shown in Page Designer below includes a PDF Viewer template component. It's available only as a Single (Partial), so it shows a single row of data. Accordingly, the Source query below returns a single row with a single PDF_PAGE_URL column.

The CASE statement uses APEX_PAGE.GET_URL to generate the URL to page 9001. It serves an inline PDF from the BLOB column in the employee job referrals table, based on the referral ID passed in the X01 parameter. Since it references the P11_REFERRAL_CANDIDATE_ID hidden page item as a bind variable, the Page Items to Submit property reflects that. This ensures the latest value of that item is always sent to the server when the region gets refreshed.

select case
        when :P11_REFERRAL_CANDIDATE_ID is not null then
            apex_page.get_url(
                p_page         => 9001,
                p_x01          => :P11_REFERRAL_CANDIDATE_ID,
                p_absolute_url => true)
       end as PDF_PAGE_URL
from dual

Tip:

An iframe is separate document inside another page that includes it. Any relative URLs it contains resolve against its own location, rather than the containing parent page's. This explains why the PDF_PAGE_URL value here must be an absolute URL to work correctly. You request one by passing true for the p_absolute_url parameter to APEX_PAGE.GET_URL.

Figure 16-62 Sourcing the PDF_URL in the Region's Query

On the Attributes tab in the Property Editor, notice Appearance > Display is set to Single (Partial). The PDF URL property gets its value from the PDF_PAGE_URL column in the regions single-row query result using the &PDF_PAGE_URL. substitution. The Other Content Height is set to 15 to indicate that the other content on the page takes up about 15 lines of height in the base font size.

Figure 16-63 Configuring PDF URL

Finally, a dynamic action on the value Change event of the P11_REFERRAL_CANDIDATE_ID candidate picker Select List page item refreshes the Candidate CV PDF Viewer region. This combination produces an interactive inline PDF viewer that updates to show the CV of the selected candidate as shown below.

Figure 16-64 Candidate Picker Select List Refreshes PDF Viewer Template Component

16.6.4 Allowing Embedding from Same Domain#

Allow same-origin frames so the PDF Viewer can preview inline PDFs served by your app.

By default, your app disallows iframe content. This ensures you are always aware of any content your pages embed after ensuring it's from a trusted source. The Shared Components Security > Browser Security > Embed in Frames property lets you control this behavior.

Leaving this setting at its default of Deny, when the PDF Viewer tries to preview an inline PDF it encounters an error as shown below.

Figure 16-65 Encountering an Error Due to "Embed in Frames" = Deny
You can see additional information in your browser's developer tools console:
Refused to display 'https://example.com/' in a frame
because it set 'X-Frame-Options' to 'deny'.

The PDF Viewer template component includes the inline PDF BLOB content from a well-known table, and is served by a page that's part of your application. Therefore, it's safe to change the Embed in Frames property to Allow from same origin as shown below. This change makes the PDF preview work as expected.

Figure 16-66 Allowing Embedded Frame Only from the Same Domain

16.7.1 Configuring Single-File Download Action#

Download a single file with a SQL query that returns the BLOB, file name, and MIME type.

When downloading a single file using the Download dynamic action, provide a three-column SQL query as shown below that retrieves the BLOB file data, the file name, and the MIME Type of a single row. If the query references page item values as bind variables, make sure to include their names in the comma-separated Items to Submit list. The query retrieves the PDF BLOB containing the CV of the candidate whose ID is stored in the hidden page item P11_REFERRAL_CANDIDATE_ID. This is the Select List page item that picks the current candidate to review for the selected job.

select candidate_cv_resume,
       file_name,
       mime_type
  from eba_demo_emp_job_referrals
 where id = :P11_REFERRAL_CANDIDATE_ID

The figure shows the Download CV dynamic action selected in the component tree and highlights the SQL query that retrieves the BLOB content to download, its file name, and MIME type.

Tip:

When using the Download page process, the SQL query is the same but there are no Items to Submit to configure since it happens on the server.

Figure 16-67 Downloading a Single File with the Native Dynamic Action

16.7.2 Downloading ZIP Archive of Multiple Files#

Download multiple files as a zip archive by returning one BLOB and file name per row.

Multiple files get downloaded automatically in a zip archive. With a couple of small changes, you can change the (Download) button to instead download all CVs for the selected job instead. To do this, enable the Multiple Files switch, configure a name for the zip file, and adjust the query to remove the MIME Type column and retrieve multiple rows as shown below. The file name field can include substitutions like &P11_SELECTED_JOB_ID. as part of the file name. Since the multi-CV query uses a different P11_SELECTED_JOB_ID page item as a bind variable, adjust the Items to Submit accordingly.

select candidate_cv_resume,
       file_name
  from eba_demo_emp_job_referrals
 where job_id = :P11_SELECTED_JOB_ID

Tip:

When using the Download page process, the SQL query is the same but there are no Items to Submit to configure since it happens on the server.

Figure 16-68 Downloading Multiple Files in a ZIP Archive with the Native Dynamic Action

16.8.1 Handling Temporary Uploaded Files#

Use APEX_APPLICATION_TEMP_FILES to stage uploaded images or files before saving them.

To support uploading multiple images or files in a logical flow that can span one or more pages, change the Storage > Type of your Image Upload or File Upload page item to Table APEX_APPLICATION_TEMP_FILES.

16.8.2 Posting Multiple Break Room Photos#

Post multiple Break Room photos by staging uploads, tracking titles, previewing them, and saving them together.

The Break Room page in the Woods HR app lets users post multiple photos at a time and assign titles to the set of pictures they choose. The solution combines an Image Upload page item with:
  • Temporary image storage in APEX_APPLICATION_TEMP_FILES with End of Session retention
  • "Sidecar" collection to store uploaded image titles, keyed by unique uploaded file name
  • EBA_DEMO_WOODSHR_TEMP_PHOTOS view joining temporary uploads and titles together
  • Interactive Grid, based on the view, to preview uploaded images and add associated titles
  • EBA_DEMO_WOODSHR_FILE package containing related application logic.

16.9.1 Understanding Object Bucket URL Format#

Understand how an OCI object bucket URL includes the region, namespace, bucket name, and object name.

As shown below, your object bucket has a Namespace like x1y2z3a4b5c6 that appears in URLs when accessing the bucket via REST API. A companion-bucket in the Frankfurt OCI data center has a URL like:
https://objectstorage.eu-frankfurt-1.oraclecloud.com/n/x1y2z3a4b5c6/b/companion-bucket
While a file myfile.jpg in that bucket will have URL:
https://objectstorage.eu-frankfurt-1.oraclecloud.com/n/x1y2z3a4b5c6/b/companion-bucket/o/myfile.jpg

Tip:

The eu-frankfurt-1 in URLs in this section reflects the OCI region in Frankfurt, Germany. Use your own OCI bucket region when putting these ideas into practice in your own application.

Figure 16-84 Object Bucket Has a Namespace Used in REST APIs

16.9.2 Defining OCI Web Credential for REST APIs#

Create an OCI Native Authentication web credential to call object bucket REST APIs.

Assume in your OCI tenancy you have:
  • configured an object bucket named companion-bucket
  • identified a user with privileges to manage the bucket, and
  • created an API Key for that user to access OCI REST APIs.

To use the REST APIs for an object bucket or other OCI services, start by creating a Web Credential. In Workspace Utilities, on the Web Credentials list page, create a new one of type OCI Native Authentication as shown below. In this example, it's named OCI Object Bucket and has a Static ID of oci_object_bucket. You use this Static ID in PL/SQL calls to work with the bucket.

Fill in all web credential details including:
  • OCI User ID – Oracle Cloud ID (OCID) for the user with privileges to manage the bucket
  • OCI Private Key – Contents of the .pem file you downloaded when creating the API Key
  • OCI Public Key Fingerprint – Additional security info provided at the time of API Key creation

Optionally, enter a base URL to limit the use of this credential to object buckets in the x1y2z3a4b5c6 namespace using a string like the following in Valid for URLs:

https://objectstorage.eu-frankfurt-1.oraclecloud.com/n/x1y2z3a4b5c6/b/

The figure shows the Web Credentials Create/Edit dialog in App Builder with all details required to create a new OCI Native Authentication Web Credential named OCI Object Bucket.

Figure 16-85 Defining OCI Native Authentication Web Credential

16.9.3 Listing Buckets with REST Data Source#

Create and test a REST Data Source that lists object buckets in an OCI namespace.

Create an Object Buckets REST Data Source of type Oracle Cloud Infrastructure (OCI) for the base URL of all object buckets in your namespace:
https://objectstorage.eu-frankfurt-1.oraclecloud.com/n/x1y2z3a4b5c6/b/

On the Authentication step of the Create REST Data Source Wizard, use the OCI Object Bucket web credential created before, and click the (Advanced >) button to add the compartmentId parameter of type URL Query String having your object bucket's compartment OCID as its static value. Then proceed with the automatic discovery of the data profile and complete the wizard.

Tip:

After creating the first REST Data Source, edit its Remote Server to remove the trailing /b/, and adjust the URL Path Prefix of the REST Data Source to add the /b/ there instead. This ensures your setup will be similar to the screenshots in this section.

The figure shows the REST Data Source edit page in App Builder for the Object Buckets component. Notice the compartmentId parameter and the use of the OCI Object Bucket Web Credential.

Figure 16-86 Defining Object Buckets REST Data Source

To test that the REST Data Source is working, click the "play" button for the GET Fetch rows operation in the Test Operation column of the Operations grid. You see the list of your object buckets to validate that the REST Data Source is working.

Figure 16-87 Testing Object Buckets REST Data Source

Examining the automatically-discovered Data Profile, as shown below, you confirm the 8 columns of information available for object buckets.

Figure 16-88 Data Profile for Object Buckets REST Data Source

16.9.4 Using REST Data Source for Bucket Files#

Create a REST Data Source for listing and retrieving files in an OCI object bucket.

Create a Bucket Objects REST Data Source of type Oracle Cloud Infrastructure (OCI) for the base URL of a named object bucket in your namespace, using :bucketname in the URL to implicitly define a URL Pattern parameter by that name.

https://objectstorage.eu-frankfurt-1.oraclecloud.com/n/x1y2z3a4b5c6/b/:bucketname/o/

Enter a default bucket name value (e.g. companion-bucket) for the bucketname parameter so the automatic data profile discovery can do its job. On the Authentication step of the Create REST Data Source Wizard, use the OCI Object Bucket web credential created before. Then proceed with the automatic discovery of the data profile and complete the wizard.

Edit the new REST Data Source to add the following additional configuration shown below:
  • Static fields URL Query String parameter with value name,size,md5,timeCreated
  • GET operation named Get File with URL Pattern o/:filename, URL Pattern parameter filename, and static ID get_file
  • POST operation named Get File PAR with URL Pattern p/ with static ID get_file_par
Figure 16-89 Defining Bucket Objects REST Data Source

16.9.5 Serving Inline File from Object Bucket#

Serve an object bucket file inline using an APEX page.

You can create a page to serve inline object bucket images using the DOWNLOAD_BUCKET_FILE procedure. Just call it from a Pre-Rendering Invoke API page process, passing in the bucket file name to download inline. It calls the private helper procedure GET_BUCKET_FILE to retrieve the BLOB contents and MIME type of the bucket file and then downloads it using APEX_HTTP.DOWNLOAD. This page will be nearly identical to the ones explained in Serving Inline BLOB Images with a Page and Serving Inline Temp Images for Preview. The pages all differ only in the "download" procedure they call.

-- In package eba_demo_woodshr_oci
-------------------------------------------------------------------------------
-- Download a file from an object bucket
-------------------------------------------------------------------------------
procedure download_bucket_file(
    p_bucket_name         in varchar2,
    p_file_name           in varchar2,
    p_module_static_id    in varchar2 default 'bucket_objects',
    p_operation_static_id in varchar2 default 'get_file')
is
    l_blob   blob;
    l_mime   varchar2(4000);
begin
    get_bucket_file(
        p_bucket_name         => p_bucket_name,
        p_file_name           => p_file_name,
        p_module_static_id    => p_module_static_id,
        p_operation_static_id => p_operation_static_id,
        p_file_contents       => l_blob,
        p_mime_type           => l_mime);

    apex_http.download(
        p_blob         => l_blob,
        p_content_type => l_mime,
        p_is_inline    => true,
        p_filename     => p_file_name);
end download_bucket_file; 

The GET_BUCKET_FILE procedure calls private helper procedure BUCKET_URL_FOR to get the absolute URL and web credential static id to use for the file "get" operation. It sets the Accept and Accept-Encoding headers to let the object bucket know it wants to receive the binary content verbatim, without compression. It uses the MAKE_REST_REQUEST_B function to perform an HTTP GET operation with the absolute URL of the file, getting the binary file contents as the return value. Finally, it retrieves the MIME Type of the file by peeking at the Content-Type response header.

Tip:

A REST Data Source operation does not support a binary response, so use MAKE_REST_REQUEST_B to work with a REST API that returns a binary file.

-- In package eba_demo_woodshr_oci
-------------------------------------------------------------------------------
-- Get file and mimetype from an object bucket file
-------------------------------------------------------------------------------
procedure get_bucket_file(
    p_bucket_name         in varchar2,
    p_file_name           in varchar2,
    p_file_contents      out blob,
    p_mime_type          out varchar2,
    p_module_static_id    in varchar2 default 'bucket_objects',
    p_operation_static_id in varchar2 default 'get_file')
is
    l_url    varchar2(32767);
    l_cred   varchar2(4000);
begin
    bucket_url_for(
        p_module_static_id     => p_module_static_id,
        p_operation_static_id  => p_operation_static_id,
        p_bucket_name          => p_bucket_name,
        p_file_name            => p_file_name,
        p_credential_static_id => l_cred,
        p_file_url             => l_url);

    apex_web_service.set_request_headers(
        p_name_01  => 'Accept',          p_value_01 => '*/*',
        p_name_02  => 'Accept-Encoding', p_value_02 => 'identity',
        p_reset    => true);

    p_file_contents := apex_web_service.make_rest_request_b(
                p_credential_static_id => l_cred,
                p_http_method          => 'GET',
                p_url                  => l_url );

    if    apex_web_service.g_status_code <> 200
       or p_file_contents is null
       or dbms_lob.getlength(p_file_contents) = 0
    then
        raise_application_error(-20001,
            'Download failed: HTTP '||apex_web_service.g_status_code);
    end if;

    p_mime_type := nvl(apex_web_service.get_request_header('Content-Type'),
                    'application/octet-stream');
end get_bucket_file; 

The BUCKET_URL_FOR helper method queries application metadata for the REST Data Source operation whose static ID passed in p_operation_static_id. The p_module_static_id parameter value identifies the REST data source context for this lookup. The lets the REST Data Source definition itself supply the runtime code with the fully qualified URL and credential static ID without having to hard-code that information anywhere.

-- In package eba_demo_woodshr_oci
-------------------------------------------------------------------------------
-- Return absolute URL of object bucket file using REST Data Source
-- operation as a template
-------------------------------------------------------------------------------
procedure bucket_url_for(
    p_module_static_id      in varchar2,
    p_operation_static_id   in varchar2,
    p_bucket_name           in varchar2,
    p_file_name             in varchar2,
    p_credential_static_id out varchar2,
    p_file_url             out varchar2)
is
    c_app_id constant number := sys_context('APEX$SESSION','APP_ID');
begin
    select replace(
             replace(m.url_endpoint||o.url_pattern, ':filename', p_file_name),
             ':bucketname', p_bucket_name),
           m.credential_static_id
      into p_file_url, p_credential_static_id
      from apex_appl_web_src_operations o
      join apex_appl_web_src_modules   m on o.module_id = m.module_id
     where o.operation_static_id = p_operation_static_id
       and m.module_static_id    = p_module_static_id
       and o.application_id      = c_app_id;
end bucket_url_for; 

16.9.6 Browsing and Selecting Bucket Files#

Browse object bucket files in a Content Row region and act on the ones the user selects.

By combining a Content Row region and the Bucket Objects REST Data Source, you can build a page that browses object bucket files and lets a user select them for further processing.

16.9.7 Storing an Uploaded File in Object Bucket#

Upload a temporary file to an OCI object bucket and store its bucket file name in your table.

The PUT_BREAKROOM_PHOTO_TEMP_FILE procedure in the EBA_DEMO_WOODSHR_OCI package adds a temporary file from APEX_APPLICATION_TEMP_FILES to an object bucket. It uses the unique uploaded file name passed in the p_unique_file_name parameter to retrieve the BLOB content, MIME Type, and file name from APEX_APPLICATION_TEMP_FILES. In the Woods HR app, to ensure the file name is unique in the bucket, it prepends the primary key of the EBA_DEMO_EMP_BREAKROOM_PHOTO row passed in the p_breakroom_photo_id parameter. It calls procedure PUT_BUCKET_FILE to put the file name in the object bucket, then updates the EBA_DEMO_EMP_BREAKROOM_PHOTO row with the final bucket file name.

Tip:

Any pages that work with bucket-stored break room photos use the EMP_BREAKROOM_PHOTO_B view explained in Studying Supporting Tables and Views. It only contains the FILE_NAME column from the underlying EBA_DEMO_EMP_BREAKROOM_PHOTOS table, since the photo data and the MIME Type come from the file in the object bucket.

-- In package eba_demo_woodshr_oci
-------------------------------------------------------------------------------
-- Put a breakroom photo temp file into an object bucket
-------------------------------------------------------------------------------
procedure put_breakroom_photo_temp_file(
    p_bucket_name         in varchar2,
    p_unique_file_name    in varchar2,
    p_breakroom_photo_id  in number,
    p_module_static_id    in varchar2 default 'bucket_objects',
    p_operation_static_id in varchar2 default 'get_file')
is
    l_temp_image_blob blob;
    l_file_name       varchar2(1000);
begin
    for j in (select blob_content, mime_type, filename
                from apex_application_temp_files
               where name = p_unique_file_name)
    loop
        if     j.blob_content is not null
           and dbms_lob.getlength(j.blob_content) > 0
        then
            for k in (select file_name, mime_type
                        from eba_demo_emp_breakroom_photos
                       where id = p_breakroom_photo_id)
            loop
                l_file_name := p_breakroom_photo_id||'_'||j.filename;
                put_bucket_file(
                    p_bucket_name         => p_bucket_name,
                    p_file_name           => l_file_name,
                    p_mime_type           => j.mime_type,
                    p_file_contents       => j.blob_content,
                    p_module_static_id    => p_module_static_id,
                    p_operation_static_id => p_operation_static_id);
            end loop;
            -- Record the filename the uploaded file gets in the bucket
            update eba_demo_emp_breakroom_photos
               set file_name = l_file_name
             where id = p_breakroom_photo_id;
        end if;
    end loop;
end put_breakroom_photo_temp_file; 

The PUT_BUCKET_FILE procedure calls private helper procedure BUCKET_URL_FOR explained in Serving Inline File from Object Bucket to get the absolute URL and web credential static id to use for the file "put" operation. It proceeds to set the Content-Type header to let the object bucket know the MIME Type of the file being stored, then uses MAKE_REST_REQUEST to perform an HTTP PUT operation with the absolute URL of the file. Notice that it passes the binary file contents as the value of the p_body_blob parameter.

Tip:

A REST Data Source operation does not currently support sending a binary request payload, so you need to use MAKE_REST_REQUEST with the p_body_blob parameter when the REST API to invoke expects the binary contents as the request body.

-- In package eba_demo_woodshr_oci
-------------------------------------------------------------------------------
-- Put a file into an object bucket
-------------------------------------------------------------------------------
procedure put_bucket_file(
    p_bucket_name         in varchar2,
    p_file_name           in varchar2,
    p_mime_type           in varchar2,
    p_file_contents       in blob,
    p_module_static_id    in varchar2 default 'bucket_objects',
    p_operation_static_id in varchar2 default 'get_file')
is
    l_clob   clob;
    l_url    varchar2(32767);
    l_cred   varchar2(4000);
begin
    bucket_url_for(
        p_module_static_id     => p_module_static_id,
        p_operation_static_id  => p_operation_static_id,
        p_bucket_name          => p_bucket_name,
        p_file_name            => p_file_name,
        p_credential_static_id => l_cred,
        p_file_url             => l_url);

    apex_web_service.set_request_headers(
        p_name_01  => 'Content-Type',
        p_value_01 => p_mime_type,
        p_reset    => true);

    l_clob := apex_web_service.make_rest_request(
                p_credential_static_id => l_cred,
                p_http_method          => 'PUT',
                p_url                  => l_url,
                p_body_blob            => p_file_contents);

    if apex_web_service.g_status_code not in (200,201) then
        raise_application_error(-20001,
            'Upload failed: HTTP '||apex_web_service.g_status_code);
    end if;
end put_bucket_file;

16.9.8 Exploring Pre-Authenticated Requests#

Compare ways to serve object bucket files, including application proxy pages and pre-authenticated request URLs.

When your application pages reference images or other assets from the object bucket by URL, you have multiple options:
  • An authenticated application page can serve inline images from the bucket, or
  • You can use a pre-authenticated request (PAR) URL to the bucket file.

Using the first strategy, your application page acts as a proxy to the bucket file. In the second approach, the browser downloads the file directly from the object bucket. A hybrid strategy can serve images using a local cache table of PAR URLs, encapsulating the details of using cached PAR URLs or creating new ones as necessary.

16.4.7.1 Using GET_BLOB_FILE_SRC for Image URL#

Build an image URL with GET_BLOB_FILE_SRC and display it in an Interactive Grid using a generated img tag.

The grid in the page below uses the following query. Its CASE expression for the IMAGE column checks that the BLOB contains data. If so, it calls APEX_STRING.FORMAT to build an <img> tag. The alt attribute aids accessibility, title is the tooltip text, and src contains the URL to fetch the image. FORMAT replaces each %s with the expression values that follow, in order. The first and second placeholders for the alt and title attributes both get the image TITLE "escaped" as needed, while the GET_BLOB_FILE_SRC function generates the image URL for the src attribute using the third %s placeholder. For details on the arguments to this function and the required P12_IMAGE BLOB page item it references by name, see Configuring Reference Image Page Item.

select id,
       title,
       case
           when     image is not null
                and dbms_lob.getlength(image) > 0
           then apex_string.format('<img alt="%s" title="%s" src="assets/images/oracle-apex-26.1/e4843f1f016e0ce9.bin" data-original-src="https://docs.oracle.com/en/database/oracle/apex/26.1/apxdc/%s">',
                apex_escape.html_attribute(title),
                apex_escape.html_attribute(title),
                apex_util.get_blob_file_src('P12_IMAGE', ID))
       end as image,
       file_name,
       posted,
       posted_by,
       updated
from eba_demo_emp_breakroom_photo_v

Tip:

Some characters have special meaning in the Hypertext Markup Language (HTML). The HTML_ATTRIBUTE function in the APEX_ESCAPE package replaces them with equivalent character references using the Unicode numeric representation. This is known as "escaping" the value. For example, a break room photo title like:
Team's "Best" Food & Drink
turns into the following when you escape it for use in an HTML attribute:
Team&#x27;s&#x20;&#x22;Best&#x22;&#x20;Food&#x20;&#x26;&#x20;Drink

Forgetting to escape values can lead to unexpected formatting or incorrect behavior.

The query produces the <img> tag as the IMAGE column value. To configure it to display as an image, set its column Type to Display Only, and its Format to HTML, as shown below.

Figure 16-29 Configuring HTML IMAGE tag to Display Only

Size the image by adding the rule below to the page's CSS > Inline section. It constrains the height and keeps the image's aspect ratio. You can also just constrain the width by specifying that property instead of height.

.a-IG img {
    /* constrain width or height + keep aspect ratio
     * width: 90px;
     */
    height: 90px;
    object-fit: contain;
}

This combination produces the Interactive Grid shown below.

Tip:

If your Interactive Grid is editable, make sure to enable Query Only on the IMAGE column.

The figure displays breakroom photos page-by-page in a read-only Interactive Grid, five rows at a time.

Figure 16-30 Displaying BLOB Image Using Image URL via GET_BLOB_FILE_SRC

16.4.7.2 Configuring Reference Image Page Item#

Use a BLOB-backed form item to let GET_BLOB_FILE_SRC infer the file metadata for an image URL.

Recall the expression used in the query to generate the image URL:
select
       ⋱
         apex_util.get_blob_file_src('P12_IMAGE', ID)
       ⋰
  from eba_demo_emp_breakroom_photo_v

ID is the primary key column in the employee break room photos view, and P12_IMAGE names a form page item whose source is the image BLOB. GET_BLOB_FILE_SRC infers the column names for the file BLOB, MIME Type, and Last Updated date from this page item. It gets the table name from the source of the item's related Form region.

The P12_IMAGE item does not need to be visible on the page. As shown below, it can reside in the Dialogs, Drawers, and Popups slot on the page or be visually hidden by applying the u‑hidden CSS class.

Figure 16-31 Form Image Item GET_BLOB_FILE_SRC References for BLOB Metadata

16.4.7.3 Serving Inline BLOB Images with a Page#

Serve an inline image from a page process by calling APEX_HTTP.DOWNLOAD with the image ID.

Instead of generating a URL for APEX’s native inline image handler with APEX_UTIL.GET_BLOB_FILE_SRC, you can create your own page to serve the image. The page calls APEX_HTTP.DOWNLOAD in a Pre‑Rendering process in the Before Header section. For simplicity, it can use the native X01 parameter APEX defines. The following DOWNLOAD_BREAKROOM_PHOTO procedure retrieves the image data and MIME type from the break room photos table using the image ID. Then it downloads the image data. Notice it passes true to the p_is_inline parameter so the image is sent to the client in the way the browser can use as an inline image.

-- In package eba_demo_woodshr_file
procedure download_breakroom_photo(
    p_id in number)
is
    l_file_blob eba_demo_emp_breakroom_photos.image%type;
    l_mime_type eba_demo_emp_breakroom_photos.mime_type%type;
begin
    select image, mime_type
      into l_file_blob, l_mime_type
      from eba_demo_emp_breakroom_photos
     where id = p_id;

    apex_http.download(
        p_blob         => l_file_blob,
        p_content_type => l_mime_type,
        p_is_inline    => true );
end download_breakroom_photo; 

Call this procedure with an Invoke API process in the Before Header section. The figure shows the Download Inline Image page process selected in the component tree in Page Designer, highlighting its Type of Invoke API and its Package name and Procedure name to invoke the DOWNLOAD_BREAKROOM_PHOTO routine in the EBA_DEMO_WOODSHR_FILE package.

Figure 16-32 Pre-Rendering Invoke API Process to Download an Image

Configure the value of its p_id parameter using the PL/SQL expression apex_application.g_x01. The figure shows the p_id parameter of the DOWNLOAD_BREAKROOM_PHOTO routine selected in the component tree in Page Designer, and highlights the value configured is a PL/SQL expression to return the value of the predefined PL/SQL global variable G_X01 in the APEX_APPLICATION package. This global variable contains the X01 query string parameter value from the URL that fetches the image by ID.

Figure 16-33 Configuring Break Room Image ID Parameter Value

16.4.7.4 Using an Inline Image Serving Page#

Generate an image URL with APEX_PAGE.GET_URL and pass the image ID to your image-serving page.

With the image-serving page 9000 in place, use APEX_PAGE.GET_URL to generate an image URL. Pass the image ID in the x01 parameter as shown below in the modified source query for an Interactive Grid:
select id,
       title,
       case
           when     image is not null
                and dbms_lob.getlength(image) > 0
           then apex_string.format('<img alt="%s" title="%s" src="assets/images/oracle-apex-26.1/e4843f1f016e0ce9.bin" data-original-src="https://docs.oracle.com/en/database/oracle/apex/26.1/apxdc/%s">',
                apex_escape.html_attribute(title),
                apex_escape.html_attribute(title),
                apex_page.get_url(
                    p_page => 9000,
                    p_x01  => ID))
       end as image,
       file_name,
       posted,
       posted_by,
       updated
from eba_demo_emp_breakroom_photo_v

Tip:

If using APEX_PAGE.GET_URL in a modal dialog or drawer page to reference an inline image, pass true to the optional p_plain_url parameter to avoid including JavaScript code to close the dialog before navigating to the supplied URL.

As shown below, this produces the same image-in-a-grid result as before without requiring the hidden form page item the GET_BLOB_FILE_SRC function needs.

Figure 16-34 Inline BLOB Images in an Interactive Grid Using Image-Serving Page

16.4.7.5 Weighing X01 Against a Secure Page Item#

The X01 parameter is convenient, but not checksum-protected. Understand the potential risk of using this parameter to decide if a secure page item is more appropriate for your use case.

16.5.1.1 Using Trigger Actions to Set Selected Job Info#

Use triggered actions to set the selected primary key and refresh other regions when the user clicks a card.

The open jobs appear on cards along the left side of the page as shown below. The small "badge" on each card reflects the count of existing referrals employees have submitted for that job. Clicking on a card sets the current job ID and updates the right side of the page based on how many referrals the selected job has. For example, the Senior Development Lead job has 3 referrals, so the right side updates to show the selected job title, a Referral Candidate Select List, a (Submit Referral) button, and a (Download) button. An inline preview of the selected referral candidate's CV also appears.

Figure 16-40 Selecting an Open Job Card with Existing Referrals

When clicking on a job card like Product Manager - Outpatient Services with no referrals yet, the right side updates as seen below to show the selected job title and a (Submit First Referral) button.

Figure 16-41 Selecting an Open Job with No Referrals Yet

Declarative Trigger Actions set the value of the selected job's referral count and ID. As shown below, the Set Selected Job ID action step of type Set Value assigns the hidden page item P11_SELECTED_JOB_ID to the ID of the selected job using the JavaScript expression $v('ID'). The companion Set Selected Job Referrals action step does the same to set the hidden page item P11_SELECTED_JOB_REFERRALS page item to $v('REFERRALS') column value.

Figure 16-42 Full Card Action Sets Select Job Referral Count and ID

16.5.1.2 Adjusting UI When Selected Job Changes#

React to a value change using a dynamic action event handler.

When the hidden P11_SELECTED_JOB_ID changes value, a When Selected Job Changes dynamic action handles that event. It evaluates its Client-side Condition highlighted below:
  • Type: Item is not null
  • Item: P11_SELECTED_JOB_ID
If P11_SELECTED_JOB_ID is not null, then the True list of action steps executes. If it is null, then the False list of action steps runs instead. These actions combine declarative Refresh, Hide, and Show action steps with appropriate client-side conditions based on the P11_SELECTED_JOB_REFERRALS to decide what to refresh, hide, and show on the right side of the page.

Tip:

Assigning a descriptive name to each action step makes the logic easier to follow when you or a teammates needs to enhance this page in the future.

The figure shows the When Selected Job Changes dynamic action event handler selected in the component tree. It highlights how the true or false result of its configured Client-side Condition affects which set of action steps execute.

Figure 16-43 Using True or False Dynamic Action Steps to Adjust the User Experience

16.5.2.1 Opening Server-Generated Dialog URL#

Open a dialog page with client-side values by passing a server-generated URL to a helper function.

To open a modal dialog or drawer page passing in dynamic client data, use triggered actions to:
  • Call APEX_PAGE.GET_URL to generate the URL on the server, then
  • Pass it to an app.openDialogURL() or app.openDrawerURL() helper function.

16.5.2.2 Passing Client Data Directly to Dialogs#

Open a dialog with dynamic client-side values by substituting page item placeholders in a URL template.

To pass dynamic client data to a dialog directly, thus avoiding a server round trip:
  • Use APEX_PAGE.GET_URL to default a dialog URL template with page item value placeholders
  • Use a JavaScript helper function to substitute the page item placeholders and open the dialog
  • Call the helper function in a triggered action to open the dialog, passing in the template URL
  • Disable session state protection on the target dialog page item receiving the dynamic value.

16.8.1.1 Controlling Retention of Temporary Files#

Control how long uploaded files remain in APEX_APPLICATION_TEMP_FILES before you process them or APEX purges them.

Setting their storage type to Table APEX_APPLICATION_TEMP_FILES makes Image Upload and File Upload items automatically save uploaded files in this table. Its columns appear in the table below.

Table 16-2 APEX_APPLICATION_TEMP_FILES Table Structure

Scroll horizontally to view the full table
Column Name Data Type Description
NAME VARCHAR2 Unique name of the uploaded file
BLOB_CONTENT BLOB Binary content of the file
FILENAME VARCHAR2 Original file name
MIME_TYPE VARCHAR2 MIME type of the file
APPLICATION_ID NUMBER APEX Application ID
CREATED_ON DATE Date and time when file was created

The related Purge Files at setting determines the retention period. Set it to End of Request if you process the uploaded files to permanent storage during the same request that submits them. Set it to End of Session instead to process them later, for example, when the user completes a multi-step process.

16.8.1.2 Referencing Temp Files by Unique Name#

Understand how upload item values identify one or more files in APEX_APPLICATION_TEMP_FILES.

The value of the Image Upload or File Upload page item contains:
  • for single-file upload – the unique name of the uploaded file
  • for multi-file upload – the colon-delimited list of unique names of the uploaded files.

16.8.1.3 Displaying Temp File Names and Types#

Show temporary upload file names and readable file types in a report region.

Show the end-user the file names and user-friendly file types in temporary storage using a report region with a query like:
select filename,
       case mime_type
          when 'application/pdf' then 'PDF Document'
          when 'image/jpeg'      then 'JPEG Image'
          when 'image/png'       then 'PNG Image'
          when 'image/gif'       then 'GIF Image'
          when 'text/plain'      then 'Text File'
          when 'application/zip' then 'ZIP Archive'
          when 'text/csv'        then 'CSV File'
          when 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
          then 'Word Document'
          when 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
          then 'Excel Spreadsheet'
          when 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
          then 'PowerPoint Presentation'
          else 'Other Type'
       end as file_type_description
 from apex_application_temp_files
where application_id = :APP_ID

16.8.1.4 Processing Temporary Uploaded Files#

Process multiple uploaded files by splitting the upload item’s colon-delimited file name list.

Process all uploaded files for the current application using a cursor for loop like the following. Notice it's passing the ':' as the delimiter character to APEX_STRING.SPLIT to process the distinct unique uploaded file names.

for j in (select blob_content,
                 filename,
                 mime_type
            from apex_application_temp_files
           where application_id = :APP_ID
             and name in (select column_value
                            from apex_string.split(:P17_IMAGE),':')))
loop
    -- Reference j.mime_type, j.filename, j.blob_content in here
end loop; 

16.8.1.5 Deleting Temporary Uploaded Files#

Delete temporary upload files from APEX_APPLICATION_TEMP_FILES when you no longer need them.

When the Purge Files at retention period ends, temporary files are automatically deleted. However, to eagerly delete them, use any form of SQL DELETE command against the APEX_APPLICATION_TEMP_FILES table. For example, to delete:
  • All temp files in the session:
    delete from apex_application_temp_files;
  • Just files for current application page's upload item:
    delete from apex_application_temp_files
     where application_id = :APP_ID
                 and name in (select column_value
                                from apex_string.split(:P17_IMAGE),':'));

16.8.2.1 Configuring Multiple File Image Upload#

Configure an Image Upload item to stage multiple photos in APEX_APPLICATION_TEMP_FILES until the session ends.

The Break Room page in the Woods HR app has a (Post Multiple Photos) button that lets users share many pictures at once. It redirects to the Add Break Room Photos modal drawer page shown below whose:
  • Image Upload item uses Table APEX_APPLICATION_TEMP_FILES,
  • Purges files at the End of Session, and
  • Enables the Allow Multiple Files switch.
Figure 16-69 Add Break Room Photos Page with Multiple Files Image Upload

16.8.2.2 Experiencing Post Multiple Photos Flow#

Walk through the multi-photo posting flow, from selecting images to adding titles and posting them.

At runtime, when the user clicks on the Image Upload preview drop zone, it opens the system file dialog. The user can select multiple images now. For example, after SMITH selects two photos to share, the preview item looks like the figure below. The Add Break Room Photos modal drawer page shows multiple file icons in the Image Upload dropzone.

Figure 16-70 Preview of Multiple Images Ready to Upload

When the user clicks (Upload), they see an Interactive Grid with image previews and a place to assign a title to each uploaded image. Validation ensures they enter a title for each one.

Figure 16-71 Entering Titles for Multiple Posted Photos with Validation

After entering a title for each posted photo, clicking (Post Photos) adds them to the break room photo gallery. The figure shows the two newly added photos first in the list in the gallery.

Figure 16-72 Both New Photos with Titles Show Up Immediately in Break Room Photo Gallery

16.8.2.3 Using a Collection to Capture Image Titles#

Use a session-scoped collection to store titles for uploaded images by their unique temporary file names.

When the user uploads photos, the APEX engine automatically stores them in the APEX_APPLICATION_TEMP_FILES table. This table is scoped by user session, so the current user only sees files she uploaded in the current session. To collect additional data like a photo title to accompany the uploaded images, use a collection to temporarily store the extra values. Make sure to store the unique uploaded file name in the collection to use as the join column, then add additional property values into other collection columns as needed. This page uses a collection named UPLOADED_PHOTOS to temporarily save the corresponding photo titles, storing:
  • Unique uploaded file name in column C001, and
  • Photo title in column C002.

Tip:

For more info on working with session-scoped collections, see Using Temporary Collections.

16.8.2.4 Joining Collection with Temp Files Table#

Join APEX_APPLICATION_TEMP_FILES to a collection on unique file name to process uploaded images with additional information related to them.

The EBA_DEMO_WOODSHR_TEMP_PHOTOS view shown below unifies the "sidecar" storage of image titles with the temporary storage of the uploaded images. It uses the unique uploaded file name to join the APEX_APPLICATION_TEMP_FILES table with the UPLOADED_PHOTOS collection. The collection stores the unique uploaded file name in column C001 and the user-entered title for each uploaded image in column C002. The diagram illustrates of the EBA_DEMO_WOODSHR_TEMP_PHOTOS view joins APEX_APPLICATION_TEMP_FILES and the APEX_COLLECTIONS view (for COLLECTION_NAME = 'UPLOADED_PHOTOS')

Figure 16-73 View Adds Titles to Temp Files by Joining with UPLOADED_PHOTOS Collection

The view pulls in session state values of the currently logged-in user and the current application id using the NV function in a common table expression. It uses the NO_MERGE query optimizer hint to avoid having those functions evaluated multiple times.

create view eba_demo_woodshr_temp_photos as
with session_state as (
    select /*+ NO_MERGE */
           nv('G_APP_USER_EMPNO') as empno,
           nv('APP_ID')           as current_app_id
      from dual
)
select atf.name as unique_file_name,
       sst.empno,
       atf.mime_type,
       atf.filename,
       col.c002 as title,
       atf.blob_content as photo
  from session_state sst
  join apex_collections col
    on col.collection_name = 'UPLOADED_PHOTOS'
  join apex_application_temp_files atf
    on atf.name = col.c001 /* Join on Unique File Name */
   and atf.application_id = sst.current_app_id

16.8.2.5 Preparing and Saving Images with Titles#

Preview the multi-file upload flow that stages images in a collection before saving them.

The Add Break Room Photos page uses application logic in a EBA_DEMO_WOODSHR_FILE package to:
  • Clear the collection to start the process
  • Prepare the collection with unique upload file names after the user uploads the images
  • Preview the temporary images in a grid where the user can add the corresponding titles, and
  • Save the images and titles to the break room photos table.

16.9.6.1 Displaying Bucket Files with Content Row#

Display object bucket files in a Content Row region with built-in selection support.

As shown below, a Content Row region works well to display a paginated list with an optional "avatar". This visual representation can be an icon or an image. This region type also has native support for single and multiple selection. Just configure the name of a page item to hold the primary keys of the selected rows. When necessary, the combination of Content Row with a Bucket Objects REST Data Source is a simple way to let users browse and select bucket files in your app with no code.

Figure 16-90 Browsing and Selecting Bucket Files with a Content Row Region

16.9.6.2 Configuring Content Row for Bucket Files#

Use a REST Data Source as the Content Row source and map its columns into the row display.

Like all regions, the Content Row can directly use a REST Data Source for the data to display. As shown below, just select REST Source for Location and choose Bucket Objects in the REST Source list.

Figure 16-91 Content Row Region Uses Bucket Objects REST Data Source

All the available data profile columns appear under the Columns heading in the rendering tree. As shown below, reference any of the columns on the Attributes tab using &NAME. notation in appropriate slots like Overline, Title, and Description. The setting of Multiple (Report) for the Appearance > Display setting confirms you want the Content Row template component to work with multiple rows. This also enables the Pagination settings.

Figure 16-92 Configuring What Appears in Each Content Row Slot

16.9.6.3 Enhancing REST Data with SQL Expressions#

Add SQL Expression columns to a REST Data Source to compute display values for bucket files.

The object bucket REST API response provides the following basic information about the files:
  • name – File name
  • md5 – Checksum for file contents
  • size – Size of the file in bytes
  • timeCreated – Date and time the file was added to the bucket.

For a more user-friendly file display, you can add additional data profile columns to the REST Data Source that you compute using SQL. These expressions or inline queries can reference other data profile columns by name as needed.

Notice below that Bucket Objects has three additional SQL Expression columns in its data profile:
  • FILE_EXTENSION_DESCRIPTION
  • DISPLAY_FILESIZE
  • URL
Figure 16-93 Data Profile Columns for Bucket Objects REST Data Source
The FILE_EXTENSION_DESCRIPTION column uses the following CASE statement to return a description of the file based on its file extension:
case lower(substr(name, instr(name,'.',-1)+1))
  when 'pdf'  then 'PDF Document'
  when 'jpg'  then 'JPEG Image'
  when 'jpeg' then 'JPEG Image'
  when 'png'  then 'PNG Image'
  when 'gif'  then 'GIF Image'
  when 'txt'  then 'Text File'
  when 'zip'  then 'ZIP Archive'
  when 'csv'  then 'CSV File'
  when 'doc'  then 'Word Document'
  when 'docx' then 'Word Document'
  when 'xls'  then 'Excel Spreadsheet'
  when 'xlsx' then 'Excel Spreadsheet'
  when 'ppt' then 'PowerPoint Presentation'
  when 'pptx' then 'PowerPoint Presentation'
  else 'Other'
end

The DISPLAY_FILESIZE column uses the TO_DISPLAY_FILESIZE function in the APEX_STRING_UTIL package to return a file size display string like 235.7KB.

apex_string_util.to_display_filesize( FILE_SIZE )

The URL columns uses APEX_PAGE.GET_URL to generate the absolute URL that serves the object bucket file as an inline image to the browser.

apex_page.get_url(
   p_page         => 9003,
   p_x01          => NAME,
   p_absolute_url => true,
   p_plain_url    => true)

16.9.6.4 Defining and Using Global Substitutions#

Define an application-level substitution for constant values you need to reuse across the app.

When your app needs to work with a constant value that you might need to reference from many places, define an application-level substitution. In this use case, the name of the object bucket the app uses is a good candidate. Visit the Substitutions tab of the application definition under Shared Components to configure one.

As shown below, an S_OBJECT_BUCKET_NAME substitution string has the corresponding value of companion-bucket. Should the object bucket name ever change in the future, you can update this value in one place, and all of its references throughout your app start using the new value.

Figure 16-94 Use Application Definition Substitutions for Global Constants

Here, you reference it using an Item named S_OBJECT_BUCKET_NAME when configuring bucketnameREST Data Source parameter value as shown below.

Figure 16-95 Referencing Global Constants as Items

16.9.6.5 Configuring Selection, Paging, and Avatar#

Configure avatars, row selection, and pagination for a Content Row file browser.

On the Attributes tab, set the Content Row region to display an image avatar by enabling the Display Avatar switch and Image as the Type. Clicking the Image button, as shown below, the Media dialog appears to configure that the image source is the URL Column named URL.

In the Row Selection section, choose Multiple Selection to let users pick several files at once, and configure the Current Selection Page Item to the name of a Hidden page item like P28_SELECTED_FILES. Its value is a colon-separated list of primary keys for the selected rows. In this use case, the NAME column is the primary key.

In the Pagination section, choose the type of pagination and if using Page-style, indicate the number of Entities per Page.

Figure 16-96 Configuring Avatar Image URL, Selection Behavior, and Pagination

16.9.8.1 Understanding Object Bucket Visibility#

Understand when to use public or private object buckets and how your app accesses private files.

You set the visibility of an Oracle Cloud Infrastructure (OCI) object bucket. A public bucket is for web site assets that any internet user can access. Use a private bucket for other use cases: it only allows authenticated access. An OCI Native Authentication web credential provides your application secure access to the bucket to put and get files. The figure shows the Oracle Cloud Infrastructure object bucket details page for companion-bucket and highlights its Visibility setting of Private.

Figure 16-97 Use a Private Object Bucket Unless It Stores Public Web Site Assets

16.9.8.2 Creating Pre-Authenticated Requests#

Create short-lived pre-authenticated request URLs so clients can access private bucket files directly.

A user with privileges to manage the object bucket can create a pre-authenticated request (PAR) for a file. It defines an alternative, impossible-to-guess URL a client can use for a limited time to access a bucket file. The PAR can expire after a few minutes, or at any date in the future. During its lifetime, any client aware of the PAR URL can use it to access the particular file.

Notice below that the Bucket Objects REST Data Source with Static ID bucket_objects has a Get File PAR POST operation defined. The operation's Static ID is get_file_par. When its URL Pattern of p/ gets appended to the REST Data Source's base URL it becomes:
https://objectstorage.eu-frankfurt-1.oraclecloud.com/n/x1y2z3a4b5c6/b/companion-bucket/p/
Figure 16-98 Get File PAR Operation Has Base URL for Creating Pre-Authenticated Request

The CREATE_OBJECT_PAR procedure below shows how your app can create a pre-authenticated request for the file identified by the p_bucket_name and p_file_name passed in. It accepts a p_ttl_seconds parameter to set the number of seconds the PAR URL will be usable, defaulting to 120 (two minutes). It calls the BUCKET_URL_FOR helper function to get the absolute URL to the create PAR endpoint and web credential static ID to use to make the POST call below.

Then, it computes the expiration date by adding the p_ttl_seconds to the systimestamp and formats the result using the ISO 8601 standard. It includes this as the value of the timeExpires property in the JSON document it creates to send as the Create PAR request body. That JSON also includes the accessType of ObjectRead and objectName with the file name. The procedure returns the new p_par_url and p_expires_utc expiration date in OUT parameters.

-- In package eba_demo_woodshr_oci
-------------------------------------------------------------------------------
-- Create a short-lived PAR (per object) and return URL + expiry.
-------------------------------------------------------------------------------
procedure create_object_par(
    p_bucket_name         in  varchar2,
    p_file_name           in  varchar2,
    p_ttl_seconds         in  pls_integer default 120,
    p_par_url             out varchar2,
    p_expires_utc         out timestamp with time zone,
    p_module_static_id    in  varchar2 default 'bucket_objects',
    p_operation_static_id in  varchar2 default 'get_file_par')
is
    l_url         varchar2(32767);
    l_cred        varchar2(4000);
    l_body        clob;
    l_resp        clob;
    l_access_uri  varchar2(32767);
    l_full_path   varchar2(32767);
    l_host        varchar2(1024);
    l_expires_str varchar2(64);
    l_resp_json   json_object_t;
begin
    bucket_url_for(
        p_module_static_id     => p_module_static_id,
        p_operation_static_id  => p_operation_static_id,
        p_bucket_name          => p_bucket_name,
        p_file_name            => p_file_name,
        p_credential_static_id => l_cred,
        p_file_url             => l_url);

    -- Compute expiration date by adding p_ttl_second to
    -- systimestamp and formatting using ISO 8601 for REST payload
    p_expires_utc := (systimestamp at time zone 'UTC')
                     + numtodsinterval(greatest(p_ttl_seconds, 1), 'SECOND');
    l_expires_str := to_char(p_expires_utc, 'YYYY-MM-DD"T"HH24:MI:SS"Z"');

    -- Build the JSON payload to send in the Create PAR request body
    select json_object(
             'name'        value ('par-'||replace(p_file_name,'/','_')),
             'accessType'  value 'ObjectRead',
             'objectName'  value p_file_name,
             'timeExpires' value l_expires_str
           returning clob)
      into l_body
      from dual;

    apex_web_service.set_request_headers(
        p_name_01 => 'Content-Type', p_value_01 => 'application/json',
        p_name_02 => 'Accept',       p_value_02 => 'application/json',
        p_reset   => true);

    l_resp := apex_web_service.make_rest_request(
                p_credential_static_id => l_cred,
                p_http_method          => 'POST',
                p_url                  => l_url,
                p_body                 => l_body);

    if apex_web_service.g_status_code not in (200,201) then
        raise_application_error(-20001,
            'Create PAR failed: HTTP '||apex_web_service.g_status_code);
    end if;

    l_resp_json  := json_object_t(l_resp);
    l_access_uri := l_resp_json.get_string('accessUri');
    l_full_path  := l_resp_json.get_string('fullPath');

    if l_full_path is not null then
        p_par_url := l_full_path;
    elsif l_access_uri is not null then
        l_host := regexp_substr(l_url, '^https?://[^/]+');
        p_par_url := case when substr(l_access_uri,1,1)='/' then l_host||l_access_uri
                          else l_access_uri end;
    else
        raise_application_error(-20001,
            'Create PAR failed: neither accessUri nor fullPath returned');
    end if;
end create_object_par;

16.9.8.3 Using Pre-Authenticated Requests#

Use cached PAR URLs to let OCI serve private bucket images directly when possible.

You could optimize app image asset serving using PAR URLs your application creates. As shown below, your app could use a local table to store PAR URLs and expiration dates for image files like x.jpg, y.jpg, and z.jpg files. It could use these PAR URLs directly if they are still valid, or alternatively create a inline image serving page that encapsulates working with the PAR URL for a requested file:
  • If the file's PAR URL in the cache table is still valid, use it
  • If not, create a new PAR URL for the file requested, and save it in the cache table
  • Redirect the browser to the PAR URL.

An automation could run periodically to clean up the expired entries in the cache table. Using an image serving strategy involving PAR URLs, you can defer most or all of the image servring to the object bucket in OCI. The diagram shows how a local table could store the PAR_URL and EXPIRES_AT time for different file OBJECT_NAME entries. Your APEX app could use the cached PAR URLs to delegate image downloading directly to the OCI object bucket.

Figure 16-99 Strategy to Use PAR URLs to Serve Object Bucket Images Directly

16.4.7.5.1 Understanding Implications of Using X01#

Understand how an authenticated user can change the value of an image ID in the image URL using an image-serving page.

When viewing a page containing image URLs using the x01 parameter, if an authenticated user knows the ID of a different image, they can view it by:
  • Selecting Copy Image Address from the context menu on an existing image in the page,
  • Creating a new browser tab and pasting the copied URL into the address bar,
  • Manually modifying the x01=123456 to x01=98765, and pressing [Enter].

In Woods HR, where any authenticated user can view any break room photo, it's no problem.

16.4.7.5.2 Downloading Image with Secure Page Item#

When images are user-specific or sensitive, use a hidden page item instead of X01.

For example, page 9005 is a copy of the image-serving page 9000, but it uses the hidden P9005_ID page item value for the P_ID parameter in its DOWNLOAD_BREAKROOM_IMAGE call.

Figure 16-35 Passing Checksum-Protected Hidden Page Item for Image ID

By default, pages use Arguments Must Have Checksum as the Page Access Protection setting as shown below. This causes APEX_PAGE.GET_URL to generate an additional checksum parameter in every URL. This extra token protects parameter values from manual manipulation.

Figure 16-36 Default Page Access Protection Setting Requires Checksum for URL Arguments

16.4.7.5.3 Generating Image URL with Secure Item#

Generate checksum-protected image URLs by passing the image ID using the tamper-resistant page item name to get_url using its p_items and p_values parameters.

When using page 9005 to generate image URLs, the get_url call appears below in the modified Interactive Grid query. The p_items and p_values parameters replace the previous p_x01:
select id,
       title,
       case
           when     image is not null
                and dbms_lob.getlength(image) > 0
           then apex_string.format('<img alt="%s" title="%s" src="assets/images/oracle-apex-26.1/e4843f1f016e0ce9.bin" data-original-src="https://docs.oracle.com/en/database/oracle/apex/26.1/apxdc/%s">',
                apex_escape.html_attribute(title),
                apex_escape.html_attribute(title),
                apex_page.get_url(
                    p_page   => 9005,
                    p_items  => 'P9005_ID',
                    p_values => ID))
       end as image,
       file_name,
       posted,
       posted_by,
       updated
from eba_demo_emp_breakroom_photo_v

Tip:

If the image to download has a multi-column primary key, then pass multiple items and values in the get_url call by separating values using commas:
apex_page.get_url(
    p_page   => 9005,
    p_items  => 'P9005_ID1,P9005_ID2',
    p_values => ID1||','||ID2)

16.4.7.5.4 Experiencing URL Checksum Protection#

Witness the URL tampering protection for image serving pages using a page item for the image ID.

As shown below, the break room Interactive Grid using the checksum-protected URLs is identical for the end user:

Figure 16-37 End Users Notice No Changes Using Checksum-Protected Image URLs

However, now every image URL ends with the a long cs checksum parameter whose impossible-to-guess value differs for each image URL produced:

⋯/woods-hr/get-breakroom-image?p9005_id=13&session=304⋯573&cs=1kSRFy⋯rUoqRt

A user who copies the URL for image ID 13 and tries to reuse it for a different image ID value receives the following error page. The figure shows the error dialog they see instead of the image they are not permitted to view.

Figure 16-38 Clever Users See Error When Manually Modifying an Image URL

16.5.2.1.1 Generating Dialog URL on the Server#

Generate a checksum-protected dialog URL on the server before opening a modal page.

In the Open Position Referrals page, the user submits a new job referral by clicking either:
  • (Submit First Referral) that opens a modal drawer, or
  • (Submit Referral) that opens a modal dialog.

Both of these dialog pages accept the selected job id as a parameter into a hidden page item so the new job referral gets linked to the appropriate job.

Use a triggered action to generate the modal drawer URL on the server and store it in a hidden page item as shown below. Using Execute Server-side Code, the Get Checksum-Protected Modal URL triggered action on the SUBMIT_FIRST_REFERRAL button sets the value of P29_MODAL_PAGE_URL by calling APEX_PAGE.GET_URL. That generates the URL to the modal drawer page 30, passing in the value of P29_SELECTED_JOB_ID to the P30_JOB_ID hidden page item in the target. Notice the Items to Submit mentions the selected job ID page item name the PL/SQL references as a bind variable. The Items to Return lists the modal page URL item name.

Figure 16-44 Triggered Action Generates Dialog URL Before Opening It Using JavaScript

A similar triggered action on the SUBMIT_REFERRAL button runs the code below to generate the modal dialog URL to open page 31, passing in the value of P29_SELECTED_JOB_ID to the P31_JOB_ID hidden page item in the target. Its Items to Submit and Items to Return properties are configured the same as above.

:P29_MODAL_PAGE_URL :=
    apex_page.get_url(
        p_page      => 31,
        p_items     => 'P31_JOB_ID',
        p_values    => :P29_SELECTED_JOB_ID,
        p_plain_url => true);

16.5.2.1.2 Opening Dialog URL Passing Client Data#

Add JavaScript helper functions that simplify opening modal dialog and drawer pages from generated URLs.

Expanding on the technique explained in Providing App-Wide Helper Functions, you can add additional ones to the application's app.js JavaScript file in Static Application Files to simplify opening modal drawer and dialog pages by URL.

With the server-generated modal page URL in the hidden item P29_MODAL_PAGE_URL, as shown below a subsequent Execute JavaScript Code triggered action uses that item's value to open the drawer. It calls the app.openDrawerURL() function, passing in values for the url and title parameters by name:
app.openDrawerURL({
    url:   $v('P29_MODAL_PAGE_URL'),
    title: "Submit New Referral"
});
Figure 16-45 Calling JavaScript Helper Function to Open Dialog
Likewise, a triggered action on the SUBMIT_REFERRAL button uses a similar helper function to open a modal dialog. It calls the app.openDialogURL() function, passing in values for the url and title parameters by name:
app.openDialogURL({
    url:   $v('P29_MODAL_PAGE_URL'),
    title: "Submit New Referral"
});

The source of these two helper functions appears below. They both call the standard APEX JavaScript API apex.navigation.dialog function, but let some common arguments like width, maxWidth, and triggeringElement get defaulted. This simplifies their use throughout your application.

Tip:

The openDialogURL and openDrawerURL functions use a handy JavaScript feature to accept "destructured" arguments. This lets you specify default values for different optional parameters. It also lets a caller pass parameters using a named notation. Default values assigned in the function get automatically used if the caller omits those optional parameters.

// In app.js static application file
//----------------------------------------------------
// Open modal dialog
//----------------------------------------------------
function openDialogURL({
    url,
    title,
    width = 400,
    maxWidth = 1000,
    triggeringElement = $('body')[0]
}) {
    apex.navigation.dialog(
    url,
    {
        title: title,
        height: "auto",
        width: width,
        maxWidth: maxWidth,
        modal: true,
        resizable: true
    },
    "t-Dialog-page--standard",
    triggeringElement
    );
}

//----------------------------------------------------
// Open Drawer URL
//----------------------------------------------------
function openDrawerURL({
    url,
    title,
    width = 400,
    triggeringElement = $('body')[0]
}) {
    apex.navigation.dialog(
    url,
    {
        title: title,
        width: width,
        modal: true
    },
    "t-Drawer-page--standard t-Drawer--pullOutEnd",
    triggeringElement
    );
}

16.5.2.2.1 Defaulting URL Template Page Items#

Use APEX_PAGE.GET_URL to compute the template URL containing page item placeholders you replace with client values when opening the dialog.

This variant of the Open Position Referrals page contains two hidden page items to store URL templates:
  • P11_DRAWER_MODAL_URL_TEMPLATE – to open modal drawer page 23
  • P11_DIALOG_MODAL_URL_TEMPLATE – to open modal dialog page 27
The expression to compute the value of the drawer modal URL is:
apex_page.get_url(
    p_page      => 23,
    p_items     => 'P23_JOB_ID',
    p_values    => '$P11_SELECTED_JOB_ID

Notice, the value of the P23_JOB_ID parameter is set to the name of the P11_SELECTED_JOB_ID page item, surrounded by dollar signs. The JavaScript helper code looks for names of this pattern and replaces them in the template with the current value of the named page item.

The expression for the dialog modal URL is the same, just using a different page number:
apex_page.get_url(
    p_page      => 27,
    p_items     => 'P27_JOB_ID',
    p_values    => '$P11_SELECTED_JOB_ID

There are three ways to default the value of page item:

Table 16-1 Three Approaches to Defaulting a Page Item Value

Scroll horizontally to view the full table
Approach Use When You Need
Default setting An initial value if the page item is otherwise null.
Source setting A computed value to always be used, when not associated to a form region.
Computation or page process assignment in the Pre-Rendering section An optionally-conditional assignment visible at the "top" of the page rendering tree.

The Open Position Referrals page shows two of these three approaches. As shown below, the P11_DRAWER_MODAL_URL_TEMPLATE's source value uses a PL/SQL Expression set to always be used. The figure shows the P11_DRAWER_MODAL_URL_TEMPLATE hidden page item selected in the component tree, and highlights the APEX_PAGE.GET_URL expression described above that generates its URL template value.

Figure 16-46 "Always" Source Defaulting the Drawer Model URL Template

As an alternative, the P11_DIALOG_MODAL_URL_TEMPLATE uses a Pre-Rendering computation. The figure shows the P11_DIALOG_MODEL_URL_TEMPLATE computation selected in the component tree, and highlights the APEX_PAGE.GET_URL expression described above that generates its URL template value.

Figure 16-47 Computation Defaulting the Drawer Model URL Template
, p_plain_url => true)