Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Предложения по модулю #3

Closed
loveorigami opened this issue Jan 26, 2015 · 18 comments
Closed

Предложения по модулю #3

loveorigami opened this issue Jan 26, 2015 · 18 comments
Assignees

Comments

@loveorigami
Copy link

Спасибо за полезное расширение!
Писал схожий функционал на CI, обратил внимание на ваше за счет aggregate_rating.
А потому из своих наработок, да и после знакомства с вашим есть что предложить :).
А там, если что-то будет полезное - оформлю в виде отдельного issue.

1. По базе

1.1 Добавить индексы для user_id и model_id
1.2 User_id сделать int. Ip адрес (для гостей) записывать через ip2long()

2. По виджету

2.1 По аналогии с action column в GridView разработать несколько свойств

  • $up_class, $down_class - класс для vote Up и vote Down
  • $up_tpl, $down_tpl - шаблоны для up и down

Например:
сейчас в виде генерируется

<span style="cursor:pointer;" onclick="vote('article',9,'like'); return false;" class="glyphicon glyphicon-thumbs-up" id="vote-up-article9">3</span>

хотелось бы получить (из своего)

<a title="нравится" id="up_47839_0" class="it_vote btn btn-success btn-xs" href="http://new.loveorigami.info/world/action"> 
<span class="glyphicon glyphicon-thumbs-up"></span> <span class="ch">0</span>
</a>

с учетом вышепредложенного

// $up_class = btn btn-success btn-xs
// $up_tpl = <span class="glyphicon glyphicon-thumbs-up"></span> <span class="ch">0</span>

<a title="нравится" id="up_47839_0" class="it_vote $up_class" href="http://new.loveorigami.info/world/action"> $up_tpl </a>

чтоб в результате получить
2015-01-26_12-11-43

@loveorigami
Copy link
Author

2.1 (Моя реализация в CI)

http://new.loveorigami.info/world

  • view
$str.= '<a href="'.base_url().$module.'/action" class="it_vote btn btn-success btn-xs" id="up_'.$id.'_'.$op['up'].'" title="'.lang('up').'">'.bs_icon('thumbs-up').' <span class="ch">'.$op['up'].'</span></a> ';

$str.= '<a href="'.base_url().$module.'/action" class="it_vote btn btn-danger btn-xs" id="down_'.$id.'_'.$op['down'].'" title="'.lang('down').'">'.bs_icon('thumbs-down').' <span class="ch">'.$op['down'].'</span></a> ';
  • js
// при клике на элемент it_vote обрабатываю данные и отправляю на action
$(document).off('.it_fav').on('click', '.it_fav',{ id_item: 'item' }, ItemAction); // для избранного тот же функционал
$(document).off('.it_vote').on('click', '.it_vote',{ id_item: 'item' }, ItemAction);

function ItemAction(e) {
        var item = $(this).attr('id').split('_');
        var trigger=item[0];
        var id=item[1];
        var val=item[2];
        var uid=item[3];
        //alert(uid);
        var id_item=e.data.id_item;
        $('#' + id_item + '_' + id).showLoading();
    $.ajax({
            url: $(this).attr('href'), // указываем URL для обработки AJAX
            type: 'POST',
            dataType: 'json',
            cache: false,   
            data:{
               trigger:trigger,
               id:id,
               val:val,
               uid:uid
           },
            success: function(msg){
                switch(msg['trigger']){
                    case "hide":
                        if(msg['res']==true){
                            //$('#item_' + id).fadeOut();
                        }
                        break;
                    case "onmain":
                        if(msg['res']==true){
                            //$('#item_' + id).fadeOut();
                            //alert(msg['label']);
                        }
                        break;
                }
                    $('#mes').html(msg['mes']);

                    if(msg['uid']){
                        $('#' + trigger + '_' + id + '_' + val + '_' + uid + ' span.ch').html(msg['label']);
                        $('#' + trigger + '_' + id + '_' + val + '_' + uid).attr("id", trigger + '_' + id + '_' + msg['val'] + '_' + msg['uid']);
                    }
                    else{
                        $('#' + trigger + '_' + id + '_' + val + ' span.ch').html(msg['label']);
                        $('#' + trigger + '_' + id + '_' + val).attr("id", trigger + '_' + id + '_' + msg['val']);
                    }
                    $('#' + id_item + '_' + id).hideLoading();

            }
        }); 
        e.preventDefault();
    };

В действии - если возникает ошибка - иконка Up меняется, выводится noty-сообщение
2015-01-26_12-26-05
Проверить можно там же
http://new.loveorigami.info/world

php - действие и результат

//------------------------------ операции над записями -------------------------
// Статус для поля hide (0-1)
    function action() {
        if (is_ajax()) {
// получаем данные из js
            $this->id        = $this->input->post('id');
            $this->val       = $this->input->post('val');
            $this->trigger   = $this->input->post('trigger');
            $this->label     = '';

// если есть доступ
            if ($this->permission) { 
                switch ($this->trigger) {
// тут много разных триггеров. Для примера оставлю fav (о нем пойдет речь ниже)
                    case 'fav':
// тут код, такой же как и ниже 
                    case 'up':
// есть ли запись у юзера
                            if (!$this->model_mx->GetVote($this->id, uid())) {
                                $this->model_mx->InsertVoteUp(
                                        array(
                                            'id'     => $this->id,
                                            'uid'    => uid(),
                                            'vote'   => 1,
                                            'date'   => date('Y-m-d H:i:s')
                                        )
                                );
                                $this->val=$this->val+1;
                                $this->label     = $this->val;
                                $this->res=true; // результат true
                                $this->message->set('jok', lang('ok_vote_up', $this->id)); // сообщение в зависимости от языка
                                $this->mes = $this->message->display('jok'); // а это показывается всплывающее окно noty
                            } else {
                                $this->message->set('jatt', lang('att_vote', $this->id));
                                $this->mes   = $this->message->display('jatt');
                                $this->res   = false;
                                $this->label = $this->val;
                            }
                        break;
                    case 'down':
                            if (!$this->model_mx->GetVote($this->id, uid())) {
                                $this->model_mx->InsertVoteDown(
                                        array(
                                            'id'     => $this->id,
                                            'uid'    => uid(),
                                            'vote'   => 0,
                                            'date'   => date('Y-m-d H:i:s')
                                        )
                                );
                                $this->val=$this->val+1;
                                $this->label     = $this->val;
                                $this->res=true;
                                $this->message->set('jok', lang('ok_vote_down', $this->id));
                                $this->mes = $this->message->display('jok');
                            } else {
                                $this->message->set('jatt', lang('att_vote', $this->id));
                                $this->mes   = $this->message->display('jatt');
                                $this->res   = false;
                                $this->label = $this->val;
                            }
                        break;
                    default:
                        $this->val= 0;
                        $this->message->set('jerr', lang('err_perm'));
                        $this->mes = $this->message->display('jerr');
                        $this->res = false;
                        break;
                }
            } 
            else {
                $this->_action_errmes(); // показываем ошибки
            }
// результат декодируем ну и потом обрабатываем там же в js
            echo json_encode(
                    array(
                        'res'        => $this->res,
                        'mes'        => $this->mes,
                        'trigger'    => $this->trigger,
                        'val'        => $this->val,
                        'label'      => $this->label,
                    )
            );
        }
    }

function _action_mes(){
    $this->message->set('jok', lang('ok_'.$this->trigger . $this->val, $this->id));
    $this->mes = $this->message->display('jok');
    $this->res=true;
}

function _action_errmes(){
    $this->val = 0;
    $this->label = bs_icon('remove');
    if (uid()){
        $this->message->set('jerr', lang('err_perm'));
    } 
    else{
        $this->message->set('jerr', lang('err_uid'));
    }
    $this->mes = $this->message->display('jerr');
    $this->res = false;
}

@loveorigami
Copy link
Author

2.3 Js - "заглушки"

  • Чтобы иметь возможность выполнить дополнительную js-операцию до и после голосования. в виджет наверно стоит добавить еще два свойства
  • js_before_vote - своя функция - у меня это индикатор загрузки
($('#' + id_item + '_' + id).showLoading();)
  • js_after_vote по завершению - он убирается
 $('#' + id_item + '_' + id).hideLoading();

2015-01-26_12-46-46

@chiliec chiliec self-assigned this Jan 26, 2015
@chiliec
Copy link
Owner

chiliec commented Jan 26, 2015

Спасибо, отличные предложения!
1.1 Добавлю, как-то не подумал об этом.
1.2 Идея хорошая, но нужно продумать, могут быть подводные камни с разрядностью ОС и IPv6.
2.1 Можно сделать, но в принципе и сейчас это реализуемо переопределением файла вида (в readme описано как это сделать). В принципе и JS таким же образом можно переопределить, но да, так наверное лучше будет :)
2.3 "заглушки" конечно надо будет добавить.
Кстати, Pull Requests приветствуются :)

@loveorigami
Copy link
Author

1.2 Об этом я тоже подумал. Надо в нете посмотреть, как привести к int. Ну или разделить на 2 поля. Один для ip, второй - для user_id. Смысл в том, что поначалу я захочу разрешить гостям голосовать, а после - запретить. И вот данные в этом столбце будут различного содержания.

2.1 Это я видел. Но в своем виде, вы, допустим, изменили классы в html, написали новый js код. А в своем виде я таких изменений не увижу.


Pull Request-ом я помогу, конечно! Главное набросать что из предлагаемого будет интересно для модуля и как это проще реализовать. C Yii я знаком буквально пару месяцев просто, и возможно, то что я делал в CI, в Yii2 имеет более простую реализацию.

Да и не все предложения я еще написал ). Завтра, постараюсь, дооформить свои мысли по этому поводу, а там уже определимся, чем смогу помочь )

@chiliec
Copy link
Owner

chiliec commented Jan 27, 2015

1.2 Это добавляет избыточность, но если предусматривать возможность динамического изменения способа учета голосовавших, то разделение на два поля будет единственно правильным решением.
2.1 Согласен.

@loveorigami
Copy link
Author

1.2 На самом деле, я размышлял над таким моментом. Что для некоторых моделей можно разрешить, а для некоторых - нет.
У меня даже в проекте возникало желание на главной под новости разрешить голосование. Т.к. пользователь только пришел, еще не регистрировался, читает новости. Что-то понравилось - он отметил. А если понравился сайт - он уже регистрируется....
А к некоторым моделям - конкурсы работ - доступ только зарегистрированным. Чтоб не накручивали голоса по ip.

учесть, в принципе, такой момент не сложно. навскидку

<?php echo \chiliec\vote\Display::widget([
    'model_name' => 'article', // name of current model
    'target_id' => $model->id, // id of current element
    'model_allow_guests' => true, // если не указано, будет браться по умолчанию из конфига.
]); ?>


'modules' => [
    ...
    'vote' => [
        'class' => 'chiliec\vote\Module',
        'matchingModels' => [ // matching model names with whatever integer ID
            'article' => 0, 
            'audio' => 1,
            'video' => 2,
            ...
        ],
        'allow_guests' => false, // это по умолчанию
    ],
    ...
],

и вот тогда, действительно понадобится 2 поля ).

@chiliec
Copy link
Owner

chiliec commented Jan 27, 2015

Выглядит красиво, только вот виджет предназначен для отображения и с логикой модуля никак не связан. Наверное тогда уж так:

'modules' => [
    'vote' => [
        'class' => 'chiliec\vote\Module',
        'matchingModels' => [ // matching model names with whatever integer ID
            'article' => 0,
            'audio' => 1,
            'video' => 2,
        ],
        'allow_guests' => [
            'article' => true,
            'audio' => false,
            'video' => false,
        ]
    ],
],

но это уже не так красиво.

@chiliec
Copy link
Owner

chiliec commented Jan 27, 2015

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

@loveorigami
Copy link
Author

Можно и так :)

'modules' => [
    'vote' => [
        'class' => 'chiliec\vote\Module',
        'matchingModels' => [ // matching model names with whatever integer ID
            'article' => ['id'=>1, 'allow_guests'=>true],
            'audio' => ['id'=>2], // если нет, значит false
            'video' =>  3, // доступна и такая запись... if (!is_array())
        ],
    ],
],

@chiliec
Copy link
Owner

chiliec commented Jan 27, 2015

Вот такой вариант мне больше нравится, хоть я и попробовал сделать переопределение из виджета, но это добавило много лишнего кода, к тому же сразу в трех местах...

@chiliec
Copy link
Owner

chiliec commented Jan 27, 2015

1.1 Вот не знаю как лучше индексы добавить. Основной поиск (проверка голосования) теперь идет по model_id+target_id+(user_id || user_ip).
Думаю сделать составной индекс model_id+target_id и отдельные на user_id и user_ip.
Либо можно два составных model_id+target_id+user_id и model_id+target_id+user_ip.
Либо ещё как-то...

chiliec added a commit that referenced this issue Jan 28, 2015
and update model for compatibility with new database schema
5dc82bf
chiliec added a commit that referenced this issue Jan 28, 2015
@chiliec
Copy link
Owner

chiliec commented Jan 28, 2015

2.3 Есть вариант перенести js в php и тогда можно будет динамически подставлять в js-код какие-то параметры и функции напрямую из конфига.

@loveorigami
Copy link
Author

1.1 Индексовать нужно логические пары для грядущих выборок.

  • Получаем количество голосов на запись в 1 модели - значит model_id+target_id
  • Получаем все голоса одного пользователя - user_id || user_ip

так что первый вариант мне кажется более подходящим.

@loveorigami
Copy link
Author

2.3 У меня так не срабатывало с регистрацией JS. Потому что сообщения я получал через ajax.

Делал вот так (как работает сейчас)

  • Все сообщения зваписываю во flash. Например
        if ($model->save()) {
                \Yii::$app->session->setFlash('success', Yii::t('noty', 'Recording status changed successfully'));
        } else {
                \Yii::$app->session->setFlash('error', Yii::t('noty', 'Incorrectly editing the recording status'));
        }
  • Затем, после того, как завершилось действие голосования, вызывается js функция, которая выводит эти сообщения (div id=response) и по аналогии с pjax. их я и назвал "заглушками" ).
// при каждом действии с grid - перекрываю его индиктором
$(document).on('pjax:send', function () {
    $('.grid-view').parent().loading();
})

// закончилось действие - индиктор скрываю, стартую получение сооббщений
$(document).on('pjax:complete', function () {
    $('.grid-view').parent().loading('stop');
    show_noty();
})

// отправляю запрос на контроллер, и получаю все flash-сообщения
function show_noty() {
    $.ajax({
        type: 'POST',
        dataType: 'json',
        cache: false,
        url: 'noty',
        "success": function (msg) {
            $('#response').append(msg);
        }
    });
}
  • В контроллере
    /**
     * Noty after action
     */
    public function actionNoty()
    {
       echo \common\components\widgets\Noty::widget();
    }
  • Ну и сам виджет может быть стандартным или всплыващим.
    https://github.com/kartik-v/yii2-widget-alert
    у меня после ajax через registerJs не срабатывало, поэтому я сделал черз noty

Это и можно сделать параметром API, где можно будет задать свой виджет вывода сообщений

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace common\components\widgets;

/**
 * Alert widget renders a message from session flash. All flash messages are displayed
 * in the sequence they were assigned using setFlash. You can set message as following:
 *
 * ```php
 * \Yii::$app->getSession()->setFlash('error', 'This is the message');
 * \Yii::$app->getSession()->setFlash('success', 'This is the message');
 * \Yii::$app->getSession()->setFlash('info', 'This is the message');
 * ```
 *
 * Multiple messages could be set as follows:
 *
 * ```php
 * \Yii::$app->getSession()->setFlash('error', ['Error 1', 'Error 2']);
 * ```
 *
 * @author Andey Lukyanov <loveorigami@mail.ru>
 */
class Noty extends \yii\bootstrap\Widget
{
    /**
     * @var array the alert types configuration for the flash messages.
     * This array is setup as $key => $value, where:
     * - $key is the name of the session flash variable
     * - $value is the bootstrap alert type (i.e. danger, success, info, warning)
     */
    public $alertTypes = [
        'error' => 'error',
        'danger' => 'danger',
        'success' => 'success',
        'info' => 'info',
        'warning' => 'warning'
    ];

    public function init()
    {
        parent::init();

        $session = \Yii::$app->getSession();
        $flashes = $session->getAllFlashes();

        foreach ($flashes as $type => $data) {
            if (isset($this->alertTypes[$type])) {
                $data = (array)$data;
                foreach ($data as $message) {

               $msg = '
                <script type="text/javascript">
                    noty({
                        text: "' . $message . '",
                        type: "' . $type . '",
                        layout: "topRight",
                        timeout: 1500, // delay for closing event. Set false for sticky notifications
                        force: true
                    });
                </script>';

                    echo json_encode($msg);
                }

                $session->removeFlash($type);
            }
        }
    }

}

@chiliec
Copy link
Owner

chiliec commented Jan 28, 2015

Сообщения и так через AJAX получаются. Зачем такие усложнения? Работа с сессиями, дополнительный экшен... Можно ведь просто https://github.com/Chiliec/yii2-vote/blob/master/assets/vote.js#L38 и https://github.com/Chiliec/yii2-vote/blob/master/assets/vote.js#L42 заменить на вызов функции note и получится почти то же самое, разве нет? :)

@loveorigami
Copy link
Author

Проще может быть и можно. Если каким-то образом присрособить "заглушки".
Это я выложил, как у меня работает, чтоб было видно, для чего нужны они перед голосованием и после. И с учетом того, что:
Noty - это отдельный asset,
http://ned.im/noty/#/about
и другой разработчик у себя на сайте может для всплывающих сообщений использовать другой плагин
https://github.com/kartik-v/yii2-widget-alert
http://ksylvest.github.io/jquery-growl/
http://bootstrap-growl.remabledesigns.com/ и др.
и noty ему в его проекте не нужен.

Поэтому, какая js функция, с каким виджетом и как будет срабатывать после голосования - решается (у меня) в actionNoty.
Вот сюда по умолчанию, мы можем подключить свой виджет со своим ассетом, который в случае надобности разработчик может поменять на свой в конфиге...
Если можно все это упростить - то с js-заглушками... И будет ли проще? с точки зрения подключения чего то своего...

chiliec added a commit that referenced this issue Jan 29, 2015
chiliec added a commit that referenced this issue Jan 29, 2015
chiliec added a commit that referenced this issue Jan 29, 2015
@chiliec
Copy link
Owner

chiliec commented Jan 29, 2015

Кажется всё здесь худо-бедно реализовано?

@loveorigami
Copy link
Author

в приниципе - да ). Спасибо.
Потестирую - на остальное будем делать отдельное issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants