In one Oracle APEX page I wanted the Interactive Grid to remember which cell the user had been working in. When the user switched to another browser tab or window and then came back, focus should return to the same cell.
The grid cells use the .a-GV-cell class, so I used a delegated focusin handler to track the active cell. I kept the DOM element itself in memory and also stored its index in sessionStorage as a fallback.
When the document becomes visible again, the script tries to focus the original cell. If that element is no longer in the DOM, it falls back to the stored cell index.
Contents
- Tracking the last active grid cell
- Storing a fallback in sessionStorage
- Restoring focus after returning to the tab
- Complete page-load code
- Limitations of the index fallback
Tracking the last active grid cell
I used a delegated focusin event on document:
var lastFocusedCell = null;
$(document).on("focusin", ".a-GV-cell", function(e) {
lastFocusedCell = this;
});
Using delegation means the handler is attached once to the document instead of directly to every grid cell. That fits an Interactive Grid because cells can be replaced when the grid is redrawn.
The variable lastFocusedCell holds a direct reference to the last focused TD. As long as that element is still part of the current DOM, this is the most direct way to return focus to it.
Storing a fallback in sessionStorage
I also stored the position of the focused cell among all elements matching .a-GV-cell:
var allCells = Array.from(
document.querySelectorAll(".a-GV-cell")
);
var idx = allCells.indexOf(this);
sessionStorage.setItem(
"ig_last_cell_index",
idx
);
This is only a fallback. The primary option is still the direct DOM reference.
sessionStorage is enough here because I only need the value for the current browser tab. I do not need to persist the selected cell after the tab is closed.
Restoring focus after returning to the tab
The browser fires visibilitychange when the document moves between visible and hidden states. I use that event to detect when the user returns.
document.addEventListener("visibilitychange", function() {
if (!document.hidden) {
setTimeout(function() {
// restore focus
}, 200);
}
});
I added a small delay before restoring focus. The implementation uses 200 ms so the page or grid has a short moment to finish any redraw that may happen while the tab becomes active again.
The first restore attempt checks whether the original cell still exists in the document:
if (
lastFocusedCell &&
document.body.contains(lastFocusedCell)
) {
lastFocusedCell.focus();
lastFocusedCell.scrollIntoView({
block: "center"
});
return;
}
If it is still there, the script focuses it and scrolls it into view.
If the reference is no longer valid, the script reads the stored index and tries to find the corresponding cell again:
var idx = Number(
sessionStorage.getItem("ig_last_cell_index")
);
var allCells = document.querySelectorAll(
".a-GV-cell"
);
if (!isNaN(idx) && allCells[idx]) {
allCells[idx].focus();
allCells[idx].scrollIntoView({
block: "center"
});
}
Complete page-load code
I placed the complete script in Execute when Page Loads:
// Global reference to the last focused IG cell (TD)
var lastFocusedCell = null;
// Remember the active IG cell when the user clicks or tabs into it
$(document).on("focusin", ".a-GV-cell", function(e) {
lastFocusedCell = this;
// Store the cell index as a fallback
var allCells = Array.from(
document.querySelectorAll(".a-GV-cell")
);
var idx = allCells.indexOf(this);
sessionStorage.setItem(
"ig_last_cell_index",
idx
);
});
// Restore the last cell when the user returns to the tab or window
document.addEventListener("visibilitychange", function() {
if (document.hidden) {
// The cell was already stored by focusin
} else {
setTimeout(function() {
// Prefer the original DOM reference if it still exists
if (
lastFocusedCell &&
document.body.contains(lastFocusedCell)
) {
lastFocusedCell.focus();
lastFocusedCell.scrollIntoView({
block: "center"
});
return;
}
// Fallback to the stored index if the DOM changed
var idx = Number(
sessionStorage.getItem(
"ig_last_cell_index"
)
);
var allCells = document.querySelectorAll(
".a-GV-cell"
);
if (!isNaN(idx) && allCells[idx]) {
allCells[idx].focus();
allCells[idx].scrollIntoView({
block: "center"
});
return;
}
}, 200);
}
});
The code has two restore paths:
- Direct DOM reference, used when the original grid cell still exists.
- Stored cell index, used when the original element is no longer available.
Limitations of the index fallback
The index stored in sessionStorage is based on the current order of all .a-GV-cell elements in the page. That means it is not a permanent identity for a particular row and column.
If the grid is sorted, filtered, refreshed or rendered with a different set of rows, the same numeric index can point to a different cell. The direct DOM reference avoids that problem while the original element remains in the document, but the fallback itself is positional.
For a page where the grid structure can change significantly while the user is away, I would improve the fallback by storing a stable row identifier together with a column identifier instead of a global cell index. The current version is simpler and matches the implementation shown here.