[xLike] Идеальная система лайков с оптимистичным интерфейсом и правильной формулой

Пакет довольно прост и одновременно функционален. Выполняет систему рейтинга для любого объекта, по-умолчанию, для ресурсов. Чем-то напоминает систему лайков на YouTube, в частности, в пакете реализован оптимистичный интерфейс.


Также, особенностью компонента является правильный подсчет рейтинга на основе вычисления нижней границы доверительного интервала Вильсона для параметра Бернулли. По-простому: эта формула не даст встать новым записям с 1 лайком и 0 дизлайками выше более старых записей с сильным рейтингом.

Список преимуществ:
  1. Оптимистичный интерфейс,
  2. Наиболее правильная формула вычисления рейтинга,
  3. Работа с любыми объектами (modResource, modUser, кастомные объекты),
  4. Голосование гостями,
  5. Анимированное изменение полосы/числа рейтинга,
  6. Событие плагина — xLikeOnVote.


Сниппет xLike


Выводит систему рейтинга на месте вызова.

Параметры

tpl — чанк шаблона вывода. По-умолчанию, tpl.xLike.
mode — режим работы сниппета: db или local. В режиме local сниппет обходится без запроса к базе для того, чтобы узнать кол-во лайков/дизлайков и рейтинг, вместо этого вам необходимо передать эти цифры в сниппет в качестве параметров. По-умолчанию, db.
guest — разрешить гостям голосовать. По-умолчанию, Да.
parent — ID объекта. По-умолчанию, ID текущего ресурса.
class — класс объекта. По-умолчанию, modResource.
list — название списка (дополнительный параметр группировки). По-умолчанию, default.

Примеры

Вывод рейтинга для текущего ресурса с возможностью голосовать гостям:
{'!xLike' | snippet}

Вывод рейтинга для текущего тикета без возможности голосовать гостям:
{'!xLike' | snippet : [
    'guest' => false,
    'class' => 'Ticket',
]}

Вывод рейтинга для текущего ресурса в режиме local:
{'!xLike' | snippet : [
    'mode' => 'local',
    'likes' => $_modx->resource['likes'],
    'dislikes' => $_modx->resource['dislikes'],
    'rating' => $_modx->resource['rating'],
]}
Подразумевается, что в ТВ полях likes, dislikes, rating хранится соответствующая информация.

 

Событие xLikeOnVote


Данное событие срабатывает после голосования пользователем и всех проверок.

Параметры плагина

parent — ID объекта, к которому привязан рейтинг,
class — класс объекта,
list — название списка,
likes — кол-во лайков,
dislikes — кол-во дизлайков,
rating — текущий рейтинг, уже пересчитанный.

Практическое применение

Задача: при голосовании в рейтинге (с параметрами сниппета class=modResource, list=default), записывать текущий рейтинг в ТВ поле rating.
Код плагина:
switch ($modx->event->name) {
    case "xLikeOnVote":
        if ($class == 'modResource' && $list == 'default') {
            if ($resource = $modx->getObject($class, array('id' => $parent))) {
                $resource->setTVValue('rating', $rating);
            }
        }
        break;
}
Таким образом, мы сможем сортировать ресурсы (или любые другие объекты) по правильно подсчитанному рейтингу.

Демо-сайт
Скачать в Modstore

Если есть желание отблагодарить:
ЯД — 4100159550314
PayPal — pavelgvozdb@yandex.ru
Павел Гвоздь
16 июля 2017, 18:05
19
1 904
+34

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

Богдан
17 июля 2017, 08:53
0
Отличный компонент, спасибо, Павел! Выглядит красиво и функционально. Единственный минус как по мне — нужно вызывать сниппет, а если это делать это из чанка например pdoResources, увеличится количество запросов к БД и время генерации страницы. Есть ли в планах сделать возможным голосование без вызова сниппета (вручную назначить класс/id для кнопок и полосы рейтинга в чанке pdoResources)?
    Павел Гвоздь
    17 июля 2017, 10:12
    0
    А как вы будете получать рейтинг и кол-во голосов без запросов к БД?
      Руслан Кундиус
      17 июля 2017, 11:06
      +2
      Количество голосов можно из tv поля, или джоином, голоса ведь в бд хранятся? Если правильно проставить js-* классы и data атрибуты должно работать, не понятно только что в propkey попадает, пока не могу «пощупать».
        Павел Гвоздь
        17 июля 2017, 12:18
        +2
        Да, все верно, если сохранять данные рейтинга и кол-ва голосов в полях ресурса или другого объекта, то можно обойтись и без лишних запросов к БД. Вот тут код компонента, а тут непосредственно сниппет, который формирует propkey.
          Богдан
          17 июля 2017, 12:28
          +2
          Отправил «спасибо» на яндекс кошелек. Если будет работать без сниппета — будет вообще круто. Позже посмотрю код, как там все устроено.
        Павел Гвоздь
        17 июля 2017, 12:22
        +2
        На счёт дополнительного режима сниппета без запросов к БД можно подумать, хорошая идея!
        Павел Гвоздь
        17 июля 2017, 14:28
        0
        Единственное, запрос к БД в любом случае будет совершаться, чтобы узнать, голосовал ли юзер уже.
    Павел Гвоздь
    17 июля 2017, 14:50
    +5
    Готово! У сниппета появился дополнительный параметр &mode, позволяет выбрать режим вывода сниппета: db или local. В режиме local сниппет обходится без запроса к базе для того, чтобы узнать кол-во лайков/дизлайков и рейтинг, вместо этого вам будет необходимо передать эти цифры в качестве параметров в сниппет:
    {'!xLike' | snippet : [
        'mode' => 'local',
        'likes' => $_modx->resource['likes'],
        'dislikes' => $_modx->resource['dislikes'],
        'rating' => $_modx->resource['rating'],
    ]}
    Подразумевается, что в ТВ полях likes, dislikes, rating хранится соответствующая информация.
      Alexander Smolenskyi
      09 августа 2017, 14:49
      0
      объясните, куда этот код вообще вставлять и чтобы записывало в ТВ поля.
      Ксения
      09 июня 2018, 17:43
      0
      Объясните пожалуйста, как вот это работает?
      {'!xLike' | snippet : [
          'mode' => 'local',
          'likes' => $_modx->resource['likes'],
          'dislikes' => $_modx->resource['dislikes'],
          'rating' => $_modx->resource['rating'],
      ]}
      В TV записаны значения и они нормально выводятся пользователю.
      Если он «голосует» то в БД попадает соответствующая запись. (НО другому пользователю опять выведется то же значение TV что и было)
      Я переношу систему рейтингов и думала, что к значению в TV будут прибавляться значения из БД, но видимо это не так.
      Чтобы обновлять TV создала плагин
      <?php
      switch ($modx->event->name) {
          case "xLikeOnVote":
              if ($class == 'modResource' && $list == 'default') {
                  if ($resource = $modx->getObject($class, array('id' => $parent))) {
                      $resource->setTVValue('myLike', $rating);
                      $resource->setTVValue('likes', $likes);
                      $resource->setTVValue('dislikes', $dislikes);
                      $resource->save();
                  }
              }
              break;
      }
      но он все предыдущие значения TV обнуляет.

      Так как он должен работать?
      Делать как-то так?
      $resource->setTVValue('likes', $resource->getTVValue('likes') + $likes);
        Павел Гвоздь
        09 июня 2018, 18:50
        0
        Делать как-то так?
        Нет, у вас всё верно в плагине, кроме
        $resource->save();
        , можно без него. MODX при
        $resource->setTVValue()
        уже выполняет сохранение. Поправил в посте.

        Вероятно у вас что-то закешено. Может база, не знаю. У меня работает корректно на демо-сайте.
          Ксения
          09 июня 2018, 19:08
          0
          Очень странно. Все настройки кэширования по умолчанию.
          в tv like стояло 520 стало 2
          в tv dislike стояло 70 стало 0
          то есть просто записались(посчитались) данные из БД, минуя значения в tv
            Павел Гвоздь
            09 июня 2018, 19:16
            0
            А, вот вы о чём. Так да, в плагине в переменных $likes и $dislikes реальные значения лайков и дизов, а не накрученные.
            Павел Гвоздь
            09 июня 2018, 19:18
            0
            Попробуйте ваши накрученные значения вместо записи в ТВшки likes и dislikes, писать их в соседние, например likes_fake, dislikes_fake. В плагине конечно оставить всё, как есть, а при выводе суммировать likes + likes_fake и с дизами тоже самое.
              Ксения
              09 июня 2018, 19:28
              0
              Я с fenom как-то совсем ни как
              в снипете заменить
              'likes' => $_modx->resource['likes']
              на
              'likes' => $_modx->resource['likes_fake'] + $_modx->resource['likes']
              ?

              PS Значения не накрученные, попрошу, просто переношу данные из LikeDislike )

                Павел Гвоздь
                09 июня 2018, 19:36
                0
                'likes' => ($_modx->resource['likes_fake'] + $_modx->resource['likes']),
                Примерно…

                Значения не накрученные, попрошу, просто переношу данные из LikeDislike )
                Без разницы. Ради удобства повествования.
                  Ксения
                  09 июня 2018, 20:30
                  0
                  Спасибо, всё заработало как надо.
                  PS При использовании tv надо в настройках системы cache_resource выставить на Нет
Наумов Алексей
17 июля 2017, 09:40
0
Привет, классно!

Вопросы от меня:
1. пример выборки для pdoResources (с LEFT JOIN имею ввиду) можно?
2. Как реализована защита от повторного голосования гостями и авторизованными пользователями?
    Павел Гвоздь
    17 июля 2017, 10:13
    0
    1) Пример выборки чего?
    2) IP или номер сессии. Для авторизованных юзеров не вижу смысла объяснять…
      Наумов Алексей
      17 июля 2017, 10:18
      0
      Даже не знаю чего у нас pdoResources выбирает… может быть ресурсы? :)
      Например — отсортировать по популярности.
        Павел Гвоздь
        17 июля 2017, 10:22
        +1
        Для справки: pdoResources способен выбирать все, что угодно. xLike способен работать с любыми объектами.
        Записываете рейтинг в ТВ при голосовании на событии xLikeOnVote и сортируете. Ничего сложного.
          Наумов Алексей
          17 июля 2017, 10:58
          0
          Не люблю TV плодить, да и как по нему сортировать то, он строка. С приведением типов разве, что тоже не гуд на больших объемах. Вот и спросил.
            Павел Гвоздь
            17 июля 2017, 12:20
            0
            Ну можно в таком случае добавить еще одно поле в таблицу ресурсов и прописать его в map массив. В этом случае в плагине на событие xLikeOnVote писать данные рейтинга в него. Тогда можно будет и сортировать без приведения типа.
              Руслан Кундиус
              17 июля 2017, 15:04
              0
              Думаю Алексей имел ввиду что-то вроде этого:
              {'pdoResources' | snippet : [
                  'loadModels' => 'xLike',
                  'where' => [
                      'xlVote.class' => 'modResource'
                  ],
                  'leftJoin' => [
                      'xlVote' => [
                          "class" => "xlVote",
                          "on" => "modResource.id = xlVote.parent"
                      ]
                  ],
                  'select' => [
                      'modResource' => '*',
                      'xlVote' => [
                          'COUNT(xlVote.value) as total',
                          'SUM(xlVote.value = 1) as likes',
                          'SUM(xlVote.value = -1) as dislikes'
                      ]
                  ]
              ]}
              Можно даже рейтинг посчитать, но это зашквар какой-то:
              '(((SUM(xlVote.value = 1) + 1.9208) / (SUM(xlVote.value = 1) + SUM(xlVote.value = -1)) - 1.96 * sqrt((SUM(xlVote.value = 1) * SUM(xlVote.value = -1)) / (SUM(xlVote.value = 1) + SUM(xlVote.value = -1)) + 0.9604) / (SUM(xlVote.value = 1) + SUM(xlVote.value = -1))) / (1 + 3.8416 / (SUM(xlVote.value = 1) + SUM(xlVote.value = -1))) * 100) as rating'
                Павел Гвоздь
                17 июля 2017, 15:10
                +1
                А зачем делать все эти подсчёты на стороне MySQL и джоины, когда в описанном мной способе преимущество в плане быстродействия и ресурсопотребления на лицо?
                  Руслан Кундиус
                  17 июля 2017, 15:23
                  0
                  Согласен, вариант с расширением таблицы гораздо лучше.
kalisto
24 октября 2017, 10:44
0
Здравствуйте! Прошу помощи — не накапливаются лайки.
В шаблоне вызываю чанк
[[!xLike?
          &parent=[[*id]]
          &tpl=`tpl.xLikeMy`
        ]]
Не работают и на xLike.tpl
Меняла
&mode=`local`
и
&mode=`db`
Пример здесь
Все равно не накпливаются лайки. как исправить?
    Павел Гвоздь
    26 октября 2017, 08:58
    0
    1) Для начала корректно пропишите вызов сниппета, строго по формату MODX.
    2) Потом мне не ясно, что вы хотите накапливать и куда?
    3) Я показал конкретные примеры на демо сайте, как надо вызывать и что из этого получается. Если почитать внимательно, то поймете, что к чему и как с этим работать.
    Игорь
    10 января 2018, 14:10
    0
    Вы решили вопрос с накоплением лайков? Не могли бы, если не сложно, здесь выложите решение и причину? Спасибо заранее.
      kalisto
      10 января 2018, 17:46
      0
      Наверно до конца не решила, взяла другой плагин LikeDislike
        Игорь
        10 января 2018, 17:47
        0
        Понял. Спасибо за отклик.
kalisto
24 октября 2017, 11:00
0
Также ставлю в списке ресурсов вывод
<small>Число голосов - {$likes | number_format : 0 : '' : ' '}</small>
Верно ли? Или как правильно вывести число лайков?
Fenom pdotools_fenom_parser активировала
    Павел Гвоздь
    26 октября 2017, 08:59
    0
    4) Где вы вызываете {$likes}? В чанке компонента xLike, надеюсь? Тогда работать должно.
      Денис
      31 октября 2017, 15:04
      0
      Здравствуйте. Подскажите как отсортировать ресурсы по рейтингу? Сейчас сортирует так: Если на одном ресурсе 1 лайк (рейтинг 20,65) а на другом 1 лайк / 1 дизлайк (рейтинг 9,45), то выводится на первое место 2-й ресурс с рейтингом 9,45.
      Создал плагин: xLikeOnVote с кодом который вы приводите в описании
      Вывод делаю так:
      [[!pdoPage?
                                              &element=`getTickets`
                                              &parents=`[[*id]]`
                                              &tpl=`afisha-popular`
                                              &hideContainers=`1`
                                              &includeTVs=`image, rating`
                                              &limit=`4`
                                              &sortby=`{"rating":"DESC"}`
                                          ]]
      В чанке afisha-popular вызываю
      [[!xLike? &parent=`[[+id]]` &tpl=`rating`]]
      Подскажите, как вывести на первое место ресурс с большим рейтингом?
Игорь
10 января 2018, 16:03
0
Так как всё же фильтровать ресурсы по рейтингам? В разделе используется mFilter
Александр
27 января 2018, 17:43
0
Здравствуйте, как скачать? не находит xLike в админке на сайте
slavkovladymyr
27 января 2018, 22:48
0
Подскажите как вывести текущее место в рейтинге для отдельного обьекта голосования? Если подробнее, то на сайте есть голосование за учасниц конкурса (modUser) и нужно на странице учасницы указать на каком она сейчас месте в рейтинге. Дизлайки скрыты, можно только ставить лайк, что и считаеться как один голос.
Станислав
29 января 2018, 01:48
0
Здравствуйте, подскажите, как правильно вставить в шаблон. Конструкция
[[!xLike?
     &guest = false
      ]]
работает, но на гостей никак не влияет и гости так же могут ставить лайки. или вызов параметров снипета в данном случае не сработают?
Михаил
03 апреля 2018, 21:53
0
Не понятно как считаеся рейтинг какие то проценты. Процент чего он считает?
    Павел Гвоздь
    03 апреля 2018, 22:09
    0
    Внимательнее.
    особенностью компонента является правильный подсчет рейтинга на основе вычисления нижней границы доверительного интервала Вильсона для параметра Бернулли
    habrahabr.ru/company/darudar/blog/143188/
      Михаил
      03 апреля 2018, 23:18
      0
      Это вычисление понятно только Вильсону и Бернулли )))) Обычный пользователь не поймёт ни чего…
Александр
01 июня 2018, 15:19
0
Добрый день. Напишите пожалуйста как этот компонент привязать к сниппету TicketComments, чтоб можно было комментарии лайкать без авторизации. Желательно без шаблонизатора феном.
Илья
08 июня 2018, 18:27
0
Как сделать конструкцию такого плана, аналогичную как на пикабу:
<span class="xlike__count xlike__count_like [ js-xlike-number ]">{$likes}-{$dislikes}</span>
Что бы обновлялось не при перезагрузке страницы, а ajax
    Илья
    08 июня 2018, 18:41
    0
    только таким образом получилось показывать общую цифру рейтинга
    <span class="xlike__count xlike__count_like xlike__count_dislike [ js-xlike-number ]">{$likes-$dislikes}</span>
    Но обновляется только после перезагрузки страницы
Ксения
09 июня 2018, 13:26
0
Как правильно записать это
{'!xLike' | snippet : [
    'mode' => 'local',
    'likes' => $_modx->resource['likes'],
    'dislikes' => $_modx->resource['dislikes'],
    'rating' => $_modx->resource['rating'],
]}
не в fenom?
[[!xLike?
          &parent=[[*id]]
          &mode=`local`
          &likes=`tv.likes`
          &dislikes=`tv.dislikes`
          &rating=`tv.myLike`
          &tpl=`tpl.xLike`
        ]]
что-то не работает корректно
Sergey
12 июня 2018, 20:36
0
Павел, подскажите пжл
Как правильно прописать чтобы после нажатия на likes появлялось кол-во лайков и рейтинг?
    Павел Гвоздь
    13 июня 2018, 11:39
    0
    Как будет работать, так и прописывайте. Нет тут понятия «правильно».
KSin
05 июля 2018, 23:39
1
0
Добрый день. Подскажите такой момент.
Есть товары. miniShop2
В карточке товара вызываю:
[[!xLike?
&parent=[[*id]]
&mode=`db`
&likes=`$_modx->resource['likes']`
&dislikes=`$_modx->resource['dislikes']`
&rating=`$_modx->resource['rating']`
&tpl=`tpl.xLike`
&class=`msProduct`
&list=`default`
]]

Создал дополнительные TV поля:
likes
dislikes
rating

Далее ставлю лайк товару. Но при редактировании товара поля likes, dislikes, rating по-прежнему пусты. Что я сделал не так? Или чего-то не сделал? Заранее спасибо.
    Павел Гвоздь
    06 июля 2018, 07:50
    0
    Плагин нужен.
      KSin
      06 июля 2018, 16:17
      0
      Павел, а можно чуть подробнее? В описании есть код плагина и в комментах есть что-то похожее. И поясните пожалуйста. Создаю плагин в папке xLine? Как назвать, где его прописать, чтобы работало? Если не затруднит. Спасибо.
        Павел Гвоздь
        07 июля 2018, 09:55
        0
        switch ($modx->event->name) {
            case "xLikeOnVote":
                if ($class == 'modResource' && $list == 'default') {
                    if ($resource = $modx->getObject($class, array('id' => $parent))) {
                        $resource->setTVValue('rating', $rating);
                        $resource->setTVValue('likes', $likes);
                        $resource->setTVValue('dislikes', $dislikes);
                    }
                }
                break;
        }
          KSin
          07 июля 2018, 12:53
          0
          Спасибо