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.
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.
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:
{
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.
- 1Open Page Designer, click your Interactive Grid region, and navigate to Attributes → Advanced → JavaScript Initialization Code.
- 2Paste 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).
- 3Make sure your IG has Selection Type set to Rows under the Selection column group attributes — otherwise row checkboxes won't render.
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:
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.
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
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." }]); } }); } });
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:
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-row | Add Row | Only visible in Edit mode |
save | Save | Submits pending IG changes |
revert-report | Revert | Resets to saved report settings |
chart | Chart | Toggles chart view |
group-by | Group By | Opens group-by dialog |
pivot | Pivot | Toggles pivot view |
flashback | Flashback | Time-travel query |
download | Download | Built-in export menu |
stretch-columns | (icon only) | Fills container width |
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:
// 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" ); });
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
apex.region("id").widget()Get the IG jQuery widget.interactiveGrid("getViews","grid")Access the grid view objectview.getSelectedRecords()Array of selected record refsmodel.getValue(rec,"COL")Read a column value from a recordmodel.setValue(rec,"COL",val)Write a value into a record.interactiveGrid("getActions")Get the actions context objectactions.add({ name, action })Register a new actionactions.lookup("name")Find an existing action by nameactions.update("name")Force toolbar re-renderapex.region("id").refresh()Re-execute SQL, sync modelapex.server.process("NAME",…)Call APEX AJAX callbackconfig.toolbar.data.push(…)Add toolbar group in init codePutting 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 callactions.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: trueand activate them reactively viaselectionchangefor 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
Post a Comment