Skip to main content

APEX IG Trick: Trap the Cursor on Invalid Cells Using gotoCell()

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.

Oracle APEX Interactive Grid Dynamic Actions JavaScript

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.

Real user experience: User leaves 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

1. Capture the row ID immediately

this.triggeringElement is only reliable inside the current event, so grab recId before any async code runs.

2. Get the grid view

Via apex.region(...).widget().interactiveGrid("getViews", "grid").

3. Let the grid finish navigating (setTimeout 200ms)

Fighting the grid's native tab/click navigation synchronously doesn't work - let it "win" first, then correct it.

4. gotoCell(recId, "CR_AMOUNT")

The officially supported IG API to programmatically move focus to a specific cell in a specific row.

5. Focus the actual input (nested setTimeout 50ms)

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_ID with 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.
  • gotoCell is part of the supported interactiveGrid widget 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

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. &#128196; 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...

AI Agents in Oracle APEX - Part 2: Building the Live Demo

agent: online AI Agents in Oracle APEX — Part 2: Building the Live Demo The config, the tools, the button, and what to type at it. This picks up straight from Part 1 — no theory this time, just the build. I'm using two small tables, EMP_D and DEPT_D , so the classic schema stays out of the way of the actual point. By the end, you'll have an agent that looks up employees, summarizes salaries by department, hires someone new, and highlights a row on the page — all from a chat box. 01 Create the AI Agent Shared Components → Tasks → AI Agents → Create. First, APEX needs an AI Service configured — that's where your provider API key (OpenAI, Gemini, etc.) lives. Workspace Utilities → AI Services → Create Static ID + pick your model — I used command-a-03-2025 , fast and cheap enough to hammer with test prompts Paste your API key under Credentials — APEX auto-wraps it in a Web Credential, so it never sits exposed in page source Hit Test Connection be...