- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
1355 lines
35 KiB
Markdown
1355 lines
35 KiB
Markdown
# LiveComponents Events Reference
|
|
|
|
Complete reference for all client-side events and lifecycle hooks with examples and best practices.
|
|
|
|
## Table of Contents
|
|
|
|
- [Lifecycle Events](#lifecycle-events)
|
|
- [livecomponent:init](#livecomponentinit)
|
|
- [livecomponent:mounted](#livecomponentmounted)
|
|
- [livecomponent:updated](#livecomponentupdated)
|
|
- [livecomponent:unmounted](#livecomponentunmounted)
|
|
- [Action Events](#action-events)
|
|
- [livecomponent:action-start](#livecomponentaction-start)
|
|
- [livecomponent:action-executed](#livecomponentaction-executed)
|
|
- [livecomponent:action-failed](#livecomponentaction-failed)
|
|
- [State Events](#state-events)
|
|
- [livecomponent:state-changed](#livecomponentstate-changed)
|
|
- [livecomponent:prop-updated](#livecomponentprop-updated)
|
|
- [Network Events](#network-events)
|
|
- [livecomponent:request-start](#livecomponentrequest-start)
|
|
- [livecomponent:request-complete](#livecomponentrequest-complete)
|
|
- [livecomponent:request-failed](#livecomponentrequest-failed)
|
|
- [livecomponent:batch-dispatched](#livecomponentbatch-dispatched)
|
|
- [Rendering Events](#rendering-events)
|
|
- [livecomponent:render-start](#livecomponentrender-start)
|
|
- [livecomponent:render-complete](#livecomponentrender-complete)
|
|
- [livecomponent:fragment-updated](#livecomponentfragment-updated)
|
|
- [SSE Events](#sse-events)
|
|
- [livecomponent:sse-connected](#livecomponentsse-connected)
|
|
- [livecomponent:sse-disconnected](#livecomponentsse-disconnected)
|
|
- [livecomponent:sse-message](#livecomponentsse-message)
|
|
- [livecomponent:sse-error](#livecomponentsse-error)
|
|
- [Upload Events](#upload-events)
|
|
- [livecomponent:upload-start](#livecomponentupload-start)
|
|
- [livecomponent:upload-progress](#livecomponentupload-progress)
|
|
- [livecomponent:upload-complete](#livecomponentupload-complete)
|
|
- [livecomponent:upload-failed](#livecomponentupload-failed)
|
|
- [Error Events](#error-events)
|
|
- [livecomponent:error](#livecomponenterror)
|
|
- [livecomponent:validation-error](#livecomponentvalidation-error)
|
|
- [livecomponent:rate-limit-exceeded](#livecomponentrate-limit-exceeded)
|
|
|
|
---
|
|
|
|
## Lifecycle Events
|
|
|
|
### livecomponent:init
|
|
|
|
**Fired**: When a LiveComponent is discovered in the DOM but before initialization.
|
|
|
|
**Use Case**: Prepare component before mounting, modify initial state.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Unique component ID
|
|
element: HTMLElement; // Component root element
|
|
props: object; // Initial component props
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:init', (event) => {
|
|
const { componentId, element, props } = event.detail;
|
|
|
|
console.log('Initializing component:', componentId);
|
|
|
|
// Modify initial props
|
|
if (props.theme === undefined) {
|
|
props.theme = localStorage.getItem('theme') || 'light';
|
|
}
|
|
|
|
// Add custom data attributes
|
|
element.dataset.initialized = Date.now();
|
|
});
|
|
```
|
|
|
|
**Common Use Cases**:
|
|
- Restore state from localStorage
|
|
- Set default props from user preferences
|
|
- Initialize third-party libraries
|
|
- Track component initialization analytics
|
|
|
|
---
|
|
|
|
### livecomponent:mounted
|
|
|
|
**Fired**: After a LiveComponent is fully mounted and ready for interaction.
|
|
|
|
**Use Case**: Initialize JavaScript behaviors, start timers, load external data.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Unique component ID
|
|
element: HTMLElement; // Component root element
|
|
component: Component; // Component instance
|
|
props: object; // Current component props
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:mounted', (event) => {
|
|
const { componentId, element, component } = event.detail;
|
|
|
|
// Initialize tooltips
|
|
const tooltips = element.querySelectorAll('[data-tooltip]');
|
|
tooltips.forEach(el => new Tooltip(el));
|
|
|
|
// Start auto-refresh timer
|
|
if (component.config.autoRefresh) {
|
|
setInterval(() => {
|
|
component.executeAction('refresh');
|
|
}, component.config.refreshInterval || 30000);
|
|
}
|
|
|
|
// Track mount analytics
|
|
analytics.track('component_mounted', {
|
|
component_type: componentId,
|
|
mount_time: Date.now()
|
|
});
|
|
});
|
|
```
|
|
|
|
**Common Use Cases**:
|
|
- Initialize third-party plugins (charts, maps, editors)
|
|
- Start polling/auto-refresh timers
|
|
- Bind custom event listeners
|
|
- Load deferred content
|
|
- Track analytics
|
|
|
|
---
|
|
|
|
### livecomponent:updated
|
|
|
|
**Fired**: After a LiveComponent has been updated (re-rendered).
|
|
|
|
**Use Case**: React to state changes, update third-party integrations, track updates.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Unique component ID
|
|
element: HTMLElement; // Component root element
|
|
component: Component; // Component instance
|
|
previousProps: object; // Props before update
|
|
currentProps: object; // Props after update
|
|
changedProps: string[]; // Names of changed props
|
|
fragments: string[]; // Updated fragments (if partial render)
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:updated', (event) => {
|
|
const { componentId, previousProps, currentProps, changedProps, fragments } = event.detail;
|
|
|
|
// Re-initialize elements in updated fragments
|
|
if (fragments.includes('chart-container')) {
|
|
const chartEl = document.querySelector('[data-lc-fragment="chart-container"]');
|
|
initializeChart(chartEl, currentProps.chartData);
|
|
}
|
|
|
|
// Track specific prop changes
|
|
if (changedProps.includes('status')) {
|
|
console.log('Status changed:', previousProps.status, '→', currentProps.status);
|
|
|
|
if (currentProps.status === 'completed') {
|
|
showSuccessNotification();
|
|
}
|
|
}
|
|
|
|
// Update document title
|
|
if (changedProps.includes('unreadCount')) {
|
|
document.title = currentProps.unreadCount > 0
|
|
? `(${currentProps.unreadCount}) Messages`
|
|
: 'Messages';
|
|
}
|
|
});
|
|
```
|
|
|
|
**Common Use Cases**:
|
|
- Re-initialize third-party components in updated fragments
|
|
- Trigger animations on state changes
|
|
- Update browser title/favicon based on state
|
|
- Track state change analytics
|
|
- Sync with external systems
|
|
|
|
---
|
|
|
|
### livecomponent:unmounted
|
|
|
|
**Fired**: Before a LiveComponent is removed from the DOM.
|
|
|
|
**Use Case**: Cleanup timers, remove event listeners, save state.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Unique component ID
|
|
element: HTMLElement; // Component root element (about to be removed)
|
|
component: Component; // Component instance
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:unmounted', (event) => {
|
|
const { componentId, element, component } = event.detail;
|
|
|
|
// Save state to localStorage
|
|
localStorage.setItem(`component-${componentId}`, JSON.stringify(component.props));
|
|
|
|
// Clear timers
|
|
if (window.componentTimers[componentId]) {
|
|
clearInterval(window.componentTimers[componentId]);
|
|
delete window.componentTimers[componentId];
|
|
}
|
|
|
|
// Destroy third-party instances
|
|
if (window.chartInstances[componentId]) {
|
|
window.chartInstances[componentId].destroy();
|
|
delete window.chartInstances[componentId];
|
|
}
|
|
|
|
// Track unmount analytics
|
|
analytics.track('component_unmounted', {
|
|
component_type: componentId,
|
|
lifetime_ms: Date.now() - parseInt(element.dataset.initialized)
|
|
});
|
|
});
|
|
```
|
|
|
|
**Common Use Cases**:
|
|
- Save component state before removal
|
|
- Clear timers and intervals
|
|
- Remove global event listeners
|
|
- Destroy third-party plugin instances
|
|
- Cancel pending network requests
|
|
- Track component lifetime analytics
|
|
|
|
---
|
|
|
|
## Action Events
|
|
|
|
### livecomponent:action-start
|
|
|
|
**Fired**: When a LiveAction is triggered (before server request).
|
|
|
|
**Use Case**: Show loading states, disable buttons, track action starts.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
action: string; // Action name
|
|
params: object; // Action parameters
|
|
element: HTMLElement; // Element that triggered action
|
|
timestamp: number; // Start timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:action-start', (event) => {
|
|
const { componentId, action, params, element } = event.detail;
|
|
|
|
// Show loading state
|
|
element.classList.add('loading');
|
|
element.disabled = true;
|
|
|
|
// Show loading spinner for long-running actions
|
|
if (['generateReport', 'processPayment'].includes(action)) {
|
|
showLoadingSpinner(`Processing ${action}...`);
|
|
}
|
|
|
|
// Track action starts
|
|
analytics.track('action_started', {
|
|
component: componentId,
|
|
action: action,
|
|
params: params
|
|
});
|
|
});
|
|
```
|
|
|
|
**Common Use Cases**:
|
|
- Show loading indicators
|
|
- Disable form buttons to prevent double-submission
|
|
- Show progress overlays
|
|
- Track action analytics
|
|
- Optimistic UI updates
|
|
|
|
---
|
|
|
|
### livecomponent:action-executed
|
|
|
|
**Fired**: After a LiveAction completes successfully.
|
|
|
|
**Use Case**: Hide loading states, show success messages, track performance.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
action: string; // Action name
|
|
params: object; // Action parameters
|
|
result: any; // Action result
|
|
duration: number; // Execution time (ms)
|
|
timestamp: number; // Completion timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:action-executed', (event) => {
|
|
const { componentId, action, result, duration } = event.detail;
|
|
|
|
// Hide loading states
|
|
hideLoadingSpinner();
|
|
|
|
// Show success message for specific actions
|
|
if (action === 'saveForm') {
|
|
showToast('Changes saved successfully!', 'success');
|
|
}
|
|
|
|
// Track slow actions
|
|
if (duration > 1000) {
|
|
console.warn(`Slow action: ${action} took ${duration}ms`);
|
|
analytics.track('slow_action', {
|
|
component: componentId,
|
|
action: action,
|
|
duration: duration
|
|
});
|
|
}
|
|
|
|
// Update badge counts
|
|
if (action === 'markAsRead') {
|
|
const badge = document.querySelector('.unread-badge');
|
|
const count = parseInt(badge.textContent) - 1;
|
|
badge.textContent = count > 0 ? count : '';
|
|
}
|
|
});
|
|
```
|
|
|
|
**Common Use Cases**:
|
|
- Hide loading indicators
|
|
- Show success notifications
|
|
- Update UI elements outside component
|
|
- Track action performance
|
|
- Trigger follow-up actions
|
|
|
|
---
|
|
|
|
### livecomponent:action-failed
|
|
|
|
**Fired**: When a LiveAction fails (validation, authorization, or server error).
|
|
|
|
**Use Case**: Show error messages, re-enable forms, track failures.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
action: string; // Action name
|
|
params: object; // Action parameters
|
|
error: {
|
|
message: string; // Error message
|
|
code: string; // Error code
|
|
details: object; // Additional error details
|
|
};
|
|
duration: number; // Time before failure (ms)
|
|
timestamp: number; // Failure timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:action-failed', (event) => {
|
|
const { componentId, action, error, element } = event.detail;
|
|
|
|
// Re-enable form elements
|
|
if (element) {
|
|
element.classList.remove('loading');
|
|
element.disabled = false;
|
|
}
|
|
|
|
// Show error message
|
|
if (error.code === 'VALIDATION_ERROR') {
|
|
showValidationErrors(error.details);
|
|
} else if (error.code === 'RATE_LIMIT_EXCEEDED') {
|
|
showToast(`Too many requests. Please wait ${error.details.retryAfter} seconds.`, 'warning');
|
|
} else {
|
|
showToast(error.message || 'An error occurred', 'error');
|
|
}
|
|
|
|
// Track failures
|
|
analytics.track('action_failed', {
|
|
component: componentId,
|
|
action: action,
|
|
error_code: error.code,
|
|
error_message: error.message
|
|
});
|
|
});
|
|
```
|
|
|
|
**Common Use Cases**:
|
|
- Display error messages
|
|
- Re-enable disabled form elements
|
|
- Show validation errors
|
|
- Track error rates
|
|
- Trigger error recovery flows
|
|
|
|
---
|
|
|
|
## State Events
|
|
|
|
### livecomponent:state-changed
|
|
|
|
**Fired**: When component state changes (any LiveProp update).
|
|
|
|
**Use Case**: React to any state change, sync with external systems.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
previousState: object; // State before change
|
|
currentState: object; // State after change
|
|
changedProps: string[]; // Names of changed props
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:state-changed', (event) => {
|
|
const { componentId, previousState, currentState, changedProps } = event.detail;
|
|
|
|
// Sync with localStorage
|
|
localStorage.setItem(`state-${componentId}`, JSON.stringify(currentState));
|
|
|
|
// Track state changes
|
|
changedProps.forEach(prop => {
|
|
console.log(`${prop} changed:`, previousState[prop], '→', currentState[prop]);
|
|
});
|
|
|
|
// Trigger side effects
|
|
if (changedProps.includes('selectedItems')) {
|
|
updateBulkActionBar(currentState.selectedItems);
|
|
}
|
|
});
|
|
```
|
|
|
|
**Common Use Cases**:
|
|
- Persist state to localStorage
|
|
- Sync with external state management
|
|
- Update derived UI elements
|
|
- Track state change analytics
|
|
- Debug state changes
|
|
|
|
---
|
|
|
|
### livecomponent:prop-updated
|
|
|
|
**Fired**: When a specific LiveProp is updated.
|
|
|
|
**Use Case**: React to specific property changes, trigger dependent updates.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
propName: string; // Updated property name
|
|
previousValue: any; // Value before update
|
|
currentValue: any; // Value after update
|
|
source: 'server' | 'client' | 'optimistic'; // Update source
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:prop-updated', (event) => {
|
|
const { componentId, propName, previousValue, currentValue, source } = event.detail;
|
|
|
|
// React to specific prop changes
|
|
if (propName === 'theme') {
|
|
document.body.classList.remove(`theme-${previousValue}`);
|
|
document.body.classList.add(`theme-${currentValue}`);
|
|
localStorage.setItem('theme', currentValue);
|
|
}
|
|
|
|
if (propName === 'count' && source === 'optimistic') {
|
|
// Animate optimistic updates
|
|
animateNumberChange(previousValue, currentValue);
|
|
}
|
|
|
|
if (propName === 'sortOrder') {
|
|
// Re-sort table when sort order changes
|
|
sortTable(currentValue);
|
|
}
|
|
});
|
|
```
|
|
|
|
**Common Use Cases**:
|
|
- Trigger animations on specific prop changes
|
|
- Update global UI state (theme, locale)
|
|
- Sync specific props with localStorage
|
|
- Track important prop changes
|
|
- Trigger dependent updates
|
|
|
|
---
|
|
|
|
## Network Events
|
|
|
|
### livecomponent:request-start
|
|
|
|
**Fired**: When a network request starts (action or state sync).
|
|
|
|
**Use Case**: Show global loading indicators, track request starts.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
requestId: string; // Unique request ID
|
|
type: 'action' | 'sync'; // Request type
|
|
payload: object; // Request payload
|
|
timestamp: number; // Start timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
let activeRequests = 0;
|
|
|
|
window.addEventListener('livecomponent:request-start', (event) => {
|
|
const { componentId, requestId, type } = event.detail;
|
|
|
|
activeRequests++;
|
|
|
|
// Show global loading indicator
|
|
if (activeRequests === 1) {
|
|
document.body.classList.add('loading');
|
|
}
|
|
|
|
// Track request
|
|
console.log(`Request ${requestId} started for ${componentId} (type: ${type})`);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:request-complete
|
|
|
|
**Fired**: When a network request completes successfully.
|
|
|
|
**Use Case**: Hide loading indicators, track request performance.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
requestId: string; // Unique request ID
|
|
type: 'action' | 'sync'; // Request type
|
|
response: object; // Server response
|
|
duration: number; // Request duration (ms)
|
|
timestamp: number; // Completion timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:request-complete', (event) => {
|
|
const { componentId, requestId, duration } = event.detail;
|
|
|
|
activeRequests--;
|
|
|
|
// Hide global loading indicator
|
|
if (activeRequests === 0) {
|
|
document.body.classList.remove('loading');
|
|
}
|
|
|
|
// Track slow requests
|
|
if (duration > 1000) {
|
|
console.warn(`Slow request: ${requestId} took ${duration}ms`);
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:request-failed
|
|
|
|
**Fired**: When a network request fails.
|
|
|
|
**Use Case**: Handle network errors, retry logic, track failures.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
requestId: string; // Unique request ID
|
|
type: 'action' | 'sync'; // Request type
|
|
error: {
|
|
message: string; // Error message
|
|
code: string; // Error code
|
|
status: number; // HTTP status code
|
|
};
|
|
duration: number; // Time before failure (ms)
|
|
timestamp: number; // Failure timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:request-failed', (event) => {
|
|
const { componentId, requestId, error, duration } = event.detail;
|
|
|
|
activeRequests--;
|
|
|
|
// Hide global loading indicator
|
|
if (activeRequests === 0) {
|
|
document.body.classList.remove('loading');
|
|
}
|
|
|
|
// Handle network errors
|
|
if (error.status === 0) {
|
|
showToast('Network error. Please check your connection.', 'error');
|
|
} else if (error.status === 429) {
|
|
showToast('Too many requests. Please slow down.', 'warning');
|
|
} else if (error.status >= 500) {
|
|
showToast('Server error. Please try again later.', 'error');
|
|
}
|
|
|
|
// Track failures
|
|
analytics.track('request_failed', {
|
|
component: componentId,
|
|
request_id: requestId,
|
|
error_code: error.code,
|
|
status: error.status
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:batch-dispatched
|
|
|
|
**Fired**: When batched requests are sent to server.
|
|
|
|
**Use Case**: Track batching efficiency, debug batch behavior.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
batchId: string; // Unique batch ID
|
|
actions: Array<{ // Batched actions
|
|
componentId: string;
|
|
action: string;
|
|
params: object;
|
|
}>;
|
|
batchSize: number; // Number of actions in batch
|
|
timestamp: number; // Dispatch timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:batch-dispatched', (event) => {
|
|
const { batchId, actions, batchSize } = event.detail;
|
|
|
|
console.log(`Batch ${batchId} dispatched with ${batchSize} actions:`, actions);
|
|
|
|
// Track batching efficiency
|
|
analytics.track('batch_dispatched', {
|
|
batch_id: batchId,
|
|
batch_size: batchSize,
|
|
components: [...new Set(actions.map(a => a.componentId))]
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Rendering Events
|
|
|
|
### livecomponent:render-start
|
|
|
|
**Fired**: Before component re-rendering begins.
|
|
|
|
**Use Case**: Save scroll position, prepare for render.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
fragments: string[]; // Fragments to be rendered
|
|
timestamp: number; // Render start timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
const scrollPositions = new Map();
|
|
|
|
window.addEventListener('livecomponent:render-start', (event) => {
|
|
const { componentId, fragments } = event.detail;
|
|
|
|
// Save scroll positions of fragments
|
|
fragments.forEach(fragmentName => {
|
|
const fragment = document.querySelector(`[data-lc-fragment="${fragmentName}"]`);
|
|
if (fragment) {
|
|
scrollPositions.set(fragmentName, {
|
|
scrollTop: fragment.scrollTop,
|
|
scrollLeft: fragment.scrollLeft
|
|
});
|
|
}
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:render-complete
|
|
|
|
**Fired**: After component re-rendering completes.
|
|
|
|
**Use Case**: Restore scroll position, re-initialize JavaScript.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
fragments: string[]; // Rendered fragments
|
|
duration: number; // Render duration (ms)
|
|
timestamp: number; // Render complete timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:render-complete', (event) => {
|
|
const { componentId, fragments, duration } = event.detail;
|
|
|
|
// Restore scroll positions
|
|
fragments.forEach(fragmentName => {
|
|
const fragment = document.querySelector(`[data-lc-fragment="${fragmentName}"]`);
|
|
const savedPosition = scrollPositions.get(fragmentName);
|
|
|
|
if (fragment && savedPosition) {
|
|
fragment.scrollTop = savedPosition.scrollTop;
|
|
fragment.scrollLeft = savedPosition.scrollLeft;
|
|
}
|
|
});
|
|
|
|
// Re-initialize JavaScript in rendered fragments
|
|
fragments.forEach(fragmentName => {
|
|
const fragment = document.querySelector(`[data-lc-fragment="${fragmentName}"]`);
|
|
initializeFragment(fragment);
|
|
});
|
|
|
|
// Track slow renders
|
|
if (duration > 100) {
|
|
console.warn(`Slow render: ${componentId} took ${duration}ms`);
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:fragment-updated
|
|
|
|
**Fired**: When a specific fragment is updated.
|
|
|
|
**Use Case**: Re-initialize fragment-specific JavaScript, track updates.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
fragmentName: string; // Updated fragment name
|
|
element: HTMLElement; // Fragment element
|
|
timestamp: number; // Update timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:fragment-updated', (event) => {
|
|
const { componentId, fragmentName, element } = event.detail;
|
|
|
|
// Re-initialize specific fragment behaviors
|
|
if (fragmentName === 'chart-container') {
|
|
const chartEl = element.querySelector('.chart');
|
|
if (chartEl) {
|
|
initializeChart(chartEl);
|
|
}
|
|
}
|
|
|
|
if (fragmentName === 'image-gallery') {
|
|
const images = element.querySelectorAll('img[data-lazy]');
|
|
lazyLoadImages(images);
|
|
}
|
|
|
|
// Track fragment updates
|
|
analytics.track('fragment_updated', {
|
|
component: componentId,
|
|
fragment: fragmentName
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## SSE Events
|
|
|
|
### livecomponent:sse-connected
|
|
|
|
**Fired**: When SSE connection is established.
|
|
|
|
**Use Case**: Show online status, enable real-time features.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
url: string; // SSE endpoint URL
|
|
timestamp: number; // Connection timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:sse-connected', (event) => {
|
|
const { componentId, url } = event.detail;
|
|
|
|
console.log(`SSE connected for ${componentId} at ${url}`);
|
|
|
|
// Show online indicator
|
|
const indicator = document.querySelector('.connection-status');
|
|
indicator.classList.remove('offline');
|
|
indicator.classList.add('online');
|
|
indicator.textContent = 'Connected';
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:sse-disconnected
|
|
|
|
**Fired**: When SSE connection is lost.
|
|
|
|
**Use Case**: Show offline status, disable real-time features.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
reason: string; // Disconnection reason
|
|
willReconnect: boolean; // Auto-reconnect enabled
|
|
timestamp: number; // Disconnection timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:sse-disconnected', (event) => {
|
|
const { componentId, reason, willReconnect } = event.detail;
|
|
|
|
console.warn(`SSE disconnected for ${componentId}: ${reason}`);
|
|
|
|
// Show offline indicator
|
|
const indicator = document.querySelector('.connection-status');
|
|
indicator.classList.remove('online');
|
|
indicator.classList.add('offline');
|
|
indicator.textContent = willReconnect ? 'Reconnecting...' : 'Offline';
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:sse-message
|
|
|
|
**Fired**: When an SSE message is received.
|
|
|
|
**Use Case**: Handle real-time updates, show notifications.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
event: string; // SSE event type
|
|
data: any; // Message data
|
|
timestamp: number; // Message timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:sse-message', (event) => {
|
|
const { componentId, event: eventType, data } = event.detail;
|
|
|
|
if (eventType === 'notification') {
|
|
showNotification(data.title, data.body);
|
|
}
|
|
|
|
if (eventType === 'user-joined') {
|
|
updateOnlineUsers(data.userId, data.userName);
|
|
}
|
|
|
|
if (eventType === 'price-update') {
|
|
animatePriceChange(data.symbol, data.price);
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:sse-error
|
|
|
|
**Fired**: When an SSE error occurs.
|
|
|
|
**Use Case**: Handle SSE errors, implement fallback.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
error: {
|
|
message: string; // Error message
|
|
code: string; // Error code
|
|
};
|
|
timestamp: number; // Error timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:sse-error', (event) => {
|
|
const { componentId, error } = event.detail;
|
|
|
|
console.error(`SSE error for ${componentId}:`, error.message);
|
|
|
|
// Fallback to polling
|
|
if (!window.pollingTimers[componentId]) {
|
|
window.pollingTimers[componentId] = setInterval(() => {
|
|
LiveComponent.getComponent(componentId).executeAction('refresh');
|
|
}, 5000);
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Upload Events
|
|
|
|
### livecomponent:upload-start
|
|
|
|
**Fired**: When file upload starts.
|
|
|
|
**Use Case**: Show upload progress UI.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
uploadId: string; // Unique upload ID
|
|
file: {
|
|
name: string;
|
|
size: number;
|
|
type: string;
|
|
};
|
|
chunks: number; // Total number of chunks
|
|
timestamp: number; // Upload start timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:upload-start', (event) => {
|
|
const { uploadId, file, chunks } = event.detail;
|
|
|
|
// Create progress UI
|
|
const progressEl = document.createElement('div');
|
|
progressEl.id = `upload-${uploadId}`;
|
|
progressEl.innerHTML = `
|
|
<div class="upload-item">
|
|
<span class="filename">${file.name}</span>
|
|
<div class="progress-bar">
|
|
<div class="progress" style="width: 0%"></div>
|
|
</div>
|
|
<span class="status">Uploading (0/${chunks} chunks)...</span>
|
|
</div>
|
|
`;
|
|
document.querySelector('.upload-queue').appendChild(progressEl);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:upload-progress
|
|
|
|
**Fired**: During file upload (per chunk).
|
|
|
|
**Use Case**: Update progress bar, show upload speed.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
uploadId: string; // Upload ID
|
|
progress: {
|
|
loaded: number; // Bytes uploaded
|
|
total: number; // Total bytes
|
|
percentage: number; // Progress percentage
|
|
chunk: number; // Current chunk
|
|
totalChunks: number; // Total chunks
|
|
};
|
|
timestamp: number; // Progress update timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:upload-progress', (event) => {
|
|
const { uploadId, progress } = event.detail;
|
|
|
|
// Update progress bar
|
|
const progressEl = document.querySelector(`#upload-${uploadId}`);
|
|
const progressBar = progressEl.querySelector('.progress');
|
|
const statusEl = progressEl.querySelector('.status');
|
|
|
|
progressBar.style.width = `${progress.percentage}%`;
|
|
statusEl.textContent = `Uploading (${progress.chunk}/${progress.totalChunks} chunks) - ${progress.percentage}%`;
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:upload-complete
|
|
|
|
**Fired**: When file upload completes successfully.
|
|
|
|
**Use Case**: Show success message, clean up progress UI.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
uploadId: string; // Upload ID
|
|
file: {
|
|
name: string;
|
|
size: number;
|
|
url: string; // Uploaded file URL
|
|
};
|
|
duration: number; // Upload duration (ms)
|
|
timestamp: number; // Completion timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:upload-complete', (event) => {
|
|
const { uploadId, file, duration } = event.detail;
|
|
|
|
// Update progress UI
|
|
const progressEl = document.querySelector(`#upload-${uploadId}`);
|
|
const statusEl = progressEl.querySelector('.status');
|
|
|
|
statusEl.textContent = `✓ Complete (${(duration / 1000).toFixed(1)}s)`;
|
|
statusEl.classList.add('success');
|
|
|
|
// Remove after 3 seconds
|
|
setTimeout(() => progressEl.remove(), 3000);
|
|
|
|
// Show success toast
|
|
showToast(`${file.name} uploaded successfully!`, 'success');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:upload-failed
|
|
|
|
**Fired**: When file upload fails.
|
|
|
|
**Use Case**: Show error message, allow retry.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
uploadId: string; // Upload ID
|
|
file: {
|
|
name: string;
|
|
size: number;
|
|
};
|
|
error: {
|
|
message: string; // Error message
|
|
code: string; // Error code
|
|
chunk: number; // Failed chunk (if applicable)
|
|
};
|
|
timestamp: number; // Failure timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:upload-failed', (event) => {
|
|
const { uploadId, file, error } = event.detail;
|
|
|
|
// Update progress UI
|
|
const progressEl = document.querySelector(`#upload-${uploadId}`);
|
|
const statusEl = progressEl.querySelector('.status');
|
|
|
|
statusEl.textContent = `✗ Failed: ${error.message}`;
|
|
statusEl.classList.add('error');
|
|
|
|
// Add retry button
|
|
const retryBtn = document.createElement('button');
|
|
retryBtn.textContent = 'Retry';
|
|
retryBtn.onclick = () => retryUpload(uploadId);
|
|
progressEl.appendChild(retryBtn);
|
|
|
|
// Show error toast
|
|
showToast(`Failed to upload ${file.name}: ${error.message}`, 'error');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Error Events
|
|
|
|
### livecomponent:error
|
|
|
|
**Fired**: When a general LiveComponent error occurs.
|
|
|
|
**Use Case**: Global error handling, error tracking.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
error: {
|
|
message: string; // Error message
|
|
code: string; // Error code
|
|
stack: string; // Stack trace
|
|
};
|
|
context: string; // Error context ('render', 'action', 'network', etc.)
|
|
timestamp: number; // Error timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:error', (event) => {
|
|
const { componentId, error, context } = event.detail;
|
|
|
|
console.error(`LiveComponent error in ${componentId} (${context}):`, error);
|
|
|
|
// Send to error tracking service
|
|
if (window.errorTracker) {
|
|
window.errorTracker.captureException(error, {
|
|
component: componentId,
|
|
context: context
|
|
});
|
|
}
|
|
|
|
// Show generic error message to user
|
|
showToast('An unexpected error occurred. Please refresh the page.', 'error');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:validation-error
|
|
|
|
**Fired**: When server-side validation fails.
|
|
|
|
**Use Case**: Display validation errors, focus first error.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
action: string; // Action that failed validation
|
|
errors: Record<string, string[]>; // Validation errors by field
|
|
timestamp: number; // Error timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:validation-error', (event) => {
|
|
const { componentId, action, errors } = event.detail;
|
|
|
|
// Display errors next to fields
|
|
Object.entries(errors).forEach(([field, messages]) => {
|
|
const input = document.querySelector(`[name="${field}"]`);
|
|
const errorEl = input.parentElement.querySelector('.error-message');
|
|
|
|
if (errorEl) {
|
|
errorEl.textContent = messages[0];
|
|
errorEl.style.display = 'block';
|
|
}
|
|
|
|
input.classList.add('invalid');
|
|
});
|
|
|
|
// Focus first invalid field
|
|
const firstInvalid = document.querySelector('.invalid');
|
|
if (firstInvalid) {
|
|
firstInvalid.focus();
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### livecomponent:rate-limit-exceeded
|
|
|
|
**Fired**: When rate limit is exceeded for an action.
|
|
|
|
**Use Case**: Show rate limit message, display countdown.
|
|
|
|
**Event Detail**:
|
|
```typescript
|
|
{
|
|
componentId: string; // Component ID
|
|
action: string; // Rate-limited action
|
|
retryAfter: number; // Seconds until retry allowed
|
|
timestamp: number; // Rate limit timestamp
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:rate-limit-exceeded', (event) => {
|
|
const { componentId, action, retryAfter } = event.detail;
|
|
|
|
// Show countdown toast
|
|
let remaining = retryAfter;
|
|
const toastId = showToast(
|
|
`Rate limit exceeded. Please wait ${remaining} seconds.`,
|
|
'warning',
|
|
{ duration: retryAfter * 1000 }
|
|
);
|
|
|
|
const countdown = setInterval(() => {
|
|
remaining--;
|
|
if (remaining > 0) {
|
|
updateToast(toastId, `Rate limit exceeded. Please wait ${remaining} seconds.`);
|
|
} else {
|
|
clearInterval(countdown);
|
|
updateToast(toastId, 'You can try again now!');
|
|
}
|
|
}, 1000);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Event Patterns
|
|
|
|
### Event Delegation
|
|
|
|
```javascript
|
|
// Instead of listening on each component
|
|
document.addEventListener('livecomponent:action-executed', (event) => {
|
|
if (event.detail.componentId.startsWith('notification-')) {
|
|
updateNotificationBadge();
|
|
}
|
|
});
|
|
```
|
|
|
|
### Debounced Event Handlers
|
|
|
|
```javascript
|
|
import { debounce } from './utils';
|
|
|
|
window.addEventListener('livecomponent:prop-updated',
|
|
debounce((event) => {
|
|
if (event.detail.propName === 'searchQuery') {
|
|
updateSearchResults(event.detail.currentValue);
|
|
}
|
|
}, 300)
|
|
);
|
|
```
|
|
|
|
### Global Loading Indicator
|
|
|
|
```javascript
|
|
let activeRequests = 0;
|
|
|
|
window.addEventListener('livecomponent:request-start', () => {
|
|
activeRequests++;
|
|
if (activeRequests === 1) {
|
|
document.body.classList.add('loading');
|
|
}
|
|
});
|
|
|
|
window.addEventListener('livecomponent:request-complete', () => {
|
|
activeRequests--;
|
|
if (activeRequests === 0) {
|
|
document.body.classList.remove('loading');
|
|
}
|
|
});
|
|
|
|
window.addEventListener('livecomponent:request-failed', () => {
|
|
activeRequests--;
|
|
if (activeRequests === 0) {
|
|
document.body.classList.remove('loading');
|
|
}
|
|
});
|
|
```
|
|
|
|
### Performance Monitoring
|
|
|
|
```javascript
|
|
window.addEventListener('livecomponent:action-executed', (event) => {
|
|
const { action, duration } = event.detail;
|
|
|
|
if (duration > 1000) {
|
|
console.warn(`Slow action: ${action} (${duration}ms)`);
|
|
}
|
|
|
|
// Track to analytics
|
|
analytics.track('action_performance', {
|
|
action: action,
|
|
duration: duration
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
1. **Use Event Delegation**: Listen on `window` instead of individual elements
|
|
2. **Cleanup Listeners**: Remove listeners in `unmounted` event
|
|
3. **Debounce/Throttle**: Use debouncing for high-frequency events
|
|
4. **Error Handling**: Always handle errors gracefully
|
|
5. **Performance**: Minimize work in event handlers
|
|
6. **Analytics**: Track important events for monitoring
|
|
7. **User Feedback**: Provide visual feedback for all events
|
|
8. **Accessibility**: Announce important events to screen readers
|
|
|
|
---
|
|
|
|
For more examples and patterns, see:
|
|
- [Advanced Features Guide](advanced-features.md)
|
|
- [Troubleshooting Guide](troubleshooting.md)
|
|
- [API Reference](api-reference.md)
|