One of the missing links in AL is the ability of a subpage to “talk” to the parent page. In this video, I’m gonna show a hack we employed in a recent solution to this decade-old problem. Check it out:

In this video, Erik tackles a common but tricky problem in Business Central AL development: how to get a subpage (like the Sales Order lines) to communicate with its parent page (the Sales Order header) when the interaction isn’t a simple record update. The solution involves a creative use of JavaScript control add-ins that bridge the iframe boundaries between the two pages.
The Problem: Subpage-to-Parent Communication
In Business Central, when you have a page part (subpage) embedded in a parent page — like the Sales Order Subform inside the Sales Order — there’s no straightforward way for the subpage to notify the parent page that something happened. The classic scenario Erik encountered: simply selecting a different line on the subpage should trigger logic on the parent page.
This isn’t an update operation. The user isn’t modifying a record — they’re just navigating. The old tricks from the NAV days don’t apply in the cloud, and the UpdatePropagation property only helps when actual record updates occur. Using CurrPage.Update introduces its own problems, especially around record creation.
The Approach: JavaScript Control Add-in as a Communication Channel
Erik’s solution is to use a JavaScript control add-in placed on both the subpage and the parent page. The control add-in acts as a communication channel by leveraging the browser’s DOM — specifically, the fact that both pages live in separate iframes within the same parent window. The JavaScript in one iframe can reach into another iframe and invoke functions there.
How It Works at a High Level
- A control add-in called
InterPageCommunicationis defined with a procedure (PingParentPage) and an event (PingFromSubPage). - The control add-in is placed on both the Sales Order Subform (lines) and the Sales Order (header).
- When the user selects a different line on the subpage,
OnAfterGetCurrRecordfires and calls the control add-in’sPingParentPageprocedure. - The JavaScript implementation of
PingParentPagetraverses the parent window’s frames, finds the other iframe (not itself), and calls a function there that invokes the Business Central event on the parent page’s control add-in. - The parent page’s AL code handles the event and executes whatever logic is needed.
The Control Add-in Definition
The control add-in is intentionally minimal — it exists purely for communication, not for any visual purpose. It defines one procedure (called from AL on the subpage) and one event (handled in AL on the parent page):
controladdin InterPageCommunication
{
MaximumHeight = 1;
MaximumWidth = 1;
MinimumHeight = 1;
MinimumWidth = 1;
HorizontalShrink = true;
VerticalShrink = true;
VerticalStretch = true;
HorizontalStretch = true;
RequestedHeight = 1;
RequestedWidth = 1;
Scripts = 'interpagecommunication.js';
event PingFromSubPage(LineNo: Integer);
procedure PingParentPage(LineNo: Integer);
}
Erik experimented with various size properties to minimize the visual footprint of the control add-in. Setting RequestedHeight and RequestedWidth to 1, along with all the shrink properties, got it down to about 17 pixels. He noted it would be great to have a “code only” or “no visuals” setting for control add-ins, similar to how reports have “ProcessingOnly.”
The JavaScript: Bridging iframes
The real magic happens in the JavaScript file. The key insight is that Business Central renders each control add-in inside its own iframe, but all iframes share the same parent window. By traversing window.parent.frames, you can find the other control add-in’s iframe and invoke functions within its context.
///
function PingParentPage(lineno) {
for (var i = 0; i < window.parent.frames.length; i++){
if (window.frameElement != window.parent.frames[i].frameElement)
if (window.parent.frames[i].frameElement.contentWindow.PingParentPage != null)
window.parent.frames[i].frameElement.contentWindow.RealPing(lineno);
}
}
function RealPing(lineno)
{
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('PingFromSubPage',[lineno]);
}
Step-by-Step Breakdown
PingParentPage(lineno)is called from the subpage's control add-in when a line is selected.- It loops through all frames in the parent window (
window.parent.frames). - It checks that the current frame (
window.frameElement) is not the same as the frame being examined — this prevents the add-in from finding itself. - It checks whether the other frame's
contentWindowhas aPingParentPagefunction — this confirms it's another instance of the same control add-in (the one on the parent page). - If found, it calls
RealPing(lineno)in the context of that other iframe's window. RealPing(lineno)callsMicrosoft.Dynamics.NAV.InvokeExtensibilityMethod, which triggers thePingFromSubPageevent in Business Central — but because it's running in the parent page's iframe context, the event fires on the parent page's control add-in.
The AL Code: Subpage (Lines)
The subpage extension is straightforward. The control add-in is added to the layout, and OnAfterGetCurrRecord calls the JavaScript procedure with the current line number:
pageextension 50100 Lines extends "Sales Order Subform"
{
layout
{
addlast(content)
{
usercontrol(Comm; InterPageCommunication)
{
ApplicationArea = all;
}
}
}
trigger OnAfterGetCurrRecord()
begin
CurrPage.Comm.PingParentPage(Rec."Line No.");
end;
}
The AL Code: Parent Page (Header)
The parent page extension also has the control add-in, but this time it handles the PingFromSubPage event. A CurrentLineNo variable is used to avoid redundant processing — OnAfterGetCurrRecord can be "trigger-happy" and fire more often than expected:
pageextension 50101 Header extends "Sales Order"
{
layout
{
addlast(content)
{
usercontrol(Comm; InterPageCommunication)
{
ApplicationArea = all;
trigger PingFromSubPage(LineNo: Integer)
begin
if LineNo <> CurrentLineNo then begin
message('Hello from subpage! %1', LineNo);
CurrentLineNo := LineNo;
end;
end;
}
}
}
var
CurrentLineNo: integer;
}
The if LineNo <> CurrentLineNo check ensures the parent page logic only runs when the user actually navigates to a different line, not every time OnAfterGetCurrRecord happens to fire.
Debugging Tip: Catching Swallowed JavaScript Errors
During the live coding session, Erik ran into an issue where his JavaScript errors were being silently suppressed — nothing appeared in the browser console. This is a known quirk with control add-ins in Business Central. His workaround is to wrap code in a try/catch block:
try {
// your code here
} catch(e) {
console.log(e);
}
This simple pattern can save significant debugging time when working with control add-in JavaScript, as errors that would normally appear in the console may be swallowed entirely by the Business Central framework.
Alternative Approach: Page Background Tasks
Erik briefly mentioned another potential approach using page background tasks. He observed that if two page extensions on the same page each have a page background task, firing one causes both OnPageBackgroundTaskCompleted triggers to fire. This could theoretically be used as a communication mechanism, though Erik wasn't sure if this behavior is by design or a side effect. He chose the control add-in approach as the more deliberate solution.
Summary
This technique uses a JavaScript control add-in as a communication bridge between a subpage and its parent page in Business Central. By exploiting the fact that both control add-ins live in sibling iframes within the same browser window, JavaScript can traverse the DOM from one iframe to another and invoke Business Central extensibility methods in the correct context. It's admittedly a bit of a "MacGyver" solution — Erik openly invites better approaches — but it works reliably for scenarios where you need the parent page to react to non-update events on a subpage, such as line selection changes.