Membuat Mega Menu Berdasarkan Kusi Bali Js ke 2 Menggunakan Sveltejs. Goalnya adalah array dari menu object akan di mapping menjadi DOM tree. Untuk mengetahu tag svelte dan penggunaan bisa lihat - lihat dokumentasi svelte
--> Hasil akhir
Mission
- Menutup semua menu yang terbuka, jika klik di luar menu
- Menutup menu yang terbuka, jika menu lain di klik
Membuat Svelte Component
Satu yang special dari svelte tag adalah <svelte:self/>
dimana ini representatif dari component itu sendiri ini mirip dengan fungsi rekursif.
<script>
export let menu = [];
export let visible = (new Array(menu.length)).fill(false);
function toggle(index, status) {
visible[index] = status
}
function handleClick (index) {
return (e) => {
toggle(index, !visible[index])
}
}
</script>
<ul>
{#each menu as item, index }
{#if item.children}
<li>
<div on:click={handleClick(index)} >{item.name}</div>
{#if visible[index]}
<div >
<svelte:self menu={item.children} />
</div>
{/if}
</li>
{:else}
<li>{item.name}</li>
{/if}
{/each}
</ul>
Menambah Transisi
Transisi pada svelte tidak bisa ditaruh pada tag spesial svelte seperti <svelte:self/>
oleh karena itu saya menaruhnya kedalam tag div dan transisi akan hanya bisa berjalan saat element di hapus atau di tambah, bisa menggunakan logic block else-if. Transisi yang saya gunakan adalah slide yaitu membuat seolah muncul dari atas ke bawah
<script>
import { slide } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
export let menu = [];
export let visible = (new Array(menu.length)).fill(false);
function toggle(index, status) {
visible[index] = status
}
function handleClick (index) {
return (e) => {
toggle(index, !visible[index])
}
}
</script>
<ul>
{#each menu as item, index }
{#if item.children}
<li>
<div on:click={handleClick(index)} >{item.name}</div>
{#if visible[index]}
<div transition:slide="{{delay: 250, duration: 300, easing: quintOut }}">
<svelte:self menu={item.children} />
</div>
{/if}
</li>
{:else}
<li>{item.name}</li>
{/if}
{/each}
</ul>
1. Menutup Menu
Untuk menutup menu ketika melakukan klik di luar submenu saya medengarkan klik pada body element menggunakan tag spesial dari svelte <svelte:body />
. kemudian menggunakan event modifier yaitu capture , hal ini dilakukan agar fungsi handleOutsde
dipanggil sebelum sebuah menu di tekan (menu tertutup dulu baru menu lain yang ditekan muncul submenunya). untuk detailnya mengenai lihat https://javascript.info/bubbling-and-capturing
<script>
import { slide } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
export let menu = [];
export let visible = (new Array(menu.length)).fill(false);
function toggle(index, status) {
visible[index] = status
}
function handleClick (index) {
return (e) => {
toggle(index, !visible[index])
}
}
function handleOutsde (event) {
visible = (new Array(menu.length)).fill(false);
}
</script>
<svelte:body on:click|capture={handleOutsde} />
<ul>
{#each menu as item, index }
{#if item.children}
<li>
<div on:click={handleClick(index)} >{item.name}</div>
{#if visible[index]}
<div transition:slide="{{delay: 250, duration: 300, easing: quintOut }}">
<svelte:self menu={item.children} />
</div>
{/if}
</li>
{:else}
<li>{item.name}</li>
{/if}
{/each}
</ul>
2. Menutup Menu lainnya
Untuk menutup menu lainnya hal pertama yang harus di pikirkan adalah bagaimana agar menu parent tetap terbuka walaupun submenu di klik ini karena fungis sebelumnya handleOutsde
menutup semua menu dengan melabeli menu telah tertutup, untuk mengatasi itu kita perlu memberi tahu parent element bahwa ada child sedang dalam keadaan terbuka untuk itu perlu dibuat sebuah custom event dimana nanti akan medispatch "memanggil fungsi di parent" memberitahu bahwa submenu telah di klik.
<script>
import { slide } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let position = null;
export let menu = [];
export let visible = (new Array(menu.length)).fill(false);
function doToggle(pos, status) {
dispatch('open', {pos, status})
}
function toggle(index, status) {
doToggle(position, true)
visible[index] = status
}
function handleOpen(event) {
toggle(event.detail.pos, event.detail.status);
}
function handleClick (index) {
return (e) => {
toggle(index, !visible[index])
}
}
function handleOutsde (event) {
visible = (new Array(menu.length)).fill(false);
}
</script>
<svelte:body on:click|capture={handleOutsde} />
<ul>
{#each menu as item, index }
{#if item.children}
<li>
<div on:click|stopPropagation={handleClick(index)} >{item.name}</div>
{#if visible[index]}
<div transition:slide="{{delay: 250, duration: 300, easing: quintOut }}">
<svelte:self position={index} menu={item.children} on:open={handleOpen} />
</div>
{/if}
</li>
{:else}
<li>{item.name}</li>
{/if}
{/each}
</ul>