Freeze Panes in Oracle APEX Interactive Grid — Driven by a Page Item

Introduction

Oracle APEX provides a built-in Show AI Assistant dynamic action to quickly add AI-powered chat capabilities to applications. This document demonstrates how to enhance the default AI Assistant with features such as voice input, message timestamps, retry, quick prompts, chat export, and text-to-speech, creating a smarter and more user-friendly chat experience without modifying the native component.

Technologies Used:

  • Oracle APEX
  • JavaScript
  • HTML, CSS


Why We Need to Do This?

1.Keep Important Columns Visible – Prevent key columns such as IDs or Names from disappearing during horizontal scrolling.

2.Improve Data Navigation – Make it easier for users to view and compare large datasets.

3.Provide Dynamic Control – Allow users to choose how many columns to freeze using a page item.

4.Deliver an Excel-Like Experience – Enhance usability with familiar freeze pane functionality.

5.Avoid Third-Party Plugins – Implement the solution using only native Oracle APEX features and JavaScript.


How Do We Solve It?

The solution reads the number of columns to freeze from a page item, measures each column’s live width from the DOM, and applies CSS position: sticky with the correct cumulative left offset to that many header and body cells. The same logic is re-run automatically after every sort, search, and pagination event. The steps below outline the setup.



Step 1: Create a Page Item

 

Create a P12_FREEZE_COLS Select List page item with static values ranging from 0 to 6 and set the default value to 0.

 

Step 2: Set the Interactive Grid Static ID

 

Set the Static ID of the Interactive Grid region to emp_grid by navigating to:

Properties → Advanced → Static ID

 

Step 3: Add JavaScript in “Execute when Page Loads”

 

Add the freeze pane logic under Page → JavaScript → Execute when Page Loads.

 

This script:

 

Reads the number of columns to freeze from the page item.

Calculates the width of each column dynamically.

Applies sticky positioning to the selected columns.

Automatically reapplies the freeze logic after sorting, searching, and pagination using the gridviewcreate and apexafterrefresh events.

 

(function () {

  "use strict";

 

  var REGION_ID  = "emp_grid";        /* Static ID of your IG region   */

  var ITEM_NAME  = "P12_FREEZE_COLS";  /* Page item holding freeze count */

 

  /* ---- Remove all freeze styles ---- */

  function clearFreeze() {

    var $tbl = apex.region(REGION_ID).widget().find(".a-GV-table");

    $tbl.find("thead tr th, tbody tr td").css({

      position    : "", left        : "", zIndex      : "",

      background  : "", boxShadow   : "", borderRight : ""

    }).removeClass("ig-frozen-col ig-freeze-edge");

  }

 

  /* ---- Apply freeze for N columns ---- */

  function applyFreeze(numCols) {

    clearFreeze();

    numCols = parseInt(numCols, 10);

    if (!numCols || numCols < 1) return;

 

    var $tbl = apex.region(REGION_ID).widget().find(".a-GV-table");

    if (!$tbl.length) return;

 

    /* Measure actual column widths from header row */

    var colWidths = [];

    $tbl.find("thead tr:first-child th").each(function (i) {

      colWidths[i] = $(this).outerWidth() || 120;

    });

 

    /* Apply sticky positioning to header and body cells */

    $tbl.find("thead tr, tbody tr").each(function () {

      var $row    = $(this);

      var inHead  = !!$row.closest("thead").length;

      var cumLeft = 0;

 

      $row.children("th, td").each(function (colIdx) {

        if (colIdx >= numCols) return false;

        var $cell  = $(this);

        var isEdge = (colIdx === numCols - 1);

 

        $cell.css({

          position   : "sticky",

          left       : cumLeft + "px",

          zIndex     : inHead ? 31 : 20,

          background : inHead ? "#1a5f9e" : "#e8f0fa",

          boxShadow  : isEdge ? "2px 0 0 0 #0572ce" : ""

        });

        cumLeft += colWidths[colIdx];

      });

    });

 

    apex.region(REGION_ID).widget()

        .find(".a-GV-w-scrollX")

        .css("overflow-x", "auto");

  }

 

  function applyFromItem() {

    var val = $v(ITEM_NAME) || "0";

    applyFreeze(val);

  }

 

  /* ---- Re-apply after IG refresh / pagination / sort ---- */

  apex.jQuery(document)

    .on("gridviewcreate apexafterrefresh", "#" + REGION_ID,

        function () { setTimeout(applyFromItem, 200); });

 

  setTimeout(applyFromItem, 800);

 

  /* ---- Expose globally so DA can call it ---- */

  window.igApplyFreeze = applyFromItem;

}());

 

Step 4: Create a Dynamic Action on P12_FREEZE_COLS

 

Create a Dynamic Action on P12_FREEZE_COLS for the Change event.

True Action: Execute JavaScript Code

Call the following function:

 

igApplyFreeze();

 

This invokes the existing freeze logic defined in the page load script without duplicating any code.

 

Step 5: Add Inline CSS

 

Add the required CSS under Page → CSS → Inline.

 

The CSS keeps the sticky header above the scrollable content and preserves zebra striping for the frozen columns, ensuring a consistent appearance.

 

/* Keep IG scroll container scrollable */

#emp_grid .a-GV-w-scrollX {

  overflow-x: auto !important;

}

 

/* Frozen header cells stay above scroll */

#emp_grid .a-GV-thead {

  position: sticky;

  top: 0;

  z-index: 30;

}

 

/* Zebra stripe for frozen body cells (even rows) */

#emp_grid .a-GV-table tbody tr:nth-child(even) td[style*="sticky"] {

  background: #d4e4f7 !important;

}

 

Conclusion :
This approach adds an Excel-like Freeze Panes feature to Oracle APEX Interactive Grid using JavaScript, CSS, and a page item. It improves data visibility, enhances the user experience, and continues to work seamlessly after sorting, filtering, and pagination all without relying on external plugins.

OUTPUT :


Recent Posts