I have full page component where a user can buy ticket etc for more persons at once. I store the states in the component in one place for easy handling. I loop the persons, every person can select the price, and discount. The discount select is a custom one used choices.js, because even native select works the designer needs custom styled select. and choices.js looks like on the plan, and its vanilla js.
So the problem comes, when the user first selects the discount, discount calculated, replaced the price with discounted one, but when the user tries to use select to choose nothing or diff. discount, the select instantly closes (js change event calls a php method for calculate...).
I added logs to the code parts, and the morphed hook always run, rerenders the choices.js, and this make the select close instantly i think.
I used morphed hook because I saw it in some tutorials to call again the "plugin" after render.
Any idea how to fix that, or how to refactor that the real time calculation functionality remain?
the component:
@props([
'id' => 'choices-optgroup-' . uniqid(),
'options' => [],
'mult' => false,
'placeholder' => 'Select an option',
'selected' => null,
'model' => null,
'index' // required for event
])
<div>
<select
id="{{ $id }}"
wire:ignore.self
class="choices-select"
@if($mult) multiple @endif
>
<option value="">{{ $placeholder }}</option>
@foreach($options as $group)
@if(is_array($group) && isset($group['label']) && isset($group['options']))
<optgroup label="{{ $group['label'] }}">
@foreach($group['options'] as $item)
<option value="{{ $item['value'] }}"
@if($mult && is_array($selected) && in_array($item['value'], $selected)) selected
@elseif(!$mult && $selected == $item['value']) selected
@endif
>{{ $item['label'] }}</option>
@endforeach
</optgroup>
@endif
@endforeach
</select>
</div>
@script
<script>
$(document).ready(function(){
function initChoices() {
const el = document.getElementById(@json($id));
if (!el) return;
if (el.choicesInstance) {
el.choicesInstance.destroy();
}
const choices = new Choices(el, {
removeItemButton: @json($mult),
allowHTML: false,
placeholder: true,
placeholderValue: @json($placeholder),
});
el.choicesInstance = choices;
@if($mult)
if (@json($selected) && Array.isArray(@json($selected)) && @json($selected).length) {
choices.setChoiceByValue(@json($selected));
}
@else
var selectedValue = @json($selected) === null ? '' : @json($selected);
if (typeof selectedValue === 'undefined') selectedValue = '';
if (selectedValue !== '') {
choices.setChoiceByValue(selectedValue);
}
@endif
el.addEventListener('change', function(e) {
let value = @json($mult) ? Array.from(el.selectedOptions).map(o => o.value) : el.value;
let called = false;
if (window.Livewire && typeof window.Livewire.find === 'function') {
function findWireIdElement(element) {
while (element && element !== document) {
if (element.hasAttribute && element.hasAttribute('wire:id')) return element;
element = element.parentElement;
}
return null;
}
const wireEl = findWireIdElement(el);
if (wireEl) {
const component = window.Livewire.find(wireEl.getAttribute('wire:id'));
if (component) {
component.call('discountChanged', @json($index), value);
called = true;
}
}
// Fallback: call on all Livewire components if not found
if (!called && window.Livewire.components && window.Livewire.components.length) {
window.Livewire.components.forEach(function(component) {
if (typeof component.call === 'function') {
component.call('discountChanged', @json($index), value);
}
});
}
}
console.log('select changed');
});
}
initChoices();
Livewire.hook('morphed', () => {
console.log('init called in morph');
initChoices();
});
});
</script>
@endscript
calling component which is in the persons loop:
<x-choices-optgroup-select
:id="'discount-select-' . $index"
:options="$discountOptions"
:selected="$persons[$index]['discount_id'] ?? ''"
:index="$index"
/>
the component works if its just sits on the page and renders at page load. You ask why $(document).ready().. DOMContentLoaded doesn't work in here..