A quick trick to fix an annoying UX gap in Oracle APEX Interactive Grid (IG) validation - stopping users from tabbing away from a required field before they've actually fixed it.
The Problem
In an Interactive Grid, it's common to validate a column on Lose Focus using
a Dynamic Action. For example, a CR_AMOUNT column that should never be left blank.
The typical setup:
- Event: Lose Focus
- Condition: Client-side condition on the column
- Action: Execute JavaScript Code
var v = this.triggeringElement.value;
if (v === "" || v === null) {
apex.message.showErrors([
{
type: "error",
location: ["page", "inline"],
message: "CR Amount cannot be null.",
unsafe: false
}
]);
// Highlight the cell
this.triggeringElement.classList.add("apex-error");
// Prevent user from leaving null value
// this.triggeringElement.focus();
}
This shows a nice inline error and highlights the cell. But there's a catch.
Why It's Not Enough
showErrors() only displays an error - it doesn't stop grid navigation.
Once the user presses Tab or clicks elsewhere, the IG has already moved focus to the next cell.
Calling .focus() immediately inside this handler doesn't help either, because the
grid's own tab-navigation logic runs right after and overrides it.
CR_AMOUNT blank and tabs out →
error appears → but the grid has already jumped to a different cell → user has to manually
scroll/click back to fix it. On data-heavy grids, this hunt-and-peck took 2–3 seconds every time.
The Fix: Force Focus Back, After the Grid Finishes Moving
The trick is to let the grid finish its own navigation first, then override it by jumping back
to the invalid cell programmatically using the IG's gotoCell API.
var el = this.triggeringElement;
var v = el.value;
if (v === "" || v === null) {
apex.message.showErrors([{
type: "error",
location: ["page", "inline"],
message: "CR Amount cannot be null.",
unsafe: false
}]);
// Read these NOW, before the grid moves on
var recId = $(el).closest("tr").data("id");
var view = apex.region("YOUR_IG_STATIC_ID").widget()
.interactiveGrid("getViews", "grid");
// Wait until tab navigation finishes, then jump back to the cell
setTimeout(function () {
view.view$.grid("gotoCell", recId, "CR_AMOUNT");
// Put the cursor inside the editor
setTimeout(function () {
view.view$.find("td.is-active input, td.is-focused input").first().focus();
}, 50);
}, 200);
}
How It Works
this.triggeringElement is only reliable inside the current event, so grab
recId before any async code runs.
Via apex.region(...).widget().interactiveGrid("getViews", "grid").
Fighting the grid's native tab/click navigation synchronously doesn't work - let it "win" first, then correct it.
The officially supported IG API to programmatically move focus to a specific cell in a specific row.
gotoCell selects the cell but doesn't always drop into edit mode instantly -
this small delay focuses the real <input> so the user can type right away.
Result: the user simply cannot leave the required field blank and move on - the grid snaps them right back, cursor ready, with zero manual searching.
A Few Notes / Gotchas
- Replace
YOUR_IG_STATIC_IDwith your actual IG's Static ID. - The 200ms / 50ms delays are tuned empirically - bump them slightly on slower devices or heavier grids.
- This is aggressive UX - only use it on columns that are genuinely mandatory.
- Consider disabling/intercepting the Save action too, in case a keyboard shortcut bypasses the trap.
gotoCellis part of the supportedinteractiveGridwidget API, not a private hack - safer across APEX versions.
Takeaway
showErrors() alone only tells the user what's wrong - it doesn't stop them from
wandering off. Pairing it with gotoCell and a short setTimeout turns
a passive validation message into an active, un-skippable guardrail - saving real time in
high-volume data entry grids.
Comments
Post a Comment