[ExtJS] Расширяем нативную гриду юзеров


После статьи о расширении профиля юзера правильными дополнительными полями мне посыпались вопросы о расширении нативной таблицы со списком юзеров. Мы знаем, что практически любой стандартный компонент системы, работающий на ExtJS, можно расширить не затрагивая исходника. Главное уметь пользоваться «методом тыка» понимать принцип того, что делаешь. Что-ж… давайте расширять нативную таблицу пользователей!
Сразу опишем задачу, которую реализуем в рамках статьи:

  1. Убрать слева каждой записи ненужный чекбокс,
  2. Добавить столбцы: Фото, Дата рождения, Страна, Город,
  3. Добавить возможность отфильтровать пользователей по стране,
  4. Заменить некрасивое поле поиска на симпатичное и компактное,
  5. Подсветить заблокированных красным цветом.

Почему не компонентом?


В виде компонента такое оформить было бы правильно, как я это делал в статье о Расширении компонента Collections, однако я не раз замечаю барьер у многих, при попытке запаковки какого-то функционала в пакет. Поэтому обойдемся более простым методом. А те, кому надо запаковать в пакет — разберутся без труда.

CSS


/assets/xusergrid/css/main.css

Стили комментировать не имеет смысла.

JavaScript


/assets/xusergrid/js/xusergrid.js

Здесь происходит объявление и инициализация основного класса.

/assets/xusergrid/js/combo.js

Содержит поле поиска и поле выбора страны. Необходимо для фильтра.

/assets/xusergrid/js/extends/modx.grid.user.js

Основной файл, расширяющий таблицу пользователей.
Я постарался указать комментарии в коде, чтобы он стал более понятным.

PHP


/assets/xusergrid/connector.php

Кастомный коннектор, ссылающийся на нашу папку процессоров.

/assets/xusergrid/processors/combo/getuserfield.class.php

Процессор, возвращающий в комбобокс, список используемых значений любого поля юзера.

/assets/xusergrid/processors/security/user/getlist.class.php

Процессор, возвращающий список пользователей. Расширяет стандартный класс modUserGetListProcessor, добавляя в него:
— фильтрацию по стране,
— дополнительные поля в выборку,
— обработку сырых данных даты рождения, страны.

/assets/xusergrid/processors/security/user/updatefromgrid.class.php

Процессор, обновляющий данные юзера при «быстром» редактировании прямо из таблицы. Удаляется ключ country для того, чтобы не перезаписывать в таблицу обработанное (переведенное) значение.

Остальные процессоры необходимы, потому что в стандартных методах объекта MODx.grid.User они вызываются непосредственно через коннектор, прописанный в свойстве основного объекта гриды. А т.к. мы заменили стандартный коннектор на свой, то и процессоры должны продублировать.
Это также можно решить, переписав немного методы activateSelected, deactivateSelected, removeSelected, removeUser и duplicateUser, а точнее, заменив this.config.url на MODx.config.connector_url. Но я считаю, что лучше расширить процессоры, к которым обращается JS, не трогая «ни грамма» исходников при этом.

/assets/xusergrid/processors/security/user/activatemultiple.class.php

/assets/xusergrid/processors/security/user/deactivatemultiple.class.php

/assets/xusergrid/processors/security/user/delete.class.php

/assets/xusergrid/processors/security/user/duplicate.class.php

/assets/xusergrid/processors/security/user/removemultiple.class.php


Плагин


Пожалуй, это самое основное, ибо без плагина на событие OnManagerPageBeforeRender про наш код MODX даже и не узнает.
$sp = &$scriptProperties;
/** @var modManagerController $controller */
$controller = $sp['controller'];
switch ($modx->event->name) {
    case "OnManagerPageBeforeRender":
        if (is_object($controller)) {
            $config = array(
                'connector_url' => $modx->getOption('assets_url') . 'xusergrid/connector.php',
            );
            $action = $controller->scriptProperties['a'];
            if ($action == 'security/user') {
                $controller->addCss($modx->getOption('assets_url') . 'xusergrid/css/main.css');
                $controller->addJavascript($modx->getOption('assets_url') . 'xusergrid/js/xusergrid.js');
                $controller->addHtml("<script type='text/javascript'>
                    xUserGrid['config'] = {$modx->toJSON($config)};
                </script>");
                $controller->addLastJavascript($modx->getOption('assets_url') . 'xusergrid/js/combo.js');
                $controller->addLastJavascript($modx->getOption('assets_url') . 'xusergrid/js/extends/modx.grid.user.js');
            }
        }
        break;
}

Рассмотрим подробнее


Вот здесь мы расширяем наш объект xUserGrid.grid.modxUserExt, добавляя в него методы и свойства из зарегистрированного компонента modx-grid-user, который хранится в Ext.ComponentMgr.types['modx-grid-user'], параллельно добавляя новые методы (там же можно заменить и существующие). Мы можем расширять наш объект и стандартным объектом гриды юзеров MODx.grid.User, но в этом случае, если до нашего кода уже внедряются какие-то изменения в гриду, то они будут утрачены.
А здесь мы регистрируем xUserGrid.grid.modxUserExt в качестве компонента modx-grid-user, фактически переопределяя зарегистрированный ранее компонент на это имя в коде системы.
Для того и используется метод addLastJavascript в плагине, дабы быть уверенным, что подключение наших файлов будет уже после подключения системного modx.grid.user.js.

В итоге


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

P.S.


Благодарность Николаю Ланецу за подсказку о более правильном расширении, с использованием зарегистрированного компонента, хранящегося в Ext.ComponentMgr.types['modx-grid-user'].
Павел Гвоздь
07 июля 2017, 19:14
modx.pro
42
4 776
+21
Поблагодарить автора Отправить деньги

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

mngatoff
07 июля 2017, 22:17
+3
спасибо, Паша)
а то я уже в плагин джаваскрипта насовал. Грид расширил, но с доп. фильтрами так и не разобрался. Так что статья кстати
    Александр Туниеков
    08 июля 2017, 19:08
    0
    Добрый день, Павел! Я вот так и не понял где и как происходит замена грида юзеров. В предыдущем примере понятно
    Ext.ComponentMgr.onAvailable('modx-user-tabs', function() {
    находиться компонент и в него добавляется нужный функционал. А здесь как? Поясните пожалуйста.
    Fi1osof
    09 июля 2017, 13:18
    -6
    Главное уметь пользоваться «методом тыка» понимать принцип того, что делаешь.
    Ну и не забывать смотреть чужие примеры. Можно было вскольз и упомянуть.
      Павел Гвоздь
      09 июля 2017, 17:46
      +2
      Упомянуть о чем?)
        Fi1osof
        09 июля 2017, 19:52
        -6
        Что-то типа «вот увидел такой-то прием и решил его развить». Или хочешь сказать, что сам допер до такого метода расширения объекта и нигде ранее не видел?
          Павел Гвоздь
          09 июля 2017, 19:58
          +1
          Комментарий, на который ты ссылаешься датируется 30 декабря 2016 года. Ровно 10 дней назад я опубликовал на GitHub проект, в котором использовал такой прием, до которого допер, да, сам… следовательно, если развивать твою логику, то это не я подглядел у тебя, а ты у меня?)) Однако, я не претендую на это, ибо ничего тут сверхъестественного не вижу…
            Fi1osof
            09 июля 2017, 20:09
            +5
            Да, действительно не так сделал. Сорян. Но лучше бы сделал так… Уточняю минус в чем: при таком подходе ты переписываешь чистый MODX-овый исходник. А если на него еще что-то навешено сторонним плагином (или не одним). Ты тогда просто перетираешь эти изменения. Собственно, по этой причине я и искал решение, которое по возможности не перетирало бы сторонние изменения. Вот листинг:
            var _prototype = Ext.ComponentMgr.types['modx-grid-user'];
            
            UserKarmaGrid = function(config) {
                this.sm = new Ext.grid.CheckboxSelectionModel();
                Ext.applyIf(config,{
                    fields: ['id','username','fullname','email','gender','blocked','role','active','cls', 'userkarma']
                    ,sm: this.sm
                    ,columns: [this.sm,{
                        header: _('id')
                        ,dataIndex: 'id'
                        ,width: 50
                        ,sortable: true
                    },{
                        header: _('name')
                        ,dataIndex: 'username'
                        ,width: 150
                        ,sortable: true
                        ,renderer: function(value, p, record){
                            return String.format('<a href="?a=security/user/update&id={0}" title="{1}" class="x-grid-link">{2}</a>', record.id, _('user_update'), Ext.util.Format.htmlEncode( value ) );
                        }
                    },{
                        header: _('user_full_name')
                        ,dataIndex: 'fullname'
                        ,width: 180
                        ,sortable: true
                        ,editor: { xtype: 'textfield' }
                        ,renderer: Ext.util.Format.htmlEncode
                    },{
                        header: _('email')
                        ,dataIndex: 'email'
                        ,width: 180
                        ,sortable: true
                        ,editor: { xtype: 'textfield' }
                    },{
                        header: _('active')
                        ,dataIndex: 'active'
                        ,width: 80
                        ,sortable: true
                        ,editor: { xtype: 'combo-boolean', renderer: 'boolean' }
                    },{
                        header: _('user_block')
                        ,dataIndex: 'blocked'
                        ,width: 80
                        ,sortable: true
                        ,editor: { xtype: 'combo-boolean', renderer: 'boolean' }
                    },{
                        header: 'Карма'
                        ,dataIndex: 'userkarma'
                        ,sortable: true
                        ,editor: { xtype: 'numberfield'}
                    }]
                });
                UserKarmaGrid.superclass.constructor.call(this,config);
            };
            Ext.extend(UserKarmaGrid,_prototype,{});
            Ext.reg('modx-grid-user',UserKarmaGrid);
            Вот такой с таким подходом одновременно на странице уживаются и userKarma, и switchUser.
            Попробуй своим способом дописать еще один чуть измененный плагин. Смогут они вместе сосуществовать на одной странице?
              Павел Гвоздь
              10 июля 2017, 08:04
              +3
              Верная мысль, Николай! В твоем случае мы берем уже зарегистрированный компонент со всеми изменениями (если таковые были) и меняем его по своему. А в моем варианте я беру напрямую объект MODx.grid.User, следовательно и изменений (если таковые были, опять же) в него не записано. Спасибо за идею! Вот теперь упомяну. ;)
                Fi1osof
                10 июля 2017, 12:03
                +2
                Пожалуйста.
      mngatoff
      12 июля 2017, 20:11
      0
      а вот такой вот вопрос нарисовался, немного по теме прошлого топика больше:
      как бы так грамотно на странице юзера вывести превью его аватарки? по типу image tv
      Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
      15