Skip to main content

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-container shown)
  • App is hidden (#app-root has opacity: 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:

  1. Stores and transforms data
  2. Extracts values for filters/aggregations
  3. Renders dynamic elements
  4. Attaches event listeners
  5. 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