Display Order Developer Guide - edmozley/freeitsm GitHub Wiki
How FreeITSM puts a saved order on top of a list that keeps growing - the personal module order and the shared Watchtower card order - and the reorder control both use. Introduced for issue #187.
User-facing: Watchtower Settings â Cards and Preferences â Display â Module order (see System).
| You want to | Use |
|---|---|
| Apply a saved order to today's list |
displayOrderMerge($saved, $defaults) - includes/display_order.php
|
| Read a stored order |
displayOrderParse($raw) - a JSON array of keys, anything else = no order |
| The modules this analyst may open, in their order | analystVisibleModules($modules) |
| The Watchtower cards in the install's order |
wtCardOrder($conn) - includes/watchtower_settings.php
|
| A "put these in order" control |
ReorderList.mount(el, {items, onChange, label}) - assets/js/reorder-list.js
|
displayOrderMerge():
- the saved keys that still exist, in the saved order;
- then every key the saved order does not mention, in its default order.
What that gives you, without any migration ever:
- A new module or card (added in a later release) appears for everybody who has saved an order, in its default place relative to the other keys they have not ordered.
- A removed one drops out of the saved order silently.
- Junk - unknown keys, duplicates, non-strings, a hand-edited row - degrades to the default order instead of losing anything.
It is the PHP twin of applyColumnConfig() in assets/js/data-table.js, which has kept saved table views working across new columns.
Ordering always happens over every key, before anything is hidden. Two consequences:
- Order never grants anything. Every launcher filters exactly as it did before #187.
- A hidden entry keeps its place. A module the analyst cannot open today, or a card switched off or unavailable, sits in its saved slot and reappears there when it comes back.
The Preferences screen relies on this: the list shows only the modules the analyst can open, and on save the new visible order is written back into the slots the visible modules already held (withHidden() in system/preferences/index.php), so the others never move.
| Stored |
user_preferences.module_order - a JSON array of module keys. '' or no row = the default order |
| Default order | the order of $modules in includes/waffle-menu.php
|
| Read |
analystModuleOrderSaved(), memoised per request |
| Written | Preferences â Display â Module order, via api/system/set_user_preference.php on every move; Reset writes ''
|
analystVisibleModules($modules) returns the modules this analyst may open, ordered, with the existing filter applied once:
- System only for administrators;
- everything else only if it is in
$_SESSION['allowed_modules'](null= all); - a Forms approver without Forms gets its approvals inbox (
'approvals' => trueon that entry).
Four launchers draw from it, so they cannot disagree:
| Launcher | Where |
|---|---|
| The waffle menu | renderWaffleMenuPanel() |
| Ctrl+K "Go to" |
renderWaffleMenuJS() â window.CP_MODULES - so Ctrl+K then Enter opens the analyst's first module |
| The home page |
index.php - one loop since #187; it was 27 hand-written cards |
| The Files start menu | files/index.php |
The home page no longer needs editing: add the module to $modules (in its default place) and it appears in all four launchers, and in every saved order, on its own. The other steps are in Module Access - Developer Guide.
| Stored |
system_settings.watchtower_card_order - a JSON array of card keys. Only stored when it differs from the default: an order put back by hand, or Reset, deletes the row, so a card added later takes its intended place |
| Default order | the order of wtCardRegistry()
|
| Read | wtCardOrder($conn) |
| Written |
api/watchtower/save_settings.php, key card_order (an array, or null to reset), with the Cards tab's permission Cap::WATCHTOWER_CARDS
|
wtCardRegistry() is now the one card list - key => [element, module]:
-
wtCardKeys()is derived from it; - the dashboard's
WT_CARD_ELEMENTSis printed from it; - the settings screen's
CARD_MODULE(each card's colour) is printed from it - the hand-kept copy had already missed Projects; -
tests/display-order.phpchecks every entry has its element on the dashboard and a module colour, and that every card on the dashboard is in the list.
The dashboard moves the cards, it does not restyle them: a small script right after the grid appends each card element to the grid in the saved order, before the page first paints. CSS order would have put the keyboard and a screen reader out of step with what is on screen. Hidden and unavailable cards are moved too, so they keep their place. It runs once - the five-minute refresh fills cards in and never redraws the grid.
Refusing, not ignoring. save_settings.php used to skip silently any section the analyst had no permission for and still answer "saved". It now answers 403 when a section the analyst may not change is sent (cards with entries or card_order without the Cards permission; items or paused_hours without the Counts permission). The settings page only sends the sections whose tabs it draws, so an honest page never trips it.
assets/js/reorder-list.js - one control for every "choose the order" setting.
const rl = ReorderList.mount(containerEl, {
items: [{ key: 'tickets', label: 'Tickets', icon: '<svgâŚ>', iconClass: 'waffle-module-icon tickets' }, âŚ],
label: 'Module order', // the list's accessible name
onChange: keys => save(keys), // after every completed move
});
rl.getOrder(); rl.setOrder(keys); // setOrder does not call onChangeAn item can carry html - its own row markup, escaped by the caller - in place of the label (Watchtower puts its show/hide tick box there). label stays its accessible name.
Three ways to move, because each fails somewhere the others work:
| Notes | |
|---|---|
| Drag by the grip | Native HTML5 drag-and-drop. Only the grip is draggable, with touch-action:none, so on a phone a long press on the grip starts it and a swipe anywhere else still scrolls (Mobile-Friendly Techniques §22). Cannot be checked in a headless browser |
| â / â buttons | Work everywhere, including where drag does not. tabindex=-1 so they do not double the tab stops |
| Alt+â / Alt+â | On a focused row. Plain â/â walks between rows. Focus stays on the item that moved, and the new position is announced through an aria-live region |
The list is real DOM order, so reading order and tab order match the screen. Styles are injected once per page from theme tokens, so dark mode follows. Labels come from common.reorder.*, with English fallbacks.
Saving is the caller's job. Preferences saves on every move, one request at a time, and puts the list back to the last saved order if a save fails (savePref() shows the error). Watchtower settings saves with its Save button, like everything else on that screen.
tests/display-order.php - no database:
- the merge rule (new keys, removed keys, duplicates, junk) and the parser;
- the Watchtower card list against the dashboard markup, the module colours and the labels, with a positive control;
- that the home page, the waffle (drawer and Ctrl+K) and the Files start menu all draw from
analystVisibleModules().
php tests/display-order.php
The screens were verified in a real browser: module order saved, followed by all four launchers, kept the hidden modules' slots for an analyst with three modules, and Reset; card order saved, shown on the dashboard, Reset, and refused (403) for an analyst without the permission.