Reference documentation for the TButtonContainer and THeaderButton components used in dashboard card headers.
The dashboard header system provides two components for managing card header buttons:
Location: Framework/Components/Dashboard/SOURCE/Obj.Dashboard.Header.pas
The header supports two display modes controlled by THeaderStyle:
| Style | Description |
|---|---|
hsLegacy | Traditional icon-only buttons (map, chart, calendar icons, etc.) |
hsCompactActions | Text buttons in the middle (Chart, Raw Data, Legend) with maximize and 3-dot menu |
/// Set header style on the canvas (propagates to all cards)
Canvas.HeaderStyle := hsCompactActions;
/// Or set on individual cards
Card.HeaderButtons.HeaderStyle := hsCompactActions;
These buttons represent the current view mode - only one can be active at a time:
These buttons trigger an action but don't stay selected:
/// In OnCompactButtonClick:
IF Action = mtLegend THEN
BEGIN
/// Legend is momentary - don't change button states
/// Just fire the action
END
ELSE
BEGIN
/// Chart/Map and Raw Data are mutually exclusive
/// Clear both, then set active on clicked button
FbtnCompactConfigure.ElementHandle.classList.remove('active');
FbtnCompactRawData.ElementHandle.classList.remove('active');
ClickedButton.ElementHandle.classList.add('active');
END;
In hsCompactActions mode, text-based pill buttons are displayed in the header center:
FCompactButtonsDiv - Container div for all compact buttonsFbtnCompactConfigure - Chart or Map button (based on DataDisplayType)FbtnCompactRawData - Raw Data buttonFbtnCompactDateRange - Legend button (charts only).compact-buttons-container {
display: flex;
align-items: center;
gap: 2px;
justify-content: center;
margin-left: auto;
margin-right: auto;
}
.compact-action-btn {
/* Pill-shaped button styling */
}
.compact-action-btn.active {
/* Active/selected state */
background: #0ea5e9;
color: white;
}
Compact buttons are only shown when the card is wide enough (default: 450px minimum):
FMinWidthForButtons := 450; /// Minimum card width to show compact buttons
/// Buttons hidden on narrow cards, shown on wider cards
IF CardWidth >= FMinWidthForButtons THEN
FCompactButtonsDiv.ElementHandle.style.setProperty('display', 'flex', 'important')
ELSE
FCompactButtonsDiv.ElementHandle.style.setProperty('display', 'none', 'important');
ResizeObserver requires {$MODESWITCH EXTERNALCLASS} at the unit top:
UNIT Obj.Dashboard.Header;
{$IFDEF PAS2JS}
{$MODESWITCH EXTERNALCLASS}
{$ENDIF}
INTERFACE
...
TYPE
{$IFDEF PAS2JS}
TJSResizeObserverEntry = CLASS external name 'ResizeObserverEntry' (TJSObject)
PUBLIC
target: TJSElement; external name 'target';
contentRect: TJSObject; external name 'contentRect';
END;
TJSResizeObserverEntryArray = ARRAY OF TJSResizeObserverEntry;
TJSResizeObserver = CLASS external name 'ResizeObserver' (TJSObject)
PUBLIC
CONSTRUCTOR new(callback: JSValue);
PROCEDURE observe(target: TJSElement);
PROCEDURE unobserve(target: TJSElement);
PROCEDURE disconnect;
END;
{$ENDIF}
PROCEDURE TButtonContainer.SetupResizeObserver;
{$IFDEF PAS2JS}
VAR
CardElement: TJSElement;
{$ENDIF}
BEGIN
{$IFDEF PAS2JS}
IF Assigned(FResizeObserver) THEN
Exit;
CardElement := TJSElement(FCompactButtonsDiv.ElementHandle.closest('.dashboard-card'));
IF NOT Assigned(CardElement) THEN
Exit;
/// Create observer with method pointer callback
FResizeObserver := TJSResizeObserver.new(@OnResizeObserverCallback);
FResizeObserver.observe(CardElement);
{$ENDIF}
END;
{$IFDEF PAS2JS}
PROCEDURE TButtonContainer.OnResizeObserverCallback(
AEntries: TJSResizeObserverEntryArray;
AObserver: TJSObject);
VAR
Entry: TJSResizeObserverEntry;
CardWidth: Integer;
ShowButtons: Boolean;
BEGIN
IF Length(AEntries) = 0 THEN
Exit;
Entry := AEntries[0];
/// Access width from contentRect object
CardWidth := Trunc(Double(Entry.contentRect.Properties['width']));
ShowButtons := CardWidth >= FMinWidthForButtons;
IF ShowButtons THEN
FCompactButtonsDiv.ElementHandle.style.setProperty('display', 'flex', 'important')
ELSE
FCompactButtonsDiv.ElementHandle.style.setProperty('display', 'none', 'important');
END;
{$ENDIF}
DESTRUCTOR TButtonContainer.Destroy;
BEGIN
{$IFDEF PAS2JS}
IF Assigned(FResizeObserver) THEN
BEGIN
FResizeObserver.disconnect;
FResizeObserver := NIL;
END;
{$ENDIF}
INHERITED;
END;
The 3-dot button opens a dropdown menu with categorized actions:
The menu is appended to document.body to avoid overflow clipping:
/// Move to body so it's not clipped by parent overflow
BodyElement := TJSHTMLElement(document.body);
BodyElement.appendChild(FActionsMenu.ElementHandle);
/// Position below the button
ButtonRect := TJSHTMLElement(btnActions.ElementHandle).getBoundingClientRect;
FActionsMenu.ElementHandle.style.setProperty('top', IntToStr(Trunc(ButtonRect.bottom) + 4) + 'px');
FActionsMenu.ElementHandle.style.setProperty('left', IntToStr(Trunc(ButtonRect.right) - 160) + 'px');
Clicking outside the menu closes it:
PROCEDURE TButtonContainer.DocumentClickHandler(Event: TJSEvent);
VAR
TargetElement: TJSHTMLElement;
BEGIN
TargetElement := TJSHTMLElement(Event.target);
/// Check if click is inside menu or on actions button
IF FActionsMenu.ElementHandle.contains(TargetElement) THEN
Exit;
IF btnActions.ElementHandle.contains(TargetElement) THEN
Exit;
/// Click was outside - hide menu
HideActionsMenu;
END;
/// In canvas initialization
Canvas := TDashboardCanvas.Create(Self);
Canvas.HeaderStyle := hsCompactActions; /// Use compact text buttons
/// OnHeaderButtonClick handler receives the action
PROCEDURE TDashboardCard.HandleHeaderButtonClick(
Sender: TObject;
Action: TMessageType;
Payload: TCardPayload);
BEGIN
CASE Action OF
mtChartButtonClick:
ShowChartView;
mtMapButtonClick:
ShowMapView;
mtCardShowGrid:
ShowRawDataGrid;
mtLegend:
ToggleLegendVisibility;
mtCanvasMaximize:
MaximizeCard;
mtCanvasMinimize:
RestoreCard;
END;
END;
/// In THeaderButton.DoOnClick - skip state change for action buttons
IF NOT (Action IN [mtCanvasMaximize, mtCanvasMinimize, mtActionsMenu,
mtCanvasExpandRight, mtCanvasExpandDown, mtCanvasExpandBoth,
mtCanvasShrinkLeft, mtCanvasShrinkUp, mtCanvasShrinkBoth,
mtCardDelete, mtAdjustLayout]) THEN
ButtonContainer.SetButtonGroupStatus(Self);