6
Data Navigasi Sidebar dan Menu Horizontal
Sidebar dan menu horizontal Mazer tidak ditulis di dalam template. Keduanya adalah dua berkas data JSON yang diimpor vite.config.js (vite.config.js:7-81), lalu disuntikkan sebagai variabel sidebarItems dan horizontalMenuItems ke setiap halaman oleh getVariables (vite.config.js:28-402). Template hanya membaca variabel itu: src/layouts/master.html meng-include src/layouts/sidebar.html (src/layouts/master.html:19-213), sedangkan halaman berlayout horizontal meng-extends src/layouts/horizontal.html (src/layout-horizontal.html:1-84).
Penentu status aktif datang dari halaman, bukan dari data. Setiap halaman menetapkan filename sendiri di awal berkasnya (src/component-alert.html:1-45). Kedua layout membandingkan nilai itu dengan url dan key milik setiap simpul menu.
Simpul menu dan relasinya
Bentuk simpulnya seragam di dua berkas: name wajib; url, key, icon, dan submenu opsional. Karena submenu berisi array simpul sejenis, kedalaman struktur tidak dibatasi skema — lihat Multi-level Menu di sidebar (src/sidebar-items.json:340-3716) dan Extra Components yang bersarang di berkas horizontal (src/horizontal-menu-items.json:72-767). Template tetap hanya merender paling dalam tiga tingkat (src/layouts/sidebar.html:55-628, src/layouts/horizontal.html:77-879).
erDiagram
SIDEBAR_FILE ||--|{ SIDEBAR_ITEM : "sidebarItems"
HORIZONTAL_FILE ||--|{ HORIZONTAL_ITEM : "horizontalMenuItems"
SIDEBAR_ITEM ||--o{ SIDEBAR_ITEM : "submenu rekursif"
HORIZONTAL_ITEM ||--o{ HORIZONTAL_ITEM : "submenu rekursif"
SIDEBAR_FILE {
string path "src/sidebar-items.json"
}
HORIZONTAL_FILE {
string path "src/horizontal-menu-items.json"
}
SIDEBAR_ITEM {
string name
string url "opsional, fallback tanda pagar"
string key "prefix untuk startsWith"
string icon "nama Bootstrap Icons"
bool isTitle
array submenu
}
HORIZONTAL_ITEM {
string name
string url "opsional, fallback tanda pagar"
string key "prefix untuk startsWith"
string icon "nama Bootstrap Icons"
array submenu
}
%% fig: Bentuk simpul menu pada dua berkas data
%% desc: Kedua berkas berisi daftar simpul dengan submenu rekursif; field isTitle hanya ada di berkas sidebar.Tabel berikut memetakan setiap field ke pembacaannya di template.
| Field | Tipe | Perilaku di template |
|---|---|---|
name | string | Label judul seksi maupun tautan menu (src/layouts/sidebar.html:41-4510) |
url | string, opsional | Dibandingkan dengan filename; bila undefined, tautan memakai '#' (src/layouts/sidebar.html:46-4911) |
key | string, opsional | Prefix yang diumpankan ke filter startsWith untuk kelas active (src/layouts/sidebar.html:41-4510) |
icon | string | Nama Bootstrap Icons, dirender sebagai bi-{{ icon }} (src/layouts/horizontal.html:56-6312) |
isTitle | boolean | Hanya di berkas sidebar; simpul ini menjadi <li class="sidebar-title"> (src/layouts/sidebar.html:41-4510) |
submenu | array | Rekursif; submenu.length > 0 menambah kelas has-sub dan memicu render <ul class="submenu"> (src/layouts/sidebar.html:50-5413) |
Perbedaan kedua berkas
sidebar-items.json menyisipkan judul seksi yang tidak bisa diklik. isTitle muncul lima kali: Menu (src/sidebar-items.json:1-1014), Forms & Tables (src/sidebar-items.json:160-16315), Extra UI (src/sidebar-items.json:254-257), Pages (src/sidebar-items.json:372-375), dan Raise Support (src/sidebar-items.json:453-456). Berkas horizontal tidak memuat field ini, dan layout-nya memang menjaganya dengan {% if not menuItem.isTitle %} (src/layouts/horizontal.html:58).
Penempatan grup juga berbeda: Extra Components berdiri sebagai simpul top-level di sidebar (src/sidebar-items.json:90-9420), tetapi bersarang di dalam Components di berkas horizontal (src/horizontal-menu-items.json:72-767). Submenu Components versi horizontal berisi 16 entri (src/horizontal-menu-items.json:12-10621).
| Aspek | src/sidebar-items.json | src/horizontal-menu-items.json |
|---|---|---|
| Indentasi | 2 spasi (src/sidebar-items.json:1-1014) | tab (src/horizontal-menu-items.json:1-1122) |
Judul seksi isTitle | 5 entri | tidak ada |
Extra Components | simpul top-level (src/sidebar-items.json:90-9420) | anak dari Components (src/horizontal-menu-items.json:72-767) |
| Tabel | satu tautan Table tanpa submenu (src/sidebar-items.json:234-23823) | grup Table dengan tiga anak (src/horizontal-menu-items.json:213-23324) |
| Kelas tingkat 2 | submenu | submenu dibungkus submenu-group |
| Kelas tingkat 3 | submenu-level-2 | subsubmenu |
Layout horizontal memecah submenu menjadi kolom: maxSubmenuItemY bernilai 8, satu <ul class="submenu-group"> dibuka saat loop.index % maxSubmenuItemY == 1 dan ditutup saat == 0 atau loop.last (src/layouts/horizontal.html:56-7125). Dengan 16 entri, submenu Components terbagi menjadi dua kolom berisi 8 item. Sidebar tidak mengelompokkan apa pun; ia merender satu <ul class="submenu"> per simpul induk (src/layouts/sidebar.html:50-5413).
Status aktif dari filename
Kelas active dihasilkan satu ekspresi: 'active' if (url == filename or filename|startsWith(key)) (src/layouts/sidebar.html:41-4510, src/layouts/horizontal.html:56-6312). Filter startsWith didefinisikan bersama containString di vite.config.js; keduanya mengembalikan false bila argumen pertama kosong, lalu startsWith meneruskan ke String.prototype.startsWith (vite.config.js:130-13926).
graph LR
F["filename halaman"] --> S["filter startsWith"]
K["item.key"] --> S
U["item.url"] --> E{"sama dengan filename"}
S --> A["kelas active"]
E --> A
T["filter containString"] -.-> X["tanpa pemanggil"]
style F fill:#336791,color:#fff
style K fill:#f59e0b,color:#000
style U fill:#f59e0b,color:#000
style S fill:#8b5cf6,color:#fff
style E fill:#8b5cf6,color:#fff
style A fill:#2d7d46,color:#fff
style T fill:#dc2626,color:#fff
style X fill:#dc2626,color:#fff
%% fig: Dua jalur penentuan kelas active
%% desc: Template membandingkan filename dengan url, atau mengumpankannya ke filter startsWith bersama item.key; containString terdaftar tetapi tidak dipanggil.key milik induk dipakai dua kali di sidebar: pada <li class="sidebar-item"> dan pada seluruh <ul class="submenu"> (src/layouts/sidebar.html:41-4510, src/layouts/sidebar.html:50-5413). Simpul anak memakai key miliknya sendiri, dan simpul paling dalam hanya dibandingkan lewat url (src/layouts/sidebar.html:50-54, src/layouts/sidebar.html:55-62). Di layout horizontal, submenu-item hanya memakai kesamaan url (src/layouts/horizontal.html:72-76), sedangkan subsubmenu-item tidak diberi kelas active sama sekali (src/layouts/horizontal.html:77-87).
filename halaman | Entri yang diuji | Hasil |
|---|---|---|
component-modal.html | Components dengan key component (src/sidebar-items.json:12-1528) | aktif lewat filter startsWith |
ui-map-leaflet.html | Maps dengan key ui-map (src/sidebar-items.json:317-32129) | aktif lewat filter startsWith |
index.html | Dashboard tanpa key (src/sidebar-items.json:6-1030) | hanya cocok lewat kesamaan url |
error-404.html | Documentation dengan key error (src/sidebar-items.json:457-47431) | ikut aktif karena prefix error |
Jebakan yang perlu dijaga
⚠️ Tiga entri sidebar memakai
keyyang sama, yaituerror, untukDocumentation,Contribute, danDonate(src/sidebar-items.json:457-47431). Berkas horizontal mengulang pola itu padaSupportbeserta ketiga anaknya (src/horizontal-menu-items.json:385-40832). Akibatnyaerror-403.html,error-404.html, danerror-500.htmlikut menandai entri dukungan tersebut sebagaiactivebersama menuErrors.
⚠️
src/layouts/horizontal.htmlmengujisub.urldi luar loop{% for sub %}dan memakaisidebarItem.key, variabel yang tidak ada di layout horizontal. Ekspresi itu tidak pernah menghasilkanactive, sehingga<div class="submenu">tidak pernah mendapat kelas tersebut (src/layouts/horizontal.html:63-7133).
⚠️ Daftar menu terduplikasi. Menambah satu halaman berarti menyunting
src/sidebar-items.jsondansrc/horizontal-menu-items.json, lalu memastikanfilenamehalaman itu cocok denganurlyang baru (src/component-alert.html:1-45).
⚠️
containStringterdaftar sebagai filter (vite.config.js:130-13926) tetapi tidak dipanggil template mana pun; satu-satunya filter yang dipakai untuk status aktif adalahstartsWith.
Berkas horizontal juga memuat beberapa grup tanpa key, misalnya Table (src/horizontal-menu-items.json:213-23324). Untuk entri semacam itu cabang startsWith tidak pernah bernilai benar, sehingga status aktif sepenuhnya bergantung pada kesamaan url.