Component Communication

WakaPAC components communicate through desktop-style messaging for all inter-component communication. All messages — whether between siblings, parent to child, or child to parent — flow through msgProc, providing a single, consistent entry point for every component.

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.

Desktop-Style Messaging

The following sections cover how components are addressed, how messages are defined, sent, and received — including targeted, broadcast, and hierarchy-aware delivery.

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', {
    ready() {
        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. Define them as named constants:

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

Any component can send a message to any other component. The sender calls a function on wakaPAC, specifies the target, and the message arrives in that component's msgProc. Messages can also be broadcast to all components at once, or to all descendants of a given component.

Every send function shares the same signature: a message ID constant, integer wParam, integer lParam, and optional extended payload object. Two axes determine which function to use:

Scope — targeted (by pacId), hierarchy-aware (resolved from the component tree), broadcast (all components or a subtree), or broadcastMessageGlobal (all tabs/windows at the same origin).

Timing — synchronous variants (sendMessage, sendMessageToParent) invoke msgProc immediately; asynchronous variants (postMessage, postMessageToParent) queue through the DOM event system and return right away. Prefer async unless the caller depends on the target having already processed the message.

Receiving Messages

msgProc receives all messages delivered to a component — including built-in events like mouse moves and key presses. Components that only handle custom messages should guard at the top to avoid unnecessary processing:

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

    msgProc(event) {
        // Skip all built-in toolkit messages
        if (event.message < wakaPAC.MSG_USER) {
            return;
        }

        switch (event.message) {
            case MSG_REFRESH_DATA:
                this.loadData();
                break;

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

Return false to cancel default toolkit handling for cancellable events (clicks, key events, form submission). For custom messages this has no effect.

Messages

Every message delivered to a component arrives as an event object in msgProc. The properties below are always present regardless of how the message was sent.

Message Event Object

PropertyTypeDescription
messagenumberThe message type constant, e.g. wakaPAC.MSG_USER + 1. Use this in switch statements to dispatch to the correct handler.
wParamnumberFirst integer parameter — flags, IDs, or small values. Meaning is defined by the message type.
lParamnumberSecond integer parameter — flags, IDs, or small values. Meaning is defined by the message type.
detailobjectThe extended object passed to the send function. Empty object {} if none was provided.
timestampnumberTime the message was created, in milliseconds since the page loaded (performance.now() epoch).
pacIdstringThe pacId of the component receiving the message.
targetElement|nullThe DOM element that originated the event. Present for DOM-originated messages (clicks, key presses, etc.); null for programmatically sent messages.
originalEventEvent|nullThe native browser event that triggered this message. Present for DOM-originated messages; null for programmatically sent messages.

API

wakaPAC.postMessage(pacId, messageId, wParam, lParam, extended?)

Queues a message for asynchronous delivery to the target component. The target's msgProc runs later in the event loop; calling code continues immediately. If the target does not exist or is no longer connected to the DOM, the message is dropped. Preferred over sendMessage in most cases.

ParameterTypeDescription
pacIdstringTarget component's pacId.
messageIdnumberMessage type constant. Custom messages must be ≥ wakaPAC.MSG_USER.
wParamnumberFirst integer parameter — flags, IDs, or small values.
lParamnumberSecond integer parameter — flags, IDs, or small values.
extendedobjectOptional. Arbitrary data, available as event.detail in the receiver.
Returns void

wakaPAC.sendMessage(pacId, messageId, wParam, lParam, extended?)

Delivers a message synchronously to the target component. The target's msgProc runs immediately, before the next line of calling code executes. If the target does not exist, the message is dropped. Use only when you genuinely need the target to have processed the message before continuing.

ParameterTypeDescription
pacIdstringTarget component's pacId.
messageIdnumberMessage type constant. Custom messages must be ≥ wakaPAC.MSG_USER.
wParamnumberFirst integer parameter — flags, IDs, or small values.
lParamnumberSecond integer parameter — flags, IDs, or small values.
extendedobjectOptional. Arbitrary data, available as event.detail in the receiver.
Returns void

wakaPAC.broadcastMessage(messageId, wParam, lParam, extended?)

Delivers a message synchronously to every registered component in the application. Use for genuinely global state changes. If only a few components care, target them directly — broadcast hits everything.

ParameterTypeDescription
messageIdnumberMessage type constant. Custom messages must be ≥ wakaPAC.MSG_USER.
wParamnumberFirst integer parameter — flags, IDs, or small values.
lParamnumberSecond integer parameter — flags, IDs, or small values.
extendedobjectOptional. Arbitrary data, available as event.detail in the receiver.
Returns void

wakaPAC.broadcastMessageGlobal(messageId, wParam, lParam, extended?)

Delivers a message to every registered component in the current page and every other tab or window of the same origin. The local dispatch happens first, synchronously. Cross-tab dispatch is handled by the browser's BroadcastChannel API; WakaPAC automatically fans the message out to all components in every receiving tab.

Cross-tab broadcast is opt-in. It is only active when a <meta name="wakapac-channel"> tag is present in the <head>. If the tag is absent, broadcastMessageGlobal falls back silently to local-only dispatch. The content attribute sets the channel name — all tabs must use the same name to form one broadcast group. Because BroadcastChannel uses structured clone for serialisation, the extended payload must contain only plain, serialisable values — strings, numbers, booleans, plain objects, and arrays. DOM nodes, component references, functions, and class instances will not survive the clone and must not be included.

const MSG_CART_UPDATED = wakaPAC.MSG_USER + 1;

// In any component — updates cart components in every open tab
wakaPAC.broadcastMessageGlobal(MSG_CART_UPDATED, 0, 0, {
    itemId: 42,
    quantity: 3
});
ParameterTypeDescription
messageIdnumberMessage type constant. Custom messages must be ≥ wakaPAC.MSG_USER.
wParamnumberFirst integer parameter — flags, IDs, or small values.
lParamnumberSecond integer parameter — flags, IDs, or small values.
extendedobjectOptional. Arbitrary data, available as event.detail in the receiver. Must be structured-clone safe — no DOM nodes, functions, or class instances.
Returns void

wakaPAC.getParentPacId(pacId)

Returns the pacId of the parent component, or null if the component has no parent. Use this when you need the parent's address for purposes other than sending a message directly. sendMessageToParent and postMessageToParent call this internally.

ParameterTypeDescription
pacIdstringThe child component whose parent to resolve.
Returns string|nullThe parent's pacId, or null if the component has no parent.

wakaPAC.sendMessageToParent(pacId, messageId, wParam, lParam, extended?)

Delivers a message synchronously to the parent of the given component. If the component has no parent, the message is silently dropped. Equivalent to calling getParentPacId() and then sendMessage().

ParameterTypeDescription
pacIdstringThe child component sending the message.
messageIdnumberMessage type constant. Custom messages must be ≥ wakaPAC.MSG_USER.
wParamnumberFirst integer parameter — flags, IDs, or small values.
lParamnumberSecond integer parameter — flags, IDs, or small values.
extendedobjectOptional. Arbitrary data, available as event.detail in the receiver.
Returns void

wakaPAC.postMessageToParent(pacId, messageId, wParam, lParam, extended?)

Queues a message for asynchronous delivery to the parent of the given component. If the component has no parent, the message is silently dropped. Asynchronous equivalent of sendMessageToParent — same parameters, queued delivery.

ParameterTypeDescription
pacIdstringThe child component sending the message.
messageIdnumberMessage type constant. Custom messages must be ≥ wakaPAC.MSG_USER.
wParamnumberFirst integer parameter — flags, IDs, or small values.
lParamnumberSecond integer parameter — flags, IDs, or small values.
extendedobjectOptional. Arbitrary data, available as event.detail in the receiver.
Returns void

wakaPAC.broadcastToChildren(pacId, messageId, wParam, lParam, extended?)

Delivers a message to all descendants of the given component. The root component itself does not receive the message — only its children, grandchildren, and so on. Traversal is depth-first. The same event object is reused across all dispatches; if a child's msgProc mutates event.detail, subsequent siblings will see the mutation.

ParameterTypeDescription
pacIdstringThe root component whose full subtree receives the message.
messageIdnumberMessage type constant. Custom messages must be ≥ wakaPAC.MSG_USER.
wParamnumberFirst integer parameter — flags, IDs, or small values.
lParamnumberSecond integer parameter — flags, IDs, or small values.
extendedobjectOptional. Arbitrary data, available as event.detail in the receiver.
Returns void

Best Practices

  • Guard against built-in messages. Add if (event.message < wakaPAC.MSG_USER) return; at the top of any msgProc that only handles custom messages. Without it, every mouse move and keypress calls your handler unnecessarily.
  • Use named constants. Define const MSG_ITEM_SELECTED = wakaPAC.MSG_USER + 1 at the top of your script. Inline numbers are not self-documenting and break when you insert a new message between existing ones.
  • Include senderPacId in the payload. When sending from a child to a parent, include { senderPacId: this.pacId, ... } in the extended data. The parent can then respond directly by pacId without coupling to any specific child implementation.
  • Send only what's needed. Avoid passing entire component state through a message. Narrow payloads make contracts easier to understand and debug.
  • Broadcast sparingly. broadcastMessage hits every component. If only two or three components care, target them directly.
  • Keep cross-tab payloads serialisable. broadcastMessageGlobal passes the extended object through structured clone. Use only plain values — strings, numbers, booleans, plain objects, and arrays.
  • Use a specific channel name. The content attribute of the wakapac-channel meta tag should identify your application — for example "my-app". A generic name risks interference if multiple WakaPAC applications run on the same origin.
  • 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 a flag to prevent re-entrancy.