Search code examples
javascriptlaravellaravel-livewire

Why custom multiselection element is not available after I selected item?


I am working on a 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 App\Livewire\Admin;

use App\Models\Permission;
use Illuminate\Support\Str;
use Livewire\Component;

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",

Solution

  • 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