Table Cell - JavaScript
Table Cell
Struktur HTML
Section titled “Struktur HTML”
<div class="ina-table">
<table class="ina-table__container">
<thead class="ina-table__header">
<tr>
<th class="ina-table__header-cell">Nama</th>
<th class="ina-table__header-cell">Unit</th>
<th class="ina-table__header-cell">Jabatan</th>
</tr>
</thead>
<tbody class="ina-table__body">
<tr class="ina-table__row">
<td class="ina-table__cell">Aji</td>
<td class="ina-table__cell">Sekretaris</td>
<td class="ina-table__cell">Staff</td>
</tr>
</tbody>
</table>
</div>Contoh Penggunaan
Section titled “Contoh Penggunaan”Table with API Integration
Section titled “Table with API Integration”Contoh implementasi Table dengan integrasi API menggunakan DummyJSON Products API. Menampilkan mekanisme fetch data, pagination, searching, sorting, dan loading state. Jika response API kosong atau terjadi error, akan menggunakan fallback dummy data.
<div id="table-with-api-container" class="space-y-4">
<!-- Search Bar -->
<div class="flex items-center gap-2">
<div class="flex-1">
<div class="ina-text-field">
<div class="ina-text-field__wrapper ina-text-field__wrapper--size-md">
<input
type="text"
id="table-search-input"
class="ina-text-field__input"
placeholder="Search products..."
/>
</div>
</div>
</div>
<button
type="button"
id="table-search-button"
class="ina-button ina-button--primary ina-button--md"
>
Cari
</button>
</div>
<!-- Table Container -->
<div id="table-container"></div>
</div>
<script is:inline>
function initializeTableAPI() {
const container = document.getElementById('table-with-api-container');
if (!container || container.dataset.initialized) return;
container.dataset.initialized = 'true';
// Fallback dummy data
const FALLBACK_PRODUCTS = [
{
id: 1,
title: 'iPhone 9',
description: 'An apple mobile which is nothing like apple',
category: 'smartphones',
price: 549,
discountPercentage: 12.96,
rating: 4.69,
stock: 94,
brand: 'Apple',
thumbnail: 'https://cdn.dummyjson.com/products/images/smartphones/iPhone%209/thumbnail.jpg',
},
{
id: 2,
title: 'iPhone X',
description: 'SIM-Free, Model A19211 6.5-inch Super Retina HD display with OLED technology',
category: 'smartphones',
price: 899,
discountPercentage: 17.94,
rating: 4.44,
stock: 34,
brand: 'Apple',
thumbnail: 'https://cdn.dummyjson.com/products/images/smartphones/iPhone%20X/thumbnail.jpg',
},
];
async function fetchDummyProducts({ page, pageSize, searchTerm, sortField, sortOrder }) {
try {
const skip = (page - 1) * pageSize;
const base = 'https://dummyjson.com/products';
let url = searchTerm
? `${base}/search?q=${encodeURIComponent(searchTerm)}&limit=${pageSize}&skip=${skip}`
: `${base}?limit=${pageSize}&skip=${skip}`;
if (sortField && sortOrder) {
url += `&sortBy=${sortField}&order=${sortOrder}`;
}
const res = await fetch(url);
if (!res.ok) throw new Error(`DummyJSON API error: ${res.status}`);
const json = await res.json();
const products = json.products || [];
const resTotal = json.total || 0;
if (products.length === 0 && !searchTerm) throw new Error('No products returned');
return { data: products, total: resTotal };
} catch (error) {
console.warn('DummyJSON Error, using fallback data:', error);
// Manual fallback search and sort for demonstration
let filteredData = [...FALLBACK_PRODUCTS];
if (searchTerm) {
const lower = searchTerm.toLowerCase();
filteredData = filteredData.filter(
(item) =>
item.title.toLowerCase().includes(lower) ||
item.description.toLowerCase().includes(lower) ||
(item.brand && item.brand.toLowerCase().includes(lower)) ||
item.category.toLowerCase().includes(lower)
);
}
if (sortField && sortOrder) {
filteredData.sort((a, b) => {
const aVal = a[sortField];
const bVal = b[sortField];
if (aVal < bVal) return sortOrder === 'asc' ? -1 : 1;
if (aVal > bVal) return sortOrder === 'asc' ? 1 : -1;
return 0;
});
}
const skip = (page - 1) * pageSize;
return { data: filteredData.slice(skip, skip + pageSize), total: filteredData.length };
}
}
if (window.InaUI && typeof window.InaUI.Table === 'function') {
new window.InaUI.Table('#table-container', {
searchContainer: '#table-search-input',
searchButton: '#table-search-button',
initialPageSize: 10,
pageSizeOptions: [10, 20, 50],
rowKey: 'id',
columns: [
{
header: 'Product',
accessor: 'title',
sortable: true,
render: (row) => `
<div style="display: flex; align-items: center; gap: 8px;">
<img
src="${row.thumbnail}"
alt="${row.title}"
style="width: 40px; height: 40px; border-radius: 4px; object-fit: cover;"
crossOrigin="anonymous"
referrerPolicy="no-referrer"
loading="lazy"
onerror="this.src='https://via.placeholder.com/40?text=No+Image'"
/>
<div style="display: flex; flex-direction: column;">
<span style="font-weight: 500;">${row.title}</span>
<span style="font-size: 12px; color: #6B7280;">${row.brand || ''}</span>
</div>
</div>
`,
},
{
header: 'Price',
accessor: 'price',
sortable: true,
render: (row) => `<span style="font-weight: 600;">$${row.price}</span>`,
},
{
header: 'Stock',
accessor: 'stock',
sortable: true,
render: (row) =>
`<span style="color: ${row.stock > 50 ? '#10B981' : '#EF4444'};">${row.stock}</span>`,
},
{
header: 'Rating',
accessor: 'rating',
sortable: true,
render: (row) => `
<div style="display: flex; align-items: center; gap: 4px;">
<span>${row.rating}</span>
<span style="color: #f6da09;">★</span>
</div>
`,
},
{
header: 'Category',
accessor: 'category',
sortable: true,
},
],
fetchData: fetchDummyProducts,
});
} else {
// Re-queue if library hasn't loaded yet
setTimeout(initializeTableAPI, 50);
}
}
// Handle initialization properly for Astro view transitions and normal loads
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeTableAPI);
} else {
initializeTableAPI();
}
document.addEventListener('astro:page-load', initializeTableAPI);
</script>
<style>
.space-y-4 > * + * {
margin-top: 1rem;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.gap-2 {
gap: 0.5rem;
}
.flex-1 {
flex: 1 1 0%;
}
</style>