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-411. 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-2842.
Jalur pemuatan modul
src/assets/js/app.js mengimpor ./mazer setelah memanggil featherIcons.replace() src/assets/js/app.js:6-83. Berkas mazer.js menaruh bootstrap bundle di window.bootstrap, lalu mengimpor modul sidebar src/assets/js/mazer.js:3-74. Di layout, urutan script menentukan kesiapan objek global: perfect-scrollbar.min.js dimuat sebelum app.js src/layouts/master.html:32-385, sehingga pengecekan typeof PerfectScrollbar == "function" di init() lolos pada layout tersebut src/assets/static/js/components/sidebar.js:94-1006.
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.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-2842. onFirstLoad dipanggil langsung bila document.readyState !== 'loading', selain itu ia menunggu DOMContentLoaded src/assets/static/js/components/sidebar.js:268-2737. Bila #sidebar tidak ada, fungsi itu berhenti lebih awal src/assets/static/js/components/sidebar.js:222-2288; kondisi inilah yang membuat layout horizontal tetap aman meski app.js ikut dimuat src/layouts/horizontal.html:123-1309.
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-24210. Ambang desktop berasal dari helper isDesktop, yaitu window.innerWidth > 1200 src/assets/static/js/helper/isDesktop.js:111, dan sejalan dengan media query min-width: 1200px pada #sidebar src/assets/scss/components/_sidebar.scss:18-3512.
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.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-14113. 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-2914.
Dua detail perilakunya:
- dengan
deep = falsehanya level pertama yang dihitung; - dengan
deep = trueisi sub-submenu ditambahkan, tetapi hanya bila.submenubersarang sudah ber-classsubmenu-open; hasilreducedipotong ke bilangan bulat memakai~~.
Pemanggilan terjadi di tiga tempat: onFirstLoad lewat setTimeout 50 ms src/assets/static/js/components/sidebar.js:243-24615, reInit_SubMenuHeight dengan pola yang sama src/assets/static/js/components/sidebar.js:261-26416, dan handler klik .submenu-item.has-sub src/assets/static/js/components/sidebar.js:83-9017. 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-9017.
Submenu bertingkat dan kelas status
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-7818. 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-9119. 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-5420.
| Class | Dipasang oleh | Efek CSS |
|---|---|---|
submenu-open | toggleSubmenu, onFirstLoad | max-height: var(--submenu-height) |
submenu-closed | toggleSubmenu, onFirstLoad | max-height: 0 |
active pada .submenu | template Nunjucks | height: var(--submenu-height) |
active pada #sidebar | show(), onResize() | .sidebar-wrapper kembali ke left: 0 |
inactive pada #sidebar | hide() | .sidebar-wrapper ke left: -300px, #main tanpa margin-left |
Sumber baris tabel: src/assets/scss/components/_sidebar.scss:126-14113, src/assets/static/js/components/sidebar.js:57-7818, src/assets/scss/components/_sidebar.scss:18-3512.
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-5421. Tombol burger berasal dari layout, misalnya tombol header yang hanya tampil di bawah breakpoint d-xl-none src/layouts/master.html:24-2622.
toggle() membaca class active pada this.sidebarEL: bila ada, hide() dipanggil; bila tidak, show() src/assets/static/js/components/sidebar.js:136-14323. show() menambah active, membuang inactive, membuat backdrop, dan menyetel overflow body; hide() melakukan kebalikannya src/assets/static/js/components/sidebar.js:148-16324.
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.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-13125. Setelah itu backdrop dihapus dan overflow body direset src/assets/static/js/components/sidebar.js:128-13026.
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-17527. 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-19428. 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-19929.
Setelah jeda 300 ms, init() memanggil forceElementVisibility untuk .sidebar-item.active src/assets/static/js/components/sidebar.js:102-10830. 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-21731.
| Method | Guard | Efek |
|---|---|---|
toggle() | tidak ada | memilih show() atau hide() berdasarkan class active |
show() | tidak ada | add("active"), remove("inactive"), backdrop, overflow |
hide() | tidak ada | remove("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-14323, src/assets/static/js/components/sidebar.js:148-16324, 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-11333. Instance otomatis tidak mengirim opsi apa pun, jadi jalur ini tidak aktif secara default src/assets/static/js/components/sidebar.js:281-28434; contoh pemakaiannya masih dikomentari di akhir berkas src/assets/static/js/components/sidebar.js:286-29235.
⚠️
reInit_SubMenuHeight(sidebarEl)di dalaminit()memakai variabel modulesidebarEl, bukanthis.sidebarELsrc/assets/static/js/components/sidebar.js:111-11333. Nilai variabel itu baru di-assign ketika modul dieksekusi src/assets/static/js/components/sidebar.js:22236, sehingga instance yang dibuat sebelum baris tersebut siap tidak punya elemen untuk dihitung.
⚠️
reInit_SubMenuHeightmenyalin isionFirstLoad: keduanya mengambil.sidebar-item.has-sub .submenu, menandaisubmenu-openatausubmenu-closed, lalu memanggilcalculateChildrenHeightlewatsetTimeout50 ms src/assets/static/js/components/sidebar.js:249-26537. Perubahan pada satu fungsi harus disalin manual ke fungsi lain.
⚠️ Instance PerfectScrollbar disimpan pada
const pslokal dan tidak pernah dipakai lagi src/assets/static/js/components/sidebar.js:95-9938. Tidak ada jalan memanggilps.update()setelah--submenu-heightberubah, sehingga tinggi scrollbar hanya akurat pada kondisi saatinit()berjalan.
Layout horizontal tidak memakai kelas ini: .burger-btn di sana ditangani skrip terpisah src/assets/static/js/pages/horizontal-layout.js:1-739, dan detail menu horizontalnya dibahas di Data Navigasi Sidebar dan Menu Horizontal.