DEV Community

Erlangga Satria
Erlangga Satria

Posted on

Membuat HTML AdminLTE Menjadi Reaktif dengan HTMP

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

#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>
`);
Enter fullscreen mode Exit fullscreen mode

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'
  }
];
Enter fullscreen mode Exit fullscreen mode

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'
};
Enter fullscreen mode Exit fullscreen mode

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'
});
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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
  );
}
Enter fullscreen mode Exit fullscreen mode

Rantai datanya menjadi:

search / role filter
        |
        v
filteredUsers
        |
        v
pageCount, pages, range
        |
        v
pagedUsers
        |
        v
table rows and pagination
Enter fullscreen mode Exit fullscreen mode

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);
  }
});
Enter fullscreen mode Exit fullscreen mode

Mount dilakukan sekali setelah pattern, state, dan program siap:

userTable.mount();
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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:

examples/admin-lte/users.html

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)