Routing and Request Flow
This page describes how HTTP requests travel from the browser to the server and back, including URL routing, the two client-side communication channels (HTMX-managed and JavaScript fetch), and timeout/error handling.
Overview
Kittox is a single-page application. The browser loads one full HTML page at startup (_Page.html template), and all subsequent interactions happen via partial HTML exchanges — the server returns HTML fragments that replace portions of the DOM.
There are two mechanisms for these exchanges:
| Channel | Used by | Response type | Timeout managed by |
|---|---|---|---|
HTMX (hx-get, hx-post) | Pagination, filters, navigation, view loading | HTML fragment swapped into a target element | htmx.config.timeout |
JavaScript fetch() | Forms, lookups, tools, charts, calendar | HTML overlay appended to <body>, or JSON, or binary download | AbortController via kxFetchWithTimeout wrapper |
Both channels share the same URL namespace (kx/...) and the same server-side handler; the difference is entirely on the client side.
URL Structure
Every Kittox request follows this pattern:
{AppPath}/kx/{resource}/{ViewName}[/{operation}][?params]| Segment | Example | Description |
|---|---|---|
AppPath | /taskitto | Application base path, set in Config.yaml → Server/Port and executable name |
kx | Fixed namespace prefix for all Kittox API calls | |
resource | view, login | Resource type |
ViewName | ActivityInput | Name of the YAML view definition |
operation | data, form, save, delete, tool/ExportExcel | Action to perform on the view |
Route Table
Kittox uses two routing layers, tried in order:
- Attribute-based routing (
TKXRoutingRoute) — handlers decorated with[TKXPath]attributes, discovered via Delphi RTTI at startup. See Attribute-Based Routing for details. - Legacy routing (
TKWebApplication.DoHandleRequest) — if/else chain for handlers not yet migrated to attribute-based routing.
The attribute-based layer is checked first. If no match is found, the request falls through to the legacy layer. This design, inspired by MARS / WiRL, enables progressive migration and custom URL handlers from application code.
Attribute-routed handlers (Enterprise)
| URL Pattern | HTTP | Handler class | Module |
|---|---|---|---|
kx/view/{V}/chart-data | GET | TKXChartHandler | Enterprise |
kx/view/{V}/calendar-data | GET | TKXCalendarHandler | Enterprise |
kx/view/{V}/map-data | GET | TKXMapHandler | Enterprise |
Legacy handlers (Core)
| URL Pattern | HTTP | Handler | Description |
|---|---|---|---|
kx/login | POST | HandleKXLoginRequest | Authenticate user |
kx/resetpassword | POST | HandleKXResetPasswordRequest | Password reset |
kx/changepassword | POST | HandleKXChangePasswordRequest | Change password |
kx/view/{V}/delete | POST | HandleKXDeleteRequest | Delete a record |
kx/view/{V}/tool/{T} | POST | HandleKXToolRequest | Execute a tool (export, action, upload) |
kx/view/{V}/lookup | GET | HandleKXLookupRequest | Open lookup selection dialog |
kx/view/{V}/blob/{F} | GET | HandleKXBlobRequest | Serve blob field content (image, file) |
kx/view/{V}/form | GET | HandleKXFormRequest | Open form dialog (edit, add, view, dup) |
kx/view/{V}/save | POST | HandleKXSaveRequest | Save form data |
kx/view/{V}/data | GET/POST | HandleKXDataRequest | Load list/grid rows + pager |
kx/view/{V} | GET | HandleKXViewRequest | Render a complete view panel |
Route ordering
Attribute-routed handlers are automatically sorted by specificity (more literal URL segments = higher priority). In the legacy layer, more specific patterns (e.g. /delete, /tool/{T}) are checked before the general /data route.
HTMX-Managed Requests
The majority of user interactions — pagination, sorting, filtering, navigating between views — are handled by HTMX attributes declared directly in the HTML generated by the server:
<!-- Pagination: generated by TKXListPanelController -->
<button hx-get="kx/view/Activities/data?start=50&limit=50"
hx-target="#kx-list-body-Activities"
hx-swap="innerHTML"
hx-include="[name^='f_']"
hx-indicator="#kx-loading">
Next
</button>When the user clicks this button, HTMX:
- Sends a GET request to
kx/view/Activities/data - Shows the
#kx-loadingspinner overlay - Receives an HTML fragment from the server
- Swaps it into the
#kx-list-body-Activitiestarget element - Hides the loading spinner
The server responds with HTML fragments only — never a full page. HTMX handles the DOM replacement transparently. Common HTMX attributes used in Kittox:
| Attribute | Purpose |
|---|---|
hx-get / hx-post | URL to call |
hx-target | DOM element to update with the response |
hx-swap | How to insert the response (innerHTML, outerHTML) |
hx-include | Additional form fields to include in the request |
hx-indicator | Element to show/hide as loading indicator |
hx-vals | Extra values to send as JSON |
hx-headers | Extra HTTP headers (Kittox sends X-KittoX: true) |
JavaScript fetch() Requests
Some operations cannot use the HTMX swap model and require explicit JavaScript. These use the fetch() API (wrapped by kxFetchWithTimeout for timeout handling). The reasons fall into five categories:
1. Modal overlays appended to <body>
Affected: Open form (/form), open lookup (/lookup)
The server returns a complete dialog overlay (backdrop + dialog HTML). This must be appended to document.body as a new element, not swapped into an existing target container. HTMX's model requires a pre-existing target element, which doesn't exist for dynamically created modals.
// kxgrid.js — openForm
kxFetchWithTimeout(url, { headers: { 'X-KittoX': 'true' } })
.then(function(r) { return r.text(); })
.then(function(html) {
var div = document.createElement('div');
div.innerHTML = html;
if (div.firstElementChild)
document.body.appendChild(div.firstElementChild); // append, not swap
});2. JSON responses (not HTML)
Affected: Chart data (/chart-data), calendar events (/calendar-data)
These endpoints return JSON, not HTML. The client passes the data to Chart.js or the calendar widget programmatically. HTMX is designed for HTML-in, HTML-out exchanges.
// kxgrid.js — kxChart.refresh
kxFetchWithTimeout('kx/view/' + viewName + '/chart-data', { ... })
.then(function(r) { return r.json(); })
.then(function(data) {
chart.data.labels = data.labels;
chart.data.datasets[0].data = data.data;
chart.update(); // Chart.js animated update
});3. Binary downloads
Affected: Tool execution (/tool/{T}) for download-type tools (Excel export, PDF, etc.)
The client must inspect the Content-Disposition response header to determine if the response is a file download, then create a temporary blob URL and trigger the browser's save dialog. HTMX has no mechanism for binary download handling or response header inspection.
4. Inline script execution
Affected: Save (/save)
The save response may contain HTML with <script> tags (e.g., close-form commands, refresh triggers). HTMX's innerHTML swap does not execute inline scripts. The save handler manually extracts and executes them:
div.querySelectorAll('script').forEach(function(script) {
var s = document.createElement('script');
s.textContent = script.textContent;
document.head.appendChild(s);
document.head.removeChild(s);
});5. Post-response JavaScript logic
Affected: Lookup (/lookup), calendar delete (/delete)
After inserting the response HTML, these operations need additional JavaScript steps: attaching event handlers (e.g., double-click for quick lookup selection), calling htmx.process() to activate HTMX attributes in the new content, updating toolbar button state, or triggering a calendar refresh. These multi-step workflows go beyond what a simple HTMX swap can express.
Summary: Which Channel Handles What
| Operation | Channel | Why |
|---|---|---|
| Load list rows / pagination | HTMX | Standard swap into #kx-list-body-{view} |
| Apply filters | HTMX | Standard swap with hx-include for filter values |
| Sort columns | HTMX | Standard swap with sort params |
| Navigate views / menu | HTMX | Standard swap into content area |
| Open edit/add form | fetch | Modal overlay appended to body |
| Save form | fetch | Response contains executable scripts |
| Delete record | HTMX | Standard swap (refreshes grid) |
| Open lookup dialog | fetch | Modal overlay + post-insert JS logic |
| Execute tool | fetch | May return binary download or needs header inspection |
| Load chart data | fetch | JSON response for Chart.js |
| Load calendar data | fetch | JSON response for calendar widget |
| Calendar delete | fetch | Post-response state update + calendar refresh |
| Login / logout | HTMX | Standard form submission |
Timeout and Error Handling
Both communication channels are protected by a configurable timeout. The timeout value is set in Config.yaml:
# Timeout in milliseconds (default: 100000 = 100 seconds)
AjaxTimeout: 100000HTMX timeout
The _Page.html template configures HTMX at page load:
htmx.config.timeout = {{ajaxTimeout}}; // injected from Config.yamlWhen an HTMX request exceeds the timeout, the htmx:timeout event fires. Kittox listens for this event (and also htmx:sendError for network failures) and shows an error dialog:
document.body.addEventListener("htmx:timeout", function(evt) {
var rc = evt.detail.requestConfig;
kxGrid.showConfirm('Error', 'Server is not responding',
'Retry', // re-issues the same HTMX call
'Reset', // reloads the page
function() { htmx.ajax(rc.verb, rc.path, {target: rc.target, ...}); },
function() { window.location.reload(); }
);
});fetch() timeout
All fetch() calls go through the kxFetchWithTimeout wrapper, which uses the standard AbortController API with the same timeout value:
function kxFetchWithTimeout(url, options) {
var timeout = htmx.config.timeout || 100000;
var controller = new AbortController();
var timeoutId = setTimeout(function() { controller.abort(); }, timeout);
return fetch(url, Object.assign({}, options, { signal: controller.signal }))
.then(function(response) {
clearTimeout(timeoutId);
return response;
})
.catch(function(err) {
clearTimeout(timeoutId);
if (err.name === 'AbortError') {
// Show Retry / Reset dialog
// Retry re-calls kxFetchWithTimeout with the same arguments
// and feeds the result back into the original promise chain
}
throw err;
});
}The Retry button re-issues the exact same request transparently — the original .then() chain continues as if the first attempt had succeeded. The Reset button reloads the page entirely.
Error dialog
Both channels show the same modal error dialog (kxGrid.showConfirm in error mode):
| Button | Action |
|---|---|
| Retry | Re-issues the exact same request (HTMX or fetch) |
| Reset | Calls window.location.reload() to return to the initial page |
Session Lost (Server Restart)
When the server is restarted, all in-memory sessions are lost. If the browser still has a session cookie from the previous server instance, the server detects this situation and shows a fatal error dialog.
How it works
- The client sends a request with the old session cookie
TKWebEngine.EnsureSessioncallsFindOrCreateSession— an atomic operation that finds or creates a session under a single lock- If no existing session matches the cookie, a new session is created and flagged as
IsSessionLost = True TKWebApplication.DoHandleRequestchecks the flag before routing:- API requests (
kx/...): raises an exception"Session lost or expired, please restart!"→ the standard error handler shows a fatal error dialog - Home/page requests (F5, page load): pass through normally — the login page is displayed
- API requests (
Atomic session creation
When the user presses F5, the browser sends multiple parallel requests (the page itself plus static resources like CSS and images). Without protection, each request could create a separate session, leading to race conditions and crashes.
TKWebSessions.FindOrCreateSession solves this by performing the find-or-create under a single MonitorEnter lock:
- Lock the session list
- Look up the session by cookie ID (or by client address as fallback)
- If found, return it; if not, create and add a new session atomically
- Unlock
This guarantees that all parallel requests from the same browser are routed to the same session, even when they arrive simultaneously after a restart or F5.
Page refresh (F5) handling
When the user presses F5 on an active session, EnsureSession detects this via IsPageRefresh (a non-Ajax request to the application root). In this case:
- The existing session's authentication state is preserved (user name, auth data, language)
- The old session is removed
- A new session is created with the same session ID (so that in-flight parallel requests still find it)
- The preserved auth state is transferred to the new session
This allows F5 to act as a session reset without losing the login, and without creating duplicate sessions from the parallel resource requests that accompany the page reload.
Fatal error dialog
When any fatal error occurs (including session lost), the error dialog behaves differently from non-fatal errors:
| Fatal error | Non-fatal error | |
|---|---|---|
| OK button | window.location.reload() — returns to login | Closes the dialog |
| Overlay click | Disabled (must click OK) | Closes the dialog |
This is correct for all fatal errors because SignalFatalError destroys the session after the response — the user must reload to get a new session.
No client-side changes needed
Both communication channels handle the error dialog HTML natively:
- HTMX: the
HX-Retarget: bodyandHX-Reswap: beforeendresponse headers tell HTMX to append the dialog overlay to the body - fetch: callers (
openForm,save, tools, etc.) already read the response as HTML text and append it todocument.body
Request Processing on the Server
Browser request
--> TKWebEngine.HandleRequest()
|-- Session lookup/creation (cookie-based)
|-- TKWebApplication.DoHandleRequest()
|-- URL pattern matching (route table above)
|-- Handler function executes:
| 1. Finds the View by name in the metadata catalog
| 2. Creates/loads data store from the database
| 3. Creates/invokes controller (TKXListPanelController, TKXFormPanelController, etc.)
| 4. Controller renders HTML fragment (or JSON, or binary stream)
|-- TKWebResponse sends the result
<-- HTTP response (HTML fragment / JSON / binary)Key parameter encoding
Record keys are encoded as URL query strings: FieldName1=Value1&FieldName2=Value2.
Example: key=ID%3D123%26Code%3DABC decodes to ID=123&Code=ABC.
Only fields marked as IsKey: True in the model are accepted, to prevent injection attacks.
The X-Kittox header
All Kittox requests (both HTMX and fetch) include the X-KittoX: true HTTP header. This allows the server to distinguish Kittox API calls from regular page loads and static resource requests. In the _Page.html template, HTMX is configured globally:
<body hx-headers='{"X-KittoX": "true"}'>JavaScript fetch() calls include it explicitly in the headers option.
