[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);
                $resource->save();
            }
        }
        break;
}
Таким образом, мы сможем сортировать ресурсы (или любые другие объекты) по правильно подсчитанному рейтингу.

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

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

Комментарии (24)

  1. Богдан 17 июля 2017, 08:53 # 0
    Отличный компонент, спасибо, Павел! Выглядит красиво и функционально. Единственный минус как по мне — нужно вызывать сниппет, а если это делать это из чанка например pdoResources, увеличится количество запросов к БД и время генерации страницы. Есть ли в планах сделать возможным голосование без вызова сниппета (вручную назначить класс/id для кнопок и полосы рейтинга в чанке pdoResources)?
    1. Павел Гвоздь 17 июля 2017, 10:12 # 0
      А как вы будете получать рейтинг и кол-во голосов без запросов к БД?
      1. Руслан Кундиус 17 июля 2017, 11:06 # +2
        Количество голосов можно из tv поля, или джоином, голоса ведь в бд хранятся? Если правильно проставить js-* классы и data атрибуты должно работать, не понятно только что в propkey попадает, пока не могу «пощупать».
        1. Павел Гвоздь 17 июля 2017, 12:18 # +2
          Да, все верно, если сохранять данные рейтинга и кол-ва голосов в полях ресурса или другого объекта, то можно обойтись и без лишних запросов к БД. Вот тут код компонента, а тут непосредственно сниппет, который формирует propkey.
          1. Богдан 17 июля 2017, 12:28 # +2
            Отправил «спасибо» на яндекс кошелек. Если будет работать без сниппета — будет вообще круто. Позже посмотрю код, как там все устроено.
          2. Павел Гвоздь 17 июля 2017, 12:22 # +2
            На счёт дополнительного режима сниппета без запросов к БД можно подумать, хорошая идея!
            1. Павел Гвоздь 17 июля 2017, 14:28 # 0
              Единственное, запрос к БД в любом случае будет совершаться, чтобы узнать, голосовал ли юзер уже.
          3. Павел Гвоздь 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 хранится соответствующая информация.
            1. Alexander Smolenskyi 09 августа 2017, 14:49 # 0
              объясните, куда этот код вообще вставлять и чтобы записывало в ТВ поля.
          4. Наумов Алексей 17 июля 2017, 09:40 # 0
            Привет, классно!

            Вопросы от меня:
            1. пример выборки для pdoResources (с LEFT JOIN имею ввиду) можно?
            2. Как реализована защита от повторного голосования гостями и авторизованными пользователями?
            1. Павел Гвоздь 17 июля 2017, 10:13 # 0
              1) Пример выборки чего?
              2) IP или номер сессии. Для авторизованных юзеров не вижу смысла объяснять…
              1. Наумов Алексей 17 июля 2017, 10:18 # 0
                Даже не знаю чего у нас pdoResources выбирает… может быть ресурсы? :)
                Например — отсортировать по популярности.
                1. Павел Гвоздь 17 июля 2017, 10:22 # +1
                  Для справки: pdoResources способен выбирать все, что угодно. xLike способен работать с любыми объектами.
                  Записываете рейтинг в ТВ при голосовании на событии xLikeOnVote и сортируете. Ничего сложного.
                  1. Наумов Алексей 17 июля 2017, 10:58 # 0
                    Не люблю TV плодить, да и как по нему сортировать то, он строка. С приведением типов разве, что тоже не гуд на больших объемах. Вот и спросил.
                    1. Павел Гвоздь 17 июля 2017, 12:20 # 0
                      Ну можно в таком случае добавить еще одно поле в таблицу ресурсов и прописать его в map массив. В этом случае в плагине на событие xLikeOnVote писать данные рейтинга в него. Тогда можно будет и сортировать без приведения типа.
                      1. Руслан Кундиус 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'
                        1. Павел Гвоздь 17 июля 2017, 15:10 # +1
                          А зачем делать все эти подсчёты на стороне MySQL и джоины, когда в описанном мной способе преимущество в плане быстродействия и ресурсопотребления на лицо?
                          1. Руслан Кундиус 17 июля 2017, 15:23 # 0
                            Согласен, вариант с расширением таблицы гораздо лучше.
              1. kalisto 24 октября 2017, 10:44 # 0
                Здравствуйте! Прошу помощи — не накапливаются лайки.
                В шаблоне вызываю чанк
                [[!xLike?
                          &parent=[[*id]]
                          &tpl=`tpl.xLikeMy`
                        ]]
                Не работают и на xLike.tpl
                Меняла
                &mode=`local`
                и
                &mode=`db`
                Пример здесь
                Все равно не накпливаются лайки. как исправить?
                1. Павел Гвоздь 26 октября 2017, 08:58 # 0
                  1) Для начала корректно пропишите вызов сниппета, строго по формату MODX.
                  2) Потом мне не ясно, что вы хотите накапливать и куда?
                  3) Я показал конкретные примеры на демо сайте, как надо вызывать и что из этого получается. Если почитать внимательно, то поймете, что к чему и как с этим работать.
                2. kalisto 24 октября 2017, 11:00 # 0
                  Также ставлю в списке ресурсов вывод
                  <small>Число голосов - {$likes | number_format : 0 : '' : ' '}</small>
                  Верно ли? Или как правильно вывести число лайков?
                  Fenom pdotools_fenom_parser активировала
                  1. Павел Гвоздь 26 октября 2017, 08:59 # 0
                    4) Где вы вызываете {$likes}? В чанке компонента xLike, надеюсь? Тогда работать должно.
                    1. Денис 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`]]
                      Подскажите, как вывести на первое место ресурс с большим рейтингом?
                  Вы должны авторизоваться, чтобы оставлять комментарии.