I am currently making a component for my website with Laravel Livewire. I have a multi-select dropdown where the options are dynamically generated with JavaScript. Issue is, after form submission the options within here are gone:
<div id="dropdown" style="display: none;"
class="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"
wire:ignore.self>
<!-- Dynamically generated options will go here -->
</div>
The problem is, that I can’t use the newest Alpine version with @livewireScripts since I’m using a library of components for my website that has Alpine v2.8 packed into the code. I would extract the code the components need from there, but I can’t since it’s all mixed together.
I’ve been stuck on this issue for like 3 hours now and I give up.
So basically the code works fine until the form within the livewire component is refreshed and poof! The dropdown list is gone.
Here’s my frontend (both console logs within the send-message event work fine):
<div class="relative">
<label class="block text-sm mb-1" for="roles">Mention roles</label>
<div class="mt-1 relative">
<button type="button" id="toggleDropdown"
class="relative w-full bg-white border border-gray-200 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary sm:text-sm">
<span id="displayText" class="block truncate">Select roles</span>
<span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
clip-rule="evenodd" />
</svg>
</span>
</button>
<div id="dropdown" style="display: none;"
class="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"
wire:ignore.self>
<!-- Dynamically generated options will go here -->
</div>
</div>
</div>
<input type="hidden" id="selectedRoles" wire:model="selectedRoles">
<script>
let isDropdownInitialized = false; // Flag to track if dropdown is initialized
document.addEventListener('livewire:initialized', () => {
@this.on('send-message', (data) => {
console.log('Message sent!');
console.log('Roles:', data.roles); // Log roles to check if they are passed correctly
roles = data.roles; // Assign the roles to the roles variable
initializeDropdown(roles); // Reinitialize the dropdown with the new roles
});
});
document.addEventListener('DOMContentLoaded', () => {
// Initialize dropdown when the page is loaded
initializeDropdown(
@json($roles)); // Assuming @json($roles) is available in the page
function initializeDropdown(roles) {
const selectedRolesInput = document.getElementById('selectedRoles');
const toggleDropdownButton = document.getElementById('toggleDropdown');
const dropdown = document.getElementById('dropdown');
const displayText = document.getElementById('displayText');
const dropdownContainer = document.querySelector('.relative');
let selectedOptions = [];
// Function to create dropdown items
function createDropdownItems() {
dropdown.innerHTML = ''; // Clear previous dropdown items
roles.forEach(role => {
const div = document.createElement('div');
div.classList.add('cursor-pointer', 'select-none', 'relative', 'py-2', 'pl-3',
'pr-9',
'hover:bg-primary', 'hover:text-white');
div.setAttribute('data-role-id', role.id);
div.addEventListener('click', function(event) {
event.stopPropagation();
toggleOption(role);
});
const span = document.createElement('span');
span.textContent = role.name;
span.classList.add('block', 'truncate');
const checkIcon = document.createElement('span');
checkIcon.classList.add('absolute', 'inset-y-0', 'right-0', 'flex', 'items-center',
'pr-4',
'text-primary');
checkIcon.style.display = selectedOptions.some(selected => selected.id === role
.id) ? 'block' :
'none';
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('class', 'h-5 w-5');
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svg.setAttribute('viewBox', '0 0 20 20');
svg.setAttribute('fill', 'currentColor');
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('fill-rule', 'evenodd');
path.setAttribute('d',
'M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z'
);
path.setAttribute('clip-rule', 'evenodd');
svg.appendChild(path);
checkIcon.appendChild(svg);
div.appendChild(span);
div.appendChild(checkIcon);
dropdown.appendChild(div);
});
}
// Toggle the dropdown visibility
toggleDropdownButton.addEventListener('click', function(event) {
event.stopPropagation();
dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
});
// Toggle selection of a role
function toggleOption(role) {
const index = selectedOptions.findIndex(selected => selected.id === role.id);
if (index !== -1) {
selectedOptions.splice(index, 1);
} else {
selectedOptions.push(role);
}
updateDisplayText();
createDropdownItems();
updateSelectedRolesInput();
}
// Update the displayed text of selected roles
function updateDisplayText() {
displayText.textContent = selectedOptions.length ? selectedOptions.map(option => option.name)
.join(', ') :
'Select roles';
}
// Update the hidden input field with selected roles
function updateSelectedRolesInput() {
const selectedIds = selectedOptions.map(option => option.id).join(',');
selectedRolesInput.value = selectedIds;
selectedRolesInput.dispatchEvent(new Event('input'));
}
// Initialize the dropdown items
createDropdownItems();
// Close the dropdown if clicked outside
document.addEventListener('click', function(event) {
if (!dropdownContainer.contains(event.target) && dropdown.style.display === 'block') {
dropdown.style.display = 'none';
}
});
}
});
</script>
And this is my backend (partially):
<?php
namespace AppLivewire;
use LivewireComponent;
use GuzzleHttpClient;
use DanHarrinLivewireRateLimitingExceptionsTooManyRequestsException;
use DanHarrinLivewireRateLimitingWithRateLimiting;
use IlluminateSupportFacadesLog;
class ManageMessage extends Component
{
public $server;
public $channels;
public $channel;
public $message;
public $roles = [];
public $selectedRoles;
public $webhook = null;
public function mount($server, $roles, $channels)
{
$this->server = $server;
$this->roles = $roles;
$this->channels = $channels;
}
// more code...
public function send()
{
// actions unrelated to the problem...
try {
$response = $client->post($url, $options);
if ($response->getStatusCode() === 200 || $response->getStatusCode() === 204) {
flash()->flash('success', 'Message has been sent.', [], 'Success');
$this->dispatch('send-message', roles: $this->roles);
} else {
flash()->flash('error', 'An error occurred while sending the message.', [], 'Error');
$this->dispatch('send-message', roles: $this->roles);
}
} catch (Exception $e) {
Log::error('Error sending message: ' . $e->getMessage());
flash()->flash('error', 'An error occurred while sending the message.', [], 'Error');
$this->dispatch('send-message', roles: $this->roles);
}
}
}
BTW my controller contains the $roles variable, which is passed on to the livewire component:
@livewire('manage-message', ['server' => $server, 'roles' => $roles, 'channels' => $channels ?? []])
Does anyone know a solution to this problem?