|
@@ -116,13 +116,13 @@ publicWidget.registry.M22HelpdeskSelect = publicWidget.Widget.extend({
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// Build HTML structure with Tailwind classes
|
|
// Build HTML structure with Tailwind classes
|
|
|
|
|
+ // Escape HTML to prevent XSS
|
|
|
|
|
+ const selectName = select.name || '';
|
|
|
|
|
+ const selectId = select.id || '';
|
|
|
|
|
+ const selectRequired = select.required ? 'required' : '';
|
|
|
|
|
+
|
|
|
wrapper.innerHTML = `
|
|
wrapper.innerHTML = `
|
|
|
- <!-- Hidden original select for form submission -->
|
|
|
|
|
- <select name="${select.name}" id="${select.id}" class="hidden" ${select.required ? 'required' : ''}>
|
|
|
|
|
- ${options.map(opt =>
|
|
|
|
|
- `<option value="${opt.value}" ${opt.value === currentValue ? 'selected' : ''} ${opt.disabled ? 'disabled' : ''}>${opt.label}</option>`
|
|
|
|
|
- ).join('')}
|
|
|
|
|
- </select>
|
|
|
|
|
|
|
+ <!-- Hidden original select will be moved here, not recreated -->
|
|
|
|
|
|
|
|
<!-- Combobox button -->
|
|
<!-- Combobox button -->
|
|
|
<button
|
|
<button
|
|
@@ -186,7 +186,9 @@ publicWidget.registry.M22HelpdeskSelect = publicWidget.Widget.extend({
|
|
|
const placeholderSpan = wrapper.querySelector('.m22-combobox-placeholder');
|
|
const placeholderSpan = wrapper.querySelector('.m22-combobox-placeholder');
|
|
|
const valueSpan = wrapper.querySelector('.m22-combobox-value');
|
|
const valueSpan = wrapper.querySelector('.m22-combobox-value');
|
|
|
const arrow = wrapper.querySelector('.m22-combobox-arrow');
|
|
const arrow = wrapper.querySelector('.m22-combobox-arrow');
|
|
|
- const hiddenSelect = wrapper.querySelector('select');
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // The hidden select is the original select element, not a new one
|
|
|
|
|
+ // We'll move it into the wrapper after creating the structure
|
|
|
|
|
|
|
|
// Methods
|
|
// Methods
|
|
|
const updateDisplay = () => {
|
|
const updateDisplay = () => {
|
|
@@ -251,17 +253,39 @@ publicWidget.registry.M22HelpdeskSelect = publicWidget.Widget.extend({
|
|
|
state.search = '';
|
|
state.search = '';
|
|
|
state.open = false;
|
|
state.open = false;
|
|
|
|
|
|
|
|
- // Update hidden select
|
|
|
|
|
- hiddenSelect.value = option.value;
|
|
|
|
|
|
|
+ // Update the original select (which is now inside the wrapper)
|
|
|
|
|
+ select.value = option.value;
|
|
|
|
|
|
|
|
// Update display
|
|
// Update display
|
|
|
updateDisplay();
|
|
updateDisplay();
|
|
|
renderOptions();
|
|
renderOptions();
|
|
|
closeDropdown();
|
|
closeDropdown();
|
|
|
|
|
|
|
|
- // Trigger change event
|
|
|
|
|
- const event = new Event('change', { bubbles: true });
|
|
|
|
|
- hiddenSelect.dispatchEvent(event);
|
|
|
|
|
|
|
+ // Trigger both change and input events for Odoo's conditional visibility system
|
|
|
|
|
+ // Odoo listens to 'input.s_website_form' events on .s_website_form_field
|
|
|
|
|
+ const changeEvent = new Event('change', { bubbles: true, cancelable: true });
|
|
|
|
|
+ select.dispatchEvent(changeEvent);
|
|
|
|
|
+
|
|
|
|
|
+ // Find the field container and trigger input event with jQuery namespace
|
|
|
|
|
+ // Odoo's listener: this.$el.on('input.s_website_form', '.s_website_form_field', ...)
|
|
|
|
|
+ const fieldContainer = select.closest('.s_website_form_field');
|
|
|
|
|
+ if (fieldContainer && window.$) {
|
|
|
|
|
+ // Use jQuery to trigger the event with the correct namespace
|
|
|
|
|
+ // This matches how Odoo listens: 'input.s_website_form'
|
|
|
|
|
+ window.$(fieldContainer).trigger('input.s_website_form');
|
|
|
|
|
+ } else if (fieldContainer) {
|
|
|
|
|
+ // Fallback: native event
|
|
|
|
|
+ const inputEvent = new Event('input', { bubbles: true, cancelable: true });
|
|
|
|
|
+ fieldContainer.dispatchEvent(inputEvent);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Also trigger on the select itself
|
|
|
|
|
+ if (window.$) {
|
|
|
|
|
+ window.$(select).trigger('input.s_website_form');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const inputEvent = new Event('input', { bubbles: true, cancelable: true });
|
|
|
|
|
+ select.dispatchEvent(inputEvent);
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const openDropdown = () => {
|
|
const openDropdown = () => {
|
|
@@ -334,9 +358,81 @@ publicWidget.registry.M22HelpdeskSelect = publicWidget.Widget.extend({
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Replace select with wrapper
|
|
// Replace select with wrapper
|
|
|
- select.parentNode.insertBefore(wrapper, select);
|
|
|
|
|
- select.style.display = 'none';
|
|
|
|
|
- wrapper.appendChild(select);
|
|
|
|
|
|
|
+ // Keep the select inside its original field container to maintain Odoo's event listeners
|
|
|
|
|
+ const fieldContainer = select.closest('.s_website_form_field');
|
|
|
|
|
+ const parentNode = select.parentNode;
|
|
|
|
|
+ const nextSibling = select.nextSibling; // Save reference before moving
|
|
|
|
|
+
|
|
|
|
|
+ if (!parentNode) {
|
|
|
|
|
+ console.warn(_t('Select element has no parent node, skipping combobox transformation'));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if select is already inside a wrapper (avoid double transformation)
|
|
|
|
|
+ if (select.closest('.m22-combobox-wrapper')) {
|
|
|
|
|
+ console.warn(_t('Select is already inside a combobox wrapper, skipping'));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // First, hide the original select but keep it in the DOM for form submission
|
|
|
|
|
+ select.style.display = 'none';
|
|
|
|
|
+
|
|
|
|
|
+ // Move the select inside the wrapper BEFORE inserting wrapper into DOM
|
|
|
|
|
+ // This prevents DOM hierarchy issues
|
|
|
|
|
+ if (wrapper.firstChild) {
|
|
|
|
|
+ wrapper.insertBefore(select, wrapper.firstChild);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ wrapper.appendChild(select);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Now insert the wrapper (with select inside) where the select was originally
|
|
|
|
|
+ // Use the saved nextSibling as reference point
|
|
|
|
|
+ if (fieldContainer && fieldContainer === parentNode) {
|
|
|
|
|
+ // Select was directly in field container
|
|
|
|
|
+ if (nextSibling && nextSibling.parentNode === parentNode) {
|
|
|
|
|
+ parentNode.insertBefore(wrapper, nextSibling);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // No next sibling, append to field container
|
|
|
|
|
+ parentNode.appendChild(wrapper);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (parentNode) {
|
|
|
|
|
+ // Select was in a nested structure
|
|
|
|
|
+ if (nextSibling && nextSibling.parentNode === parentNode) {
|
|
|
|
|
+ parentNode.insertBefore(wrapper, nextSibling);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // No next sibling, append to parent
|
|
|
|
|
+ parentNode.appendChild(wrapper);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw new Error(_t('Cannot find valid parent node for wrapper insertion'));
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(_t('Error inserting combobox wrapper:'), error, {
|
|
|
|
|
+ select: select,
|
|
|
|
|
+ parentNode: parentNode,
|
|
|
|
|
+ fieldContainer: fieldContainer,
|
|
|
|
|
+ wrapper: wrapper,
|
|
|
|
|
+ errorMessage: error.message,
|
|
|
|
|
+ errorName: error.name
|
|
|
|
|
+ });
|
|
|
|
|
+ // Clean up on error - restore select if needed
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (wrapper.contains(select)) {
|
|
|
|
|
+ // Move select back to original position
|
|
|
|
|
+ if (parentNode) {
|
|
|
|
|
+ parentNode.insertBefore(select, wrapper.nextSibling);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (wrapper.parentNode) {
|
|
|
|
|
+ wrapper.parentNode.removeChild(wrapper);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (cleanupError) {
|
|
|
|
|
+ console.error(_t('Error during cleanup:'), cleanupError);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Don't throw - just skip this select
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// Initial render
|
|
// Initial render
|
|
|
updateDisplay();
|
|
updateDisplay();
|