Hi Keen team,
I've come across a problem while using livewire with ktui.
I've got a form inside a modal. I'm using wire:ignore.self on the modal div to let ktui work on the opening and closing of the modal.
Inside I've a form with different inputs using wire:model to synchronise it with a livewire form handling validation.
For the select I've tried many options and none are working properly. I've put a wire:ignore on the div surronding the label and select, i've put the kt-select class and the data-kt-select attribute.
this select also triggers the change on the rest of the form depending on its value. I've used this script on the modal to make it work using the change event of ktselect
const selectElement = document.getElementById("pack");
if (selectElement) {
selectElement.addEventListener("change", (event) => {
const { value } = event.detail.payload;
$wire.$set("form.pack", parseInt(value));
});
}document.addEventListener("livewire:initialized", () => {
Livewire.hook("morph.updated", ({el, component}) => {
KTSelect.createInstances()
})
});Hi
Sorry for the delay. Check your Metronic 9.3.4 download includes the latest ktui.min.js.
Verify the file includes getInstance by checking in the browser console:
console.log(typeof KTSelect.getInstance); // Should be "function"Hi Pierre Ankh
In the recent version, KTSelect has a refresh() method that's designed exactly for this use case. For static selects (non-remote), refresh() syncs the selection from the native select element without triggering change events.
Instead of recreating the entire select element, you can use the refresh() method on the KTSelect instance:
https://gist.github.com/faizalmy/825f876102125363f613ae0a7c6d7e9a
Use wire:ignore on the wrapper div to prevent Livewire from re-rendering the select
Include data-kt-select attribute for auto-initialization
Use wire:model on the select element itself (Livewire will handle the value attribute)
<div class="flex flex-col gap-1.5" wire:ignore>
<label class="kt-label">Pack Premium</label>
<select class="kt-select kt-select-lg" data-kt-select>
<option value="0">No</option>
<option value="1">Yes</option>
</select>
</div>Thanks for the gist and the code example, unfortunately I've tried with the last metronic so far (9.3.4) and the ktui.min.js provided might not be up to date as I have a console error saying that KTSelect.getInstance is not a function.
I've tried using the ktui npm librairie importing the ktui.min.js in my app.js but then I couldn't make KTSelect available with window.KTSelect
As there is not much documented on the KTSelect methods, I've found a workaround by completely removing the select and recreating it entirely.
I don't know if that's the good way and hopefully methods on KTSelect could help doing that with less code but here is my implementation:
<div class="flex flex-col gap-1.5" wire:ignore>
<label class="kt-label">
Pack Premium
</label>
<select
class="kt-select kt-select-lg
>
<option value="0" selected>No</option>
<option value="1">Yes</option>
</select>
</div>
@script
<script>
document.addEventListener("livewire:initialized", () => {
let selectElement = document.getElementById("pack_premium");
let ktSelectInstance = null;
function initializeKTSelect() {
const currentValue = String($wire.form.pack_premium);
if (ktSelectInstance) {
if (ktSelectInstance.dispose) {
ktSelectInstance.dispose();
}
ktSelectInstance = null;
}
const parent = selectElement.parentElement;
const newSelect = document.createElement("select");
newSelect.id = "pack_premium";
newSelect.className = "kt-select kt-select-lg";
const option0 = document.createElement("option");
option0.value = "0";
option0.textContent = "No";
const option1 = document.createElement("option");
option1.value = "1";
option1.textContent = "Yes";
if (currentValue === "0") {
option0.setAttribute("selected", "selected");
option0.selected = true;
} else {
option1.setAttribute("selected", "selected");
option1.selected = true;
}
newSelect.appendChild(option0);
newSelect.appendChild(option1);
newSelect.value = currentValue;
parent.replaceChild(newSelect, selectElement);
selectElement = newSelect;
ktSelectInstance = new KTSelect(selectElement);
let isManualChange = false;
selectElement.addEventListener("change", (e) => {
if (!isManualChange) {
$wire.$set("form.pack_premium", parseInt(e.target.value));
}
});
}
const add_pack_modal = KTModal.getInstance(document.querySelector("#add_pack_modal"));
add_pack_modal.on("hide", (detail) => {
$wire.$dispatch("reset-form");
});
$wire.$on("open-pack-create-modal", () => {
$wire.$set("form.pack_premium", 0);
initializeKTSelect();
});
$wire.$on("pack-form-loaded", () => {
initializeKTSelect();
});
$wire.$on("pack-updated-success", (event) => {
KTToast.show({ message: "Pack updated", variant: "success" })
add_pack_modal.hide();
})
$wire.$on("pack-created-success", (event) => {
KTToast.show({ message: "Pack created", variant: "success" })
add_pack_modal.hide();
})
});
</script>
@endscriptIt sounds like you’re running into a common issue when integrating Livewire with `ktSelect`—usually it comes down to the fact that `ktSelect` (or any JS-based select component) initializes on page load, but Livewire can re-render DOM elements, causing the select to lose its event listeners or selected state. One way to handle this is to use Livewire’s `wire:ignore` on the select element and then re-initialize `ktSelect` in a Livewire hook like `Livewire.hook('message.processed', ...)`. I’ve found that approach keeps the component functional even after updates. Do you also use dynamic content or live streams on your project, like embedding Photocall TV online links, that might complicate the Livewire re-renders?
I indeed had an interference with a wire:key which provokes a rerendering even though I had wire:ignore on the div surrounding the select.
I've made the condition on the form work with hooking to the livewire:initialized event (livewire:init doesn't seem to work)
document.addEventListener("livewire:initialized", () => {
const selectElement = document.getElementById("pack_sans_stand");
if (selectElement) {
selectElement.addEventListener("change", (event) => {
const {value} = event.detail.payload;
$wire.$set("form.pack_sans_stand", parseInt(value));
});
}
}