Session log — Course deletion no longer reloads the admin
Session log — Course deletion no longer reloads the admin
What happened, in order
1. The brief
One sentence and a clear failure mode. The admin lists all courses in a table, often with a filter or a tab applied. Deleting a single row was breaking that context every time. The fix had to be small, behaviour-preserving for everything else, and live in the EP Courses plugin only.
2. What was happening before
The admin's delete handler did the simplest thing it could: send the AJAX delete request, and on success, reload the page.
ajax({ action: 'delete_course', id: $(this).data('id') }, function(res) {
if (res.success) window.location.reload();
});
Two lines, perfectly readable, and wrong. window.location.reload() drops every piece of in-page state — the active tab, the search box, the scroll position, any expanded row. The admin lands back at the default view, has to re-apply their filter, and re-find their place in the list. For a single delete in a quiet admin that is mildly annoying. For the kind of housekeeping pass where six or eight courses get tidied in a row, it stops being mild.
3. Why a full reload was the wrong tool
A reload solves a real problem: it guarantees the page reflects what is on the server. But the only thing that changed when a course was deleted is that one row went away. The rest of the page is still correct. Reloading to remove a single row is like burning the table to take the plate off it. The right move is to remove the row.
This is the same idiom used elsewhere in the EP plugin family. EP Comments removes a comment row in place. EP Newsletter removes a subscriber row in place. The course list was the outlier.
4. The new behaviour: in-place removal
The new handler looks like this:
var courseId = $(this).data('id');
var $row = $admin.find('tr[data-id="' + courseId + '"]');
ajax({ action: 'delete_course', id: courseId }, function(res) {
if (res && res.success) {
$row.fadeOut(150, function() { $(this).remove(); });
} else {
alert((res && res.message) || 'Delete failed.');
}
});
A few small things to notice. The row is found before the AJAX call, not after — if the delete succeeds, jQuery already has the right element to act on. The fade is a hundred and fifty milliseconds — long enough to register as motion, short enough not to feel slow. And after the fade, the row is removed from the DOM rather than just hidden, so the table stays honest about what is in it.
Everything else on the page — the active tab, the search box, the scroll position — is untouched. The admin stays exactly where they were.
5. A small alert when the server says no
The previous handler had a quiet failure mode: if the server returned success: false, the row would not be removed and the admin would be left wondering whether the delete had worked. The new handler shows an alert with the server's own message if there is one, falling back to a generic "Delete failed." otherwise. Not glamorous, but truthful, and immediately visible.
An inline notice would be friendlier. That is on the list for the next round of admin polish, alongside a confirmation step before the AJAX call so an accidental click is recoverable.
6. Cache-busting the JS so the fix lands
One small but important second change. Browsers happily cache JavaScript files for hours, and admins are precisely the people who hit the same admin URL several times a day. Without a cache-bust, an admin would still be running yesterday's broken handler tomorrow. So the JS asset URL now appends the file's modification time:
'url' => $this->plugin_url() . '/js/ep-courses.js?v=' . filemtime(__DIR__ . '/js/ep-courses.js'),
The query string is enough to make every browser treat the new file as a new resource. Future edits to the JS will tick the timestamp again and trigger a fresh fetch automatically. No manual cache-clearing, no "try a hard refresh" exchange in support tickets.
js/ep-courses.js.bak.20260501123933 and plugin.php.bak.20260501124506. Tested live, deletion is smooth.7. Going forward
Two small bits of follow-on work suggest themselves. A confirmation prompt before the AJAX call, so a fat-fingered click can be undone. And an inline toast in place of the alert() on failure, so the visual treatment of "deletion failed" matches the rest of the admin. Neither is urgent. The original bug, the one that prompted the session, is gone.
The wider lesson, written for the future reader of this log: full-page reloads are a heavy hammer. They are easy to reach for and hard to argue with, but they erase context, and context is most of the value of an admin screen. The next time a piece of admin code finishes with window.location.reload(), that is a flag to look again.