Data Loading
This guide explains the data loading mechanism in Myop components, from initial state to fully rendered UI.
Loading Lifecycle
Step 1: Initial State (Loader Visible)
When the component first loads, it shows a loading indicator while waiting for data:
/* Loader is visible by default */
#loader-container {
display: flex;
}
/* App is hidden by default */
#app-root {
opacity: 0;
}
At this point:
- Loader is visible (
#loader-containershown) - App is hidden (
#app-roothasopacity: 0) - Component waits for data via
window.myop_init_interface()
Step 2: Host Provides Data
The host application calls window.myop_init_interface() with data:
// From the host application
window.myop_init_interface({
components: [
{
id: "comp-1",
name: "AuthService",
developerName: "Alice Green",
lastEditedDate: "2024-03-15T10:30:00.000Z",
lastVersionName: "v1.2.3",
environments: ["prod", "dev"],
tags: ["backend", "security"]
},
// ... more components
]
});
Step 3: Component Processes Data
Inside myop_init_interface(), the component:
- Stores and transforms data
- Extracts values for filters/aggregations
- Renders dynamic elements
- Attaches event listeners
- Hides loader and shows app
function initializeComponent(componentsData) {
// 1. Store and transform data
allComponentsState = componentsData.map(c => ({
...c,
lastEditedDate: new Date(c.lastEditedDate)
}));
// 2. Extract unique values for filters
const allDevelopers = [...new Set(
allComponentsState.map(c => c.developerName)
)].sort();
// 3. Render filter groups dynamically
renderFilterGroup('Developer', allDevelopers, 'developers');
// 4. Attach event listeners (only once)
if (!isInitialized) {
attachSearchListener();
attachFilterListeners();
isInitialized = true;
}
// 5. Render the component list
applyFiltersAndRender();
// 6. Hide loader and show app
hideLoader();
}
Step 4: Component Becomes Interactive
After initialization, the component is fully interactive:
function hideLoader() {
document.getElementById('loader-container').style.display = 'none';
document.getElementById('app-root').style.opacity = '1';
}
Data Transformation
Parsing Dates
Always parse date strings during initialization for consistent handling:
function initializeComponent(data) {
allComponentsState = data.components.map(component => ({
...component,
// Parse ISO date string to Date object once
lastEditedDate: new Date(component.lastEditedDate),
createdDate: new Date(component.createdDate)
}));
}
Normalizing Data
Normalize data structures for easier rendering:
function normalizeComponent(raw) {
return {
id: raw.id,
name: raw.name || 'Unnamed',
description: raw.description || '',
status: raw.status?.toLowerCase() || 'unknown',
tags: Array.isArray(raw.tags) ? raw.tags : [],
metadata: {
author: raw.developerName || raw.author || 'Unknown',
version: raw.lastVersionName || raw.version || '1.0.0',
updatedAt: new Date(raw.lastEditedDate || raw.updatedAt)
}
};
}
Updating Component Data
Full Data Replacement
Replace all data with a new dataset:
window.myop_init_interface = function(data) {
if (data && data.components) {
// Full replacement
initializeComponent(data);
}
};
Action-Based Updates
Support incremental updates with action types:
window.myop_init_interface = function(input) {
// No input = getter mode
if (!input) {
return { components: allComponentsState };
}
// Action-based update
if (input.action) {
switch (input.action) {
case 'setData':
initializeComponent(input.payload);
break;
case 'addItem':
addComponent(input.payload);
break;
case 'removeItem':
removeComponent(input.payload.id);
break;
case 'updateItem':
updateComponent(input.payload);
break;
}
return;
}
// Direct data initialization
initializeComponent(input);
};
function addComponent(component) {
allComponentsState.push(normalizeComponent(component));
applyFiltersAndRender();
}
function removeComponent(id) {
allComponentsState = allComponentsState.filter(c => c.id !== id);
applyFiltersAndRender();
}
function updateComponent(updated) {
const index = allComponentsState.findIndex(c => c.id === updated.id);
if (index !== -1) {
allComponentsState[index] = {
...allComponentsState[index],
...normalizeComponent(updated)
};
applyFiltersAndRender();
}
}
Loading States
Skeleton Loading
For better UX, show skeleton placeholders:
<div id="app-root">
<div class="skeleton-list">
<div class="skeleton-item">
<div class="skeleton-line" style="width: 60%"></div>
<div class="skeleton-line" style="width: 40%"></div>
</div>
<div class="skeleton-item">
<div class="skeleton-line" style="width: 55%"></div>
<div class="skeleton-line" style="width: 45%"></div>
</div>
</div>
<div class="actual-content" style="display: none;">
<!-- Actual content rendered here -->
</div>
</div>
.skeleton-item {
padding: 16px;
border-bottom: 1px solid #eee;
}
.skeleton-line {
height: 16px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
margin-bottom: 8px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
Error States
Handle loading errors gracefully:
window.myop_init_interface = function(data) {
if (!data) {
return { components: allComponentsState };
}
// Handle error state
if (data.error) {
showErrorState(data.error.message);
return;
}
initializeComponent(data);
};
function showErrorState(message) {
const loaderContainer = document.getElementById('loader-container');
const appRoot = document.getElementById('app-root');
loaderContainer.style.display = 'none';
appRoot.innerHTML = `
<div class="error-state">
<div class="error-icon">!</div>
<h3>Failed to load</h3>
<p>${escapeHtml(message)}</p>
<button onclick="window.myop_cta_handler('retry')">
Try Again
</button>
</div>
`;
appRoot.style.opacity = '1';
}
Empty States
Show meaningful empty states:
function renderItems(items) {
const container = document.querySelector('.item-list');
if (items.length === 0) {
container.innerHTML = `
<div class="empty-state">
<div class="empty-icon">📭</div>
<h3>No items found</h3>
<p>Try adjusting your filters or create a new item.</p>
<button onclick="window.myop_cta_handler('create_new')">
Create New
</button>
</div>
`;
return;
}
container.innerHTML = items.map(renderItem).join('');
}
Data Flow Diagram
┌─────────────────────────────────────────────────────────────┐
│ HOST APPLICATION │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. MyopContainer renders component │ │
│ │ 2. onReady callback fires │ │
│ │ 3. Set myop_cta_handler │ │
│ │ 4. Fetch data from API │ │
│ │ 5. Call myop_init_interface(data) │ │
│ └────────────────────────┬────────────────────────────┘ │
└───────────────────────────│─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ MYOP COMPONENT │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ window.myop_init_interface(data): │ │
│ │ 1. Store data in private state │ │
│ │ 2. Transform data (parse dates, normalize) │ │
│ │ 3. Render component UI │ │
│ │ 4. Attach event listeners (once) │ │
│ │ 5. Hide loader, show app │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ User Interaction: │ │
│ │ → Call window.myop_cta_handler('action', payload) │ │
│ └────────────────────────┬────────────────────────────┘ │
└────────────────── ─────────│─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ HOST APPLICATION │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ myop_cta_handler receives event: │ │
│ │ 1. Parse action_id │ │
│ │ 2. Handle action (navigate, open modal, etc.) │ │
│ └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────── ─────────────────────────┘
Next Steps
- Set up Mock Data for development
- Learn the full Public API reference
- See Host Integration examples