Repository navigation
Display Order Developer Guide
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.
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
-
Architecture
- β³ π§Ή Root folder tidy (URL routing)
- β³ π Database Integrity (FKs & constraints)
- β³ π οΈ Database Verification β Developer Guide
- β³ π§© Service Layer (UI + API share code)
- β³ πͺ€ Code traps (the TRAP: comments)
- β³ π Commenting for the Engineering wiki
- β³ π οΈ Engineering wiki scanner - Developer Guide
- β³ π Service-Layer progress
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- π± Currencies
- π§ The sidebar (optional module rail)
- π Display order (modules, Watchtower cards) β Developer Guide
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π Mobile: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: Projects
- β³ ποΈ Mobile: Files
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π REST API: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ π§βπΌ REST API: Analysts
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π§Ύ Custom ticket fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ π Attachment size limits and large emails
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
- π Projects
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Leases
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π·οΈ QR asset labels
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Domains
- ποΈ Files
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ π’ One board across every company
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)
- What this is
-
π Bugs resolved
- β³ π Repeating tasks ignored company scope
- β³ π¬ #183 Mailbox checks overlapped
- β³ π #188 Transfer lock shown off when unknown
- β³ β #184 Subtask names and Overdue
- β³ π§© #186 Teams: the buttons did nothing
- β³ π Text in a change could run script
- β³ π AI settings changed without the AI permission
- β³ πͺ #178 Deleting an analyst failed
- β³ π« A team with no departments showed 0 tickets
- β³ π Projects: adding a phase did nothing on Save
- β³ π Leavers could still sign in
- β³ πΌοΈ Logo and courses broke on Apache with PHP-FPM
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96