Component Communication

WakaPAC components communicate through two complementary mechanisms: hierarchy-based messaging enforces architectural integrity for parent-child relationships, while Win32-style messaging provides flexible communication for everything else. Each system is required for its domain — they solve different problems rather than competing approaches.

Component Hierarchies

Parent-child relationships are determined entirely by DOM nesting, not by the order in which components are created. When WakaPAC initialises a component, it walks the DOM tree to check whether that element is nested inside another component's element. If it is, the outer component becomes the parent.

<div id="app">                <!-- Root component -->
    <div id="sidebar">       <!-- Child of app -->
        <div id="menu">      <!-- Child of sidebar (grandchild of app) -->
        </div>
    </div>
    <div id="content">       <!-- Child of app -->
    </div>
</div>

<script>
    // Creation order is irrelevant — hierarchy is resolved from DOM structure
    wakaPAC('#menu',    { /* ... */ });
    wakaPAC('#app',     { /* ... */ });
    wakaPAC('#sidebar', { /* ... */ });
    wakaPAC('#content', { /* ... */ });
</script>

This produces two hierarchy chains: app → sidebar → menu and app → content. Each component has at most one parent, but can have any number of children. Notifications travel one level at a time — menu notifies sidebar, not app directly.

Hierarchy-Based Communication

When components are in a parent-child relationship, the hierarchy methods enforce proper component architecture. They provide compile-time-like guarantees through direct method invocation: a child with no parent cannot call notifyParent, and a parent cannot command a component that isn't its child. This structural enforcement catches bugs that would silently fail in a flat messaging system.

The hierarchy system is also faster — direct function calls rather than event system dispatch — making it the correct choice for tight parent-child communication loops like form validation or nested widget updates.

There is an intentional asymmetry in how the two directions work. A child sends an event — it reports what happened, with no expectation of what the parent will do about it ('item-added', 'form-submitted'). A parent sends a command — it tells children what to do ('refresh', 'theme-change'). Keeping this distinction clean makes hierarchy-based communication self-documenting.

Child → Parent

A child notifies its parent with notifyParent(type, data). The parent handles it in receiveFromChild(type, data, childComponent):

<div id="cart">
    <p>Items: {{itemCount}}</p>

    <div id="add-button">
        <button data-pac-bind="click: add">Add Item</button>
    </div>
</div>

<script>
    wakaPAC('#add-button', {
        add() {
            this.notifyParent('item-added', null);
        }
    });

    wakaPAC('#cart', {
        itemCount: 0,

        receiveFromChild(type, data, child) {
            if (type === 'item-added') {
                this.itemCount++;
            }
        }
    });
</script>
notifyParent(type, data)
Parameter Type Description
type string Event name describing what happened (e.g. 'item-added', 'form-submitted')
data any Payload accompanying the event. null if none needed
receiveFromChild(type, data, childComponent)
Parameter Type Description
type string The event name passed by the child
data any The payload passed by the child
childComponent object Reference to the child component instance that sent the notification

Parent → All Children

Use notifyChildren(command, data) to broadcast a command to every direct child. Grandchildren are not reached — if sidebar has its own children, they will not receive a command sent by app to sidebar unless sidebar explicitly forwards it:

wakaPAC('#dashboard', {
    currentTheme: 'light',

    toggleTheme() {
        this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';
        this.notifyChildren('theme-change', { theme: this.currentTheme });
    }
});
notifyChildren(command, data)
Parameter Type Description
command string Command name (e.g. 'theme-change', 'refresh')
data any Payload accompanying the command. null if none needed

Parent → Specific Child

Use notifyChild(pacId, command, data) to target one child by identifier (see Component Identification below for resolution rules):

wakaPAC('#dashboard', {
    refreshWidget1() {
        this.notifyChild('widget-1', 'refresh', null);
    }
});
notifyChild(pacId, command, data)
Parameter Type Description
pacId string Target child's data-pac-id (or id if no data-pac-id is set)
command string Command name
data any Payload accompanying the command. null if none needed

Receiving Commands from the Parent

Children handle incoming commands in receiveFromParent(command, data). A child only needs to handle the commands it cares about — unrecognised commands are silently ignored:

wakaPAC('#widget-1', {
    theme: 'light',
    refreshCount: 0,

    receiveFromParent(command, data) {
        if (command === 'theme-change') {
            this.theme = data.theme;
        } else if (command === 'refresh') {
            this.refreshCount++;
        }
    }
});
receiveFromParent(command, data)
Parameter Type Description
command string The command name sent by the parent
data any The payload sent by the parent

Neither handler is required. If a component doesn't define receiveFromChild or receiveFromParent, messages on that channel are silently dropped — no stub methods needed.

Win32-Style Messaging

When components lack a parent-child relationship, the Win32-style messaging system is architecturally required. It provides flat addressing by pacId, synchronous or asynchronous dispatch, broadcast capabilities, integer-based message parameters (wParam/lParam) for compact data, and msgProc-based event handling with numeric constants.

Required For

  • Sibling components — no parent-child relationship exists. While relaying through a shared parent works (child → notifyParent() → parent → notifyChild()), direct messaging is simpler for peer-to-peer communication
  • External scripts — analytics, third-party integrations, or non-component code driving component behaviour
  • Application-wide broadcasts — auth state, global theme, or scenarios where hierarchy would require walking the entire tree
  • Decoupled architectures — sender and receiver have no compile-time dependency on each other's existence

Component Identification

Every component has a pacId, resolved in this order: the data-pac-id attribute, the element's id attribute, or an auto-generated identifier. This is the address you use when sending a message:

wakaPAC('#sidebar', {
    init() {
        console.log(this.pacId); // "sidebar"
    }
});

Defining Custom Message Types

All custom message IDs must start at wakaPAC.MSG_USER (0x1000) to avoid collisions with built-in messages:

const MSG_ITEM_SELECTED  = wakaPAC.MSG_USER + 1;
const MSG_REFRESH_DATA   = wakaPAC.MSG_USER + 2;
const MSG_THEME_CHANGED  = wakaPAC.MSG_USER + 3;

Sending Messages

Three functions handle delivery, with an important distinction between synchronous and asynchronous delivery:

postMessage — Asynchronous, Single Target (Preferred)

The message is queued through the DOM event system. Calling code continues immediately; the target's msgProc runs later in the event loop.

wakaPAC.postMessage('sidebar', MSG_REFRESH_DATA, 0, 0);
Parameter Type Description
targetId string Target component's pacId
messageId number Message type constant (must be ≥ MSG_USER)
wParam number First integer parameter — flags, IDs, small values
lParam number Second integer parameter — flags, IDs, small values
extraData object Optional. Arbitrary data, available as event.detail in the receiver

sendMessage — Synchronous, Single Target

Calls the target's msgProc directly, before the next line of calling code executes. Uses the same parameters as postMessage above. Use only when you genuinely need the target to have processed the message before you continue — for example, querying a value back out of another component.

// Caller needs the target to update state immediately
wakaPAC.sendMessage('data-store', MSG_REFRESH_DATA, 0, 0);
// data-store's msgProc has already run by this point

broadcastMessage — Asynchronous, All Components

Delivers to every component in the application. Omits targetId parameter; otherwise identical to postMessage. Use for genuinely global state changes.

wakaPAC.broadcastMessage(MSG_THEME_CHANGED, isDarkMode ? 1 : 0, 0);

Receiving Messages

All three delivery methods arrive in the component's msgProc. Return false once you handle a message to claim it. If you don't return false, the message continues through any other handlers registered on the same component for that event type:

wakaPAC('#sidebar', {
    darkMode: false,

    msgProc(event) {
        switch (event.message) {
            case MSG_REFRESH_DATA:
                this.loadData();
                return false;

            case MSG_THEME_CHANGED:
                this.darkMode = event.wParam === 1;
                return false;
        }
    }
});

Passing Complex Data

wParam and lParam are integers — useful for flags, IDs, and coordinates, but not for objects or arrays. The optional fifth argument to any send function becomes event.detail:

// Sender
wakaPAC.postMessage('product-details', MSG_ITEM_SELECTED, itemId, 0, {
    product: { name: 'Widget', price: 10, tags: ['sale', 'new'] }
});

// Receiver
msgProc(event) {
    if (event.message === MSG_ITEM_SELECTED) {
        const product = event.detail.product;
        this.display(product);
        return false;
    }
}

Message Event Structure

{
    type:          'pac:event',
    message:       0x1001,              // Your MSG_USER + N constant
    wParam:        0,                   // First integer parameter
    lParam:        0,                   // Second integer parameter
    detail:        {},                  // extraData object (if provided)
    timestamp:     1640995200000       // Delivery time in ms
}
Property Type Description
type string Always 'pac:event' for messages delivered through the msgProc pipeline
message number The message type constant — your MSG_USER + N value, or a built-in message ID
wParam number First integer parameter, as passed by the sender
lParam number Second integer parameter, as passed by the sender
detail object The extraData object passed as the fifth argument to the send function. undefined if none was provided
timestamp number When the message was delivered, in milliseconds (same epoch as Date.now())

Choosing the Right Mechanism

Use this table to identify which communication mechanism is architecturally appropriate for your situation. Each row describes a scenario where one system is either required (hierarchy for parent-child) or correct (Win32 for non-hierarchical communication).

Situation Use Why
Child needs to report an event to its parent notifyParent() Enforces parent-child relationship through direct method invocation. Faster than event dispatch, catches architectural errors at call time
Parent needs to update all children notifyChildren() Commands only reach direct children — hierarchical scoping prevents unintended global side effects
Parent needs to command one specific child notifyChild() Targeted, stays within the hierarchy
Components without parent-child relationship need to communicate postMessage() Async delivery via event queue. Sender and receiver are decoupled — neither needs to know if the other exists. Perfect for sibling components, plugin architectures, or external integration
App-wide state change (auth, locale, theme) broadcastMessage() Every component that cares will receive it; others ignore it
Send a message and need the result before continuing sendMessage() Calls the target's msgProc directly and blocks until it returns. Use when you need to read back state or depend on the target having finished — for example, asking a data-store component to refresh before you pull its updated values.

Best Practices

  • Use hierarchy methods for parent-child relationships. The structural enforcement and performance benefits are detailed in the Hierarchy-Based Communication section above.
  • Use Win32 messaging for non-hierarchical communication. See "Required For" section above. Don't force these scenarios through hierarchy relaying unless the parent genuinely needs to mediate.
  • Prefer postMessage over sendMessage. Async delivery keeps components loosely coupled. Reserve sendMessage for cases where execution order genuinely matters.
  • Use descriptive names and constants. Hierarchy types like 'form-submit' and messaging constants like MSG_ITEM_SELECTED are both self-documenting — generic names like 'update' or MSG_USER + 1 without a named constant are not.
  • Send only what's needed. Avoid passing entire component state through either mechanism. Narrow payloads make contracts easier to understand and debug.
  • Return false promptly. In msgProc, return as soon as you handle a message. The handler runs for every message delivered to that component; unnecessary fall-through adds cost.
  • Watch for loops. A sends to B, B sends back to A — this is the easiest way to create an infinite message cycle. Add a guard or use a flag.
  • Broadcast sparingly. broadcastMessage hits every component. If only two or three components care, target them directly.