Custom Process Progress Bar in Oracle APEX

Introduction

Oracle APEX provides several standard components for tracking progress, but they are mostly percentage-based. In applications where a batch or job moves through defined sequential operations, users need a step-by-step visual representation rather than a single progress value. 

This blog explains how to build a dynamic, step-based process progress bar in Oracle APEX using AJAX Callback (On-Demand Process), Dynamic Actions, JavaScript, and CSS, and clearly shows where each piece of code is placed inside APEX. 

Why We Need to Do This 

Standard APEX progress bars: 

  • Do not represent individual steps 
  • Do not reflect real process flow 
  • Are difficult to map to operation sequences 

A custom process progress bar: 

  • Visually mirrors the actual workflow 
  • Improves usability and clarity 
  • Reduces dependency on textual status indicators 

 

How Do We Solve This 

We split the solution into four APEX layers:

  1. AJAX Callback (On-Demand Process) – Fetch data 
  2. Dynamic Action – Trigger the refresh 
  3. JavaScript – Render the progress bar 
  4. CSS – Style the UI 

Each layer has a clear responsibility and location inside APEX. 

Step 1: Create the Container Region (UI) 

Where:
APEX Page → Create Region 

  • Region Type: Static Content
  • Static ID: process_progress 

This region acts as the placeholder where the progress bar will be rendered. 

 

Step 2: Create AJAX Callback (On-Demand Process) 

Where:
APEX Page → Processing → Ajax Callback 

  • Name: GET_PROCESS_PROGRESS 

Code: 

DECLARE 

BEGIN 

    apex_json.open_array; 

  

    FOR r IN ( 

        SELECT 

            w.OPERATION_SEQ_NUM AS process_code, 

            w.DESCRIPTION || ‘ (‘ || w.OPERATION_SEQ_NUM || ‘)’ AS process_name, 

            CASE 

                WHEN COUNT(DISTINCT e.OPERATION_SEQUENCE_ID) > 0 

                THEN ‘Y’ 

                ELSE ‘N’ 

            END AS completed 

        FROM apps.WIP_OPERATIONS_V w 

        JOIN apps.wip_discrete_jobs_v j 

            ON w.WIP_ENTITY_ID = j.WIP_ENTITY_ID 

        LEFT JOIN XXTTK_ODM_RUN_CARD_ENTRIES e 

            ON e.OPERATION_SEQUENCE_ID = w.OPERATION_SEQ_NUM 

            AND e.BATCH_NO = j.WIP_ENTITY_NAME 

        WHERE j.WIP_ENTITY_NAME = :P11_BATCH_NO 

        GROUP BY w.OPERATION_SEQ_NUM, w.DESCRIPTION 

        ORDER BY w.OPERATION_SEQ_NUM 

    ) LOOP 

        apex_json.open_object; 

        apex_json.write(‘process_code’, r.process_code); 

        apex_json.write(‘completed’, r.completed); 

        apex_json.write(‘process_name’, r.process_name); 

        apex_json.close_object; 

    END LOOP; 

    apex_json.close_array; 

END; 

This process: 

  • Runs via AJAX 
  • Returns process data as JSON 
  • Keeps business logic on the server side 

 

Step 3: Create Dynamic Action to Trigger JavaScript 

Where:
APEX Page → Dynamic Actions 

  • Event: Page Load
    (or Change of P11_BATCH_NO if batch can change) 
  • Selection Type: Page 
  • True Action: Execute JavaScript Code 

This ensures the progress bar loads automatically when the page opens or when batch changes. 

 

Step 4: JavaScript to Call AJAX Process 

Where:
Inside the Dynamic Action → Execute JavaScript Code 

apex.server.process(
   “GET_PROCESS_PROGRESS”,
   {},
   {
       success: function (data) {

  let wrapper = document.createElement(“div”);
           wrapper.className = “process-wrapper”;

           let rowContainer = document.createElement(“div”);
           rowContainer.className = “process-container”;
           wrapper.appendChild(rowContainer);

           data.forEach(function (row, index) {

               let step = document.createElement(“div”);
               step.className = “process-step”;

               if (row.completed === “Y”) {
                   step.classList.add(“completed”);
               }

               step.innerText = row.process_code;
               step.title = row.process_name;
               rowContainer.appendChild(step);

               if (index < data.length – 1) {
                   let line = document.createElement(“div”);
                   line.className = “process-line”;

                   if (row.completed === “Y”) {
                       line.classList.add(“completed”);
                   }

                   rowContainer.appendChild(line);
               }
           });

           let region = document.getElementById(“process_progress”);
           region.innerHTML = “”;
           region.appendChild(wrapper);
       }
   }
);
 

This JavaScript: 

  • Calls the AJAX Callback 
  • Dynamically creates HTML elements 
  • Applies styles based on completion status 

 

Step 5: Add CSS for Styling 

Where (Recommended): 

  • Page → CSS → Inline
    (or Application → Shared Components → Theme → Custom CSS) 

.process-wrapper {
   display: flex;
   flex-direction: column;
   gap: 3px;
}

.process-container {
   display: flex;
   align-items: center;
   flex-wrap: nowrap;
}

.process-step {
   width: 22px;
   height: 22px;
   border-radius: 50%;
   display: flex;
   align-items: center;
   justify-content: center;
   font-weight: bold;
   font-size: 11px;
   border: 1px solid #ccc;
   background: #eee;
   color: #555;
}

.process-step.completed {
   background: #1b1c6d;
   border-color: #1b1c6d;
   color: #fff;
}

.process-line {
   width: 10px;
   border-top: 2px solid #ccc;
   margin: 0 1px;
}

.process-line.completed {
   border-top-color: #1b1c6d;
}

Conclusion 

This implementation demonstrates how Oracle APEX can be extended beyond standard components to create custom, business-aligned visualizations. 

By clearly separating: 

  • Data logic (AJAX Callback)
  • UI behavior (Dynamic Actions & JavaScript) 
  • Presentation (CSS)

we achieve a solution that is scalable, maintainable, and easy to enhance.

 

Recent Posts