[BUG] Product edit - Categories field collapses to width: 0px after tab switch (Chosen.js + UIkit hidden tab race condition)VirtueMart version: 4.6.8
Joomla version: 5.x
PHP version: 8.x
Template: vmadmin (default backend template)
DescriptionWhen editing a product, switching away from the "Product Information" tab and then switching back causes the Categories multi-select field to collapse to zero width and become unusable. The browser inspector shows:
<div class="chosen-container chosen-container-multi" id="categories_chosen" style="width: 0px;"> Steps to reproduce- Open any product for editing (the "Product Information" tab is active by default).
- Click any other tab, e.g. "Product Images".
- Click back on "Product Information".
- The Categories field is now crushed to 0px width and invisible.
The bug also reproduces reliably on first load when the product was previously saved while on a tab other than "Product Information" (UIkit stores the active tab in a cookie - on reload, the "Information" tab starts hidden).
Root causeVirtuemart.loadCategoryTree() (in
components/com_virtuemart/assets/js/ajax_catree.js) fires an AJAX request during
document.ready. On AJAX success, it calls:
jQuery('select#' + id).chosen({ select_some_options_text: Virtuemart.selectSomeCategory });
// no width option passed
Chosen.js computes its container width from the
<select> element at the moment of initialization. If the AJAX response arrives while the "Product Information"
<li> is hidden by UIkit's switcher (
display: none), the measured width is 0. Chosen then stamps
style="width: 0px;" on its container, and nothing corrects it when the tab becomes visible again.
This is a classic
AJAX-vs-hidden-element race condition. The AJAX delay is unpredictable, and once the user has switched tabs, the hidden tab has zero dimensions.
A secondary trigger:
Virtuemart.updateChosenDropdownLayout() (generated by
vmjsapi.php) passes
width: '100%' to
.chosen(), which avoids the problem. But the categories field carries the class
vm-chzn-add, which intentionally excludes it from that initializer (
select:not(.vm-chzn-add)). So the categories select is always initialized through the AJAX path, without a width fallback.
Proposed fix - two-part1. Root fix (in ajax_catree.js): pass
width: '100%' to the
.chosen() call so the container always gets a percentage-based width instead of a pixel-measured one.
// Before (line ~87):
jQuery('select#'+id).chosen({select_some_options_text: Virtuemart.selectSomeCategory});
// After:
jQuery('select#'+id).chosen({select_some_options_text: Virtuemart.selectSomeCategory, width: '100%'});
2. Safety net (UIkit show event): UIkit 3 dispatches
show with
bubbles: true on each
<li> of the switcher when it becomes active (confirmed in uikit.js -
createEvent(e, bubbles = true)). Adding a listener on the tabs container catches any other Chosen widget that may have suffered the same fate:
jQuery(document).ready(function($) {
var tabsContainer = document.getElementById('vmuikit-admin-ui-tabs');
if (!tabsContainer) { return; }
tabsContainer.addEventListener('show', function(e) {
$(e.target).find('.chosen-container').each(function() {
if (this.style.width === '0px') {
$(this).css('width', '100%');
}
});
});
});
This listener is harmless on every other tab switch (no
.chosen-container with
width: 0px means the
.each() body never runs).
Workaround for existing installationsAdd the safety-net JS snippet above to a template override of
administrator/templates/vmadmin/html/com_virtuemart/product/product_edit_information.php via
vmJsApi::addJScript().
Thanks for reading all of that