Contoh praktis mengubah halaman AdminLTE yang sudah ada menjadi reaktif dengan Proxy, tanpa menulis ulang layout dan tanpa manipulasi DOM jQuery yang berlapis.
Membuat HTML AdminLTE Menjadi Reaktif dengan HTMP
Banyak aplikasi admin sudah memiliki HTML yang matang: layout, sidebar, modal, tabel, dan plugin jQuery. Masalahnya muncul ketika sebagian halaman membutuhkan data reaktif.
Pilihan yang sering muncul adalah menulis ulang halaman dengan framework besar. Padahal, sering kali kita hanya perlu membuat satu bagian menjadi hidup. Atau menulis Jquery step by step untuk beserta event listenernya untuk membuat terasa reaktif.
Di artikel ini kita akan melihat pola yang lebih kecil: mempertahankan HTML AdminLTE, lalu menjadikan User Directory reaktif dengan HTMP.
Apa Itu HTMP?
HTMP adalah runtime kecil untuk membuat HTML biasa menjadi reaktif menggunakan API browser:
-
Pattern: template HTML dengan binding. -
Proxy: state reaktif. -
Program: fungsi untuk event dan logika aplikasi.
Tidak ada JSX, virtual DOM, compiler, atau diffing tree.
Pattern -> Proxy -> Program -> surgical DOM update
Sesuai nama HyperText Mutation Projection, teman-teman tetap tulis hypertext syntax HTML seperti biasa, binding state sehingga menangkap mutasi, maka Projection presisi langsung update.
HTMP hanya mengelola root yang kita pilih. Seluruh halaman tidak harus menjadi aplikasi HTMP.
HTML Lama Tetap Bisa Dipakai
Halaman AdminLTE tetap memiliki struktur normal:
<div class="card">
<div class="card-header">
<h3 class="card-title">User Directory</h3>
</div>
<div id="users-card-content"></div>
</div>
#users-card-content menjadi satu projection root. Navbar, sidebar, modal, dan komponen AdminLTE lain tetap menjadi HTML biasa.
Membuat Pattern Card User
Pattern ini mengisi card body, tabel, dan pagination dalam satu projection:
const userTable = new HTMP('users-card-content', /* html */ `
<div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle m-0">
<thead>
<tr>
<th>User</th>
<th>Email</th>
<th>Role</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr :for="user in pagedUsers">
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td>
<span :class="'badge ' + roleClass(user.role)">
{{ user.role }}
</span>
</td>
<td>
<span :class="'badge ' + statusClass(user.status)">
{{ user.status }}
</span>
</td>
<td>
<button
class="btn btn-sm btn-outline-secondary"
@click="editUser(user)"
>
Edit
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card-footer">
Showing {{ rangeStart }} to {{ rangeEnd }} of {{ totalUsers }} users
<select @change="changePageSize(e)">
<option :for="size in pageSizes" :value="size">
{{ size }}
</option>
</select>
<button @click="previousPage()">Previous</button>
<button :for="pageNumber in pages" @click="goToPage(pageNumber)">
{{ pageNumber }}
</button>
<button @click="nextPage()">Next</button>
</div>
</div>
`);
Perhatikan bahwa data user tidak menyimpan class CSS. Data tetap fokus pada domain aplikasi. Misal aplikasi teman-teman bisa fetch atau GET dari server berupa json update ke proxy.user
const users = [
{
id: 1,
name: 'Alexander Pierce',
email: 'alexander.pierce@example.com',
role: 'Administrator',
status: 'Active'
},
{
id: 2,
name: 'Sarah Bullock',
email: 'sarah.bullock@example.com',
role: 'Editor',
status: 'Active'
}
];
Class AdminLTE diselesaikan oleh helper presentation, jadi kita bisa menggunakan object alih-alih if else atau switch:
const roleClasses = {
Administrator: 'text-bg-danger',
Editor: 'text-bg-primary',
Author: 'text-bg-info',
Subscriber: 'text-bg-secondary'
};
const statusClasses = {
Active: 'text-bg-success',
Pending: 'text-bg-warning',
Suspended: 'text-bg-danger'
};
State Dengan Proxy
Semua state card berada di satu Proxy:
userTable.setProxy({
users,
filteredUsers: users.slice(),
pagedUsers: users.slice(0, 10),
search: '',
roleFilter: 'all',
page: 1,
pageSize: 10,
pageSizes: [10, 50, 100],
totalUsers: users.length,
pages: [1],
pageCount: 1,
rangeStart: 1,
rangeEnd: Math.min(10, users.length),
roleClass: (role) => roleClasses[role] || 'text-bg-secondary',
statusClass: (status) => statusClasses[status] || 'text-bg-secondary'
});
Efek Berantai
Ketika user mengetik pencarian, kita tidak perlu mencari row, menghapus HTML lama, atau memasang ulang event handler.
function applyFilters() {
const search = userTable.proxy.search.trim().toLowerCase();
const role = userTable.proxy.roleFilter;
userTable.proxy.filteredUsers = userTable.proxy.users.filter((user) => {
const matchesSearch = !search ||
`${user.name} ${user.email}`.toLowerCase().includes(search);
const matchesRole = role === 'all' ||
user.role.toLowerCase() === role;
return matchesSearch && matchesRole;
});
userTable.proxy.page = 1;
updatePagination();
}
Pagination mengambil data dari hasil filter:
function updatePagination() {
const total = userTable.proxy.filteredUsers.length;
const pageCount = Math.max(
1,
Math.ceil(total / userTable.proxy.pageSize)
);
userTable.proxy.totalUsers = total;
userTable.proxy.pageCount = pageCount;
userTable.proxy.pages = Array.from(
{ length: pageCount },
(_, index) => index + 1
);
userTable.proxy.rangeStart = total === 0
? 0
: (userTable.proxy.page - 1) * userTable.proxy.pageSize + 1;
userTable.proxy.rangeEnd = Math.min(
userTable.proxy.page * userTable.proxy.pageSize,
total
);
const start = (userTable.proxy.page - 1) * userTable.proxy.pageSize;
userTable.proxy.pagedUsers = userTable.proxy.filteredUsers.slice(
start,
start + userTable.proxy.pageSize
);
}
Rantai datanya menjadi:
search / role filter
|
v
filteredUsers
|
v
pageCount, pages, range
|
v
pagedUsers
|
v
table rows and pagination
Satu perubahan state memperbarui bagian-bagian yang bergantung padanya.
Event Tanpa Manipulasi DOM
Program cukup mengubah Proxy:
userTable.setProgram({
searchUsers: (event) => {
userTable.proxy.search = event.target.value;
applyFilters();
},
filterByRole: (event) => {
userTable.proxy.roleFilter = event.target.value;
applyFilters();
},
changePageSize: (event) => {
userTable.proxy.pageSize = Number(event.target.value);
userTable.proxy.page = 1;
updatePagination();
},
goToPage: (page) => {
userTable.proxy.page = page;
updatePagination();
},
editUser: (user) => {
console.log('Edit user:', user.id);
}
});
Mount dilakukan sekali setelah pattern, state, dan program siap:
userTable.mount();
Bagaimana Dengan jQuery?
HTMP tidak harus menggantikan jQuery.
jQuery atau AdminLTE tetap dapat mengelola bagian legacy seperti:
- sidebar;
- modal;
- dropdown;
- plugin pihak ketiga;
- AJAX lama;
- layout global.
HTMP mengelola projection root yang dipilih. Untuk hasil search, kita tidak perlu melakukan hal seperti ini:
$('.user-row').remove();
$('#user-table tbody').html(rowsHtml);
$('.pagination').html(paginationHtml);
Cukup ubah state, lalu HTMP memperbarui projection berdasarkan registry binding.
Self-Healing
Jika DOM di dalam root berubah karena script legacy, browser extension, atau plugin pihak ketiga, HTMP dapat mencari kembali node berdasarkan marker _htmp ketika path lama tidak lagi valid.
Setelah path ditemukan, registry diperbaiki dan update berikutnya kembali menggunakan surgical update.
Demo Lengkap
Implementasi lengkap halaman AdminLTE dapat dilihat di:
Demo tersebut memakai halaman AdminLTE asli dan hanya mengganti isi User Directory dengan satu projection HTMP.
Penutup
HTMP cocok untuk aplikasi yang sudah memiliki HTML, CSS, dan plugin legacy. Kita tidak perlu memindahkan seluruh aplikasi ke framework baru untuk mendapatkan reaktivitas.
Mulai dari satu card. Tambahkan Proxy. Hubungkan event ke state. Biarkan HTMP mengurus projection DOM.
HTML tetap menjadi struktur. Proxy menjadi sumber data. HTMP menjaga projection tetap sinkron.
Top comments (0)