Notification System Architecture

Inter-component messaging system for dashboard applications using a transmitter-receiver pattern.

Contents

Overview

The notification system allows dashboard components to communicate without direct coupling:

Location: Framework/COMMON/WEBSITE/Intf.Supports.Messages.pas

Pattern Change: The older broadcast-based message bus was replaced with transmitter-receiver pattern for cleaner architecture and easier debugging.

Message Types

TMessageType enum defines all message categories:

Card Actions

TypeDescription
mtChartButtonClickSwitch to chart view
mtMapButtonClickSwitch to map view
mtCardShowGridShow raw data grid
mtLegendToggle legend visibility
mtCardDeleteDelete card request

Canvas Actions

TypeDescription
mtCanvasMaximizeMaximize card to full canvas
mtCanvasMinimizeRestore card from maximized
mtCanvasExpandRightExpand card width by 1 column
mtCanvasExpandDownExpand card height by 1 row
mtCanvasShrinkLeftShrink card width by 1 column
mtAdjustLayoutRecalculate grid layout

Data Actions

TypeDescription
mtRefreshDataReload card data from server
mtDateRangeChangedDate filter changed
mtFilterChangedGeneral filter changed

Transmitter Pattern

IMessageTransmitter Interface

TYPE
   IMessageTransmitter = INTERFACE
      ['{GUID}']
      PROCEDURE TransmitMessage(MessageType: TMessageType; 
         Payload: TCardPayload = NIL);
      PROCEDURE ConnectReceiver(Receiver: IMessageReceiver);
      PROCEDURE DisconnectReceiver(Receiver: IMessageReceiver);
   END;

Implementation

TYPE
   TMessageTransmitter = CLASS(TInterfacedObject, IMessageTransmitter)
   PRIVATE
      FReceivers: TList<IMessageReceiver>;
   PUBLIC
      PROCEDURE TransmitMessage(MessageType: TMessageType; 
         Payload: TCardPayload = NIL);
      BEGIN
         FOR Receiver IN FReceivers DO
            Receiver.HandleMessage(MessageType, Payload);
      END;
   END;

Receiver Pattern

IMessageReceiver Interface

TYPE
   IMessageReceiver = INTERFACE
      ['{GUID}']
      PROCEDURE HandleMessage(MessageType: TMessageType; 
         Payload: TCardPayload);
   END;

Implementation in Component

TYPE
   TDashboardCanvas = CLASS(TCoreDiv, IMessageReceiver)
   PRIVATE
      PROCEDURE HandleMessage(MessageType: TMessageType; 
         Payload: TCardPayload);
   END;

PROCEDURE TDashboardCanvas.HandleMessage(MessageType: TMessageType; 
   Payload: TCardPayload);
BEGIN
   CASE MessageType OF
      mtCanvasMaximize:
         MaximizeCard(Payload.CardID);
      mtCanvasMinimize:
         RestoreCard(Payload.CardID);
      mtCardDelete:
         DeleteCard(Payload.CardID);
      mtAdjustLayout:
         RecalculateLayout;
   END;
END;

Payload Structure

TCardPayload Class

TYPE
   TCardPayload = CLASS
   PUBLIC
      CardID: Integer;           /// Affected card ID
      SourceComponent: TObject;  /// Sender reference
      Data: TJSObject;           /// Optional JSON data
      StringValue: STRING;       /// Optional string data
      IntValue: Integer;         /// Optional integer data
      
      CONSTRUCTOR Create(ACardID: Integer);
   END;

Creating Payload

VAR
   Payload: TCardPayload;
BEGIN
   Payload := TCardPayload.Create(Card.CardID);
   Payload.SourceComponent := Self;
   Payload.StringValue := 'additional info';
   
   Transmitter.TransmitMessage(mtCardDelete, Payload);
END;

Usage Examples

Header Button → Canvas

/// In THeaderButton (transmitter)
PROCEDURE THeaderButton.DoOnClick;
VAR
   Payload: TCardPayload;
BEGIN
   Payload := TCardPayload.Create(OwnerCard.CardID);
   Payload.SourceComponent := Self;
   
   CASE Action OF
      mtCanvasMaximize:
         FTransmitter.TransmitMessage(mtCanvasMaximize, Payload);
      mtCardShowGrid:
         FTransmitter.TransmitMessage(mtCardShowGrid, Payload);
   END;
END;

Card → Canvas Connection

/// When card is created
PROCEDURE TDashboardCanvas.AddCard(Card: TDashboardCard);
BEGIN
   /// Connect card's transmitter to canvas receiver
   Card.Transmitter.ConnectReceiver(Self);
   FCards.Add(Card);
END;

/// When card is removed
PROCEDURE TDashboardCanvas.RemoveCard(Card: TDashboardCard);
BEGIN
   Card.Transmitter.DisconnectReceiver(Self);
   FCards.Remove(Card);
END;

Sidebar → All Cards

/// Sidebar filter change broadcasts to all cards
PROCEDURE TSidebar.OnDateRangeChange(NewStart, NewEnd: TDateTime);
VAR
   Payload: TCardPayload;
BEGIN
   Payload := TCardPayload.Create(0);  /// 0 = all cards
   Payload.Data := TJSObject.new;
   Payload.Data['startDate'] := DateToISO(NewStart);
   Payload.Data['endDate'] := DateToISO(NewEnd);
   
   FTransmitter.TransmitMessage(mtDateRangeChanged, Payload);
END;
Best Practice: Always disconnect receivers in destructor to prevent dangling references. Use CardID = 0 in payload to indicate a message affects all receivers.