Onboard.qs runs on two distinct Qlik platforms — client-managed (Qlik Sense Enterprise on Windows) and Qlik Cloud. The platform layer provides a unified interface while keeping platform-specific code strictly separated.
- Separate code paths — Cloud and client-managed are standalone modules. Cloud does NOT delegate to client-managed. Either platform can change independently without breaking the other.
- Version-aware client-managed — The client-managed adapter detects the running Sense version and maps it to a "code path" name, enabling version-specific selector overrides.
- Single-file bundle — Both adapters are statically imported (no dynamic
import()) so the Rollup UMD bundle remains one file. Tree-shaking is not a concern since both adapters are small.
flowchart TD
A[Extension loaded] --> B{URL matches<br/>qlikcloud.com?}
B -- Yes --> C[Platform = cloud]
B -- No --> D[Platform = client-managed]
C --> E[codePath = 'default'<br/>version = null]
D --> F[Fetch /resources/autogenerated/<br/>product-info.js]
F -- Success --> G[Parse AMD wrapper<br/>Extract composition.version]
G --> H[resolveCodePath(version)]
H --> I[Match version against<br/>versionRanges array]
I -- Match found --> J[codePath = matched range name]
I -- No match --> K[codePath = 'default']
F -- Fetch failed --> K
E --> L[Return platform info]
J --> L
K --> L
style C fill:#4a9eda,color:#fff
style D fill:#e8a838,color:#fff
Simple URL regex check. Used when only the platform type is needed (no version info).
qlikcloud.com → 'cloud'
.qlik.com/sense → 'cloud'
everything else → 'client-managed'
Full detection including version. Called once at startup, result is cached in platformRef.
Returns: { type, version, codePath }
| Platform | type |
version |
codePath |
|---|---|---|---|
| Cloud | 'cloud' |
null |
'default' |
| Client-managed | 'client-managed' |
e.g. '14.254.6' |
e.g. 'default' |
Returns the statically-imported adapter module matching the current platform URL.
sequenceDiagram
participant CM as client-managed.js
participant FS as Sense Web Server
participant Cache as Module-level cache
CM->>Cache: Check cachedVersionInfo
alt Already cached
Cache-->>CM: { version, releaseLabel }
else Not cached
CM->>FS: fetch('/resources/autogenerated/product-info.js')
FS-->>CM: AMD module text
Note over CM: Strip define([], function() { return ... })
Note over CM: JSON.parse the inner object
CM->>CM: Extract composition.version<br/>e.g. "14.254.6"
CM->>CM: Extract composition.releaseLabel<br/>e.g. "February 2024 Patch 2"
CM->>Cache: Cache result
Cache-->>CM: { version, releaseLabel }
end
The /resources/autogenerated/product-info.js file is an AMD module present in all Qlik Sense Enterprise on Windows installations. Its structure:
define([], function () {
return {
composition: {
version: '14.254.6',
releaseLabel: 'February 2024 Patch 2',
// ... other fields
},
};
});The extension strips the AMD wrapper with a regex, parses the JSON payload, and caches the result for the session.
Code paths allow the extension to use different CSS selectors for different Sense versions. This is necessary because Qlik periodically changes its DOM structure.
flowchart LR
V[Sense version<br/>e.g. "14.254.6"] --> R[resolveCodePath]
R --> VR[versionRanges array]
VR --> |"v >= min && v <= max"| CP[Code path name]
VR --> |No range matched| DF["'default'"]
CP --> S[selectors.js<br/>getSelectors(platform, codePath)]
DF --> S
const versionRanges = [
// { minVersion: '15.0.0', maxVersion: '99.999.999', codePath: 'future' },
];- Ranges are evaluated top-to-bottom; first match wins.
- Comparison is simple semver:
major.minor.patchnumeric. - If no range matches (or version is
null),'default'is returned. - Currently the array is empty — all versions use
'default'. Add entries when Qlik ships a breaking DOM change.
Both client-managed.js and cloud.js export the same functions:
| Function | Signature | Description |
|---|---|---|
getCurrentSheetId() |
() → string | null |
Get the active sheet ID |
getSheetObjects(app) |
(app) → Promise<Array> |
List objects on the current sheet via Engine API |
getObjectSelector(objectId, codePath?) |
(string, string?) → string |
Get CSS selector for a Qlik object |
isEditMode(options) |
(options) → boolean |
Check if in edit mode |
injectCSS(css, id) |
(string, string) → void |
Inject a <style> element into <head> |
Client-managed additionally exports:
getSenseVersion()— async, fetches and caches version inforesolveCodePath(version)— maps version to a code-path name
flowchart TD
A[getCurrentSheetId] --> B{URL has /sheet/ID?}
B -- Yes --> C[Return ID from URL]
B -- No --> D{window.qlik API exists?}
D -- Yes --> E[qlik.navigation.getCurrentSheetId]
E --> F{Got ID?}
F -- Yes --> G[Return ID]
F -- No --> H[DOM fallback]
D -- No --> H
H --> I{Sheet element found?}
I -- Yes --> J[Read data-id / data-qid / id attribute]
I -- No --> K[Return null]
Cloud only uses URL parsing (/sheet/ID pattern). There is no window.qlik global API in Cloud.
Both adapters use the same Engine API flow, maintained independently:
flowchart TD
A[getSheetObjects(app)] --> B[app.getAllInfos]
B --> C{Sheet ID known?}
C -- Yes --> D[app.getObject(sheetId)]
D --> E[sheetObj.getLayout]
E --> F[Extract cells[].name +<br/>qChildList.qItems[].qInfo.qId]
F --> G[Filter infos to sheet objects only]
C -- No --> H[Use all infos]
G --> I[Exclude system types<br/>sheet, story, appprops, etc.]
H --> I
I --> J{< 100 objects?}
J -- Yes --> K[Enrich titles via<br/>getObject + getLayout per obj]
J -- No --> L[Skip enrichment]
K --> M[Sort by title, return]
L --> M