В последнее время все реже и реже появляются сложные задачи, но вот сегодня я потратил довольно много времени на решение парочки таких (в сумме часов 17 просидел)… Сразу скажу, что я не буду особо расписывать решения (хотя коды покажу). Это все так, пища для размышлений.
Задача первая. Выборка по одному варианту товара на одну модель из множества, с приоритетом на наличие картинки
Есть N моделей товаров. У каждой модели есть N вариантов (конечных товаров с индивидуальными параметрами (цвет, размер, изображение и т.п.)). Так вот, в каталоге в общем списке выводятся не товары (чтобы не было 100500 дублей с мелкими отличиями), а модели. Но картинки указаны именно у вариантов, а не у моделей. То есть мы получаем произвольную выборку товаров, но выводим именно модели на основе данных этих товаров. И загвоздка самая была именно в картинках. Дело в том, что в рамках одной модели у каких-то товаров картинки есть, а у каких-то нет. А выбрать надо только одну модель, и лучше, если именно с картинкой.
У кого-то вопрос может возникнуть «а что сразу не назначить картинку модели?». В данном случае это совершенно не оправдано, так как в зависимости от параметров поиска, в модели будет изображение только от того товара, который попал по условиям поиска.
Вот собственно этот процессор:
<?php /* * Получаем данные только моделей товаров * То есть по товару находим модель, и для модели получаем только одну вариацию товара */ require_once dirname(__FILE__).'/getdata.class.php'; class modWebCatalogProductsGetmodeldataProcessor extends modWebCatalogProductsGetdataProcessor{ protected $modelsIDs = array(); public function initialize() { $this->setDefaultProperties(array( 'sort' => "Models.menuindex", )); return parent::initialize(); } // Готовим запрос на выборку уникальных объектов // Нам надо вернуть товары с приоритетом на наличие картинки protected function PrepareUniqObjectsQuery(xPDOQuery &$query) { $query = parent::PrepareUniqObjectsQuery($query); /* * Без изврата с подзапросом не обошлось, так как group by parent не учитывает * сортировку по картинке. */ $IDs = array(); $sub_query = clone $query; $sub_query->select(array( "{$this->classKey}.parent", )); // Сортируем по картинкам, а то могут попасть без картинки $sub_query->leftJoin('modTemplateVarResource', 'image', "image.contentid = {$this->classKey}.id and image.tmplvarid=8 and image.value != ''"); $sub_query->sortby('image.id', "DESC"); // Сбрасываем лимиты, так как нам нужные все возможные ID-шники, так как у нас нет группировки в // этом запросе $sub_query->query['offset'] = 0; $sub_query->query['limit'] = 0; if($sub_query->prepare() AND $sub_query->stmt->execute() AND $rows = $sub_query->stmt->fetchAll(PDO::FETCH_ASSOC)){ $parents = array(); foreach($rows as $row){ // Фиксируем только по одному ID на модель if(in_array($row['parent'], $parents)){ continue; } $parents[] = $row['parent']; $IDs[] = $row['id']; } $query->where(array( "id:IN" => $IDs, )); } // Группируем по родителям $query->groupby("{$this->classKey}.parent"); return $query; } /* * Подсчитываем количество товаров именно по ID-шникам их предков (то есть именно моделей), * так как нам нужны уникальные модели */ protected function countTotal($className, xPDOQuery &$criteria) { $count= 0; if ($query= $this->modx->newQuery($className, $criteria)) { if (isset($query->query['columns'])) $query->query['columns'] = array(); $query->select(array ("COUNT(DISTINCT {$this->classKey}.parent)")); if ($stmt= $query->prepare()) { if ($stmt->execute()) { if ($results= $stmt->fetchAll(PDO::FETCH_COLUMN)) { $count= reset($results); $count= intval($count); } } } } return $count; } public function prepareQueryBeforeCount(xPDOQuery $c){ $c = parent::prepareQueryBeforeCount($c); $c->innerJoin('modResource', 'Models', "Models.id = {$this->classKey}.parent"); return $c; } public function setSelection(xPDOQuery $c) { $c = parent::setSelection($c); $c->select(array( 'Models.id as model_id', 'Models.pagetitle as model_title', )); return $c; } } return 'modWebCatalogProductsGetmodeldataProcessor';
Без подзапроса здесь не обошлось, так как при группировке по родителю не учитывается сортировка по картинкам.
Задача вторая. JS-скрипт выбора доступных вариантов товара по параметрам.
А вот с этим я вообще более 10-ти часов сегодня просидел.
?
Суть заключается в том, что в карточке товара должна быть возможность выбрать конкретный товар по имеющимся параметрам. Но не все товары имеют все представленные характеристики, потому при выборе одних характеристик должны скрываться те, которые не актуальны для выбранных параметров (к примеру, для выбранного размера не все цвета имеются.).
Задача на самом деле имеет множество подводных камней, и думаю, здесь требуется очень хорошее знание математики. Наверняка человек, владеющий знаниями по математике на хорошем уровне, формулу бы написал за 10 минут максимум. Но это не я. В любом случае, скрипт возможно кривенький, но задачу свою решает. Вот код:
var smCart = function(options){ var self = this; this.options = options || {}; this.data = { color: null, design: null, size: null }; this.cart_variants = this.options.cart_variants; var c = console.log; this.IDs = []; // ID-шники возможных товаров по условиям выбора this.items = $('.product_var'); this.lastUsed = null; /* * Фиксируем все допустимые варианты **/ this.setAvailables = function(){ this.available = []; for(var i in this.data){ this.available[i] = []; if(!this.data[i]){ for(var x in this.cart_variants[i]){ this.ArrayMergeUnique(this.available[i], this.cart_variants[i][x]); } } else{ var el = $('[name='+i+']:checked'); this.ArrayMergeUnique(this.available[i], this.cart_variants[i][el.val()]); } } return; } this.checkAllowed = function(activeItem){ // c($(activeItem).parent().attr('class')); if($(activeItem).parent().hasClass('disabled')){ $(':checked').each(function(){ if(activeItem[0] == this){ return; }; var item = $(this); item.parent().removeClass('disabled'); self.unsetData(item.attr('name')); }); self.setAvailables(); } var elements = $('.product_var'); elements.parent().removeClass('disabled'); elements.each(function(i){ var el = $(this); // Если это текущий элемент, прерываем процесс if(activeItem[0] == this){ return; } var debug = false; var name = el.attr('name'); // design|color|size var value = el.val(); var IDs = self.cart_variants[name][value]; // Надо проверить каждый ID во всех массивах, кроме текущего var exists = false; var searched = false; var finded = false; $(IDs).each(function(y){ var id = IDs[y]; // Проходим каждый ID по порядку. // Найден должен быть во всех разделах // Проверяем для конкретного id var id_finded = false; for(var x in self.available){ if(!self.available[x].length || x == name // || !self.data[x] ){ continue; } id_finded = false; if($.inArray(id, self.available[x]) == -1){ id_finded = false; break; } id_finded = true; } if(id_finded == true){ finded = true; return false; } }); if(finded == false){ // Если это отмеченный ранее элемен, снимаем значение if(el.attr('checked')){ self.unsetData(name); self.setAvailables(); self.checkAllowed(activeItem); return; } else{ el.parent().addClass('disabled'); } } }); return; } this.onChange = function(e){ var item = $(this); var name = item.attr('name'); var value = item.val(); // Фиксируем новое значение поля self.data[name] = value; // Набиваем массив всех допустимых вариантов self.setAvailables(); self.checkAllowed(item); return; }; // Мержим уникальный массив this.ArrayMergeUnique = function(array1, array2){ $(array2).each(function(i){ if($.inArray(array2[i], array1) == -1){ array1.push(array2[i]); } }); return array1; } // Снимаем отметку с элемена this.unsetData = function(name){ $('[name='+ name +']:checked').attr('checked', false); this.data[name] = null; } this.items.bind('change', this.onChange); };
Конечно его надо будет еще подправить, но в целом я им доволен :-) Он получился довольно универсальным, и отыгрывает любые варианты с товарами.