Skip to main content

Documentation Index

Fetch the complete documentation index at: https://human-resource-docs.ha-consultancy.com/llms.txt

Use this file to discover all available pages before exploring further.

This page documents the contract between the AL side (the hosting page) and the React side (the control add-in). Treat it as the source of truth when changing either layer.

How a control add-in works

  1. The AL page declares a usercontrol(Name; "Control Add-in Name") field in its layout.
  2. The Control Add-in object (controls/RosterGridHAC.al) declares the JS files to load, the event names it raises (calls from AL into JS), and the procedures it exposes (calls from JS into AL).
  3. BC injects an iframe sourcing the bundle’s HTML wrapper. The React app boots inside.
  4. The control’s Scripts property points at /scripts/assets/js/base.js — the Vite output.

The event contract

Outbound (React → AL)

Every call is Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(eventName, [parameters]). The AL trigger with the matching name runs, reads data from BC tables, builds a JsonArray or JsonObject, and calls a control-add-in procedure (back into JS) with the result.
Event nameTriggered byPurpose
ControlReadyscripts/startup.js once the iframe is readyTells AL the JS layer is up; AL then calls InitControls()
GetShiftCodesgetShiftCodesAsync()Asks AL for shift configurations marked Add to Roster = true
GetRosterEntriesgetRosterEntriesAsync(from, to)Asks AL for roster entries within a date window
SaveRosterEntrysaveRosterEntryAsync(empNo, date, shiftCode, entryNo?)Insert or update an Employee Roster HAC row
DeleteRosterEntrydeleteRosterEntryAsync(entryNo)Delete the row by entry number
GetResourcesgetAllResourcesAsync(from, to)Return the BC Employee list
Gantt-specific events (GetProjects, CheckIfUserCanEdit, NewTask, CreateTaskPlanningLines, etc.) are also declared and fire when that screen is re-mounted.

Inbound (AL → React)

The control add-in declares procedures, which BC compiles into JavaScript callbacks that the AL page calls with CurrPage.Control.<ProcedureName>(<Args>). The React app registers each as a window.<NAME_IN_UPPER_SNAKE> function in dataService.ts.
ProcedureCalled from ALPurpose
InitControls()After ControlReady eventSignals JS to start fetching initial data
GET_SHIFT_CODES(JsonArray)GetShiftCodes triggerHands the shift list back to React
GET_ROSTER_ENTRIES(JsonArray)GetRosterEntries triggerHands the entries back
SAVE_ROSTER_ENTRY(JsonObject)SaveRosterEntry triggerReturns the saved row (with the new entryNo on insert)
DELETE_ROSTER_ENTRY()DeleteRosterEntry triggerAcknowledges deletion
GET_RESOURCES(JsonArray)GetResources triggerHands the resource/employee list back

The startup handshake

If anything in steps 4-7 fails, the iframe stays blank. Debug by opening browser DevTools while inside BC, set a breakpoint in startup.js, and watch the console.

Working with the bridge in code

// react-roster-shift-board/src/services/dataService.ts

async function getDataFromBusinessCentralAsync<T>(
  eventName: string,
  callbackName: string,
  parameter: unknown
): Promise<T> {
  if (!isBC) {
    // Dev mode: fetch the mock JSON
    return fetch(`/api/${eventName}.json`).then(r => r.json());
  }
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error('BC timeout')), 120_000);
    (window as any)[callbackName] = (result: T) => {
      clearTimeout(timer);
      resolve(result);
    };
    Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(eventName, [parameter]);
  });
}
The 120-second timeout protects against AL never calling back.

Bundle layout (after npm run build)

scripts/
├── startup.js              Boots the iframe, fires ControlReady
├── dev.html                Used only during local development
└── assets/
    ├── js/
    │   └── base.js         The full React bundle (single file)
    ├── app.css             Compiled styles
    └── api/                Dev-mode JSON fixtures (used when isBC=false)
        ├── shift-codes.json
        ├── roster-entries.json
        └── resources.json
The Control Add-in’s Scripts and StyleSheets properties name js/base.js and app.css verbatim. Never rename them.

Updating the contract

When you add a new feature that needs a new event or procedure:
  1. Add the event/procedure to controls/RosterGridHAC.al.
  2. Implement the AL trigger on the hosting page (Pag70003175).
  3. Add the matching service function in dataService.ts.
  4. Rebuild the React bundle (npm run build).
  5. Rebuild the AL .app.
  6. Republish.
Skipping the React rebuild is the single most common cause of “feature works in dev but not in BC.”

Why no direct OData or fetch from React

Permission uniformity (AL triggers run under the BC user’s permission set), audit (every BC table write goes through the AL trigger and into BC’s change log), multi-environment support (the iframe inherits the BC session), and no CORS/auth issues — the iframe and the AL page share an origin within BC’s web shell.