Excel-Like
Drag & Fill
in APEX IG
"I spent way too long trying to get this working…
and then one weird jQuery trick changed everything."
The Backstory
If you've used Excel for even ten minutes, you already know that little green handle at the corner of a selected cell. You drag it down, and boom, values fill across every row automatically. It's one of those features users love so much they don't even think about it. They just expect it everywhere.
So when a client asked me, "Hey, can we have that same fill-down thing in our APEX grid?". I said, "Yeah, shouldn't be a problem."
Reader: it was a problem.
What I Tried First (spoiler: it failed)
draggable="true" on every editable cell,
use the native dragstart / drop events, and wire them to the model.
Spent about 45 minutes on this. Works great in a static table. Falls apart completely in an Interactive Grid
because APEX re-renders rows constantly. Every time the model updates, all your event listeners vanish.
Back to square one.
model.setValue(record, "CITY", value).
It worked! For exactly one column. The moment I needed it to work across any column dynamically,
the whole approach crumbled. Also, the day I rename a column in the DB, everything breaks silently. Not great.
mousedown for capturing the drag start, clicking any cell to
edit it started misbehaving. The grid's own focus management was colliding with my handler.
The fix? One tiny line: event.preventDefault(). I cannot tell you how long I stared
at this before figuring that out. Maybe 30 minutes. Maybe more. We don't talk about it.
modelColumns config object using Object.keys().
Feels like a hack, but it's actually the supported pattern, and it works consistently.
Approaches Compared
| Approach | Works? | Why / Why Not |
|---|---|---|
| HTML5 draggable | ✗ No | Events die on APEX grid re-render |
| Hardcoded column names | ✗ Fragile | Breaks on rename, not scalable |
| mousemove on cells (direct) | ✗ No | Re-rendered cells lose listeners |
| Delegated mousedown + mousemove | ✓ Yes | Survives re-renders, dynamic columns |
The Approach That Finally Worked
$(document) so they survive any re-render:
mousedown captures the value and column name when you press down on a cell,
mousemove on rows fills the captured value into each row you hover over while holding,
and mouseup resets everything cleanly. The APEX IG model does all the heavy lifting.
my_ig. The JS references it by that exact ID.checkname CSS Class to Columnscheckname for every column you want drag-fill enabled on. This is the hook the JavaScript uses.::after pseudo-element on .checkname adds the red ^ drag handle visual, no extra HTML needed.APEX$ROW_STATUS. Make sure every bind variable exactly matches your column names. Case matters.IG Source Query
Nothing fancy here, just a straightforward SELECT. The magic is entirely in the JS layer.
SELECT ROW_ID, EMPLOYEE_NAME, MANUAL_VALUE, PHONE_NUMBER, CITY, PIN_CODE FROM EMPLOYEES;
JavaScript: Execute When Page Loads
Why delegated events? APEX's IG re-renders table rows on every model change.
If you attach events directly to .checkname elements, they disappear the next time the grid updates.
Delegating from $(document) means your listeners always catch events no matter how many times the DOM rebuilds.
$(document).ready(function () { let draggedValue = null; let draggedColumn = null; $(document).on("mousedown", ".checkname", function (event) { let $cell = $(this); let $row = $cell.closest("tr"); let ig$ = apex.region("my_ig").widget(); let model = ig$.interactiveGrid("getViews", "grid").model; let record = model.getRecord($row[0].dataset.id); let columnIdx = $cell.index(); let view = ig$.interactiveGrid("getViews", "grid"); let config = view.modelColumns; let columnName = Object.keys(config)[columnIdx]; if (columnName) { draggedValue = model.getValue(record, columnName); draggedColumn = columnName; } event.preventDefault(); }); $(document).on("mousemove", "tr", function () { if (draggedValue !== null && draggedColumn !== null) { let $row = $(this); let ig$ = apex.region("my_ig").widget(); let model = ig$.interactiveGrid("getViews", "grid").model; let record = model.getRecord($row[0].dataset.id); model.setValue(record, draggedColumn, draggedValue); } }); $(document).on("mouseup", function () { draggedValue = null; draggedColumn = null; }); });
CSS: The Handle and Visual Cues
The ::after on .checkname adds the red ^ handle, a visual signal that
the column supports drag-fill. Took me a few tries to get the positioning right so it doesn't overlap the cell text.
.manual-value-container { position: relative; display: flex; align-items: center; width: 100%; } .manual-value-input { flex-grow: 1; padding-right: 30px; border: 1px solid #ccc; padding: 4px; border-radius: 4px; width: 100%; } .drag-handle { cursor: grab; color: #007bff; margin-right: 5px; } .checkname { position: relative; padding-right: 25px; } .checkname::after { content: "^"; font-size: 16px; font-weight: bold; color: #db3338; position: absolute; top: 50%; right: 5px; transform: translateY(-50%); background: white; border-radius: 50%; width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 3px rgba(0,0,0,0.3); cursor: pointer; } button#B24398529473943637001 { background-color: cadetblue; }
PL/SQL: DML Process
Standard IG process, nothing surprising. The one thing I always double-check: bind variable names must match your column names exactly. APEX is case-insensitive here but it's a good habit to keep them consistent.
BEGIN CASE :APEX$ROW_STATUS WHEN 'C' THEN INSERT INTO EMPLOYEES ( ROW_ID, EMPLOYEE_NAME, MANUAL_VALUE, PHONE_NUMBER, CITY, PIN_CODE ) VALUES ( :ROW_ID, :EMPLOYEE_NAME, :MANUAL_VALUE, :PHONE_NUMBER, :CITY, :PIN_CODE ); WHEN 'U' THEN UPDATE EMPLOYEES SET EMPLOYEE_NAME = :EMPLOYEE_NAME, MANUAL_VALUE = :MANUAL_VALUE, PHONE_NUMBER = :PHONE_NUMBER, CITY = :CITY, PIN_CODE = :PIN_CODE WHERE ROW_ID = :ROW_ID; WHEN 'D' THEN DELETE FROM EMPLOYEES WHERE ROW_ID = :ROW_ID; END CASE; END;
๐ Finally working and the client loves it
Once all the pieces clicked into place, the whole thing works exactly like Excel's fill handle. The APEX model handles dirty tracking automatically, so hitting Save just works. Users have been using it daily without a single bug report. That's a win.
Things I'd Tell Past Me
my_igapex.region("my_ig"): if your region's Static ID doesn't match this string exactly, you'll get a silent JS error
and nothing will work. Check the region's Static ID property in Page Designer first.
checkname class per-column in Column AttributesTry It
The live demo is up. Pull the source on GitHub and make it yours.
Comments
Post a Comment