How to Build a Photo Gallery with a Carousel in Oracle APEX

Upload many pictures into a BLOB table, show them in a Cards grid and a Carousel Container, open them in full size and reject files that are not images.

A photo gallery lets users upload several pictures at once and browse them as a grid of cards or as a slideshow. Oracle APEX has the parts for both: a multiple file upload item, a Cards region that reads pictures straight from a BLOB column, and a Carousel Container region template that turns its sub regions into slides.

This guide builds a gallery with all three. It uses products as the example, and the same steps work for any table that owns pictures. The photos are stored in their own table, uploaded in one go, shown in a grid and in a carousel, opened in full size, and deleted.

The guide How to Upload an Image and Show It in a Form and a Report in Oracle APEX stores one image in a column of the record. This guide is for many pictures per record, and for the gallery and carousel that show them.

Step 1: Create the Table

The pictures go into their own table, PRODUCT_IMAGES, so a product can have any number of them. The table has no sample rows. The sample photos of this guide are uploaded through the page in the test section.

Example:

create table product_images (
    image_id     number generated by default on null as identity,
    product_id   number         not null,
    file_name    varchar2(200)  not null,
    mime_type    varchar2(100)  not null,
    file_size    number         not null,
    image_blob   blob           not null,
    uploaded_by  varchar2(100)  default on null coalesce(sys_context('APEX$SESSION', 'APP_USER'), user) not null,
    uploaded_on  timestamp with time zone default on null systimestamp not null,
    constraint product_images_pk primary key (image_id),
    constraint product_images_product_fk foreign key (product_id)
        references products (product_id) on delete cascade,
    constraint product_images_mime_ck check (mime_type like 'image/%'),
    constraint product_images_size_ck check (file_size > 0)
);

create index product_images_product_ix on product_images (product_id);

select table_name, constraint_name, constraint_type
from   user_constraints
where  table_name = 'PRODUCT_IMAGES'
and    generated = 'USER NAME'
order by constraint_type, constraint_name;

Output:

Table PRODUCT_IMAGES created.

Index PRODUCT_IMAGES_PRODUCT_IX created.

TABLE_NAME        CONSTRAINT_NAME              CONSTRAINT_TYPE
_________________ ____________________________ __________________
PRODUCT_IMAGES    PRODUCT_IMAGES_MIME_CK       C
PRODUCT_IMAGES    PRODUCT_IMAGES_SIZE_CK       C
PRODUCT_IMAGES    PRODUCT_IMAGES_PK            P
PRODUCT_IMAGES    PRODUCT_IMAGES_PRODUCT_FK    R

A few details in the table:

  • The foreign key to PRODUCTS has ON DELETE CASCADE, so deleting a product deletes its photos, and an index on PRODUCT_ID keeps the list of one product's photos fast.
  • UPLOADED_BY and UPLOADED_ON use DEFAULT ON NULL, so the insert statement never sets them. The user name comes from the APEX session.
  • The check constraint PRODUCT_IMAGES_MIME_CK accepts only MIME types that start with image/. It is the database side of the file type rule, in case a file gets past the page.
  • The existing PRODUCTS table is not changed.

Step 2: Create the Photo Gallery Page

In App Builder, open the application and click Create Page. On the Component tab, select Blank Page:

Create a Page wizard with the Blank Page tile marked with a red box
Choose the Blank Page page type.

On the next screen, enter Page Number 1160 and Name Photo Gallery. Turn off Use Breadcrumb, keep Use Navigation on and enter the Icon fa-picture-o. Then click Create Page:

Create Blank Page wizard with Page Number 1160, Name Photo Gallery, Use Breadcrumb off, Use Navigation on and Icon fa-picture-o marked with red boxes
The page number, name and navigation entry.

Step 3: Add the Upload Region

In Page Designer, drag a Static Content region into the Body. Set Name to Upload Photos:

Page Designer with the Upload Photos region and its Name and Type marked with a red box
A Static Content region holds the upload items.

The first item chooses the product. Create a page item named P1160_PRODUCT_ID of type Select List with the label Product. Set Page Action on Selection to Redirect and Set Value, so the page reloads with the chosen product:

Item P1160_PRODUCT_ID of type Select List with Page Action on Selection set to Redirect and Set Value marked with red boxes
The product select list reloads the page.

In the List of Values group, set Type to SQL Query and enter the query below. Turn on Display Null Value and enter - Select Product - as the Null Display Value:

Example:

select name as display_value,
       product_id as return_value
from   products
order by name
List of Values group with Type SQL Query, the query, Display Null Value and Null Display Value marked with a red box
The list of values reads the products.

In the Validation group, turn on Value Required:

Validation group with Value Required turned on and marked with a red box
A photo cannot be uploaded without a product.

The second item takes the files. Create a page item named P1160_FILES of type File Upload with the label Photos. Set Display As to Block Dropzone and enter a title and a description for the drop zone:

Item P1160_FILES of type File Upload with Display As Block Dropzone, Dropzone Title and Dropzone Description marked with red boxes
The file upload item as a block drop zone.

In the Storage group, set Type to Table APEX_APPLICATION_TEMP_FILES and Purge File at to End of Request. Turn on Allow Multiple Files, enter image/* as File Types and 5120 as Maximum File Size, which is in KB:

Storage group with Type Table APEX_APPLICATION_TEMP_FILES, Purge File at End of Request, Allow Multiple Files, File Types image/* and Maximum File Size 5120 marked with a red box
Several images, up to 5 MB each.

The temporary files table holds the uploads only until the end of the request. The process in Step 4 copies them into PRODUCT_IMAGES before that.

Two hidden items carry values from the gallery to the server. P1160_DELETE_ID holds the photo to delete, and P1160_VIEW_ID holds the photo to open. Create both with type Hidden and turn off Value Protected, because the browser sets them:

Item P1160_VIEW_ID of type Hidden with its Name and Type marked with a red box
A hidden item for the photo to open.

In the Security group, Session State Protection shows Unrestricted after Value Protected is turned off:

Security group of P1160_VIEW_ID with Session State Protection Unrestricted marked with a red box
The hidden items accept values from the browser.

Finally, create a button named UPLOAD with the label Upload in the same region. Select the Text button template and turn on Hot:

Button UPLOAD with Button Name, Label, Button Template Text and Hot marked with red boxes
The Upload button submits the page.

Step 4: Add the Validation and the Processes

Click the Processing tab. Create a validation named Photos selected. Set Type to Item is NOT NULL, Item to P1160_FILES and Error Message to "Choose at least one photo to upload." Set When Button Pressed to UPLOAD, so the check runs only for the upload:

Validation Photos selected with Type Item is NOT NULL, Item P1160_FILES, the error message and When Button Pressed UPLOAD marked with red boxes
The validation for an empty upload.

Create a process named Save photos with Type Execute Code and this PL/SQL code. One statement copies every selected file. The file names come from the colon separated value of P1160_FILES, and APEX_STRING.SPLIT turns that value into rows:

Example:

insert into product_images
       (product_id, file_name, mime_type, file_size, image_blob)
select :P1160_PRODUCT_ID, f.filename, f.mime_type,
       dbms_lob.getlength(f.blob_content), f.blob_content
from   apex_application_temp_files f
where  f.name in (select column_value
                  from   apex_string.split(:P1160_FILES, ':'));
Process Save photos of type Execute Code with the PL/SQL code and Sequence 10, Point Processing marked with red boxes
The process that saves the photos.

Enter "Photos uploaded." as Success Message and set When Button Pressed to UPLOAD:

Success Message Photos uploaded and When Button Pressed UPLOAD marked with red boxes
The success message and the button condition.

Create a second process named Delete photo with Sequence 20 and this code. The condition on PRODUCT_ID makes sure a photo can only be deleted from the product that is selected:

Example:

delete from product_images
where  image_id   = :P1160_DELETE_ID
and    product_id = :P1160_PRODUCT_ID;
Process Delete photo of type Execute Code with the PL/SQL code and Sequence 20, Point Processing marked with red boxes
The process that deletes a photo.

Enter "Photo deleted." as Success Message. Set the Server-side Condition Type to Request = Value and the Value to DELETE. The gallery sends this request when a user confirms a delete:

Success Message Photo deleted and Server-side Condition Type Request = Value with Value DELETE marked with red boxes
The delete runs only for the DELETE request.

Create a branch to page 1160 after the processes. It keeps the chosen product, with Items set to P1160_PRODUCT_ID and the value &P1160_PRODUCT_ID., and clears the cache of the page.

Step 5: Add an Application Process to Show a Photo in Full Size

The cards show small pictures. To show a picture in full size, the browser needs a URL that returns the BLOB. An Ajax Callback application process can do that.

Open Shared Components. Under Logic, click Application Processes. Click Create and enter these values:

  • Name: VIEW_PHOTO
  • Process Point: Ajax Callback
  • Type: Execute Code, with the code below

Example:

declare
    l_blob blob;
    l_mime product_images.mime_type%type;
    l_name product_images.file_name%type;
begin
    select image_blob, mime_type, file_name
    into   l_blob, l_mime, l_name
    from   product_images
    where  image_id = to_number(:P1160_VIEW_ID);

    owa_util.mime_header(l_mime, false);
    htp.p('Content-Length: ' || dbms_lob.getlength(l_blob));
    htp.p('Content-Disposition: inline; filename="' || l_name || '"');
    owa_util.http_header_close;
    wpg_docload.download_file(l_blob);
    apex_application.stop_apex_engine;
exception
    when no_data_found or value_error then
        htp.p('Photo not found.');
end;
Application Process VIEW_PHOTO with Process Point Ajax Callback, the name and the PL/SQL code marked with red boxes
The application process that returns the picture.

The code sends the MIME type and the length as headers, then the BLOB, and stops the APEX engine so no page is added to the picture. The list of application processes now shows VIEW_PHOTO:

Application Processes list with the VIEW_PHOTO row marked with a red box
The process in the list.

Step 6: Show the Photos in a Cards Region

Drag a Cards region into the Body, below the upload region. Set Name to Gallery, set Source Type to SQL Query and enter the query below. The column VIEW_URL builds a link that calls VIEW_PHOTO with a checksum, so the link cannot be changed in the browser:

Example:

select image_id,
       file_name,
       to_char(uploaded_on, 'DD-MON-YYYY HH24:MI') as uploaded,
       uploaded_by,
       round(file_size / 1024, 1) || ' KB' as size_label,
       mime_type,
       uploaded_on,
       apex_page.get_url(p_page    => :APP_PAGE_ID,
                         p_request => 'APPLICATION_PROCESS=VIEW_PHOTO',
                         p_items   => 'P1160_VIEW_ID',
                         p_values  => image_id) as view_url,
       image_blob
from   product_images
where  product_id = :P1160_PRODUCT_ID
order by uploaded_on desc, image_id desc

Set Page Items to Submit to P1160_PRODUCT_ID:

Region Gallery of type Cards with the SQL query and Page Items to Submit marked with red boxes
A Cards region on the photo query.

In the Server-side Condition group, set Type to Item is NOT NULL and Item to P1160_PRODUCT_ID. The query then runs only after a product is chosen:

Server-side Condition with Type Item is NOT NULL and Item P1160_PRODUCT_ID marked with a red box
The gallery waits for a product.

Click the Attributes tab. In the Appearance group, set Layout to Grid and Grid Columns to 3 Columns. In the Card group, set Primary Key Column 1 to IMAGE_ID:

Appearance group with Layout Grid and Grid Columns 3 Columns, and Card group with Primary Key Column 1 IMAGE_ID marked with red boxes
A grid of three columns.

Set Title to FILE_NAME, Subtitle to UPLOADED and Body to SIZE_LABEL:

Title column FILE_NAME, Subtitle column UPLOADED and Body column SIZE_LABEL marked with red boxes
The text of each card.

In the Media group, set Source to BLOB Column and BLOB Column to IMAGE_BLOB. Set Appearance to Widescreen and Sizing to Cover, so every picture fills the same frame. Enter FILE_NAME as Image Description.

In the BLOB Attributes group, set Mime Type Column to MIME_TYPE and Last Updated Column to UPLOADED_ON:

Media group with Source BLOB Column, BLOB Column IMAGE_BLOB, Appearance Widescreen, Sizing Cover and Image Description, and BLOB Attributes with the MIME type and last updated columns marked with red boxes
The pictures come straight from the BLOB column.

Enter "No photos yet. Upload one above." as When No Data Found in the Messages group.

Now add two actions to the cards. Right-click the Gallery region and choose Create Action. Name the first action View. Set Type to Button, Label to View, Behavior Type to Redirect to URL and Target URL to &VIEW_URL.

Enter target="_blank" as Link Attributes, so the picture opens in a new tab. Set Display Type to Text with Icon and the Icon to fa-search-plus:

Action View with Type Button, Label View, Behavior Type Redirect to URL, Target URL &VIEW_URL. and Link Attributes marked with red boxes
The View action opens the picture in a new tab.

Create a second action named Delete with the Icon fa-trash-o. Set Behavior Type to Redirect to URL and enter this Target URL. It asks for a confirmation, then submits the request DELETE with the ID of the photo:

Example:

javascript:apex.confirm('Delete this photo?', {request: 'DELETE', set: {'P1160_DELETE_ID': '&IMAGE_ID.'}});
Action Delete with Label Delete, Behavior Type Redirect to URL and the JavaScript Target URL marked with red boxes
The Delete action asks first, then sends the request.

In a first test, the target URL used #FILE_NAME# and #IMAGE_ID# in the JavaScript, as in a classic report. The confirmation then showed the text #FILE_NAME# instead of the file name. Cards use the &COLUMN. syntax for column values, so the final URL uses &IMAGE_ID. and a fixed text.

Step 7: Add the Carousel

The Carousel Container is a region template. It shows each of its sub regions as one slide, with arrows and dots to move between them. It does not read rows by itself, so each slide here is a small Cards region that shows one photo.

Drag a Static Content region into the Body between the two other regions. Set Name to Latest Photos. In the Appearance group, set Template to Carousel Container. The template option Show Next and Previous Buttons turns on the arrows:

Region Latest Photos of type Static Content with Template Carousel Container and the Show Next and Previous Buttons template option marked with red boxes
The carousel container region.

Without a product or without photos, the container would show an empty box with arrows. Set the Server-side Condition Type to Rows returned and use this query. The carousel then appears only when the product has photos:

Example:

select 1
from   product_images
where  product_id = :P1160_PRODUCT_ID
Server-side Condition with Type Rows returned and the SQL query marked with a red box
The carousel hides itself when there is nothing to show.

Now create the first slide. Drag a Cards region onto the Latest Photos region, or set its Parent Region. Name it Slide 1, set Source Type to SQL Query and enter this query. OFFSET 0 returns the newest photo, and Slide 2 and Slide 3 use OFFSET 1 and OFFSET 2:

Example:

select image_id,
       file_name,
       to_char(uploaded_on, 'DD-MON-YYYY HH24:MI') as uploaded,
       round(file_size / 1024, 1) || ' KB' as size_label,
       mime_type,
       uploaded_on,
       image_blob
from   product_images
where  product_id = :P1160_PRODUCT_ID
order by uploaded_on desc, image_id desc
offset 0 rows fetch next 1 row only

Set Page Items to Submit to P1160_PRODUCT_ID:

Region Slide 1 of type Cards with the SQL query and Page Items to Submit marked with red boxes
The first slide shows the newest photo.

In the Layout group, set Parent Region to Latest Photos and Slot to Slides. Slides is the slot that the carousel template defines for its sub regions:

Layout group with Parent Region Latest Photos and Slot Slides marked with a red box
The slide belongs to the carousel.

A slide must not appear when the product has fewer photos than the slide number. Set the Server-side Condition Type to Rows returned and use the same query as the slide, which returns a row only when that photo exists:

Server-side Condition of Slide 1 with Type Rows returned and the SQL query marked with a red box
A slide without a photo is not rendered.

On the Attributes tab, set Layout to Horizontal (Row) and Primary Key Column 1 to IMAGE_ID. Set Title to FILE_NAME, Subtitle to UPLOADED and Body to SIZE_LABEL:

Appearance group with Layout Horizontal (Row), Card group with Primary Key Column 1 IMAGE_ID marked with red boxes
One card per slide, with the picture on the left.

In a first test, Layout was Grid with one column. Page Designer allows only 2 to 5 grid columns, so a slide would have filled half of the width. The Horizontal layout shows the picture and the text side by side in the full width.

Use the same Media settings as the gallery: Source BLOB Column, BLOB Column IMAGE_BLOB, Appearance Widescreen, Sizing Cover. Set the BLOB Attributes to MIME_TYPE and UPLOADED_ON:

Media group of Slide 1 with Source BLOB Column, BLOB Column IMAGE_BLOB, Position Body, Appearance Widescreen and Sizing Cover marked with a red box
The slide reads the same BLOB column.

Copy Slide 1 twice. Rename the copies to Slide 2 and Slide 3, and change OFFSET 0 to OFFSET 1 and OFFSET 2 in both queries, the region query and the condition. Save the page.

Test the Gallery and the Carousel

Run the page as the user DEMO_DEV. Before any product is chosen, the page shows only the upload region. The photos table is empty at the start:

Example:

select p.name as product, i.image_id, i.file_name, i.mime_type, i.file_size,
       dbms_lob.getlength(i.image_blob) as blob_bytes, i.uploaded_by
from   product_images i
join   products p on p.product_id = i.product_id
order by i.image_id;

select count(*) as photo_count from product_images;

Output:

no rows selected

   PHOTO_COUNT
______________
             0

Select Laptop Pro 14. The carousel is hidden and the gallery shows the empty message:

Photo Gallery page with the product Laptop Pro 14 selected, the drop zone and the message No photos yet. Upload one above.
A product without photos.

Upload three photos

Click the drop zone, or drop files on it, and choose three images: sunrise.jpg, lake.jpg and dusk.jpg. The drop zone shows the number of files and their total size:

Drop zone showing 3 files with a total size of 49.43 KB and the Upload button
Three files are ready to upload.

Click Upload. The page shows the success message, the carousel with the newest photo, and the gallery with three cards:

Photo Gallery page after the upload with the message Photos uploaded, the carousel with dusk.jpg and a gallery of three cards
The carousel and the gallery after the upload.

The table has three rows. The BLOB length matches the file size of each photo, and the user name came from the session:

Example:

select p.name as product, i.image_id, i.file_name, i.mime_type, i.file_size,
       dbms_lob.getlength(i.image_blob) as blob_bytes, i.uploaded_by
from   product_images i
join   products p on p.product_id = i.product_id
order by i.image_id;

select count(*) as photo_count from product_images;

Output:

PRODUCT             IMAGE_ID FILE_NAME      MIME_TYPE        FILE_SIZE    BLOB_BYTES UPLOADED_BY
________________ ___________ ______________ _____________ ____________ _____________ ______________
Laptop Pro 14             21 sunrise.jpg    image/jpeg           16189         16189 DEMO_DEV
Laptop Pro 14             22 lake.jpg       image/jpeg           16581         16581 DEMO_DEV
Laptop Pro 14             23 dusk.jpg       image/jpeg           17850         17850 DEMO_DEV

   PHOTO_COUNT
______________
             3

Move through the carousel

Click the right arrow, or a dot, to go to the next slide. The second slide shows lake.jpg, the second newest photo:

Carousel region showing the second slide with lake.jpg and the second dot selected
Slide 2 of the carousel.

Open a photo in full size

Click View on a card. The picture opens in a new tab at its real size, served as image/jpeg by the VIEW_PHOTO process:

Browser tab showing the full size picture dusk.jpg
The full size picture in a new tab.

Delete a photo

Click Delete on the lake.jpg card. The confirmation shows the fixed text:

Confirmation dialog Delete this photo? over the gallery
The delete asks for a confirmation.

Click OK. The photo is gone from the gallery and the carousel, which now has two dots:

Photo Gallery page after the delete with the message Photo deleted, two dots in the carousel and two cards in the gallery
Two photos are left.

Example:

select p.name as product, i.image_id, i.file_name, i.mime_type, i.file_size,
       dbms_lob.getlength(i.image_blob) as blob_bytes, i.uploaded_by
from   product_images i
join   products p on p.product_id = i.product_id
order by i.image_id;

select count(*) as photo_count from product_images;

Output:

PRODUCT             IMAGE_ID FILE_NAME      MIME_TYPE        FILE_SIZE    BLOB_BYTES UPLOADED_BY
________________ ___________ ______________ _____________ ____________ _____________ ______________
Laptop Pro 14             21 sunrise.jpg    image/jpeg           16189         16189 DEMO_DEV
Laptop Pro 14             23 dusk.jpg       image/jpeg           17850         17850 DEMO_DEV

   PHOTO_COUNT
______________
             2

Because only two photos exist, Slide 3 is not rendered. The query behind its condition returns no row:

Example:

select image_id,
       file_name,
       to_char(uploaded_on, 'DD-MON-YYYY HH24:MI') as uploaded,
       round(file_size / 1024, 1) || ' KB' as size_label,
       mime_type
from   product_images
where  product_id = 1
order by uploaded_on desc, image_id desc
offset 0 rows fetch next 1 row only;

select image_id, file_name
from   product_images
where  product_id = 1
order by uploaded_on desc, image_id desc
offset 2 rows fetch next 1 row only;

Output:

   IMAGE_ID FILE_NAME    UPLOADED             SIZE_LABEL    MIME_TYPE
___________ ____________ ____________________ _____________ _____________
         23 dusk.jpg     26-OCT-2026 03:17    17.4 KB       image/jpeg

no rows selected

Reject a file that is not an image

Choose a text file named notes.txt and click Upload. The upload item refuses it with the message Invalid file type, and nothing is saved:

Drop zone with notes.txt, the error message Invalid file type. Supported file types image/*. under it and an error notification
A text file is rejected.

Click Upload again without choosing a file. The validation answers:

Error notification Choose at least one photo to upload. and the message under the drop zone
An empty upload is rejected.

Both attempts changed nothing. The count is still two:

Example:

select p.name as product, i.image_id, i.file_name, i.mime_type, i.file_size,
       dbms_lob.getlength(i.image_blob) as blob_bytes, i.uploaded_by
from   product_images i
join   products p on p.product_id = i.product_id
order by i.image_id;

select count(*) as photo_count from product_images;

Output:

PRODUCT             IMAGE_ID FILE_NAME      MIME_TYPE        FILE_SIZE    BLOB_BYTES UPLOADED_BY
________________ ___________ ______________ _____________ ____________ _____________ ______________
Laptop Pro 14             21 sunrise.jpg    image/jpeg           16189         16189 DEMO_DEV
Laptop Pro 14             23 dusk.jpg       image/jpeg           17850         17850 DEMO_DEV

   PHOTO_COUNT
______________
             2

The page is the first line of defense. The table is the second. An insert of a text file with the MIME type text/plain is refused by the check constraint:

Example:

insert into product_images (product_id, file_name, mime_type, file_size, image_blob)
values (1, 'notes.txt', 'text/plain', 22, utl_raw.cast_to_raw('This is not an image.'));

Output:

Error starting at line : 1 File @ b119/14-bad-mime.sql
In command -
insert into product_images (product_id, file_name, mime_type, file_size, image_blob)
values (1, 'notes.txt', 'text/plain', 22, utl_raw.cast_to_raw('This is not an image.'))
Error report -
ORA-02290: check constraint (APXSOL.PRODUCT_IMAGES_MIME_CK) violated
Help: https://docs.oracle.com/error-help/db/ora-02290/

Another product and the cascade

Select Laptop Air 13. It has no photos, so the page shows the empty message and no carousel. The photos belong to one product only:

Photo Gallery page with Laptop Air 13 selected and the message No photos yet. Upload one above.
Another product has its own photos.

The cascade was tested with a scratch product. The product got one photo, and deleting the product deleted the photo too. The scratch rows are removed:

Example:

insert into products (name, category, price) values ('ZZ Scratch Product', 'Accessories', 1);

insert into product_images (product_id, file_name, mime_type, file_size, image_blob)
select product_id, 'scratch.png', 'image/png', 4, utl_raw.cast_to_raw('abcd')
from   products
where  name = 'ZZ Scratch Product';

select count(*) as images_before from product_images where file_name = 'scratch.png';

delete from products where name = 'ZZ Scratch Product';

select count(*) as images_after from product_images where file_name = 'scratch.png';

commit;

Output:

1 row inserted.

1 row inserted.

   IMAGES_BEFORE
________________
               1

1 row deleted.

   IMAGES_AFTER
_______________
              0

Commit complete.

Things to Know

  • The carousel has a fixed number of slides, here three, because each slide is a sub region. Add more slide regions with a higher OFFSET for a longer carousel. A slide without a photo is hidden by its condition.
  • The Cards region has the layouts Grid, Float and Horizontal (Row). None of them is a slideshow, so the Carousel Container region template is what turns cards into slides.
  • For a BLOB picture, the Cards region needs the BLOB column, the primary key column and the MIME type column in the query and in the attributes.
  • In card actions, write column values as &COLUMN. and not as #COLUMN#.
  • The File Types value image/* is checked by the upload item, and the check constraint on MIME_TYPE checks again in the database. Neither one proves that a file is a real picture, so scan uploads if the files come from the public.
  • The VIEW_PHOTO link has a checksum. In a test, a link with the ID changed by hand returned HTTP 400, so a user cannot swap the ID. The process does not check the product, so any signed in user who has a link can open that photo. Add an authorization scheme to the process if photos are private.
  • Pictures live in the database here. Many large photos make the table grow quickly, so keep the 5 MB limit or resize the pictures before they are saved.

Related Guides

Conclusion

A gallery needs a table for the pictures, a multiple file upload item, a process that copies the files into the table, and a Cards region on the BLOB column. A Carousel Container with one small Cards region per slide turns the newest photos into a slideshow.

The tests showed that the upload accepts several images at once, that a text file and an empty upload are refused, that the carousel and the gallery follow the chosen product, and that the database rejects a file that is not an image. Keep the checks in the page and in the table.

Get more Vinish.dev tutorials on Google

See my articles more often when you search Google.

Vinish Kapoor
Vinish Kapoor

An Oracle ACE, author of four books on Oracle APEX, SQL and PL/SQL, and Oracle Forms, and a software developer building Oracle database applications since 2001.

guest

0 Comments
Oldest
Newest Most Voted