Skip to main content

Best Practices

Guidelines for building maintainable, performant, and accessible Myop components.

Code Organization​

1. Use IIFE Encapsulation​

Keep all implementation private. Only expose the public API:

(function() {
// ===== PRIVATE =====
let state = {};
const cache = {};

function privateHelper() { /* ... */ }
function render() { /* ... */ }

// ===== PUBLIC API =====
window.myop_init_interface = function(data) { /* ... */ };
window.myop_cta_handler = function(action, payload) { /* ... */ };
})();

2. Cache DOM References​

Query the DOM once during initialization:

// Good - cached references
const elements = {
appRoot: document.getElementById('app-root'),
loader: document.getElementById('loader-container'),
list: document.querySelector('.item-list'),
searchInput: document.querySelector('.search-input'),
filterPanel: document.querySelector('.filter-panel')
};

function render() {
elements.list.innerHTML = renderItems();
}

// Bad - repeated queries
function render() {
document.querySelector('.item-list').innerHTML = renderItems();
}

3. Single Initialization Flag​

Attach event listeners only once:

let isInitialized = false;

function initializeComponent(data) {
// Process data...
render();

// Attach listeners only on first init
if (!isInitialized) {
attachEventListeners();
isInitialized = true;
}

hideLoader();
}

Event Handling​

1. Use Event Delegation​

For dynamic content, attach listeners to stable parent elements:

// Good - event delegation
elements.list.addEventListener('click', (e) => {
const item = e.target.closest('.item');
if (item) {
handleItemClick(item.dataset.id);
}

const deleteBtn = e.target.closest('.delete-btn');
if (deleteBtn) {
handleDelete(deleteBtn.dataset.id);
}
});

// Bad - attaching to each element
items.forEach(item => {
item.addEventListener('click', () => handleItemClick(item.id));
});

2. Debounce Expensive Operations​

function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}

const debouncedSearch = debounce((query) => {
filterComponents(query);
render();
}, 300);

elements.searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});

Data Handling​

1. Parse Dates Once​

Transform date strings during initialization, not during render:

// Good - parse once
function initializeComponent(data) {
state.components = data.components.map(c => ({
...c,
lastEditedDate: new Date(c.lastEditedDate)
}));
}

function formatDate(date) {
return date.toLocaleDateString(); // Already a Date object
}

// Bad - parse on every render
function renderItem(item) {
const date = new Date(item.lastEditedDate); // Parsed every time
return `<div>${date.toLocaleDateString()}</div>`;
}

2. Provide Fallback Values​

Handle missing or malformed data:

function normalizeComponent(raw) {
return {
id: raw.id || `temp-${Date.now()}`,
name: raw.name || 'Unnamed Component',
description: raw.description || '',
tags: Array.isArray(raw.tags) ? raw.tags : [],
environments: Array.isArray(raw.environments) ? raw.environments : [],
developerName: raw.developerName || 'Unknown',
lastEditedDate: raw.lastEditedDate
? new Date(raw.lastEditedDate)
: new Date()
};
}

3. Immutable Updates​

Don't mutate the original state:

// Good - immutable update
function updateComponent(updated) {
state.components = state.components.map(c =>
c.id === updated.id ? { ...c, ...updated } : c
);
render();
}

// Bad - mutation
function updateComponent(updated) {
const index = state.components.findIndex(c => c.id === updated.id);
state.components[index] = { ...state.components[index], ...updated }; // Mutates array
render();
}

Performance​

1. Minimize DOM Operations​

Batch DOM updates:

// Good - single innerHTML update
function render() {
elements.list.innerHTML = state.components
.map(renderItem)
.join('');
}

// Bad - multiple appendChild calls
function render() {
elements.list.innerHTML = '';
state.components.forEach(comp => {
const div = document.createElement('div');
div.innerHTML = renderItem(comp);
elements.list.appendChild(div); // Triggers reflow each time
});
}

2. Use Document Fragments for Large Lists​

function render() {
const fragment = document.createDocumentFragment();

state.components.forEach(comp => {
const div = document.createElement('div');
div.className = 'item';
div.innerHTML = renderItemContent(comp);
fragment.appendChild(div);
});

elements.list.innerHTML = '';
elements.list.appendChild(fragment);
}

3. Virtual Scrolling for Large Datasets​

For lists with 100+ items, consider virtual scrolling:

const ITEM_HEIGHT = 60;
const VISIBLE_ITEMS = 20;
const BUFFER = 5;

function renderVisibleItems(scrollTop) {
const startIndex = Math.max(0, Math.floor(scrollTop / ITEM_HEIGHT) - BUFFER);
const endIndex = startIndex + VISIBLE_ITEMS + (BUFFER * 2);

const visibleItems = state.components.slice(startIndex, endIndex);

elements.list.style.paddingTop = `${startIndex * ITEM_HEIGHT}px`;
elements.list.style.height = `${state.components.length * ITEM_HEIGHT}px`;
elements.list.innerHTML = visibleItems.map(renderItem).join('');
}

elements.scrollContainer.addEventListener('scroll', (e) => {
requestAnimationFrame(() => {
renderVisibleItems(e.target.scrollTop);
});
});

Security​

1. Escape User Content​

Always escape HTML in user-provided content:

function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}

function renderItem(item) {
return `
<div class="item">
<h3>${escapeHtml(item.name)}</h3>
<p>${escapeHtml(item.description)}</p>
</div>
`;
}

2. Validate Data Attributes​

Use data attributes safely:

// Good - validate before use
const itemId = e.target.dataset.id;
if (itemId && /^[a-zA-Z0-9-]+$/.test(itemId)) {
handleItemClick(itemId);
}

// Bad - trusting user input
const itemId = e.target.dataset.id;
document.querySelector(`[data-id="${itemId}"]`); // XSS risk

3. Sanitize URLs​

function createLink(url, text) {
// Only allow http/https URLs
if (!/^https?:\/\//i.test(url)) {
return escapeHtml(text);
}
return `<a href="${escapeHtml(url)}" rel="noopener noreferrer">${escapeHtml(text)}</a>`;
}

Accessibility​

1. Use Semantic HTML​

<!-- Good -->
<nav aria-label="Component filters">
<ul role="listbox">
<li role="option" aria-selected="true">All</li>
<li role="option">Frontend</li>
</ul>
</nav>

<main>
<ul class="component-list" role="list">
<li class="item" role="listitem">...</li>
</ul>
</main>

<!-- Bad -->
<div class="nav">
<div class="filter">All</div>
<div class="filter">Frontend</div>
</div>
<div class="list">
<div class="item">...</div>
</div>

2. Add ARIA Labels​

function renderItem(item) {
return `
<article
class="item"
aria-label="${escapeHtml(item.name)}"
tabindex="0"
>
<h3>${escapeHtml(item.name)}</h3>
<button
class="delete-btn"
aria-label="Delete ${escapeHtml(item.name)}"
data-id="${item.id}"
>
Delete
</button>
</article>
`;
}

3. Support Keyboard Navigation​

elements.list.addEventListener('keydown', (e) => {
const item = e.target.closest('.item');
if (!item) return;

switch (e.key) {
case 'Enter':
case ' ':
e.preventDefault();
handleItemClick(item.dataset.id);
break;
case 'Delete':
case 'Backspace':
e.preventDefault();
handleDelete(item.dataset.id);
break;
case 'ArrowDown':
e.preventDefault();
item.nextElementSibling?.focus();
break;
case 'ArrowUp':
e.preventDefault();
item.previousElementSibling?.focus();
break;
}
});

Error Handling​

1. Graceful Degradation​

function initializeComponent(data) {
try {
if (!data || !Array.isArray(data.components)) {
throw new Error('Invalid data format');
}

state.components = data.components.map(normalizeComponent);
render();
hideLoader();
} catch (error) {
console.error('Failed to initialize:', error);
showErrorState(error.message);
}
}

function showErrorState(message) {
elements.loader.style.display = 'none';
elements.appRoot.innerHTML = `
<div class="error-state" role="alert">
<h2>Unable to load components</h2>
<p>${escapeHtml(message)}</p>
<button onclick="window.myop_cta_handler('retry')">
Try Again
</button>
</div>
`;
elements.appRoot.style.opacity = '1';
}

2. Safe CTA Handler​

window.myop_cta_handler = function(action_id, payload) {
try {
// Validate action
if (typeof action_id !== 'string') {
console.warn('Invalid action_id type');
return;
}

console.log('CTA:', action_id, payload);
} catch (error) {
console.error('CTA handler error:', error);
}
};

Summary Checklist​

CategoryPractice
StructureUse IIFE pattern
StructureCache DOM references
StructureInitialize once flag
EventsEvent delegation
EventsDebounce expensive operations
DataParse dates once
DataProvide fallbacks
DataImmutable updates
PerformanceBatch DOM updates
PerformanceUse fragments for large lists
SecurityEscape HTML
SecurityValidate data attributes
SecuritySanitize URLs
AccessibilitySemantic HTML
AccessibilityARIA labels
AccessibilityKeyboard navigation
ErrorsGraceful degradation
ErrorsSafe handlers

Next Steps​