---
sidebar_position: 1
---
# Component Structure

A Myop HTML component consists of three main parts that work together to create a dynamic, data-driven UI.

## 1. HTML Structure

The main container for your component UI:

```html
<div id="app-root">
    <!-- Your component UI goes here -->
    <header class="component-header">
        <h1>My Component</h1>
    </header>

    <main class="component-content">
        <div class="item-list">
            <!-- Dynamic content rendered by JavaScript -->
        </div>
    </main>

    <footer class="component-footer">
        <button class="action-btn">Take Action</button>
    </footer>
</div>
```

### Key Points

- Use a root container (`#app-root`) to wrap all component UI
- Keep the initial HTML minimal - dynamic content is rendered by JavaScript
- Use semantic HTML for accessibility
- Apply `opacity: 0` by default (shown after data loads)

## 2. Loader UI

The loader is displayed while waiting for data from the host application:

```html
<div id="loader-container">
    <div class="loader-spinner"></div>
    <div class="loader-text">Loading...</div>
</div>
```

### Loader Styles

```css
#loader-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    gap: 16px;
}

.loader-spinner {
    width: 40px;
    height: 40px;
    border: 4px solid #f3f3f3;
    border-top: 4px solid #3498db;
    border-radius: 50%;
    animation: spin 1s linear infinite;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

.loader-text {
    color: #666;
    font-size: 14px;
}
```

### Initial State CSS

```css
/* Loader visible by default */
#loader-container {
    display: flex;
}

/* App hidden by default */
#app-root {
    opacity: 0;
    transition: opacity 0.3s ease-in-out;
}

/* App visible state */
#app-root.visible {
    opacity: 1;
}
```

## 3. JavaScript Logic (IIFE Pattern)

All JavaScript is encapsulated in an Immediately Invoked Function Expression (IIFE):

```javascript
(function() {
    // ===== PRIVATE SCOPE =====
    // All implementation details stay private

    // Private state
    let isInitialized = false;
    let componentData = [];

    // DOM references (cached for performance)
    const appRoot = document.getElementById('app-root');
    const loaderContainer = document.getElementById('loader-container');
    const itemList = appRoot.querySelector('.item-list');

    // Private functions
    function hideLoader() {
        loaderContainer.style.display = 'none';
        appRoot.classList.add('visible');
    }

    function renderItems(items) {
        itemList.innerHTML = items.map(item => `
            <div class="item" data-id="${item.id}">
                <h3>${item.name}</h3>
                <p>${item.description}</p>
            </div>
        `).join('');
    }

    function attachEventListeners() {
        // Use event delegation for dynamic content
        itemList.addEventListener('click', (e) => {
            const item = e.target.closest('.item');
            if (item) {
                const itemId = item.dataset.id;
                window.myop_cta_handler('item_clicked', { itemId });
            }
        });
    }

    function initializeComponent(data) {
        // 1. Store data
        componentData = data.items || [];

        // 2. Render UI
        renderItems(componentData);

        // 3. Attach event listeners (only once)
        if (!isInitialized) {
            attachEventListeners();
            isInitialized = true;
        }

        // 4. Show the component
        hideLoader();
    }

    // ===== PUBLIC API =====
    // Only these two functions are exposed globally

    window.myop_init_interface = function(data) {
        if (data) {
            initializeComponent(data);
        }
        // When called without arguments, return current state
        return { items: componentData };
    };

    window.myop_cta_handler = function(action_id, payload) {
        // Default handler - will be overwritten by host
        console.log('CTA:', action_id, payload);
    };
})();
```

## Why IIFE Pattern?

The IIFE pattern provides several benefits:

| Benefit | Description |
|---------|-------------|
| **Encapsulation** | All variables and functions are private to the component |
| **No Global Pollution** | Only `myop_init_interface` and `myop_cta_handler` are exposed |
| **Clean Namespace** | Multiple Myop components can coexist without conflicts |
| **Maintainability** | Clear separation between public API and implementation |

## Complete Example

Here's a full component template:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Myop Component</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: #fff;
        }

        /* Loader styles */
        #loader-container {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            gap: 16px;
        }

        .loader-spinner {
            width: 40px;
            height: 40px;
            border: 4px solid #f3f3f3;
            border-top: 4px solid #3498db;
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        /* App styles */
        #app-root {
            opacity: 0;
            transition: opacity 0.3s ease-in-out;
        }

        #app-root.visible {
            opacity: 1;
        }

        .item {
            padding: 16px;
            border-bottom: 1px solid #eee;
            cursor: pointer;
        }

        .item:hover {
            background: #f9f9f9;
        }

        .item h3 {
            font-size: 16px;
            margin-bottom: 4px;
        }

        .item p {
            font-size: 14px;
            color: #666;
        }
    </style>
</head>
<body>
    <!-- Loader -->
    <div id="loader-container">
        <div class="loader-spinner"></div>
        <div class="loader-text">Loading...</div>
    </div>

    <!-- App -->
    <div id="app-root">
        <div class="item-list"></div>
    </div>

    <!-- Component Logic -->
    <script>
    (function() {
        let isInitialized = false;
        let componentData = [];

        const appRoot = document.getElementById('app-root');
        const loaderContainer = document.getElementById('loader-container');
        const itemList = appRoot.querySelector('.item-list');

        function hideLoader() {
            loaderContainer.style.display = 'none';
            appRoot.classList.add('visible');
        }

        function renderItems(items) {
            itemList.innerHTML = items.map(item => `
                <div class="item" data-id="${item.id}">
                    <h3>${item.name}</h3>
                    <p>${item.description}</p>
                </div>
            `).join('');
        }

        function attachEventListeners() {
            itemList.addEventListener('click', (e) => {
                const item = e.target.closest('.item');
                if (item) {
                    window.myop_cta_handler('item_clicked', {
                        itemId: item.dataset.id
                    });
                }
            });
        }

        function initializeComponent(data) {
            componentData = data.items || [];
            renderItems(componentData);

            if (!isInitialized) {
                attachEventListeners();
                isInitialized = true;
            }

            hideLoader();
        }

        window.myop_init_interface = function(data) {
            if (data) initializeComponent(data);
            return { items: componentData };
        };

        window.myop_cta_handler = function(action_id, payload) {
            console.log('CTA:', action_id, payload);
        };
    })();
    </script>

    <!-- Preview Mock Data -->
    <script id="myop_preview">
        setTimeout(() => {
            window.myop_init_interface({
                items: [
                    { id: '1', name: 'First Item', description: 'This is the first item' },
                    { id: '2', name: 'Second Item', description: 'This is the second item' },
                    { id: '3', name: 'Third Item', description: 'This is the third item' }
                ]
            });
        });
    </script>
</body>
</html>
```

## Next Steps

- Learn about the [Data Loading](/docs/learnMyop/html-components/data-loading) lifecycle
- Understand the [Public API](/docs/learnMyop/html-components/public-api) in detail
- Set up [Mock Data](/docs/learnMyop/html-components/mock-data) for development
