Skip to main content

Mastering Interactive Grids: Toolbar Customization with JavaScript

Mastering Interactive Grids: Toolbar Customization with JavaScript

Mastering Interactive Grids:
Toolbar Customization with JavaScript

The Interactive Grid toolbar is one of those features most developers accept as-is — a row of default buttons that do the job, but rarely match the specific workflows users actually need. What if you could add a one-click "Export Selected" button, inject a contextual "Approve All" action, or remove the buttons your users never touch? With a handful of JavaScript lines and the apex.region API, you can reshape the toolbar entirely.

This section covers everything: understanding the toolbar's internal data model, adding custom buttons with PL/SQL callbacks, conditionally enabling/disabling controls, and practical patterns for real enterprise use cases.

Prerequisites

You should be comfortable with APEX Dynamic Actions, basic JavaScript/jQuery, and have used Interactive Grids before. PL/SQL knowledge is needed for the server-side callback examples.

How the Toolbar Data Model Works

Before adding buttons, you need to understand that the Interactive Grid toolbar isn't a static HTML structure — it's driven by a JavaScript configuration object that APEX processes at render time. Every button, separator, and dropdown menu is defined as a plain object in a toolbar data array. You interact with this array to customize the toolbar.

APEX exposes two hooks for this: the JavaScript Initialization Code attribute on the Interactive Grid region itself, and Dynamic Actions using the Execute JavaScript Code action. The cleanest approach — especially for persistent customizations — is the Initialization Code attribute.

Page Designer → Interactive Grid Region → Attributes → JavaScript Initialization Code
// This function runs when the IG is initialized
function(config) {
// config.toolbar — modify toolbar items here
// config.columns — modify column definitions
// config.features — toggle built-in features
return config;
}

The config.toolbar object contains a data array. Each element in data is a toolbar group, and each group has an items array of individual controls. Here's what a typical item object looks like:

Toolbar item anatomy JavaScript
{
  type:     "BUTTON",              // BUTTON, SELECT, TEXT, MENU, SEPARATOR
  id:       "my-custom-btn",       // unique string ID
  label:    "Approve Selected",    // visible text
  title:    "Approve Selected Rows", // tooltip on hover
  icon:     "fa-check-circle",      // Font Awesome class
  iconBeforeLabel: true,           // icon left of label
  hot:      false,                 // true = primary button style
  disabled: false,                // toggle interactivity
  action:   "my-custom-action"     // bound action name (see below)
}

Adding a Custom Button Step by Step

Let's build a practical example: an "Export Selected" button that exports only the rows the user has checked — not the entire dataset. This is one of the most commonly requested IG customizations.

  1. 1
    Open Page Designer, click your Interactive Grid region, and navigate to Attributes → Advanced → JavaScript Initialization Code.
  2. 2
    Paste the initialization function below into the attribute field (not a Dynamic Action — the Initialization Code runs earlier and has access to the full config object).
  3. 3
    Make sure your IG has Selection Type set to Rows under the Selection column group attributes — otherwise row checkboxes won't render.
IG Region → JavaScript Initialization Code JavaScript
function(config) {

  // 1. Define the action that the button will invoke
  config.initActions = function( actions ) {

    actions.add({
      name:  "export-selected-rows",
      label: "Export Selected",
      action: function() {
        // Get the IG model
        var ig$     = apex.region("emp_ig").widget();
        var model   = ig$.interactiveGrid("getViews", "grid").model;
        var selected = ig$.interactiveGrid("getViews", "grid")
                          .getSelectedRecords();

        if ( selected.length === 0 ) {
          apex.message.showErrors([{
            type:     "error",
            location: "page",
            message:  "Please select at least one row to export."
          }]);
          return;
        }

        // Build a CSV from selected rows
        var csv = "EMP_ID,NAME,SALARY,STATUS\n";
        selected.forEach( function( rec ) {
          csv += [
            model.getValue( rec, "EMP_ID" ),
            model.getValue( rec, "NAME" ),
            model.getValue( rec, "SALARY" ),
            model.getValue( rec, "STATUS" )
          ].join(",") + "\n";
        });

        // Trigger browser download
        var blob = new Blob( [csv], { type: "text/csv" } );
        var url  = URL.createObjectURL( blob );
        var a    = document.createElement("a");
        a.href     = url;
        a.download = "selected_employees.csv";
        a.click();
        URL.revokeObjectURL( url );
      }
    });
  };

  // 2. Add the button to the toolbar
  //    We'll insert it into the ACTIONS group (right side toolbar)
  var toolbarData = config.toolbar.data;

  toolbarData.push({
    type:  "GROUP",
    id:    "custom-actions-group",
    items: [{
      type:            "BUTTON",
      id:              "btn-export-selected",
      label:           "Export Selected",
      title:           "Export only the checked rows to CSV",
      icon:            "fa-download",
      iconBeforeLabel: true,
      hot:             false,
      action:          "export-selected-rows"
    }]
  });

  return config;
}

This is what your IG toolbar will look like after this code runs:

🔷 Employee Directory — Interactive Grid (preview)
Add Row
Save
Actions ▾
⬇ Export Selected
Name
Department
Salary
Status
Priya Suresh
Engineering
₹ 92,000
Active
Rajan Mehta
Finance
₹ 78,500
Pending
Anitha Nair
Engineering
₹ 1,05,000
Active

Calling a PL/SQL Process from a Toolbar Button

Client-side actions are useful, but real enterprise workflows usually need to hit the database. Here's how to wire a toolbar button to a server-side APEX Process — for example, an "Approve All Selected" button that updates a status column in your table.

The pattern is: collect selected row IDs in JavaScript → pass them to an APEX item → fire an AJAX callback to your PL/SQL process.

Step A: Create the AJAX Callback Process

In Page Designer, go to Processing → Create Process. Set the type to PL/SQL Code, and check Execution Scope: On Demand (AJAX Callback). Give it the name APPROVE_SELECTED.

Page Process: APPROVE_SELECTED (PL/SQL) PL/SQL
DECLARE
  l_ids  apex_t_varchar2;
BEGIN
  -- :P1_SELECTED_IDS is a hidden page item
  -- Value is a colon-separated list of EMP_IDs
  l_ids := apex_string.split( :P1_SELECTED_IDS, ':' );

  FORALL i IN 1..l_ids.COUNT
    UPDATE employees
       SET  status       = 'APPROVED',
            approved_by  = :APP_USER,
            approved_dt  = SYSDATE
     WHERE emp_id       = l_ids(i);

  apex_json.open_object;
  apex_json.write( 'status', 'success' );
  apex_json.write( 'count',  l_ids.COUNT );
  apex_json.close_object;
END;

Step B: Wire the Button to the Process

IG Region → JavaScript Initialization Code (button action) JavaScript
actions.add({
  name:  "approve-selected",
  label: "Approve Selected",
  action: function() {
    var selected = apex.region("emp_ig").widget()
                        .interactiveGrid("getViews", "grid")
                        .getSelectedRecords();

    if ( !selected.length ) { return; }

    // Collect primary key values into a colon-separated string
    var model  = apex.region("emp_ig").widget()
                      .interactiveGrid("getViews", "grid").model;

    var ids = selected.map( function( rec ) {
      return model.getValue( rec, "EMP_ID" );
    }).join(":");

    // Set the hidden page item then fire the AJAX callback
    $s( "P1_SELECTED_IDS", ids );

    apex.server.process( "APPROVE_SELECTED", {
      pageItems: "#P1_SELECTED_IDS"
    }, {
      success: function( data ) {
        if ( data.status === "success" ) {
          apex.message.showPageSuccess(
            data.count + " row(s) approved successfully."
          );
          // Refresh the IG to reflect updated statuses
          apex.region("emp_ig").refresh();
        }
      },
      error: function() {
        apex.message.showErrors([{
          type:    "error",
          location: "page",
          message: "Approval failed. Check application logs."
        }]);
      }
    });
  }
});
Pro Tip

Always call apex.region("your_ig_static_id").refresh() after a server-side update. This re-executes the IG's SQL query and syncs the client model with the database — without losing the user's current filter or sort settings.

Removing and Reordering Default Toolbar Items

Sometimes the best customization is subtraction. If your IG is read-only, the Add Row, Save, and Edit buttons are noise. Here's how to surgically remove them using their well-known IDs:

Remove default toolbar buttons by ID JavaScript
function(config) {

  // IDs of buttons to hide
  var removeIds = [
    "save",          // Save button
    "add-row",        // Add Row button
    "revert-report", // Revert report changes
    "chart"           // Chart view toggle
  ];

  config.toolbar.data.forEach( function( group ) {
    if ( group.items ) {
      group.items = group.items.filter( function( item ) {
        return removeIds.indexOf( item.id ) === -1;
      });
    }
  });

  return config;
}

Known Default Toolbar Button IDs

Button ID Visible Label Notes
add-rowAdd RowOnly visible in Edit mode
saveSaveSubmits pending IG changes
revert-reportRevertResets to saved report settings
chartChartToggles chart view
group-byGroup ByOpens group-by dialog
pivotPivotToggles pivot view
flashbackFlashbackTime-travel query
downloadDownloadBuilt-in export menu
stretch-columns(icon only)Fills container width
Caution

APEX does not guarantee toolbar item IDs are stable across major versions. After upgrading APEX, verify your customizations still work, particularly if you depend on internal IDs not listed in official documentation.

Dynamically Enabling or Disabling Buttons

The most polished UX pattern: buttons that are disabled by default and only activate when the user selects rows. This prevents accidental "Approve All" clicks and makes the interface self-documenting.

APEX IG fires a selectionchange event on its jQuery widget. Listen to it and toggle your button's disabled state using the actions API:

Dynamic Action: Execute JavaScript → After Refresh (or Page Load) JavaScript
// Run this in a Dynamic Action with event: "After Refresh" on the IG region
// OR inline in a Page-level DA with event: "Page Load"

$( "#emp_ig" ).on( "selectionchange", function() {

  var view     = apex.region("emp_ig").widget()
                      .interactiveGrid("getViews", "grid");
  var selected = view.getSelectedRecords();
  var hasRows  = selected.length > 0;

  // Get the actions context for this IG
  var actions = apex.region("emp_ig").widget()
                     .interactiveGrid("getActions");

  // Enable/disable based on selection count
  actions.lookup( "approve-selected"  ).disabled = !hasRows;
  actions.lookup( "export-selected-rows" ).disabled = !hasRows;

  // Force toolbar to re-render the updated states
  actions.update( "approve-selected"  );
  actions.update( "export-selected-rows" );
});
Also set initial state

In your initActions function, add disabled: true to the action definition so the button starts disabled on page load. The selectionchange listener then activates it once rows are checked.

Key API Methods Cheat Sheet

Interactive Grid JavaScript API — Essential Methods
apex.region("id").widget()Get the IG jQuery widget
.interactiveGrid("getViews","grid")Access the grid view object
view.getSelectedRecords()Array of selected record refs
model.getValue(rec,"COL")Read a column value from a record
model.setValue(rec,"COL",val)Write a value into a record
.interactiveGrid("getActions")Get the actions context object
actions.add({ name, action })Register a new action
actions.lookup("name")Find an existing action by name
actions.update("name")Force toolbar re-render
apex.region("id").refresh()Re-execute SQL, sync model
apex.server.process("NAME",…)Call APEX AJAX callback
config.toolbar.data.push(…)Add toolbar group in init code

Putting It All Together

With these patterns you can transform a generic Interactive Grid into a purpose-built data management tool that feels native to your users' workflow. The key principles to take away:

  • Always define actions in initActions, not inline in toolbar items — it keeps logic centralized and allows you to call actions.update() later.
  • Use getSelectedRecords() + model.getValue() instead of reading DOM values — it works regardless of pagination.
  • Fire apex.region().refresh() after server-side updates, not a full page submit, to preserve the user's IG state.
  • Start custom buttons as disabled: true and activate them reactively via selectionchange for polished UX.

In the next section we'll look at row-level validation with JavaScript model events — catching bad data before it ever reaches the database, entirely on the client side.

Comments

Popular posts from this blog

Generate Custom PDFs in Oracle APEX with jsPDF

Generate Custom PDFs in Oracle APEX with jsPDF A complete step-by-step guide to building a client-side PDF generator with profile image embedding and PL/SQL database persistence. 📄 Step-by-step guide Oracle APEX jsPDF Dynamic Actions PL/SQL JavaScript "Picture this: your Oracle APEX application is polished, your users love it — but when they ask for a downloadable report with a profile picture and custom styling, you realize vanilla APEX isn't built for that out of the box. What if a single JavaScript library and one Dynamic Action could change everything?" Welcome to the world of jsPDF inside Oracle APEX . In this tutorial, we wire up a PDF generator that captures a user's name, email, phone number, and profile picture — renders them into a beautiful PDF layout — and saves everything to Oracle, all in one button click. ⓘ What you'll build: A dynamic PDF generat...

APEX Custom Auth Settings, Decoded!

Oracle APEX Security PL/SQL APEX Custom Auth Settings, Decoded Why I Build this I was building an internal APEX app — strictly for people on our corporate network. The default authentication worked, but it had a few things that kept bothering me. Anyone with valid credentials could log in from anywhere. Sessions expired with a useless error page. There was no audit trail — no way to know who logged in, when, or from where. And passwords were stored in plain text, which I just couldn't leave alone. So I did what any developer does — I built it myself. Custom authentication, from scratch, in PL/SQL. Turned out to be one of the best learning experiences I've had with APEX. Here's exactly how I did it. Img 1 : Head to Shared Components → Authentication Schemes → Create, choose Custom as the Scheme Type, and plug in your function and procedure names. Quick note before we get into the code — the s...

Record, Preview, Save — Video Recording in Oracle APEX

Oracle APEX · Video Recording Record Video Directly in Oracle APEX 🎬 Have you ever wished your users could say it instead of type it? In many modern applications, we've seen the ability to record a short video clip directly in the browser — no file uploads, no external tools. Just hit record, watch it back, and decide — save it or try again. Today, we're bringing that same experience into Oracle APEX: record, pause/resume, preview, save to DB, or retry. Let's build it. 1 Step 1 : Start by creating a Static Content Region on your APEX page. This will act as the container for our entire video recorder UI.Paste the provided HTML code into the region source. This gives you two video panels — one for the live camera preview and one for the recorded playback — along with your action buttons: Start, Pause, Resume, Stop, Save, and Retry. Copy <!-- Recorder UI --> < div id= "recorderUI...