Lompat ke konten
dokumentasi.id
Commit 6ea47ab
v2
Star3,1 rb

10

Komponen Sidebar JavaScript

Sidebar adalah kelas yang mengendalikan navigasi vertikal Mazer. Kelas ini didefinisikan di src/assets/static/js/components/sidebar.js, menyimpan elemen sidebar pada this.sidebarEL dan opsi pada this.options, lalu memanggil init() dari constructor src/assets/static/js/components/sidebar.js:36-41. Modul itu tidak di-export sebagai ES module; ia mendaftarkan kelasnya ke window.Sidebar dan langsung membuat satu instance bila #sidebar ditemukan src/assets/static/js/components/sidebar.js:278-284.

Jalur pemuatan modul

src/assets/js/app.js mengimpor ./mazer setelah memanggil featherIcons.replace() src/assets/js/app.js:6-8. Berkas mazer.js menaruh bootstrap bundle di window.bootstrap, lalu mengimpor modul sidebar src/assets/js/mazer.js:3-7. Di layout, urutan script menentukan kesiapan objek global: perfect-scrollbar.min.js dimuat sebelum app.js src/layouts/master.html:32-38, sehingga pengecekan typeof PerfectScrollbar == "function" di init() lolos pada layout tersebut src/assets/static/js/components/sidebar.js:94-100.

graph LR
  layout["master.html"] --> app["assets/js/app.js"]
  app --> mazer["assets/js/mazer.js"]
  mazer --> bs["bootstrap bundle"]
  mazer --> sb["components/sidebar.js"]
  sb --> desk["helper/isDesktop.js"]
  sb --> win["window.Sidebar = Sidebar"]
  win --> inst["new Sidebar(#sidebar)"]
  inst --> init["Sidebar.init()"]
  init --> ps["PerfectScrollbar(.sidebar-wrapper)"]
  style sb fill:#2d7d46,color:#fff
  style inst fill:#2d7d46,color:#fff
  style ps fill:#8b5cf6,color:#fff
  style win fill:#f59e0b,color:#000
  style desk fill:#f59e0b,color:#000
%% fig: Rantai impor sampai instance Sidebar
%% desc: app.js mengimpor mazer.js, mazer.js memuat bundle dan modul sidebar, lalu modul itu mendaftar ke window dan membuat satu instance.
Fig. 1: Rantai impor sampai instance Sidebarapp.js mengimpor mazer.js, mazer.js memuat bundle dan modul sidebar, lalu modul itu mendaftar ke window dan membuat satu instance.

Siklus hidup saat halaman dimuat

Modul menjalankan dua urutan terpisah di level atas. onFirstLoad menyiapkan class awal, dan instance Sidebar dibuat setelahnya src/assets/static/js/components/sidebar.js:278-284. onFirstLoad dipanggil langsung bila document.readyState !== 'loading', selain itu ia menunggu DOMContentLoaded src/assets/static/js/components/sidebar.js:268-273. Bila #sidebar tidak ada, fungsi itu berhenti lebih awal src/assets/static/js/components/sidebar.js:222-228; kondisi inilah yang membuat layout horizontal tetap aman meski app.js ikut dimuat src/layouts/horizontal.html:123-130.

Pada layar desktop, onFirstLoad menambahkan class active dan sidebar-desktop, lalu menandai setiap .submenu sebagai submenu-open bila .sidebar-item induknya active, atau submenu-closed bila tidak src/assets/static/js/components/sidebar.js:229-242. Ambang desktop berasal dari helper isDesktop, yaitu window.innerWidth > 1200 src/assets/static/js/helper/isDesktop.js:1, dan sejalan dengan media query min-width: 1200px pada #sidebar src/assets/scss/components/_sidebar.scss:18-35.

sequenceDiagram
  participant L as master.html
  participant B as app.js / mazer.js
  participant M as sidebar.js
  participant S as Sidebar
  participant V as --submenu-height

  L->>L: muat perfect-scrollbar.min.js
  L->>B: muat app.js type=module
  B->>M: import components/sidebar
  M->>M: sidebarEl = getElementById('sidebar')
  M->>M: onFirstLoad(sidebarEl)
  M->>M: isDesktop(window) lalu add active, sidebar-desktop
  M->>M: submenu-open atau submenu-closed per item
  Note over M: setTimeout 50 ms
  M->>V: calculateChildrenHeight(submenu, true)
  M->>S: new window.Sidebar(sidebarEl)
  S->>S: bind .burger-btn, .sidebar-hide, resize
  S->>S: bind .sidebar-link dan .submenu-item.has-sub
  S->>S: new PerfectScrollbar(.sidebar-wrapper)
  Note over S: setTimeout 300 ms, forceElementVisibility(.sidebar-item.active)
%% fig: Urutan inisialisasi sidebar saat halaman dimuat
%% desc: Modul menyiapkan class awal dan tinggi submenu lebih dulu, baru setelah itu instance Sidebar memasang listener dan PerfectScrollbar.
Fig. 2: Urutan inisialisasi sidebar saat halaman dimuatModul menyiapkan class awal dan tinggi submenu lebih dulu, baru setelah itu instance Sidebar memasang listener dan PerfectScrollbar.

Menghitung --submenu-height

Submenu dianimasikan lewat max-height, bukan display, agar transisinya halus. Nilai max-height diambil dari variabel CSS --submenu-height src/assets/scss/components/_sidebar.scss:126-141. Variabel itu diisi calculateChildrenHeight, yang menjumlahkan clientHeight setiap .submenu-link anak lalu menyimpannya dengan el.style.setProperty('--submenu-height', height + 'px') src/assets/static/js/components/sidebar.js:8-29.

Dua detail perilakunya:

  • dengan deep = false hanya level pertama yang dihitung;
  • dengan deep = true isi sub-submenu ditambahkan, tetapi hanya bila .submenu bersarang sudah ber-class submenu-open; hasil reduce dipotong ke bilangan bulat memakai ~~.

Pemanggilan terjadi di tiga tempat: onFirstLoad lewat setTimeout 50 ms src/assets/static/js/components/sidebar.js:243-246, reInit_SubMenuHeight dengan pola yang sama src/assets/static/js/components/sidebar.js:261-264, dan handler klik .submenu-item.has-sub src/assets/static/js/components/sidebar.js:83-90. Pada kasus ketiga, fungsi menerima item.parentElement, yaitu .submenu satu tingkat di atas item yang diklik, bukan submenu milik item itu sendiri src/assets/static/js/components/sidebar.js:83-90.

init() memasang satu listener click untuk setiap .sidebar-link milik .sidebar-item.has-sub. Handler memanggil e.preventDefault() supaya tautan # tidak melompat, lalu toggleSubmenu menukar submenu-open dan submenu-closed src/assets/static/js/components/sidebar.js:57-78. Level kedua ditangani terpisah: setiap .submenu-item.has-sub mendapat listener sendiri yang menukar class .submenu anaknya dan menghitung ulang tinggi induknya src/assets/static/js/components/sidebar.js:80-91. Struktur class yang menjadi kontraknya dihasilkan template: sidebar-item has-sub, submenu, submenu-item has-sub, submenu submenu-level-2, dan submenu-link src/layouts/sidebar.html:44-54.

ClassDipasang olehEfek CSS
submenu-opentoggleSubmenu, onFirstLoadmax-height: var(--submenu-height)
submenu-closedtoggleSubmenu, onFirstLoadmax-height: 0
active pada .submenutemplate Nunjucksheight: var(--submenu-height)
active pada #sidebarshow(), onResize().sidebar-wrapper kembali ke left: 0
inactive pada #sidebarhide().sidebar-wrapper ke left: -300px, #main tanpa margin-left

Sumber baris tabel: src/assets/scss/components/_sidebar.scss:126-141, src/assets/static/js/components/sidebar.js:57-78, src/assets/scss/components/_sidebar.scss:18-35.

Tombol burger dan status #sidebar

init() memasang toggle pada setiap .burger-btn dan .sidebar-hide, lalu onResize pada event resize jendela src/assets/static/js/components/sidebar.js:46-54. Tombol burger berasal dari layout, misalnya tombol header yang hanya tampil di bawah breakpoint d-xl-none src/layouts/master.html:24-26.

toggle() membaca class active pada this.sidebarEL: bila ada, hide() dipanggil; bila tidak, show() src/assets/static/js/components/sidebar.js:136-143. show() menambah active, membuang inactive, membuat backdrop, dan menyetel overflow body; hide() melakukan kebalikannya src/assets/static/js/components/sidebar.js:148-163.

stateDiagram-v2
    [*] --> Inactive
    Inactive --> Active: click .burger-btn atau .sidebar-hide
    Active --> Inactive: toggle() saat class active ada
    Active --> Active: onResize() di desktop
    Inactive --> Inactive: onResize() di mobile
    note right of Active
        show() add active, remove inactive,
        createBackdrop(), toggleOverflowBody()
    end note
    style Active fill:#2d7d46,color:#fff
    style Inactive fill:#f59e0b,color:#000
%% fig: Perpindahan class active dan inactive pada sidebar
%% desc: Klik burger menukar keadaan, show() dan hide() mengatur class serta backdrop, sedangkan onResize hanya menambah active di desktop.
Fig. 3: Perpindahan class active dan inactive pada sidebarKlik burger menukar keadaan, show() dan hide() mengatur class serta backdrop, sedangkan onResize hanya menambah active di desktop.

onResize() memeriksa isDesktop(window): di desktop ia menambah active dan membuang inactive, di layar sempit ia hanya membuang active src/assets/static/js/components/sidebar.js:120-131. Setelah itu backdrop dihapus dan overflow body direset src/assets/static/js/components/sidebar.js:128-130.

Backdrop, overflow body, dan item aktif

createBackdrop() keluar lebih awal ketika isDesktop(window) bernilai true, jadi backdrop hanya muncul di layar sempit src/assets/static/js/components/sidebar.js:168-175. Elemen div.sidebar-backdrop ditambahkan ke document.body dengan listener click yang memanggil hide(); gayanya position: fixed, latar rgba(0, 0, 0, 0.5), dan z-index: 9 src/assets/scss/components/_sidebar.scss:186-194. toggleOverflowBody() juga melewati desktop, lalu menyetel body.style.overflowY menjadi hidden saat sidebar active dan auto saat tidak; argumen eksplisit true memaksa nilai auto src/assets/static/js/components/sidebar.js:190-199.

Setelah jeda 300 ms, init() memanggil forceElementVisibility untuk .sidebar-item.active src/assets/static/js/components/sidebar.js:102-108. Fungsi itu memakai isElementInViewport, yang membandingkan getBoundingClientRect dengan window.innerHeight dan window.innerWidth, dan hanya memanggil el.scrollIntoView(false) bila item berada di luar viewport src/assets/static/js/components/sidebar.js:201-217.

MethodGuardEfek
toggle()tidak adamemilih show() atau hide() berdasarkan class active
show()tidak adaadd("active"), remove("inactive"), backdrop, overflow
hide()tidak adaremove("active"), add("inactive"), hapus backdrop, overflow
onResize()isDesktop(window)desktop: tambah active; mobile: buang active
createBackdrop()isDesktop(window)append div.sidebar-backdrop
toggleOverflowBody()isDesktop(window)body.style.overflowY menjadi hidden atau auto
forceElementVisibility()isElementInViewport()el.scrollIntoView(false)

Sumber baris tabel: src/assets/static/js/components/sidebar.js:136-143, src/assets/static/js/components/sidebar.js:148-163, src/assets/static/js/components/sidebar.js:120-131, src/assets/static/js/components/sidebar.js:168-175, src/assets/static/js/components/sidebar.js:190-199, src/assets/static/js/components/sidebar.js:213-217.

Opsi recalculateHeight dan jebakan implementasi

Constructor menerima options dengan satu kunci yang benar-benar dibaca: recalculateHeight. Bila truthy, init() memanggil reInit_SubMenuHeight src/assets/static/js/components/sidebar.js:111-113. Instance otomatis tidak mengirim opsi apa pun, jadi jalur ini tidak aktif secara default src/assets/static/js/components/sidebar.js:281-284; contoh pemakaiannya masih dikomentari di akhir berkas src/assets/static/js/components/sidebar.js:286-292.

⚠️ reInit_SubMenuHeight(sidebarEl) di dalam init() memakai variabel module sidebarEl, bukan this.sidebarEL src/assets/static/js/components/sidebar.js:111-113. Nilai variabel itu baru di-assign ketika modul dieksekusi src/assets/static/js/components/sidebar.js:222, sehingga instance yang dibuat sebelum baris tersebut siap tidak punya elemen untuk dihitung.

⚠️ reInit_SubMenuHeight menyalin isi onFirstLoad: keduanya mengambil .sidebar-item.has-sub .submenu, menandai submenu-open atau submenu-closed, lalu memanggil calculateChildrenHeight lewat setTimeout 50 ms src/assets/static/js/components/sidebar.js:249-265. Perubahan pada satu fungsi harus disalin manual ke fungsi lain.

⚠️ Instance PerfectScrollbar disimpan pada const ps lokal dan tidak pernah dipakai lagi src/assets/static/js/components/sidebar.js:95-99. Tidak ada jalan memanggil ps.update() setelah --submenu-height berubah, sehingga tinggi scrollbar hanya akurat pada kondisi saat init() berjalan.

Layout horizontal tidak memakai kelas ini: .burger-btn di sana ditangani skrip terpisah src/assets/static/js/pages/horizontal-layout.js:1-7, dan detail menu horizontalnya dibahas di Data Navigasi Sidebar dan Menu Horizontal.