Упрощён компонент поиска товаров: убран мультивыбор, только single-select
- Удалён весь функционал множественного выбора - Удалены кнопки 'Выбрать все' и 'Сбросить' - Удалён счётчик выбранных товаров - state.selected теперь содержит один объект вместо словаря - Убраны параметры multi_select, max_selection, show_select_all - onAddSelected теперь возвращает объект вместо массива - Удалены методы getSelectedIds() и setSelection() - Упрощена логика _toggleProduct для single-select - Обновлены все callback'и для работы с одним товаром - Компонент стал значительно проще и понятнее
This commit is contained in:
@@ -1,19 +1,18 @@
|
||||
/**
|
||||
* ProductSearchPicker - переиспользуемый модуль для поиска и выбора товаров
|
||||
* ProductSearchPicker - переиспользуемый модуль для поиска и выбора товара
|
||||
* Только single-select режим.
|
||||
*
|
||||
* API:
|
||||
* - ProductSearchPicker.init(containerSelector, options) - инициализация
|
||||
* - instance.getSelected() - получить выбранные товары (массив объектов)
|
||||
* - instance.getSelectedIds() - получить ID выбранных товаров (массив строк)
|
||||
* - instance.getSelected() - получить выбранный товар (объект или null)
|
||||
* - instance.clearSelection() - сбросить выбор
|
||||
* - instance.setSelection(productIds) - установить выбор программно
|
||||
* - instance.refresh() - обновить список товаров
|
||||
* - instance.destroy() - уничтожить экземпляр
|
||||
*
|
||||
* Пример использования:
|
||||
* const picker = ProductSearchPicker.init('#my-picker', {
|
||||
* onAddSelected: function(products, instance) {
|
||||
* console.log('Добавлены товары:', products);
|
||||
* onAddSelected: function(product, instance) {
|
||||
* console.log('Выбран товар:', product);
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
@@ -39,23 +38,20 @@
|
||||
// Настройки по умолчанию
|
||||
this.options = Object.assign({
|
||||
apiUrl: this.container.dataset.apiUrl || '/products/api/search-products-variants/',
|
||||
multiSelect: this.container.dataset.multiSelect !== 'false',
|
||||
maxSelection: parseInt(this.container.dataset.maxSelection) || null,
|
||||
excludeKits: this.container.dataset.excludeKits !== 'false',
|
||||
debounceDelay: 300,
|
||||
pageSize: 30,
|
||||
|
||||
// Callbacks
|
||||
onSelect: null, // (products, instance) => {}
|
||||
onDeselect: null, // (products, instance) => {}
|
||||
onSelectionChange: null, // (selectedProducts, instance) => {}
|
||||
onAddSelected: null // (selectedProducts, instance) => {}
|
||||
onSelect: null, // (product, instance) => {}
|
||||
onDeselect: null, // (instance) => {}
|
||||
onAddSelected: null // (product, instance) => {}
|
||||
}, options || {});
|
||||
|
||||
// Состояние
|
||||
this.state = {
|
||||
products: [],
|
||||
selected: {}, // {productId: productData}
|
||||
selected: null, // Единичный выбранный товар (объект или null)
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
isLoading: false,
|
||||
@@ -94,9 +90,6 @@
|
||||
loading: c.querySelector('.product-picker-loading'),
|
||||
empty: c.querySelector('.product-picker-empty'),
|
||||
content: c.querySelector('.product-picker-content'),
|
||||
selectedCount: c.querySelector('.product-picker-selected-count'),
|
||||
selectAllBtn: c.querySelector('.product-picker-select-all'),
|
||||
clearSelectionBtn: c.querySelector('.product-picker-clear-selection'),
|
||||
addSelectedBtn: c.querySelector('.product-picker-add-selected'),
|
||||
viewButtons: c.querySelectorAll('.product-picker-view')
|
||||
};
|
||||
@@ -210,25 +203,11 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Выбрать все
|
||||
if (this.elements.selectAllBtn) {
|
||||
this.elements.selectAllBtn.addEventListener('click', function() {
|
||||
self._selectAll();
|
||||
});
|
||||
}
|
||||
|
||||
// Сбросить выбор
|
||||
if (this.elements.clearSelectionBtn) {
|
||||
this.elements.clearSelectionBtn.addEventListener('click', function() {
|
||||
self.clearSelection();
|
||||
});
|
||||
}
|
||||
|
||||
// Добавить выбранные
|
||||
// Добавить выбранный
|
||||
if (this.elements.addSelectedBtn) {
|
||||
this.elements.addSelectedBtn.addEventListener('click', function() {
|
||||
if (self.options.onAddSelected) {
|
||||
self.options.onAddSelected(self.getSelected(), self);
|
||||
if (self.state.selected && self.options.onAddSelected) {
|
||||
self.options.onAddSelected(self.state.selected, self);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -348,7 +327,7 @@
|
||||
products.forEach(function(product) {
|
||||
// Извлекаем ID (убираем префикс "product_")
|
||||
var productId = String(product.id).replace('product_', '');
|
||||
var isSelected = self.state.selected.hasOwnProperty(productId);
|
||||
var isSelected = self.state.selected && self.state.selected.id === productId;
|
||||
|
||||
var html = self.state.currentView === 'grid'
|
||||
? self._renderGridCard(product, productId, isSelected)
|
||||
@@ -446,7 +425,7 @@
|
||||
};
|
||||
|
||||
/**
|
||||
* Переключение выбора товара
|
||||
* Переключение выбора товара (single-select)
|
||||
*/
|
||||
ProductSearchPicker.prototype._toggleProduct = function(productId) {
|
||||
var self = this;
|
||||
@@ -457,47 +436,40 @@
|
||||
var p = this.state.products[i];
|
||||
if (String(p.id).replace('product_', '') === productId) {
|
||||
product = p;
|
||||
product.id = productId; // Сохраняем очищенный ID
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!product) return;
|
||||
|
||||
var isSelected = this.state.selected.hasOwnProperty(productId);
|
||||
var isSelected = this.state.selected && this.state.selected.id === productId;
|
||||
|
||||
if (isSelected) {
|
||||
// Снять выбор
|
||||
delete this.state.selected[productId];
|
||||
var oldProductId = this.state.selected.id;
|
||||
this.state.selected = null;
|
||||
this._updateProductUI(oldProductId);
|
||||
if (this.options.onDeselect) {
|
||||
this.options.onDeselect([product], this);
|
||||
this.options.onDeselect(this);
|
||||
}
|
||||
} else {
|
||||
// Проверка множественного выбора
|
||||
if (!this.options.multiSelect) {
|
||||
this.state.selected = {};
|
||||
// Сначала снимаем выбор со старого товара
|
||||
if (this.state.selected) {
|
||||
var oldProductId = this.state.selected.id;
|
||||
this._updateProductUI(oldProductId);
|
||||
}
|
||||
|
||||
// Проверка лимита
|
||||
if (this.options.maxSelection &&
|
||||
Object.keys(this.state.selected).length >= this.options.maxSelection) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Добавить в выбор
|
||||
this.state.selected[productId] = product;
|
||||
|
||||
// Выбираем новый товар
|
||||
this.state.selected = product;
|
||||
this._updateProductUI(productId);
|
||||
if (this.options.onSelect) {
|
||||
this.options.onSelect([product], this);
|
||||
this.options.onSelect(product, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновить UI
|
||||
this._updateProductUI(productId);
|
||||
this._updateSelectionUI();
|
||||
|
||||
// Общий callback
|
||||
if (this.options.onSelectionChange) {
|
||||
this.options.onSelectionChange(this.getSelected(), this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -507,7 +479,7 @@
|
||||
var card = this.elements.grid.querySelector('[data-product-id="' + productId + '"]');
|
||||
if (!card) return;
|
||||
|
||||
var isSelected = this.state.selected.hasOwnProperty(productId);
|
||||
var isSelected = this.state.selected && this.state.selected.id === productId;
|
||||
|
||||
if (isSelected) {
|
||||
card.classList.add('selected');
|
||||
@@ -545,52 +517,16 @@
|
||||
};
|
||||
|
||||
/**
|
||||
* Обновление UI выбора (счетчик, кнопки)
|
||||
* Обновление UI выбора (кнопка)
|
||||
*/
|
||||
ProductSearchPicker.prototype._updateSelectionUI = function() {
|
||||
var count = Object.keys(this.state.selected).length;
|
||||
|
||||
// Счетчик
|
||||
if (this.elements.selectedCount) {
|
||||
this.elements.selectedCount.textContent = count;
|
||||
}
|
||||
|
||||
// Кнопка "Добавить"
|
||||
if (this.elements.addSelectedBtn) {
|
||||
this.elements.addSelectedBtn.disabled = count === 0;
|
||||
this.elements.addSelectedBtn.disabled = !this.state.selected;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Выбрать все видимые товары
|
||||
*/
|
||||
ProductSearchPicker.prototype._selectAll = function() {
|
||||
var self = this;
|
||||
var currentCount = Object.keys(this.state.selected).length;
|
||||
var maxToSelect = this.options.maxSelection
|
||||
? this.options.maxSelection - currentCount
|
||||
: Infinity;
|
||||
|
||||
var added = 0;
|
||||
this.state.products.forEach(function(product) {
|
||||
if (added >= maxToSelect) return;
|
||||
|
||||
var productId = String(product.id).replace('product_', '');
|
||||
if (!self.state.selected.hasOwnProperty(productId)) {
|
||||
self.state.selected[productId] = product;
|
||||
self._updateProductUI(productId);
|
||||
added++;
|
||||
}
|
||||
});
|
||||
|
||||
this._updateSelectionUI();
|
||||
|
||||
if (this.options.onSelectionChange) {
|
||||
this.options.onSelectionChange(this.getSelected(), this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Обновление кнопок переключателя вида
|
||||
*/
|
||||
ProductSearchPicker.prototype._updateViewButtons = function() {
|
||||
@@ -625,68 +561,25 @@
|
||||
// ========== ПУБЛИЧНОЕ API ==========
|
||||
|
||||
/**
|
||||
* Получить массив выбранных товаров
|
||||
* Получить выбранный товар (объект или null)
|
||||
*/
|
||||
ProductSearchPicker.prototype.getSelected = function() {
|
||||
var result = [];
|
||||
for (var key in this.state.selected) {
|
||||
if (this.state.selected.hasOwnProperty(key)) {
|
||||
result.push(this.state.selected[key]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Получить массив ID выбранных товаров
|
||||
*/
|
||||
ProductSearchPicker.prototype.getSelectedIds = function() {
|
||||
return Object.keys(this.state.selected);
|
||||
return this.state.selected;
|
||||
};
|
||||
|
||||
/**
|
||||
* Сбросить выбор
|
||||
*/
|
||||
ProductSearchPicker.prototype.clearSelection = function() {
|
||||
var self = this;
|
||||
var ids = Object.keys(this.state.selected);
|
||||
this.state.selected = {};
|
||||
|
||||
ids.forEach(function(productId) {
|
||||
self._updateProductUI(productId);
|
||||
});
|
||||
|
||||
this._updateSelectionUI();
|
||||
|
||||
if (this.options.onSelectionChange) {
|
||||
this.options.onSelectionChange([], this);
|
||||
if (this.state.selected) {
|
||||
var productId = this.state.selected.id;
|
||||
this.state.selected = null;
|
||||
this._updateProductUI(productId);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Установить выбор программно
|
||||
*/
|
||||
ProductSearchPicker.prototype.setSelection = function(productIds) {
|
||||
var self = this;
|
||||
this.clearSelection();
|
||||
|
||||
productIds.forEach(function(id) {
|
||||
var productId = String(id).replace('product_', '');
|
||||
// Ищем товар в загруженном списке
|
||||
for (var i = 0; i < self.state.products.length; i++) {
|
||||
var p = self.state.products[i];
|
||||
if (String(p.id).replace('product_', '') === productId) {
|
||||
self.state.selected[productId] = p;
|
||||
self._updateProductUI(productId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._updateSelectionUI();
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Обновить список товаров
|
||||
*/
|
||||
ProductSearchPicker.prototype.refresh = function() {
|
||||
|
||||
Reference in New Issue
Block a user