@plotinus/matrix-package-observable-coordinator
Version:
Observable coordinator pattern components using IntrospectableBaseCommunicationComponent and proper presentation architecture
323 lines (319 loc) β’ 22.7 kB
JavaScript
/**
* ========================================================================================================
* π― APP PRESENTATION ELEMENT - REAL-TIME INTROSPECTION UI COMPONENT
* ========================================================================================================
*
* This custom HTML element demonstrates the PRESENTATION LAYER of the Matrix Framework's
* IntrospectableBaseCommunicationComponent pattern. It provides real-time visualization
* of observable state changes and events from the AppComponent communication layer.
*
* π₯ INTROSPECTION PATTERNS DEMONSTRATED:
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* β’ Real-time State Observation: Listens to _stateChanged events for automatic UI updates
* β’ Property-Level Tracking: Responds to _propertyChanged events for granular change detection
* β’ Event Emission Monitoring: Observes _eventEmitted events for complete system transparency
* β’ Two-Way Communication: UI interactions trigger communication component methods
* β’ Presentation Layer Separation: Pure presentation logic separate from business logic
*
* π PRESENTATION RESPONSIBILITIES:
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* β’ Subscribe to introspection events from bound communication component
* β’ Provide real-time UI updates based on observable state changes
* β’ Display system status: coordinator readiness, processing state, job completion
* β’ Offer UI controls for triggering system operations (Start Processing button)
* β’ Maintain presentation-specific state (log messages, UI interactions)
*
* π EVENT-DRIVEN UI LIFECYCLE:
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* 1. bindTo() β Subscribe to communication component's introspection events
* 2. onStateChanged() β Re-render UI when observable state changes
* 3. onPropertyChanged() β Update specific UI elements when individual properties change
* 4. User interactions β Trigger communication component methods via direct calls
* 5. Log display β Track all events and state changes for debugging visibility
*
* ========================================================================================================
*/
/**
* π― AppPresentationElement - Custom Element for Observable State Visualization
*
* Extends HTMLElement to provide:
* β’ Shadow DOM encapsulation for isolated presentation styling
* β’ Event-driven UI updates based on communication component introspection
* β’ Real-time logging and state display for system transparency
* β’ User interaction controls that trigger communication component methods
*/
export class AppPresentationElement extends HTMLElement {
/**
* ποΈ Element Construction - Setting Up Presentation Infrastructure
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* Initializes the custom element with Shadow DOM and presentation-specific state.
* This demonstrates clean separation between presentation state (logMessages)
* and communication component state (managed by AppComponent).
*/
constructor() {
super();
this.logMessages = [];
// π PRESENTATION STATE: UI-specific data separate from communication component
this.logMessages = []; // Array of log messages for UI display
// π DEBUGGING: Track element lifecycle for transparency
console.log('ποΈ AppPresentationElement: Constructor called');
// π SHADOW DOM SETUP: Encapsulated styling and structure
this.attachShadow({ mode: 'open' });
// π INITIAL LOG: Track element creation in UI log
this.logToUI('ποΈ AppPresentation element constructed');
}
/**
* π Component Binding - The Core Introspection Connection
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* This method establishes the CRITICAL connection between the presentation layer and the
* communication component's introspection system. It subscribes to three key introspection
* events that enable real-time UI updates based on observable state changes.
*
* INTROSPECTION EVENTS SUBSCRIBED:
* β’ _stateChanged: Full state object updates (triggers complete UI re-render)
* β’ _propertyChanged: Individual property updates (triggers targeted UI updates)
* β’ _eventEmitted: Component event emissions (for debugging and system transparency)
*/
bindTo(commComponent) {
console.log(`π AppPresentation: bindTo() called with:`, commComponent?.id || 'undefined');
this.logToUI(`π bindTo() called with: ${commComponent?.id || 'undefined'}`);
// π― STORE COMMUNICATION COMPONENT REFERENCE: Enable direct method calls from UI
this.commComponent = commComponent;
// π§ INTROSPECTION EVENT SUBSCRIPTIONS: The heart of observable UI patterns
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if (commComponent.eventBus) {
console.log(`π AppPresentation: Setting up event listeners for ${commComponent.id}`);
// π STATE CHANGE SUBSCRIPTION: Complete state object updates
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Triggered when AppComponent calls setState() with full or partial state updates
// Results in complete UI re-render to reflect new system status
commComponent.eventBus.on(`cmp:${commComponent.id}:_stateChanged`, (data) => {
console.log(`π― AppPresentation: Received _stateChanged event:`, data);
this.onStateChanged(data.state);
});
// π PROPERTY CHANGE SUBSCRIPTION: Individual property updates
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Triggered for each individual property when setState() modifies specific fields
// Enables granular UI updates and detailed change tracking
commComponent.eventBus.on(`cmp:${commComponent.id}:_propertyChanged`, (data) => {
console.log(`π― AppPresentation: Received _propertyChanged event:`, data);
this.onPropertyChanged(data.propertyName, data.newValue, data.oldValue);
});
// π‘ EVENT EMISSION SUBSCRIPTION: Component event tracking
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Triggered when AppComponent calls emitEvent() for inter-component communication
// Provides complete system transparency for debugging and monitoring
commComponent.eventBus.on(`cmp:${commComponent.id}:_eventEmitted`, (data) => {
console.log(`π― AppPresentation: Received _eventEmitted event:`, data);
// Could add UI indicators for events emitted, notifications, etc.
});
console.log(`β
AppPresentation: Event listeners registered for ${commComponent.id}`);
}
else {
console.warn(`β οΈ AppPresentation: No eventBus found on communication component!`);
}
// π¨ INITIAL RENDER: Display current state immediately after binding
this.render();
}
/**
* π¨ UI Rendering - State-Driven Dynamic Display
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* This method demonstrates REACTIVE UI PATTERNS where the presentation layer
* automatically updates based on observable state changes from the communication component.
* It transforms abstract state data into visual indicators and user-friendly status displays.
*
* RENDERING PATTERNS DEMONSTRATED:
* β’ State-to-UI Mapping: Convert boolean/array state to visual status indicators
* β’ Conditional UI Elements: Show/hide elements based on state values
* β’ Real-time Data Binding: Automatic UI updates when state changes via introspection
* β’ Presentation State Integration: Combine communication state with UI-specific data
*/
render() {
console.log(`π¨ AppPresentation: Rendering with current state:`, this.commComponent?.state);
// π AVOID INFINITE LOOPS: Don't call logToUI here as render() is called from onStateChanged()
// π STATE EXTRACTION: Get current observable state from communication component
const state = this.commComponent?.state || {};
// π― STATE-TO-UI MAPPING: Transform abstract state into visual indicators
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const coordinatorStatus = state.coordinatorReady ? 'β
Ready' : 'β³ Waiting';
const processingStatus = state.processingStarted ? 'π Processing' : 'βΈοΈ Idle';
const completionStatus = state.systemCompleted ? 'β
Complete' : 'π In Progress';
const jobCount = state.completedJobs?.length || 0;
// π SHADOW DOM CONTENT: Complete UI structure with state-driven content
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// This demonstrates how presentation layer renders dynamic content based on
// observable state from communication components, creating a real-time dashboard
this.shadowRoot.innerHTML = `
<style>
/* π¨ COMPONENT STYLING: Encapsulated styles for presentation layer */
:host { display: block; }
.app {
border: 2px solid #8b5cf6;
padding: 16px;
border-radius: 8px;
background: #1f2937;
color: #e5e7eb;
}
.app h3 { color: #8b5cf6; margin-top: 0; }
.status { margin: 8px 0; padding: 8px; background: #374151; border-radius: 4px; }
.status-item { margin: 4px 0; }
.logger {
background: #111827;
color: #10b981;
padding: 8px;
margin: 8px 0;
border-radius: 4px;
font-family: monospace;
font-size: 11px;
max-height: 150px;
overflow-y: auto;
border: 1px solid #374151;
}
.log-entry { margin: 2px 0; word-break: break-all; }
button {
background: #8b5cf6;
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
margin: 4px;
transition: background-color 0.2s;
}
button:hover { background: #7c3aed; }
.jobs { font-family: monospace; font-size: 12px; color: #fbbf24; }
</style>
<div class="app">
<h3>π― Observable App: ${this.commComponent?.id}</h3>
<!-- π REAL-TIME STATUS DISPLAY: Observable state visualized as status indicators -->
<div class="status">
<div class="status-item"><strong>Coordinator:</strong> ${coordinatorStatus}</div>
<div class="status-item"><strong>Processing:</strong> ${processingStatus}</div>
<div class="status-item"><strong>System:</strong> ${completionStatus}</div>
<div class="status-item"><strong>Completed Jobs:</strong> ${jobCount}</div>
${state.completedJobs?.length ? `<div class="jobs">Jobs: ${state.completedJobs.join(', ')}</div>` : ''}
</div>
<!-- π INTROSPECTION LOG: Real-time event and state change tracking -->
<div class="logger">
<div style="color: #fbbf24; font-weight: bold;">π APP PRESENTATION LOG:</div>
${this.logMessages.map(msg => `<div class="log-entry">${new Date().toLocaleTimeString()}: ${msg}</div>`).join('')}
</div>
<!-- π UI INTERACTION: Button triggers communication component method -->
<button onclick="this.getRootNode().host.startProcessing()">π Start Processing</button>
<slot></slot>
</div>
`;
}
/**
* π User Interaction Handler - UI-to-Communication Bridge
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* This method demonstrates the TWO-WAY COMMUNICATION pattern where UI interactions
* trigger communication component methods. The presentation layer acts as a bridge
* between user actions and the observable communication layer.
*
* INTERACTION FLOW:
* Button click β startProcessing() β commComponent.handleStartProcessing() β setState() β introspection events β UI updates
*/
startProcessing() {
console.log(`π AppPresentation: Start processing button clicked`);
this.logToUI('π Button clicked!');
// π― UI-TO-COMMUNICATION BRIDGE: Direct method call on communication component
// This triggers the observable state changes that will flow back to the UI via introspection events
this.commComponent?.handleStartProcessing();
}
/**
* π Presentation Logging - UI-Specific State Management
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* This method demonstrates PRESENTATION-SPECIFIC STATE management separate from
* communication component observable state. It maintains UI-only data (log messages)
* and provides targeted DOM updates to avoid infinite render loops.
*
* PRESENTATION STATE PATTERNS:
* β’ UI-Only Data: logMessages array is presentation-specific, not part of communication state
* β’ Targeted Updates: Update only the logger section to avoid triggering full re-renders
* β’ Circular Reference Prevention: Careful not to call this from render() method
*/
logToUI(message) {
// π PRESENTATION STATE UPDATE: Add to UI-specific log array
this.logMessages.push(message);
// π§Ή MEMORY MANAGEMENT: Prevent unlimited log accumulation
if (this.logMessages.length > 20)
this.logMessages.shift(); // Keep last 20 messages
// π― TARGETED DOM UPDATE: Update only logger section to avoid infinite render loops
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// This demonstrates how to update specific UI sections without triggering
// full re-renders that could cause circular update loops
if (this.shadowRoot?.innerHTML) {
const loggerDiv = this.shadowRoot.querySelector('.logger');
if (loggerDiv) {
loggerDiv.innerHTML = `
<div style="color: #fbbf24; font-weight: bold;">π APP PRESENTATION LOG:</div>
${this.logMessages.map(msg => `<div class="log-entry">${new Date().toLocaleTimeString()}: ${msg}</div>`).join('')}
`;
}
}
}
/**
* π State Change Handler - Observable State Response
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* This method is called automatically when the communication component's setState() method
* is called, demonstrating the REACTIVE UI PATTERN core to the Matrix Framework's
* introspection system. It shows how presentation layers stay synchronized with business logic.
*
* INTROSPECTION EVENT FLOW:
* setState() in AppComponent β _stateChanged event β this.onStateChanged() β render() β UI updates
*/
onStateChanged(newState) {
console.log(`π AppPresentation: State changed, re-rendering with state:`, newState);
// π LOG STATE CHANGE: Track state transitions for debugging transparency
this.logToUI(`π State changed: ${JSON.stringify(newState)}`);
// π¨ TRIGGER RE-RENDER: Update entire UI to reflect new state
// This demonstrates the core reactive pattern where state changes automatically update UI
this.render();
}
/**
* π Property Change Handler - Granular State Tracking
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* This method provides GRANULAR CHANGE TRACKING for individual properties within the
* observable state. It's called for each property that changes when setState() is called,
* enabling detailed monitoring and potentially targeted UI updates.
*
* GRANULAR INTROSPECTION PATTERN:
* setState({prop1: val1, prop2: val2}) β triggers onPropertyChanged() twice β individual property tracking
*/
onPropertyChanged(propertyName, newValue, oldValue) {
console.log(`π AppPresentation: Property ${propertyName} changed from ${oldValue} to ${newValue}, re-rendering`);
// π LOG PROPERTY CHANGE: Track individual property changes for detailed debugging
this.logToUI(`π Property ${propertyName}: ${oldValue} β ${newValue}`);
// π¨ TRIGGER RE-RENDER: Could be optimized to update only affected UI sections
// This demonstrates the basic reactive pattern; advanced implementations might
// update only the UI elements affected by specific property changes
this.render();
}
/**
* π DOM Connection Lifecycle - Web Component Integration
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* This Web Components lifecycle method is called when the element is added to the DOM.
* It demonstrates the integration between Matrix Framework components and standard
* Web Components lifecycle, ensuring proper initialization and rendering.
*
* WEB COMPONENT LIFECYCLE INTEGRATION:
* Element creation β constructor() β bindTo() β connectedCallback() β DOM ready
*/
connectedCallback() {
console.log('π AppPresentationElement: Connected to DOM');
// π LOG DOM CONNECTION: Track element lifecycle for debugging
this.logToUI('π Connected to DOM');
// π¨ ENSURE RENDERING: Make sure UI is displayed when element enters DOM
// This provides a safety net for rendering in case bindTo() was called before DOM connection
this.render();
}
}