UI Integration
How an App and the ERP talk to each other inside the iframe — postMessage protocol, dialogs, height, widgets.
Your App runs in an iframe inside the ERP. The manifest declares where it appears; this page describes how the two sides talk once it is there.
Everything goes through postMessage, wrapped in two helpers:
import { sendMain, receiveMain } from '@vario-software/vario-app-framework-frontend/script/communication.js';
sendMain({ height: 640 });
const stop = receiveMain({
updateComponents: () => reload(),
route: route => console.log(route.name),
});sendMain automatically adds your appIdentifier and integrationId to every message — the ERP uses both to route it back to the right frame.
Because those two keys are added first and your payload is spread after them, a payload key called appIdentifier would overwrite the routing information. Don't reuse those names.
receiveMain calls your handler for every key present in an incoming message, and it checks nothing about the sender. Treat inbound data as untrusted.
The ERP must hear from you within 15 seconds
The host waits for a first valid message as a loaded-handshake. If none arrives within 15 seconds, your App is replaced by an "app not available" message.
The designated signal for this is:
sendMain({ ready: true });The framework sends it for you from secureIntegration(), which runs on DOMContentLoaded. That function does two things: it verifies your page is really embedded in a VARIO Cloud frame (by checking the referrer against the *.vario.cloud hosts and localhost), and then reports readiness.
Send it yourself rather than relying on the framework. In the current published version secureIntegration() throws before it reaches the ready message (see the warning at the end of this page), so nothing is sent. Apps still work because their own first message satisfies the handshake — but if your App renders slowly and sends nothing early, it will be marked unavailable.
During installation the deadline is harsher. The install dialog rolls the installation back after 120 seconds. Send installationComplete when done, or keep sending uninstallAlive while you work — otherwise your App is uninstalled again.
Sizing the frame
An iframe cannot grow with its content. There is no height: auto that means "as tall as the page inside" — the browser gives the frame a fixed box, and anything taller simply scrolls inside it. The result is the thing users complain about: a second scrollbar within the ERP page, and your content cut off at an arbitrary line.
The ERP cannot fix this on its own either. Your App is served from your own domain, so the host is not allowed to measure the document inside the frame. Only you know how tall your content is.
Hence the contract: you measure, the host applies.
Send it once the content is rendered, and again whenever it changes — after loading data, expanding a section, opening a form. The framework can do this continuously for you via initHeightTransfer(), which watches the body and re-sends on every change.
That creates a feedback loop: you send a height → the host resizes the iframe → your layout changes → you send again. It is only damped by an "unchanged value" check, so content whose layout depends on the frame height can oscillate.
The robust alternative is to let the host own the height:
The host then computes the height from the viewport and sends it to you. Note the type flip: outbound height is a number of pixels, inbound height is a CSS string such as "650px" or "calc(100vh - 259px)".
Once the host owns the height — in full-height mode, in a dialog, or in a dashboard widget — your own height messages are ignored. Inside a dialog use sendMain({ dialog: { fullHeight: true } }) instead.
Dialogs
Your App can open another of its own integrations as a modal, and get a value back.
Open it (the integrationId must be declared in your manifest, typically without a pointOfIntegration):
Close it and return a value — this is what the dialog's own code sends:
Receive the result in the opener:
Inside a dialog you can also send dialog.disablePadding and stickynavButtons (rendered into the dialog header).
Using the ERP's own dialogs
You don't have to build pickers. Ask the host to open its own and send you the result:
searchDialog
searchDialog-<key> — { selected }
datepicker
datepicker-<key> — JSON string of { date, to }
cron
cron-<key> — JSON string of { value }
colorpicker
colorpicker-<key> — JSON string of { value }
confirmation
confimation — { key, values }
Each accepts an optional key so you can tell several instances apart; without it the reply key ends in -result.
Two things will cost you an afternoon:
confimationis misspelled — oneris missing, on both sides. Listening forconfirmationreceives nothing.datepicker,cronandcolorpickerresults arrive as JSON strings and must be parsed.searchDialogonly stringifiesselectedwhen multiple selection is enabled.dialogResultis passed through raw.
Cancelling a picker sends nothing — only confirmation reports a cancel, as { key: false }.
Integrating into the ERP shell
ready
Confirms your integration has loaded — the handshake described above
updateComponents
Tells the ERP to refresh its datagrids and badges
notify
Shows an ERP toast; route adds an open-in-new-tab action
badge
Puts a badge on your tab
backlink
Custom back link; clicking it sends you back
stickynavButtons
Buttons in the sticky nav; a click sends you button-<key>
stickynavTabs
Sub-tabs bound to the route; the host rewrites the URL rather than reloading you
editmode
Register, enter or leave the ERP's edit mode; you then receive edit, save, cancel
documentTitle
Sets the browser tab title
copyToClipboard
Copies text and shows a confirmation
customActivity
Resets the idle timer, preventing auto-logout during long interactions
minWidth
Minimum width for your frame
openInNewTab / openInSameTab
Navigate the ERP (see below)
updateSettings
Change a shared setting — only the level-of-detail and UI-mode keys are accepted
Some of these are vetoed by the host depending on where you are embedded: stickynavButtons, backlink and editmode are ignored inside dialogs and widgets.
Nothing is queued. A message you send before the host has attached its listener is lost, and a message the host sends before your frame exists is dropped. Send state-establishing messages (buttons, tabs, badge, backlink) on DOMContentLoaded or later, and re-send them after you receive routeChanged.
What the host sends you
height
New frame height (CSS string) in full-height mode
updateComponents
Something changed in the ERP; refresh
changeLanguageCode
The user switched language
route + routeChanged
The ERP route changed
submit
An ERP form is submitting — see below
dialogOk / dialogResult
Your dialog resolved
back
Your custom backlink was clicked
appToken
A refreshed App token
edit / save / cancel
Edit-mode actions
button-<key>
One of your sticky-nav buttons was clicked
beforeUnmount
Your integration is being torn down
Answering a submit
When the ERP submits a form that contains your integration, it asks you to confirm:
correlationId must be sent back as a sibling key, not nested inside an object — otherwise the ERP cannot match your answer. And if you don't answer at all, the ERP proceeds anyway after its timeout.
Navigating the ERP
The target is a standard vue-router location, so name must be a route the ERP actually has.
openInSameTab navigates the ERP window itself and is deliberately restricted — a string target is only accepted for VARIO Cloud hosts.
Parameters you receive
The host puts context on your integration URL. The framework exposes the common ones:
appIdentifier, integrationId, language, supportMode, myCompanyId, uimode, detailDatagridTable, superUser, permissions, additionalPayload
Also on the URL but not exposed by the framework — read them from the query string yourself: userId, detailDatagrid, routeName, and every parameter of the current ERP route.
All values are strings. superUser must be compared to 'true'; using it as a boolean makes every user a super user.
additionalPayload is a JSON string when the host passes an object — parse it.
On installation and uninstallation pages the parameter set is smaller: there is no integrationId, no superUser and no permissions. Calling a permission check there fails, so guard it.
Dashboard widgets
A widget is an integration mounted at one of the dashboard.* integration points. Its title and icon come from the manifest; the grid fixes the minimum size.
Settings are exchanged, not stored by you. Announce your defaults, and the host sends back the user's saved settings merged over them:
When the user changes something, report it and the dashboard persists it:
Two details:
The inbound edit-mode key is spelled
widgeEditmode— missing at. Use it exactly as written.If you send
widget.initwithout any defaults, the host concludes your widget has no settings and shows no settings affordance at all.
To follow the ERP's own edit mode, report your state with sendMain({ widget: { editmode: { active } } }).
Reusing the ERP's look
The framework ships CSS only — no JavaScript components. Link the stylesheet and write plain markup with v-* classes:
Controls rely on structure. A checkbox needs a wrapper, and the input must come immediately before its label, because the checked state is expressed with an adjacent-sibling selector:
Put anything between the input and the label and nothing renders. v-toggle has the same structure for switches.
Dark mode and responsive breakpoints are applied by initSharedSettings() and initScreenSize(), which set classes on body.
Call those initialisers explicitly. In the current published framework, secureIntegration() throws before it can start them, which silently costs you dark mode, responsive classes, height transfer and token refresh. Call initHeightTransfer(), initAppTokenHandling(), initSharedSettings() and initScreenSize() yourself rather than relying on the bootstrap.
Also: initStickynav() waits for a reply the ERP never sends. Do not await it.
Related
App Manifest — declaring where your integration appears
App Authentication — the App token you receive
Last updated
Was this helpful?