Now.js Framework Documentation
ApiComponent - UI Component Wrapper
ApiComponent - UI Component Wrapper
Documentation for ApiComponent, a UI Component Wrapper for calling APIs and displaying results in HTML
📋 Table of Contents
- Overview
- Installation and Import
- Basic Usage
- HTML Attributes
- Configuration Options
- Template System
- Pagination
- Polling
- Events
- CSS State Classes
- JavaScript API
- Usage Examples
- API Reference
- Best Practices
Overview
ApiComponent is a UI Component that allows you to call APIs and display results in HTML easily without writing JavaScript yourself.
Key Features
- ✅ HTML-Based: Configure via HTML attributes
- ✅ Template Support: Support template system for rendering
- ✅ Auto-Loading: Auto-load data when creating component
- ✅ Caching: Cache data to improve performance
- ✅ Pagination: Support pagination and infinite scroll
- ✅ Polling: Auto-update data at intervals
- ✅ Event System: Emit events on changes
- ✅ Retry Logic: Auto-retry on errors
- ✅ Loading States: Handle loading, error, empty states
When to Use ApiComponent
✅ Use ApiComponent when:
- You need to display API data in HTML
- You need ease of use (no JavaScript required)
- You need pagination or infinite scroll
- You need auto-refresh data (polling)
- You need automatic loading/error states
❌ Don't use ApiComponent when:
- You need detailed rendering control (use
ApiService+ custom code) - Working with complex real-time data (use WebSocket)
- You need very complex logic
Installation and Import
ApiComponent is loaded with the Now.js Framework and is ready to use immediately via the window object:
// No import needed - ready to use immediately
console.log(window.ApiComponent); // ApiComponent objectDependencies
ApiComponent requires these dependencies:
- HttpClient or simpleFetch - For API calls
- TemplateManager - For rendering (optional)
Two Modes of Operation
ApiComponent supports 2 different modes and automatically detects which mode to use:
1. Simple Binding Mode
When: When there is NO <template> element inside the component
Purpose: Display simple data like user names, images, numbers by updating values in existing elements
Example:
<!-- Simple Binding Mode - no template -->
<div data-component="api"
data-endpoint="/api/v1/auth/me"
data-cache="true">
<img data-source-key="data.avatar.0.url" data-source-attr="src" class="avatar">
<span data-source-key="data.name">Loading...</span>
<span data-source-key="data.email">Loading...</span>
</div>Supported Attributes:
data-source-key- path to data (dot notation)data-source-attr- attribute to update (textContent, src, href, innerHTML, etc.)data-source-template- simple template like"Hello, {data.name}!"
Best for:
- Logged-in user info (topbar, profile card)
- Notification badges (count displays)
- Simple single-value displays
2. Template Rendering Mode
When: When there IS a <template> element inside the component
Purpose: Create new HTML from template for complex data like lists, cards, dynamic content
Example:
<!-- Template Rendering Mode - has template -->
<div data-component="api"
data-endpoint="/api/users">
<template>
<ul>
<li data-for="user of data">
<img data-bind="user.avatar" data-attr="src">
<span data-bind="user.name"></span>
<span data-if="user.status === 'active'">Active</span>
</li>
</ul>
</template>
</div>Supported Features:
data-for- loop through datadata-if- conditional renderingdata-bind- bind data- Other TemplateManager directives
Best for:
- Lists (users, products, posts)
- Dynamic components with multiple items
- UI requiring loops and conditions
Mode Comparison
| Feature | Simple Binding | Template Rendering |
|---|---|---|
| Template Element | ❌ No | ✅ Yes |
| Use Case | Single values | Lists/Collections |
| Complexity | Simple | Complex |
| HTML Output | Updates existing | Creates new HTML |
| Loops | ❌ | ✅ |
| Conditions | ❌ | ✅ |
| Performance | Faster | Slower (creates HTML) |
Basic Usage
1. HTML-Based (Recommended)
<div data-component="api"
data-endpoint="/api/users"
data-method="GET">
<template>
<div data-for="item in data">
<h3>{item.name}</h3>
<p>{item.email}</p>
</div>
</template>
</div>2. JavaScript-Based
const element = document.querySelector('#my-api-component');
const instance = ApiComponent.create(element, {
endpoint: '/api/users',
method: 'GET',
autoload: true
});3. Auto-Initialization
ApiComponent will automatically create instances for elements with data-component="api":
<!-- Will be created automatically when page loads -->
<div data-component="api" data-endpoint="/api/products"></div>HTML Attributes
ApiComponent supports these attributes for configuration:
Basic Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-endpoint |
string | - | API endpoint URL (required) |
data-method |
string | 'GET' |
HTTP method (GET, POST, PUT, DELETE) |
data-base-url |
string | '' |
Base URL for API |
data-autoload |
boolean | true |
Auto-load data on create |
data-timeout |
number | 30000 |
Request timeout (ms) |
Cache Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-cache |
boolean | false |
Enable caching |
data-cache-time |
number | 60000 |
Cache duration (ms) |
Template Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-template |
string | - | External template ID |
Pagination Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-pagination |
boolean | true |
Enable pagination |
data-page-param |
string | 'page' |
Page parameter name |
data-limit-param |
string | 'pageSize' |
Limit parameter name |
data-page-size |
number | 10 |
Items per page |
Polling Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-polling |
boolean | false |
Enable polling |
data-polling-interval |
number | 30000 |
Polling interval (ms) |
Event Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-refresh-event |
string | - | Event names to trigger refresh (comma-separated) |
Data Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-params |
JSON | {} |
Query parameters |
data-data |
JSON | {} |
Request body data |
data-headers |
JSON | {} |
Custom headers |
data-data-path |
string | 'data' |
Path to data in response |
data-meta-path |
string | 'meta' |
Path to meta in response |
Props Attribute
You can use data-props to set all configuration in JSON format:
<div data-component="api"
data-props='{
"endpoint": "/api/users",
"method": "GET",
"cache": true,
"cacheTime": 300000,
"params": {"status": "active"}
}'>
</div>Configuration Options
When creating an instance with JavaScript, you can configure all options:
const instance = ApiComponent.create(element, {
// Basic
method: 'GET',
baseURL: '',
endpoint: '/api/users',
autoload: true,
timeout: 30000,
// Cache
cache: false,
cacheTime: 60000,
// Retry
retry: 3,
// Messages
loadingText: 'Loading...',
errorText: 'Error occurred',
emptyText: 'No data found',
// Event Handlers
onSuccess: (data, response) => {
console.log('Success:', data);
},
onError: (error) => {
console.error('Error:', error);
},
onEmpty: () => {
console.log('No data');
},
// Pagination
pagination: true,
pageParam: 'page',
limitParam: 'pageSize',
pageSize: 10,
// Polling
polling: false,
pollingInterval: 30000,
// Events
refreshEvent: 'user:updated',
// Data
params: {},
data: {},
headers: {},
// Response Paths
dataPath: 'data',
metaPath: 'meta',
// Template
template: 'my-template-id',
// Debug
debug: false
});Template System
ApiComponent uses Now.js's TemplateManager for rendering
Inline Template
<div data-component="api" data-endpoint="/api/users">
<template>
<div data-for="user in data">
<h3>{user.name}</h3>
<p>{user.email}</p>
<span data-if="user.status === 'active'">Active</span>
</div>
</template>
</div>External Template
<!-- Define template -->
<template id="user-template">
<div class="user-card">
<h3>{name}</h3>
<p>{email}</p>
</div>
</template>
<!-- Use template -->
<div data-component="api"
data-endpoint="/api/users"
data-template="user-template">
</div>Template Directives
ApiComponent supports TemplateManager directives:
data-for (Loop)
<template>
<ul>
<li data-for="item in data">
{item.name}
</li>
</ul>
</template>data-if (Conditional)
<template>
<div data-for="user in data">
<h3>{user.name}</h3>
<span data-if="user.status === 'active'" class="badge">Active</span>
<span data-if="user.status === 'inactive'" class="badge">Inactive</span>
</div>
</template>Nested Loops
<template>
<div data-for="category in data">
<h2>{category.name}</h2>
<ul>
<li data-for="product in category.products">
{product.name} - {product.price}
</li>
</ul>
</div>
</template>Pagination
ApiComponent supports server-side pagination
Basic Pagination
<div data-component="api"
data-endpoint="/api/products"
data-pagination="true"
data-page-size="20">
<template>
<div data-for="product in data">
<h3>{product.name}</h3>
<p>{product.price}</p>
</div>
</template>
<!-- Pagination Controls -->
<button onclick="this.closest('.api-component').apiInstance.loadMore()">
Load More
</button>
</div>Custom Pagination Parameters
<div data-component="api"
data-endpoint="/api/posts"
data-page-param="p"
data-limit-param="limit"
data-page-size="15">
</div>Request will be:
GET /api/posts?p=1&limit=15Expected Response Format
{
"data": [
{"id": 1, "name": "Item 1"},
{"id": 2, "name": "Item 2"}
],
"meta": {
"total": 100,
"last_page": 10,
"current_page": 1,
"per_page": 10
}
}Disable Pagination
<div data-component="api"
data-endpoint="/api/settings"
data-pagination="false">
</div>Polling
ApiComponent can poll endpoints automatically to update data
Enable Polling
<div data-component="api"
data-endpoint="/api/notifications"
data-polling="true"
data-polling-interval="10000">
<!-- Will reload data every 10 seconds -->
</div>Control Polling via JavaScript
const instance = ApiComponent.getInstance('#my-component');
// Start polling
ApiComponent.startPolling(instance);
// Stop polling
ApiComponent.stopPolling(instance);Polling Events
element.addEventListener('api:polling:start', (e) => {
console.log('Polling started');
});
element.addEventListener('api:polling:stop', (e) => {
console.log('Polling stopped');
});Events
ApiComponent emits events on changes
Event Types
| Event | When | Detail |
|---|---|---|
api:init |
Component initialized | {instance} |
api:loaded |
Data loaded successfully | {data, response, fromCache} |
api:error |
Error occurred | {error, message} |
api:empty |
No data returned | - |
api:content-rendered |
Content rendered | {data} |
api:polling:start |
Polling started | - |
api:polling:stop |
Polling stopped | - |
api:destroy |
Component destroyed | - |
Listen to Events
const element = document.querySelector('#my-api-component');
element.addEventListener('api:loaded', (e) => {
console.log('Data loaded:', e.detail.data);
console.log('From cache:', e.detail.fromCache);
});
element.addEventListener('api:error', (e) => {
console.error('Error:', e.detail.error);
});
element.addEventListener('api:empty', (e) => {
console.log('No data available');
});Refresh on Custom Event
<div data-component="api"
data-endpoint="/api/cart"
data-refresh-event="cart:updated">
<!-- Will refresh when 'cart:updated' event is emitted -->
</div>
<script>
// Anywhere in code
EventManager.emit('cart:updated');
</script>CSS State Classes
ApiComponent automatically adds CSS classes to indicate the current state of the component. These classes use the api- prefix to avoid conflicts with common class names.
State Classes
| Class | When Applied | Description |
|---|---|---|
api-component |
Always | Added when component is initialized |
api-loading |
During data fetch | Data is being loaded from API |
api-content |
After successful load | Data loaded and rendered successfully |
api-error |
On error | An error occurred during loading |
api-empty |
When no data | API returned empty data or empty array |
Usage Example
/* Style loading state */
.api-component.api-loading {
opacity: 0.6;
pointer-events: none;
}
.api-component.api-loading::after {
content: 'Loading...';
display: block;
text-align: center;
padding: 20px;
}
/* Style error state */
.api-component.api-error {
background-color: #fee;
border: 1px solid #fcc;
padding: 20px;
}
.api-component.api-error::before {
content: '⚠️ Error loading data';
display: block;
color: #c00;
font-weight: bold;
margin-bottom: 10px;
}
/* Style empty state */
.api-component.api-empty {
text-align: center;
padding: 40px;
color: #999;
}
.api-component.api-empty::after {
content: 'No data available';
display: block;
}
/* Style content state */
.api-component.api-content {
/* Your content styles */
}Complete Example
<div data-component="api"
data-endpoint="/api/products"
class="product-list">
<template>
<div data-for="product in data" class="product-card">
<h3>{product.name}</h3>
<p>{product.price}</p>
</div>
</template>
</div>
<style>
/* Base styling */
.product-list {
min-height: 200px;
transition: all 0.3s ease;
}
/* Loading state */
.product-list.api-loading {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s ease-in-out infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Error state */
.product-list.api-error {
background-color: #fee2e2;
border: 2px solid #ef4444;
padding: 20px;
border-radius: 8px;
}
/* Empty state */
.product-list.api-empty {
background-color: #f9fafb;
border: 2px dashed #d1d5db;
padding: 40px;
text-align: center;
color: #6b7280;
}
/* Content loaded */
.product-list.api-content {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
</style>Benefits of api- Prefix
The api- prefix prevents conflicts with commonly used class names:
-
❌
.loading- might conflict with your own loading classes -
❌
.error- very common class name in many projects -
❌
.content- extremely common, often used for layout -
❌
.empty- commonly used for empty states -
✅
.api-loading- specific to ApiComponent -
✅
.api-error- clear it's from ApiComponent -
✅
.api-content- won't conflict with layout classes -
✅
.api-empty- specific to API data state
JavaScript API
ApiComponent has methods for control via JavaScript
Create Instance
const instance = ApiComponent.create(element, options);Get Instance
// From element
const instance = ApiComponent.getInstance(element);
// From ID
const instance = ApiComponent.getInstance('#my-component');
// From element property
const instance = element.apiInstance;Load Data
// Load data (reset pagination)
await ApiComponent.loadData(instance);
// Load more (append data)
await ApiComponent.loadMore(instance);Refresh Data
// Refresh (reset to page 1)
ApiComponent.refresh(instance);
// Or use instance method
instance.refresh();Submit Data
// Submit data (POST)
await ApiComponent.submit(instance, {
name: 'John Doe',
email: 'john@example.com'
});Polling Control
// Start polling
ApiComponent.startPolling(instance);
// Stop polling
ApiComponent.stopPolling(instance);Cache Management
// Clear all cache
ApiComponent.clearCache();Destroy Instance
// Destroy instance and cleanup
ApiComponent.destroy(instance);Usage Examples
1. Simple Product List
<div data-component="api"
data-endpoint="/api/products"
data-cache="true"
data-cache-time="300000">
<template>
<div class="product-grid">
<div data-for="product in data" class="product-card">
<img data-src="product.image" data-alt="product.name">
<h3>{product.name}</h3>
<p class="price">{product.price} USD</p>
<button>Add to Cart</button>
</div>
</div>
</template>
</div>2. User Profile with POST
<form id="profile-form">
<input type="text" name="name" placeholder="Name">
<input type="email" name="email" placeholder="Email">
<button type="submit">Save</button>
</form>
<div id="profile-result"
data-component="api"
data-endpoint="/api/profile"
data-method="POST"
data-autoload="false">
<template>
<div class="alert success">
Profile updated: {data.name}
</div>
</template>
</div>
<script>
document.getElementById('profile-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
const instance = ApiComponent.getInstance('#profile-result');
await ApiComponent.submit(instance, data);
});
</script>3. Infinite Scroll
<div data-component="api"
data-endpoint="/api/posts"
data-page-size="20"
id="posts">
<template>
<div data-for="post in data" class="post">
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</div>
</template>
</div>
<script>
// Infinite scroll
window.addEventListener('scroll', () => {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500) {
const instance = ApiComponent.getInstance('#posts');
if (!instance.loading && instance.page < instance.totalPages) {
ApiComponent.loadMore(instance);
}
}
});
</script>4. Real-time Notifications (Polling)
<div data-component="api"
data-endpoint="/api/notifications"
data-polling="true"
data-polling-interval="10000"
data-cache="false">
<template>
<div class="notification-list">
<div data-for="notif in data" class="notification">
<span class="icon" data-if="notif.type === 'info'">ℹ️</span>
<span class="icon" data-if="notif.type === 'warning'">⚠️</span>
<span class="icon" data-if="notif.type === 'error'">❌</span>
<div class="content">
<h4>{notif.title}</h4>
<p>{notif.message}</p>
<time>{notif.created_at}</time>
</div>
</div>
</div>
</template>
</div>5. Search with Debounce
<input type="text" id="search-input" placeholder="Search products...">
<div data-component="api"
data-endpoint="/api/products/search"
data-autoload="false"
id="search-results">
<template>
<div data-for="product in data">
<h4>{product.name}</h4>
<p>{product.description}</p>
</div>
</template>
</div>
<script>
let searchTimeout;
const searchInput = document.getElementById('search-input');
const instance = ApiComponent.getInstance('#search-results');
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
instance.options.params = { q: e.target.value };
ApiComponent.refresh(instance);
}, 300);
});
</script>6. Dynamic Endpoint
<select id="category-select">
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
<option value="books">Books</option>
</select>
<div data-component="api"
data-endpoint="/api/products"
data-params='{"category": "electronics"}'
id="products">
<template>
<div data-for="product in data">
<h3>{product.name}</h3>
</div>
</template>
</div>
<script>
document.getElementById('category-select').addEventListener('change', (e) => {
const instance = ApiComponent.getInstance('#products');
instance.options.params.category = e.target.value;
ApiComponent.refresh(instance);
});
</script>7. Nested Data Structure
<div data-component="api"
data-endpoint="/api/categories">
<template>
<div data-for="category in data" class="category">
<h2>{category.name}</h2>
<div class="products">
<div data-for="product in category.products" class="product">
<h4>{product.name}</h4>
<p>{product.price} USD</p>
</div>
</div>
</div>
</template>
</div>8. Custom Response Path
<!-- API Response: { "success": true, "result": { "items": [...] } } -->
<div data-component="api"
data-endpoint="/api/items"
data-data-path="result.items">
<template>
<div data-for="item in data">
{item.name}
</div>
</template>
</div>9. Event-Driven Updates
<!-- Shopping Cart -->
<div data-component="api"
data-endpoint="/api/cart"
data-refresh-event="cart:updated"
id="cart">
<template>
<div>
Total Items: {data.count}
Total Price: {data.total} USD
</div>
</template>
</div>
<!-- Add to Cart Button -->
<button onclick="addToCart(123)">Add to Cart</button>
<script>
async function addToCart(productId) {
await fetch('/api/cart/add', {
method: 'POST',
body: JSON.stringify({ productId }),
headers: {'Content-Type': 'application/json'}
});
// Trigger refresh
EventManager.emit('cart:updated');
}
</script>10. Error Handling with Custom UI
<div data-component="api"
data-endpoint="/api/products"
id="products">
<template>
<div data-for="product in data">
<h3>{product.name}</h3>
</div>
</template>
</div>
<script>
const element = document.getElementById('products');
element.addEventListener('api:error', (e) => {
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-error';
errorDiv.textContent = 'Failed to load products. ';
const retryBtn = document.createElement('button');
retryBtn.textContent = 'Retry';
retryBtn.onclick = () => {
ApiComponent.refresh(e.detail.instance);
errorDiv.remove();
};
errorDiv.appendChild(retryBtn);
element.insertBefore(errorDiv, element.firstChild);
});
</script>API Reference
Methods
// Create
create(element: HTMLElement|string, options?: object): Instance
// Get Instance
getInstance(element: HTMLElement|string): Instance|null
// Data Operations
loadData(instance: Instance, append?: boolean): Promise<void>
refresh(instance: Instance): void
loadMore(instance: Instance): void
submit(instance: Instance, data: object): Promise<void>
// Polling
startPolling(instance: Instance): void
stopPolling(instance: Instance): void
// Cache
clearCache(): void
// Lifecycle
destroy(instance: Instance): boolean
init(options?: object): Promise<ApiComponent>Instance Properties
{
id: string,
element: HTMLElement,
options: object,
data: any,
loading: boolean,
error: string|null,
timestamp: number|null,
page: number,
totalPages: number,
pageSize: number,
totalItems: number,
polling: boolean,
timer: number|null,
abortController: AbortController|null,
templateContent: string|null
}Best Practices
1. ✅ Use Data Attributes for Simple Cases
<!-- ❌ Bad: Use JavaScript when not necessary -->
<div id="products"></div>
<script>
ApiComponent.create('#products', {endpoint: '/api/products'});
</script>
<!-- ✅ Good: Use HTML attributes -->
<div data-component="api" data-endpoint="/api/products"></div>2. ✅ Enable Caching for Static Data
<!-- ✅ Good: Cache data that doesn't change often -->
<div data-component="api"
data-endpoint="/api/categories"
data-cache="true"
data-cache-time="300000">
</div>3. ✅ Use External Templates for Reusability
<!-- ✅ Good: Separate template for reusability -->
<template id="product-card">
<div class="card">
<h3>{name}</h3>
<p>{price}</p>
</div>
</template>
<div data-component="api"
data-endpoint="/api/products"
data-template="product-card">
</div>
<div data-component="api"
data-endpoint="/api/featured-products"
data-template="product-card">
</div>4. ✅ Clean Up When Removing Components
// ✅ Good: Destroy instance when no longer needed
const instance = ApiComponent.getInstance('#my-component');
ApiComponent.destroy(instance);
// Or when removing element
element.remove(); // Auto-cleanup if using ComponentManager5. ✅ Use Pagination for Large Datasets
<!-- ✅ Good: Use pagination for large datasets -->
<div data-component="api"
data-endpoint="/api/posts"
data-page-size="20">
</div>6. ✅ Handle Empty States
<div data-component="api"
data-endpoint="/api/notifications">
<template>
<div data-if="data.length > 0">
<div data-for="notif in data">
{notif.message}
</div>
</div>
<div data-if="data.length === 0">
No notifications
</div>
</template>
</div>7. ✅ Use Polling Wisely
<!-- ❌ Bad: Polling every 1 second -->
<div data-polling="true" data-polling-interval="1000"></div>
<!-- ✅ Good: Polling every 30 seconds or use WebSocket -->
<div data-polling="true" data-polling-interval="30000"></div>8. ✅ Disable Autoload When Needed
<!-- ✅ Good: Disable autoload when you need control -->
<div data-component="api"
data-endpoint="/api/search"
data-autoload="false"
id="search">
</div>
<script>
// Load when user types search
searchInput.addEventListener('input', (e) => {
const instance = ApiComponent.getInstance('#search');
instance.options.params = {q: e.target.value};
ApiComponent.refresh(instance);
});
</script>9. ✅ Use Custom Data Paths
<!-- ✅ Good: Specify path when API response isn't {data: [...]} -->
<div data-component="api"
data-endpoint="/api/legacy"
data-data-path="result.items"
data-meta-path="result.pagination">
</div>10. ✅ Listen to Events for Custom Actions
// ✅ Good: Use events for custom actions
element.addEventListener('api:loaded', (e) => {
// Update other UI elements
updateStats(e.detail.data);
// Track analytics
analytics.track('data_loaded', {
endpoint: e.detail.instance.options.endpoint
});
});Summary
When to Use ApiComponent
| Use Case | Use ApiComponent? |
|---|---|
| Display list of data from API | ✅ Yes - HTML-based |
| Need pagination | ✅ Yes - built-in support |
| Need infinite scroll | ✅ Yes - loadMore() |
| Auto-refresh data | ✅ Yes - polling support |
| Need detailed control | ❌ No - use ApiService |
| Complex real-time | ❌ No - use WebSocket |
| Form submission | ✅ Can use - submit() |
| Simple static content | ❌ Not needed |
Key Features
| Feature | Available | Note |
|---|---|---|
| HTML Attributes | ✅ | No JavaScript needed |
| Template System | ✅ | TemplateManager integration |
| Auto-Loading | ✅ | Configurable |
| Caching | ✅ | In-memory |
| Pagination | ✅ | Server-side |
| Polling | ✅ | Auto-refresh |
| Events | ✅ | Custom events |
| Retry | ✅ | Auto-retry on error |
| Loading States | ✅ | loading, error, empty |