Исправлен Select2 поиск товаров при создании группы вариантов
Проблема: при создании новой группы вариантов (VariantGroup) поиск товаров через Select2 не работал. При редактировании существующих групп всё работало корректно. Причина: отсутствовали проверки инициализации Select2, обработка ошибок AJAX запросов и валидация параметров. Изменения: 1. select2-product-init.html - улучшена функция initProductSelect2: - Добавлена валидация входных параметров (element, apiUrl) - Добавлена проверка загрузки jQuery и Select2 - Улучшена проверка повторной инициализации - Добавлен try-catch для обработки ошибок - Функция возвращает boolean (успех/неудача) - Добавлено логирование для отладки 2. variantgroup_form.html - улучшены все функции работы с формой: initSelect2ForRow: - Добавлена проверка существования row и select элемента - Удаление старых обработчиков перед инициализацией - Проверка результата инициализации Select2 updateRowData: - Добавлен timeout (5 сек) для fetch запросов - Добавлена проверка статуса HTTP ответа - Улучшена обработка ошибок с fallback данными - Добавлено логирование ошибок DOMContentLoaded инициализация: - Добавлена валидация контейнера, totalFormsInput и apiUrl - Задержка перед инициализацией существующих строк (100ms) - Проверка успешности инициализации перед updateRowData Добавление нового товара: - Задержка (50ms) перед инициализацией Select2 - Повторная попытка при неудаче (через 500ms) - Улучшена надежность работы с динамическими элементами Результат: Select2 поиск работает корректно как при создании новых групп, так и при редактировании существующих. Добавлена надежная обработка ошибок и логирование для отладки. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,10 +40,32 @@
|
||||
* @param {Element} element - DOM элемент select
|
||||
* @param {string} type - Тип поиска ('product' или 'variant')
|
||||
* @param {string} apiUrl - URL API для поиска
|
||||
* @returns {boolean} - true если инициализация прошла успешно, false иначе
|
||||
*/
|
||||
window.initProductSelect2 = function(element, type, apiUrl) {
|
||||
if (!element || $(element).data('select2')) {
|
||||
return; // Уже инициализирован
|
||||
// Валидация входных параметров
|
||||
if (!element) {
|
||||
console.error('initProductSelect2: element is null or undefined');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!apiUrl || typeof apiUrl !== 'string') {
|
||||
console.error('initProductSelect2: invalid apiUrl', apiUrl);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверка загрузки jQuery и Select2
|
||||
if (typeof $ === 'undefined' || typeof $.fn.select2 === 'undefined') {
|
||||
console.error('initProductSelect2: jQuery or Select2 not loaded');
|
||||
return false;
|
||||
}
|
||||
|
||||
var $element = $(element);
|
||||
|
||||
// Улучшенная проверка инициализации
|
||||
if ($element.hasClass('select2-hidden-accessible')) {
|
||||
console.log('initProductSelect2: already initialized, skipping', element.name);
|
||||
return true; // Уже инициализирован - это не ошибка
|
||||
}
|
||||
|
||||
var placeholders = {
|
||||
@@ -51,7 +73,8 @@
|
||||
'variant': 'Начните вводить название группы...'
|
||||
};
|
||||
|
||||
$(element).select2({
|
||||
try {
|
||||
$element.select2({
|
||||
theme: 'bootstrap-5',
|
||||
placeholder: placeholders[type] || 'Выберите...',
|
||||
allowClear: true,
|
||||
@@ -83,6 +106,12 @@
|
||||
templateResult: formatSelectResult,
|
||||
templateSelection: formatSelectSelection
|
||||
});
|
||||
console.log('initProductSelect2: successfully initialized for', element.name);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('initProductSelect2: initialization error', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -215,12 +215,43 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const totalFormsInput = document.querySelector('[name$="TOTAL_FORMS"]');
|
||||
const apiUrl = '{% url "products:api-search-products-variants" %}';
|
||||
|
||||
// Валидация
|
||||
if (!container) {
|
||||
console.error('items-tbody container not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!totalFormsInput) {
|
||||
console.error('TOTAL_FORMS input not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!apiUrl || apiUrl.includes('undefined')) {
|
||||
console.error('Invalid API URL:', apiUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Initializing variant group form with API URL:', apiUrl);
|
||||
|
||||
// Инициализируем Select2 для всех селектов товаров
|
||||
function initSelect2ForRow(row) {
|
||||
const productSelect = row.querySelector('[name$="-product"]');
|
||||
if (productSelect) {
|
||||
window.initProductSelect2(productSelect, 'product', apiUrl);
|
||||
if (!row) {
|
||||
console.error('initSelect2ForRow: row is null');
|
||||
return false;
|
||||
}
|
||||
|
||||
const productSelect = row.querySelector('[name$="-product"]');
|
||||
if (!productSelect) {
|
||||
console.warn('initSelect2ForRow: product select not found in row');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Убираем старые обработчики перед повторной инициализацией
|
||||
$(productSelect).off('select2:select');
|
||||
|
||||
const success = window.initProductSelect2(productSelect, 'product', apiUrl);
|
||||
|
||||
if (success) {
|
||||
// Обработчик события при выборе товара
|
||||
$(productSelect).on('select2:select', function(e) {
|
||||
// Извлекаем числовой ID из формата "product_123"
|
||||
@@ -239,13 +270,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
updateRowData(row);
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
console.error('initSelect2ForRow: failed to initialize Select2');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для обновления данных строки при выборе товара
|
||||
function updateRowData(row) {
|
||||
if (!row) return;
|
||||
|
||||
const productSelect = row.querySelector('[name$="-product"]');
|
||||
if (!productSelect || !productSelect.value) {
|
||||
// Сброс данных
|
||||
row.querySelector('[data-product-sku]').textContent = '-';
|
||||
row.querySelector('[data-product-price]').textContent = '-';
|
||||
row.querySelector('[data-product-stock]').innerHTML = '-';
|
||||
@@ -262,13 +300,26 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Получаем цену и статус через AJAX (используем числовой ID)
|
||||
const numericId = productSelect.value;
|
||||
fetch(`{% url "products:api-search-products-variants" %}?id=${numericId}`)
|
||||
.then(response => response.json())
|
||||
|
||||
// Добавляем timeout для fetch
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||
|
||||
fetch(`${apiUrl}?id=${numericId}`, { signal: controller.signal })
|
||||
.then(response => {
|
||||
clearTimeout(timeoutId);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.results && data.results.length > 0) {
|
||||
const product = data.results[0];
|
||||
row.querySelector('[data-product-sku]').textContent = product.sku || sku;
|
||||
row.querySelector('[data-product-price]').innerHTML = `<strong>${product.price}</strong> руб.` || '-';
|
||||
|
||||
const price = product.price || product.actual_price || '0';
|
||||
row.querySelector('[data-product-price]').innerHTML = `<strong>${price}</strong> руб.`;
|
||||
|
||||
// Отображаем статус наличия
|
||||
const stockCell = row.querySelector('[data-product-stock]');
|
||||
@@ -277,21 +328,33 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
} else {
|
||||
stockCell.innerHTML = '<span class="badge bg-danger"><i class="bi bi-x-circle"></i> Нет</span>';
|
||||
}
|
||||
} else {
|
||||
// Fallback: используем данные из опции
|
||||
row.querySelector('[data-product-sku]').textContent = sku;
|
||||
row.querySelector('[data-product-price]').textContent = '-';
|
||||
row.querySelector('[data-product-stock]').textContent = '-';
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(error => {
|
||||
clearTimeout(timeoutId);
|
||||
console.error('updateRowData: fetch error', error);
|
||||
// Fallback: данные из опции
|
||||
row.querySelector('[data-product-sku]').textContent = sku;
|
||||
row.querySelector('[data-product-price]').textContent = '-';
|
||||
row.querySelector('[data-product-stock]').textContent = '-';
|
||||
});
|
||||
}
|
||||
|
||||
// Инициализируем для существующих строк
|
||||
// Инициализируем для существующих строк с небольшой задержкой
|
||||
setTimeout(function() {
|
||||
container.querySelectorAll('.item-row').forEach(row => {
|
||||
initSelect2ForRow(row);
|
||||
// Загружаем данные товара при загрузке страницы (с небольшой задержкой)
|
||||
setTimeout(() => updateRowData(row), 100);
|
||||
const success = initSelect2ForRow(row);
|
||||
if (success) {
|
||||
// Загружаем данные только если Select2 инициализирован
|
||||
setTimeout(() => updateRowData(row), 200);
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
|
||||
// ДОБАВЛЕНИЕ НОВОГО ТОВАРА
|
||||
document.getElementById('add-item-btn').addEventListener('click', function(e) {
|
||||
@@ -342,8 +405,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Увеличиваем счетчик форм
|
||||
totalFormsInput.value = newFormIndex + 1;
|
||||
|
||||
// Инициализируем Select2 для нового селекта
|
||||
// Даем время браузеру отрисовать элемент перед инициализацией Select2
|
||||
setTimeout(function() {
|
||||
const success = initSelect2ForRow(newRow);
|
||||
if (!success) {
|
||||
console.error('Failed to initialize Select2 for new row, retrying...');
|
||||
// Повторная попытка через 500ms
|
||||
setTimeout(function() {
|
||||
initSelect2ForRow(newRow);
|
||||
}, 500);
|
||||
}
|
||||
}, 50);
|
||||
|
||||
// Обновляем приоритеты
|
||||
updatePriorities();
|
||||
|
||||
Reference in New Issue
Block a user