skip to Main Content

On laravel 10 site using docs at https://dev.to/koossaayy/laravel-livewire-multiple-selection-with-virtual-select-1f87 I try to make multiselection.
I made it with some changes in the code. In component :

<?php

namespace AppLivewireAdmin;

use AppModelsPermission;
use IlluminateSupportStr;
use LivewireComponent;

class UsersPermissionsEditor extends Component
{
    public $selectedPermissions= [];
    public $permissionsListing= [];

    public function render()
    {
        $this->permissionsListing = Permission::query()->get()/*->pluck('name', 'id')*/
            ->map(function ($selectedPermissionItem) {
                return ['id' => $selectedPermissionItem->id, 'label' => Str::replace('_', ' ', Str::title($selectedPermissionItem->name))];
            })
        ->toArray();
        return view('livewire.admin.users-permissions-editor');
    }
}

and in blade :

<div class="admin_page_container" id="users_permissions_editor_admin_page_container">
    <div class="editor_listing_wrapper_bix_width" x-data="adminUsersPermissionsEditorComponent()"
         x-init="[onAdminUsersPermissionsEditorComponentInit() ]" x-cloak>

        $selectedPermissions::{{ print_r($selectedPermissions, true) }}

        <input id="permissionsListing" name="permissionsListing" value="add permissionsListing TEST">
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/virtual-select.min.js"
                integrity="sha256-Gsn2XyJGdUeHy0r4gaP1mJy1JkLiIWY6g6hJhV5UrIw=" crossorigin="anonymous"></script>

        <link rel="stylesheet"
              href="https://cdn.jsdelivr.net/npm/[email protected]/dist/virtual-select.min.css"
              integrity="sha256-KqTuc/vUgQsb5EMyyxWf62qYinMUXDpWELyNx+cCUr0=" crossorigin="anonymous">

        <div>
             AAAAA<div id="permissions"></div>BBB
            <script>

                function adminUsersPermissionsEditorComponent() {
                    console.log('adminUsersPermissionsEditorComponent::')

                    return {
                        fillUsersPermissions: function () {
                            var permissionsListing = @json($permissionsListing)

                                VirtualSelect.init({
                                    ele: '#permissions',
                                    multiple: true,
                                    options: permissionsListing,
                                });
                            let selectedPermissions = document.querySelector('#permissions');
                            selectedPermissions.addEventListener('change', () => {
                                let data = selectedPermissions.value;
                                @this.set('selectedPermissions', data);
                            });
                        },



                        onAdminUsersPermissionsEditorComponentInit: function () {
                        },


                    }
                }


            </script>

        </div>
    </div>

I see available multiselection element, but when I click on one of elements the multiselection element is not visible at all,

What I see in browsers console :

enter image description here

How that can be fixed ?

"laravel/framework": "^10.48.4",
"livewire/livewire": "^3.4.9",

Thanks in advance!

2

Answers


  1. The main problem is that every time @this.set(‘selectedPermissions’,…. is applied a call to the backend is done, so the DOM is redrawn and the changes and the event listeners applied by VirtualSelect are lost.

    A simple fix is to add a wire:ignore to the <div> used by VirtualSelect, in this way Livewire will not alter the content of that <div>:

    class UsersPermissionsEditor extends Component
    {
        public $selectedPermissions = [1, 3];
        public $permissionsListing = [];
    
    
        public function render()
        {
            $this->permissionsListing = Permission::query()
                      ->get(['id', 'name'])
                      ->map(function ($selectedPermissionItem) {
                                return ['value' => $selectedPermissionItem->id,
                                        'label' => Str::replace('_', ' ', Str::title($selectedPermissionItem->name))
                                ];
            })->toArray();
    
            return view('livewire.admin.users-permissions-editor');
        }
    }
    
    <div class="admin_page_container" id="users_permissions_editor_admin_page_container">
    
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/virtual-select.min.js"
                integrity="sha256-Gsn2XyJGdUeHy0r4gaP1mJy1JkLiIWY6g6hJhV5UrIw=" crossorigin="anonymous">
        </script>
    
        <link rel="stylesheet"
              href="https://cdn.jsdelivr.net/npm/[email protected]/dist/virtual-select.min.css"
              integrity="sha256-KqTuc/vUgQsb5EMyyxWf62qYinMUXDpWELyNx+cCUr0=" crossorigin="anonymous"
        >
    
        <div class="editor_listing_wrapper_bix_width"
             x-data="adminUsersPermissionsEditorComponent('permissions')"
             x-cloak
        >
            <div>
    
                <div wire:ignore id="permissions"></div>
    
            </div>
    
        </div>
    
        selectedPermissions: {{ implode(', ', $selectedPermissions) }}
    
    </div>
    
    <script>
    
        function adminUsersPermissionsEditorComponent(element) {
    
            return {
    
                init: function () {
    
                    VirtualSelect.init({
    
                        ele: "#" + element,
                        multiple: true,
                        options: @json($permissionsListing), 
                        selectedValue: @json($selectedPermissions) 
                    });
    
                    document.getElementById(element).addEventListener("change", function () {
    
                        // console.log (this.value);
                        @this.set("selectedPermissions", this.value);
                    });
                },
            }
        }
    
    </script>
    

    In this example I’ve applied some changes:

    • in the class the map() must return value not id
    • in the view x-init is not needed: I’ve replaced the name of the fillUsersPermissions() function with init() to allow it’s auto-execution
    • I parameterized the id of the <div> affected by VirtualSelect
    • I’ve added a display of the $selectedPermissions variable for debugging
    Login or Signup to reply.
  2. Here is a different version using Tailwind CSS, Livewire and Alpine I created to allow multiple roles to be selected for a user.

    <div x-data="{ open: false, selectedRoles: @entangle('selectedRoles') }" class="relative">
        <x-label for="roles" required value="{{ __('Role(s)') }}" />
        <div class="relative mt-2">
            <button @click="open = !open" type="button" class="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-sky-600 sm:text-sm sm:leading-6" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label">
                <span class="block truncate" x-text="selectedRoles.length > 0 ? `${selectedRoles.length} role${selectedRoles.length > 1 ? 's' : ''} selected` : '{{ __('Select Roles') }}'">{{ __('Select Roles') }}</span>
                <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
                    <svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
                        <path fill-rule="evenodd" d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" clip-rule="evenodd" />
                    </svg>
                </span>
            </button>
    
            <ul x-show="open" @click.away="open = false" x-cloak class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" tabindex="-1">
                @foreach($roles as $role)
                    <li x-bind:class="selectedRoles.includes('{{ $role->name }}') ? 'bg-gray-50' : ''" @click="selectedRoles.includes('{{ $role->name }}') ? selectedRoles = selectedRoles.filter(name => name !== '{{ $role->name }}') : selectedRoles.push('{{ $role->name }}')" class="text-gray-900 relative cursor-default select-none py-2 pl-3 pr-9">
                    <span x-bind:class="selectedRoles.includes('{{ $role->name }}') ? 'font-semibold text-sky-800' : 'font-normal'" class="block truncate">
                        {{ $role->name }}
                    </span>
                        <!-- Display a checkmark if the role is selected -->
                        <span x-show="selectedRoles.includes('{{ $role->name }}')" class="text-sky-600 absolute inset-y-0 right-0 flex items-center pr-4">
                            <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
                                <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
                            </svg>
                        </span>
                    </li>
                @endforeach
            </ul>
        </div>
    </div>
    

    In your Livewire component:

    public $selectedRoles = [];
    
    public function render()
    {
        return view('livewire.componenet', [
            'roles' => Role::all()
        ]);
    }
    

    To prefill the roles, for instance if you are editing, you would load the selected roles via mount():

    public $selectedRoles = [];
    
    public function mount(): void
    {
        $this->selectedRoles = $this->user->getRoleNames();
    }
    

    Output:

    Multiselect Screenshot

    If you’re using the Tall stcak there will be no need to import any additional JS libraries to run the code above.

    Dropdown will show a checkmark next to the selected items and the number of selected items in the field.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search