Почему пользовательский элемент множественного выбора недоступен после того, как я выбрал элемент?

Я работаю над сайтом Laravel 10. Используя документацию по адресу https://dev.to/koossaayy/laravel-livewire-multiple-selection-with-virtual-select-1f87, я пытаюсь сделать множественный выбор. Я сделал это с некоторыми изменениями в коде.

В компоненте:

<?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');
    }
}

и в Блейде:

<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.info('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>

Я вижу доступный элемент множественного выбора, но когда я нажимаю на один из элементов, элемент множественного выбора вообще не отображается.

Что я вижу в консоли браузера:

Как это можно исправить?

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

🤔 А знаете ли вы, что...
JavaScript поддерживает работу с различными форматами данных, такими как JSON и XML.


69
2

Ответы:

Решено

Основная проблема заключается в том, что каждый раз, когда применяется @this.set('selectedPermissions',....), выполняется вызов бэкэнда, поэтому DOM перерисовывается, а изменения и прослушиватели событий, примененные VirtualSelect, теряются.

Простое решение — добавить провод:игнорировать в <div>, используемый VirtualSelect, таким образом Livewire не будет изменять содержимое этого <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.info (this.value);
                    @this.set("selectedPermissions", this.value);
                });
            },
        }
    }

</script>

В этом примере я применил некоторые изменения:

  • в классе карта() должна возвращать значение, а не идентификатор
  • в представлении x-init не нужен: я заменил имя функции fillUsersPermissions() на init(), чтобы разрешить ее автоматическое выполнение
  • Я параметризовал идентификатор <div>, на который влияет VirtualSelect.
  • Я добавил отображение переменной $selectedPermissions для отладки.

Вот другая версия, созданная мной с использованием Tailwind CSS, Livewire и Alpine, чтобы позволить пользователю выбирать несколько ролей.

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

В вашем компоненте Livewire:

public $selectedRoles = [];

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

Чтобы предварительно заполнить роли, например, если вы редактируете, вы должны загрузить выбранные роли через mount():

public $selectedRoles = [];

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

Выход:

Если вы используете Tall stcak, вам не нужно будет импортировать какие-либо дополнительные библиотеки JS для запуска приведенного выше кода.

В раскрывающемся списке рядом с выбранными элементами и количеством выбранных элементов в поле будет отображаться галочка.