Облако тегов для miniShop2

UPDATE 20.03.2018: Сниппет обновлён на корректный и добавлен pdoTools для обработки чанка.

Появилась задача реализовать облако тегов для удобства поиска товаров в интернет-магазине. Для этого сделал простенький сниппет, где теги собираются со всех товаров на сайте.



Сниппет умеет принимать параметры базового размера(font), шага в пикселях(step), количества размеров(levels) и чанка для вывода(tpl).

Алгоритм работы прост:
1. Собираются теги со всех товаров сайта.
2. Подсчитывается количество товаров для каждого тега.
3. Рассчитываются размеры для каждого уровня и границы по количеству использования тега.
4. Если не указан шаблон вывода, то на экране также будет облако тегов, но без ссылок.


В чанке для вывода доступны плейсхолдеры:
[[+fontlevel]] //число от 0 до levels, можно использовать для класса level1, level2
[[+tag]] // сам тэг
[[+fontsize]] // размер в пикселях


Вызывать так:
[[msCloudTags? &tpl=`tpl.tag` &font=`13` &step=`2` &levels=`10`]]

Сам сниппет msCloudTags:
<?php
if (!isset($step)) {$step = 2;}  // шаг в пикселях
if (!isset($font)) {$font = 12;}  // начальный размер шрифта
if (!isset($levels)) {$levels = 10;}  // максимальное число уровней
if (!isset($key)) {$key = 'tags';}
if (!isset($tpl)) {$tpl = '@INLINE <span class="level{$fontlevel}" style="font-size:{$fontsize}px;">{$tag}</span>;';}
$pdo = $modx->getService('pdoTools');

$q = $modx->newQuery('msProductOption');
$q->innerJoin('modResource','modResource','modResource.id = msProductOption.product_id');
$q->where(array('msProductOption.key' => $key));
$q->select(array('msProductOption.value'));
$q->sortby('msProductOption.value','ASC');

$tags = array();
if ($q->prepare() && $q->stmt->execute()) {
	while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
		$tags[]= $row['value'];
	}
}

$tagsCount = array_count_values($tags); // массив с подсчитанным вхождением
$maxCount = max($tagsCount);  // максимальное количество вхождений
$minCount = min($tagsCount);  // минимальное


$fontSizes[] = $font; 
for ($i=0;$i<$levels;$i++) {
    $fontSizes[] = $font+=$step;
}
$fontLevels[] = $minCount;
$stepCounts = ($maxCount-$minCount)/$levels;
for ($i=0;$i<$levels;$i++) {
    $fontLevels[] = (int)($minCount+=$stepCounts);
}

$fontsize = $fontSizes[0];
$result='';
foreach ($tagsCount as $tag => $count) {
    for ($i=$levels;$i >= 0;$i--) {
        if ($count >= $fontLevels[$i]) {
            $fontsize = $fontSizes[$i];
            $fontlevel = $i;
            break;
        } 
    }
    
    $result.= $pdo->getChunk($tpl, array(
        'fontlevel' => $fontlevel, 
        'tag' => $tag, 
        'fontsize' => $fontsize
    ));
}
return $result;

P.S.: У сайта на скриншоте товары только добавляются, большинство тегов используются 1-3 раза, а те, что крупные, больше 20 раз.
P.P.S.: Наверняка, код можно написать покороче и лучше, но я совсем недавно начал работать на MODX. Буду рад вопросам и замечаниям :)

UPDATE 20.03.2018: Сниппет обновлён на корректный и добавлен pdoTools для обработки чанка.
Евгений Шеронов
17 февраля 2016, 21:46
modx.pro
27
6 204
+9
Поблагодарить автора Отправить деньги

Комментарии: 24

Воеводский Михаил
18 февраля 2016, 01:06
0
В сниппете можно навести красоту, но и в таком виде хорошо читается.
Евгений, собери пакет из этого? ;)
    Іван Клімчук
    18 февраля 2016, 01:16
    0
    Может имеет смысл запилить что-то вроде gists (github) для коллекции подобного рода сниппетов/решений? Пакет для такого собирать — оверхед (я конфиг сборки как правило вдумчиво пишу с нуля, чтобы ничего лишнего). Так, мысли в слух
      Воеводский Михаил
      18 февраля 2016, 01:42
      +2
      Иван, уж если в репо есть компонент If, то это точно имеет право на жизнь, дабы парой кликов поставить на сайт без лишних телодвижений.
      Да и компонент собрать не так уж сложно. На мой взгляд, конечно :)
        Илья Уткин
        18 февраля 2016, 16:55
        0
        Пакет из сниппета и пары чанков можно собрать PackMan'ом — просто и быстро
          Евгений Шеронов
          18 февраля 2016, 18:03
          0
          Про PackMan не знал, спасибо, Илья!
          Но я думаю пока рановато собирать пакет из одного сниппета :)

          Это будет иметь смысл, когда понадобится больше функционала.
          Например, выбор категорий, для которых нужно вывести теги, количество товаров рядом с тегом и что-то ещё.
          Может даже можно будет приделать сферический 3D режим облака тегов.

          Если будет спрос на это, то допишу и упакую в бесплатный пакет)
            Илья Уткин
            18 февраля 2016, 21:23
            +1
            Есть смысл. Если не в modstore, то хотя бы в modx.com. Правда, гораздо проще установить, чем копировать код
              Роман Садоян
              18 февраля 2016, 23:56
              0
              Так собери пакет уже сейчас, хотя бы с modExtra и выложи. Ведь у всех решения кастомные, а у тебя получилось более менее общее — можно будет с лёгкостью форкнуть и дописать под свои нужды!

              Тем более раз там 1 сниппет и чанк вывода тэгов, плюс для людей, которые не особо знакомы с MODX будет хорошо: кликнул — > установил -> вызвал -> profit.
                mngatoff
                19 февраля 2016, 03:30
                0
                если к этому еще и динамическую генерацию страниц с транслитом добавить, спрос точно будет) и сниппет уже не один.
                чтобы не site.com/?tags=хоп%20%хей, а site.com/tags/hop-hei
            Abu
            Abu
            18 февраля 2016, 04:35
            0
            А как выводите с ссылками?
            С msearch2 просто обернул ссылкой
            <a href="[[~25]]?query=[[+tag]]">tpl</a>
              Евгений Шеронов
              18 февраля 2016, 11:44
              0
              Вот чанк вывода, который используется на скриншоте:
              <a class="tag tag[[+fontlevel]]" href="[[~68]]?tags=[[+tag]]" style="font-size:[[+fontsize]]px;">[[+tag]]</a>

              Где 68 — это страница каталоге, на который вызывается mFilter2 и прописан aliases: msoption|tags==tags.
              Но можно сделать чтобы работало и по адресу /tags/[[+tag]] с помощью собственной маршрутизации.

              t3mnikov
              19 февраля 2016, 09:30
              +1
              Мой вариант облака тегов:
              $tpl = 'tpl.Tag'; //чанк одного тега
              $tags = array(); // массив тегов
              $params = array();// массив параметров
              $per = 75; // начальный  % шрифта
              $output=''; //вывод
              $c = $modx->newQuery('msProductOption');
              $c->where(array('key' => 'tags')); //берём значения тегов
              $c->sortby('value','ASC');
              $options = $modx->getCollection('msProductOption',$c);
              foreach($options as $option){
                  $tags[] = $option->get('value');
              }
              $tags = array_count_values($tags); //подсчитываем сколько одинаковых
              
              //выводим в чанк
              foreach($tags as $tag => $count){
                  $params['tag'] = $tag;
                  $params['href'] = '?tag=' . $tag;
                  $params['fontsize'] = $per*$count;
                  $output .= $modx->getChunk($tpl,$params);    
              }
              
              return $output;
              Чанк:
              <li><a href="[[~41]][[+href]]" style="font-size:[[+fontsize]]%;">[[+tag]]</a></li>
              Вывод товаров по тегу (нашел у Василия):
              $tag = $_GET['tag'];
              
              $key = 'tags'; // имя опции товара
              $category = 0; // фильтрация по категории
              
              $q = $modx->newQuery('msProductOption');
              $q->innerJoin('msProduct', 'msProduct', 'msProduct.id=msProductOption.product_id');
              $q->where(array('msProductOption.key' => $key, 'msProductOption.value'=> $tag));
              $q->sortby('msProductOption.value','ASC');
              $q->select('DISTINCT(msProductOption.value), msProduct.id');
              $q->where(array('msProductOption.key' => $key));
              if (!empty($category)) {
                  $ids = $modx->getChildIds($category);
                  $ids[] = $category;
                  $q->innerJoin('msCategory', 'msCategory', 'msCategory.id=msProduct.parent');
                  $q->where(array('msCategory.id:IN' => $ids));
              }
              
              $result = array();
              if ($q->prepare() && $q->stmt->execute()) {
                  while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
                  	$res['id'][] = $row['id'];
              	}
              	$result = implode(",", array_unique($res['id'])) ;
              }
              
              $params = array(
                  'includeThumbs' => '148x148',
                  'parents' => 2,
                  'tpl'=> 'tpl.msProducts.News.row',
                  'resources' => $result
                  );
              
              $output = $modx->runSnippet('msProducts', $params);
              return $output;
                Алексей
                20 марта 2018, 10:14
                0
                Добрый день!
                Подскажите, а как сделать несколько уровней, или остановить увеличение шрифта? Чем больше используется тег, тем больше шрифт. Спасибо!
                  Евгений Шеронов
                  20 марта 2018, 10:50
                  0
                  Добрый день!
                  Вопрос наверно не мне задан, но если будете использовать код из заметки, то там есть ограничение на максимальное число уровней)
                    Алексей
                    20 марта 2018, 10:54
                    0
                    Евгений, добрый день!
                    Ваш код из заметки почему-то не работает. Возможно, вы выводите через mFilter2, а я вывожу без него. Вы не подскажите, как вывести товары через ваше облако тегов. Вывод товаров по тегу на странице не работает.Спасибо.
                      Евгений Шеронов
                      20 марта 2018, 11:34
                      0
                      Поправил сниппет в заметке — попробуйте :)
                        Алексей
                        20 марта 2018, 11:53
                        0
                        Нет, при клике на тег выводит все товары, а не те товары у которых данный тег. Вывожу вот так:
                        <a class="tag tag[[+fontlevel]]" href="[[~26]]?tags=[[+tag]]" style="font-size:[[+fontsize]]px;">[[+tag]]</a>
                        На странице вызова вывод товаров из последнего комментария. Может что не так делаю?
                          Евгений Шеронов
                          20 марта 2018, 12:25
                          0
                          Вот такую ссылку надо:
                          href="[[~26]]?tag=[[+tag]]"
                            Алексей
                            20 марта 2018, 12:34
                            0
                            Заработало!!! Евгений, спасибо большое за помощь!
                              Алексей
                              12 апреля 2018, 09:48
                              0
                              Евгений, добрый день!
                              Не могу вывести товары по тегу, через PdoPage, со страницами. Просто когда один тег у сотни товаров, они выводятся все. Вы не подскажите, как вывести товары со страницами?
                                Евгений Шеронов
                                12 апреля 2018, 10:15
                                0
                                Добрый день!

                                Вывод, конечно, не зависит от облака тегов.
                                Но стандартный вызов pdoPage должен решить проблему:
                                [[!pdoPage?
                                	&element=`msProducts`
                                	&optionFilters=`[[!#GET.tag:notempty=`{"tags":"[[!#GET.tag]]"}`]]`
                                        &limit=`10`
                                	//ваши параметры какие угодны
                                ]]
                                [[!+page.nav]]

                                Но если сильно привязались к решению из комментария, то вместо этого:
                                $params = array(
                                    'includeThumbs' => '148x148',
                                    'parents' => 2,
                                    'tpl'=> 'tpl.msProducts.News.row',
                                    'resources' => $result
                                    );
                                
                                $output = $modx->runSnippet('msProducts', $params);
                                return $output;
                                напишите
                                return $result;
                                и уже вызов этого сниппета вставьте в resources
                                [[!pdoPage?
                                	&element=`msProducts`
                                	&resources=`[[!вашСниппет]]`
                                	//...
                                ]]
                                  Алексей
                                  12 апреля 2018, 12:56
                                  0
                                  Евгений, спасибо за Вашу помощь!!!
                                  Но я пытаюсь вывести на Fenom. Ничего не получается :( Наверное ошибаюсь в синтаксисе.
                                    Евгений Шеронов
                                    12 апреля 2018, 19:45
                                    0
                                    Возможно, попробуйте все варианты, всё получится!)
                                    Я тоже могу ошибаться, например в optionFilter или ещё где-то :)
                                    Алексей
                                    13 апреля 2018, 13:05
                                    0
                                    Добрый день!
                                    Вообщем решил так:
                                    <?php
                                    $tag = $_GET['tag'];
                                    
                                    $key = 'tags'; // имя опции товара
                                    $category = 0; // фильтрация по категории
                                    
                                    $q = $modx->newQuery('msProductOption');
                                    $q->innerJoin('msProduct', 'msProduct', 'msProduct.id=msProductOption.product_id');
                                    $q->where(array('msProductOption.key' => $key, 'msProductOption.value'=> $tag));
                                    $q->sortby('msProductOption.value','ASC');
                                    $q->select('DISTINCT(msProductOption.value), msProduct.id');
                                    $q->where(array('msProductOption.key' => $key));
                                    if (!empty($category)) {
                                        $ids = $modx->getChildIds($category);
                                        $ids[] = $category;
                                        $q->innerJoin('msCategory', 'msCategory', 'msCategory.id=msProduct.parent');
                                        $q->where(array('msCategory.id:IN' => $ids));
                                    }
                                    
                                    $result = array();
                                    if ($q->prepare() && $q->stmt->execute()) {
                                        while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
                                        	$res['id'][] = $row['id'];
                                    	}
                                    	$result = implode(",", array_unique($res['id'])) ;
                                    }
                                    
                                    $params = array(
                                        'element' => 'msProducts',
                                        'includeThumbs' => '260x195',
                                        'parents' => 9,
                                        'limit' => 21,
                                        'tpl'=> 'tpl.msProducts.new',
                                        'ajaxMode' => 'default',
                                        'tplPageWrapper' => '@INLINE <ul class="pagination uk-pagination">{{+prev}}{{+pages}}{{+next}}</ul>',
                                        'tplPageActive' => '@INLINE <li class="uk-active"><span>{{+pageNo}}</span></li>',
                                        'resources' => $result
                                        );
                                    
                                    $output = $modx->runSnippet('pdoPage', $params);
                                    return $output;
                                    И вывод на странице:

                                    <div id="pdopage">
                                        <div class="rows uk-child-width-1-3@s uk-grid-match" uk-grid>
                                            {'!Tags' | snippet}
                                        </div>
                                        
                                        <div class="uk-panel uk-panel-box uk-flex uk-flex-center">{'page.nav' | placeholder}</div>
                                    </div>
                                    Все работает.

                                    Спасибо Вам за помощь!!!
                Кирилл
                10 мая 2020, 22:44
                0
                Спасибо огромнейшее!
                  Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                  24