Search code examples
laravellaravel-livewirealpine.js

Alpine JS - Creating a menu with active states


I am trying to create a sidebar menu with Alpine JS

enter image description here

I am not even sure if this is possible to do with Alpine JS.

@foreach($kanbans as $kanban)
  <div x-data="activeKanban : ????">
    <div @click="activeKanban = {{$kanban->id}}">

       <div x-show="activeKanban !== {{$kanban->id}}">
        // Design for collapsed kanban
       </div>

    <div>

    <div x-show="activeKanban === {{$kanban->id}}">
        // Design for active kanban
    </div>

  </div>
@endforeach

As I switch trough the pages, the $kanban->id changes, and I was wondering instead of manually setting activeKanban : 1 is there a way to pass this information to AlpineJS?

So that by default if I load the other page, the default menu that would be open would be based on the ID instead of them all being collapsed or just 1 that is specified being open?

enter image description here


Solution

  • If you're aiming for an accordion menu of sorts here's how you might achieve it with AlpineJs based on the code you shared:

    // Set x-data on a div outside the loop and add your active kanban as a property
    <div x-data="{
        activeKanban: {{ $activeKanbanId ?? null }}
    }">
        @foreach($kanbans as $kanban)
            <div @click="activeKanban = {{ $kanban->id }}">
                <div x-show="activeKanban !== {{ $kanban->id }}">
                    // Collapsed view
                </div>
    
                <div x-show="activeKanban === {{ $kanban->id }}">
                    // Expanded view
                </div>
            </div>
        @endforeach
    </div>
    

    Here each kanban menu item will have access to the activeKanban property in AlpineJs component instance and can set it reactively.

    i.e. if activeKanban is set to a new id the current open item will close and the new one will open


    Adding flexibility

    What if you want to open and close them all independently though? There's more than one way to achieve this but in this case we can modify the code above to allow for it:

    // Here we add an array of openItems and two helper functions:
    // isOpen() - to check if the id is either the activeKanban or in the openItems array
    // toggle() - to add/remove the item from the openItems array
    <div x-data="{
        activeKanban: {{ $activeKanbanId ?? null }},
        openItems: [],
    
        isOpen(id){
            return this.activeKanban == id || openItems.includes(id)
        },
        toggle(id){
            if(this.openItems.includes(id)){
                this.openItems = this.openItems.filter(item => {
                    return item !== id
                });
            }else{
                this.openItems.push(id)
            }
        }
    }">
        @foreach($kanbans as $kanban)
            <div @click="toggle({{ $kanban->id }})">
                <div x-show="!isOpen({{$kanban->id}})">
                    // Collapsed view
                </div>
    
                <div x-show="isOpen({{$kanban->id}})">
                    // Expanded view
                </div>
            </div>
        @endforeach
    </div>
    

    This allows us to set an active item and also optionally open/close other menu items.