[ExtJS] Расширяем компонент Collections
Часто ли вам приходится расширять какие-то стандартные штуки в MODX или в компонентах для него? Мне вот часто! Поэтому, давно хотелось поделиться чем-нибудь интересным на этот счёт, что я собственно и сделал в статье Дополнительные поля профиля юзера, где описал, как можно расширить профиль юзера, чтобы всё выглядело натурально. На этот раз расскажу, как можно расширять компонент Collections, не прибегая к крайним мерам, вроде правки исходников приложения.
- Устанавливаем MODX, пакет Collections.
- Создаём ТВ поле, например status, с типом Список (одиночный выбор).
Привязываем к шаблону, который будем назначать записям, находящимся внутри ресурса-коллекции.
В поле Возможные значения пишем:
Не оптимизирован==1||Оптимизирован==2||Не требует оптимизации==3
- Заходим на страницу пакета Collections в бек-энде по пути Приложения > Виды коллекции. Здесь кликаем на имеющемся по-умолчанию пункте правой мышью и жмём Копировать.
- Сейчас, пожалуй, начинается самое интересное!
Качаем заготовку для создания пакетов — modExtra, из GitHub репозитория Василия Наумкина, заливаем в корень сайта, распаковываем. Сразу переименуем название компонента с modExtra на, например, extCollections — заходим по адресу _http://вашсайт/modExtra-master/rename_it.php?name=extCollections
- Теперь нам нужно настроить заготовку под наши нужды:
- Удаляем папки и файлы:
/extCollections/assets/components/extcollections/js/mgr/sections/
/extCollections/assets/components/extcollections/js/mgr/widgets/
/extCollections/assets/components/extcollections/js/mgr/misc/
/extCollections/assets/components/extcollections/css/
/extCollections/core/components/extcollections/controllers/
/extCollections/core/components/extcollections/elements/chunks/
/extCollections/core/components/extcollections/elements/snippets/
/extCollections/core/components/extcollections/elements/templates/
/extCollections/core/components/extcollections/model/schema/
/extCollections/core/components/extcollections/model/extcollections/mysql/
/extCollections/core/components/extcollections/model/extcollections/extcollectionsitem.class.php
/extCollections/core/components/extcollections/model/extcollections/metadata.mysql.php
/extCollections/core/components/extcollections/processors/mgr/item/
/extCollections/_build/properties/
/extCollections/_build/resolvers/ - Заменяем всё содержимое файла /extCollections/_build/data/transport.chunks.php на:
<?php $BUILD_CHUNKS = array(); return array();
- Заменяем всё содержимое в файлах:
/extCollections/_build/data/transport.menu.php
на:
/extCollections/_build/data/transport.settings.php
/extCollections/_build/data/transport.snippets.php
<?php return array();
- В файле /extCollections/_build/build.config.php меняем версию на 1.0.0 (не обязательно), а также в самом низу файла опустошаем массив $BUILD_RESOLVERS:
$BUILD_RESOLVERS = array();
- В файле /extCollections/_build/build.transport.php на строках 13-15 комментируем подключение файла build.model.php:
// if (file_exists('build.model.php')) { // require_once 'build.model.php'; // }
- В файле /extCollections/_build/data/transport.plugins.php добавляем плагин на событие OnDocFormPrerender:
$tmp = array( 'extCollections' => array( 'file' => 'extcollections', 'description' => '', 'events' => array( 'OnDocFormPrerender' => array(), ) ), );
- Создаём файл плагина в /extCollections/core/components/extcollections/elements/plugins/plugin.extcollections.php с содержимым:
Как видно из этого кода, мы вначале подключаем конфиг со свойством connectorUrl равным /assets/components/extcollections/connector.php. После чего подключаем основной файл /assets/components/extcollections/js/mgr/extcollections.js, в котором создаётся и объявляется наш класс extCollections. Далее, подключаем файл /assets/components/extcollections/js/mgr/collections/ext.js, в котором у нас и будет происходить вся основная логика по расширению функционала Collections.<?php if ($modx->event->name != 'OnDocFormPrerender' || $modx->context->key != 'mgr' || !is_object($resource)) { return; } $core_path = $modx->getOption('core_path') . 'components/extcollections/'; $assets_url = $modx->getOption('assets_url') . 'components/extcollections/'; $connector_url = $assets_url . 'connector.php'; $modx->controller->addHtml("<script type=\"text/javascript\"> extCollectionsConfig = { connectorUrl: \"{$connector_url}\", }; </script>"); $modx->controller->addJavascript($assets_url .'js/mgr/extcollections.js'); $modx->controller->addLastJavascript($assets_url .'js/mgr/collections/ext.js');
- Создаём файл /extCollections/assets/components/extcollections/js/mgr/collections/ext.js и добавляем в него такое содержимое:
В этом куске кода мы, на событие beforerender гриды #collections-grid-container-collections, повесили добавление своих пунктов меню в выпадающую кнопку «Массовые действия».// Добавляем свои элементы в меню "Массовые действия" Ext.ComponentMgr.onAvailable('collections-grid-container-collections', function () { this.on('beforerender', function () { // Распечатываем в консоль объект this, чтобы найти в дереве свойств расположение меню // console.log('this', this); // Благодаря коду выше мы узнали, что кнопка "Массовые действия" // находится по адресу this.topToolbar.items.items[0].items.items[1] // Присваиваем её переменной, чтобы было проще с работать var btn = this.topToolbar.items.items[0].items.items[1]; // Добавляем разделитель под оригинальными пунктами пакета Collections btn.menu.addMenuItem({ xtype: 'menuseparator', }); // Добавляем наши кастомные пункты меню btn.menu.addMenuItem({ text: 'Изменить статус на "Не оптимизирован"', handler: extCollections.utils.setTVValue, scope: this, tvName: 'status', tvValue: 1, }); btn.menu.addMenuItem({ text: 'Изменить статус на "Оптимизирован"', handler: extCollections.utils.setTVValue, scope: this, tvName: 'status', tvValue: 2, }); btn.menu.addMenuItem({ text: 'Изменить статус на "Не требует оптимизации"', handler: extCollections.utils.setTVValue, scope: this, tvName: 'status', tvValue: 3, }); }); });
В этом же файле пишем обработчик действия по кнопке:
Думаю, тут дополнительные комментарии будут излишни — хватает комментариев в коде.extCollections.utils.setTVValue = function (btn, e) { // В объекте btn хранятся свойства, которые мы прописали при добавлении элемента в меню. // Отсюда, мы получаем название ТВ поля и значение, которое необходимо установить. var tv_name = btn['tvName']; var tv_value = btn['tvValue']; if (typeof(tv_value) == 'undefined') { return; } // Объект this хранит в себе информацию о гриде, т.к. при добавлении элемента в меню, мы прописали свойство scope: this. // Отсюда, мы получаем список id у выделенных элементов гриды методом getSelectedAsList(). var ids = this.getSelectedAsList(); if (ids === false) { return false; } // Отправляем Ajax запрос по адресу /assets/components/extcollections/connector.php, в этом файле произойдёт вызов процессора /core/components/extcollections/processors/mgr/resource/settvvalue.class.php MODx.Ajax.request({ url: extCollectionsConfig['connectorUrl'], params: { action: 'mgr/resource/settvvalue', ids: ids, name: tv_name, value: tv_value, }, listeners: { 'success': {fn: function() { this.getSelectionModel().clearSelections(true); this.refresh(); }, scope:this } } }); return true; };
И, наконец, добавим функцию рендера, которую позже пропишем в настройки вида коллекции:
extCollections.utils.renderTVStatus = function(value) { var color = 'white'; if (value == 1) { color = 'red'; } else if (value == 2) { color = 'green'; } else if (value == 3) { color = 'grey'; } return '<div style="width: 12px; height: 12px; line-height: 0; background: ' + color + '; border-radius: 50%;"> </div>'; };
- Создаём файл процессора /core/components/extcollections/processors/mgr/resource/settvvalue.class.php:
<?php class extCollectionsResourceSetTvValueProcessor extends modObjectProcessor { public $objectType = 'modResource'; public $classKey = 'modResource'; public $languageTopics = array('extcollections'); /** * @return array|string */ public function process() { if (!$this->checkPermissions()) { return $this->failure($this->modx->lexicon('access_denied')); } $name = $this->getProperty('name'); if (empty($name)) { return $this->failure('error'); } $ids = array_map('trim', explode(',', $this->getProperty('ids'))); if (empty($ids)) { return $this->failure('error'); } foreach ($ids as $id) { /** @var modResource $obj */ if (!$obj = $this->modx->getObject($this->classKey, $id)) { return $this->failure('error'); } $obj->setTVValue($name, $this->getProperty('value')); } return $this->success(); } } return 'extCollectionsResourceSetTvValueProcessor';
- Удаляем папки и файлы:
- Идём в Приложения > Виды коллекции, кликаем правой мышью на недавно созданный нами вид коллекции > Изменить, крутим в самый низ > Добавить столбец:
Заголовок: Статус
Название: tv_status
Ширина: 40
Рендерер: extCollections.utils.renderTVStatus
- Назначаем ресурсу-коллекции этот вид, чтобы увидеть новый столбец.
- Билдим пакет — _http://вашсайт/extCollections/_build/build.transport.php
Поблагодарить автора
Отправить деньги
Комментарии: 19
Спасибо за подробный мануал. На заметку:
$obj->setTVValue($name, $this->getProperty('value'));
$obj->save();
save() нет необходимости делать, будет работать и так:$obj->setTVValue($name, $this->getProperty('value'));
Ой не соглашусь, совсем не соглашусь. $obj->save(); обязательно к использованию, да бывает конечно все сохраняется, но для подстраховки все таки лучше использовать, потому что было у меня пару раз ситуация когда тв просто отказывались сохранятся, особенно если идут прямо после
$obj->set();
Можно глянуть в документацию.
> было у меня пару раз ситуация когда тв просто отказывались сохранятся
Наверное, потому что ресурс еще не создан. Т.е. у него еще нету ID, поэтому и setTVValue не может в БД записать значение. В исходном примере такая ситуация не возможна.
Use setTVValue to save a new value to a TV. Unlike some other xPDO API methods, this method stores values to the database immediately, so you do not need to invoke a separate call to a save() method. This method does not clear the resource cache.
> было у меня пару раз ситуация когда тв просто отказывались сохранятся
Наверное, потому что ресурс еще не создан. Т.е. у него еще нету ID, поэтому и setTVValue не может в БД записать значение. В исходном примере такая ситуация не возможна.
Так и есть. Но это не отменяет того факта что save лучше использовать.
Отменяет.
Вот вы зануды.
Лучше глянуть в код, там всё понятно. ТВ это другой объект.
Каеф
Позволю себе побрюзжать немного :)
1. Людям не знакомым с Collections (как я например) из описания совершенно не понятно, что и для чего. Нужно лезть в инструкцию, чтобы понять, что добавляются пункты контекстного меню.
2. Ну и зачем всё это писать, если можно выложить на гитхаб уже готовое решение с очень маленькой инструкцией по кастомизации — где и что изменить в заготовке под себя. А в такой простыне кода даже мне страшно разбираться.
Это два, как мне кажется, упущения.
Ну а по мелочи… Думаю, такие вещи как
Согласен, коллега?
1. Людям не знакомым с Collections (как я например) из описания совершенно не понятно, что и для чего. Нужно лезть в инструкцию, чтобы понять, что добавляются пункты контекстного меню.
2. Ну и зачем всё это писать, если можно выложить на гитхаб уже готовое решение с очень маленькой инструкцией по кастомизации — где и что изменить в заготовке под себя. А в такой простыне кода даже мне страшно разбираться.
Это два, как мне кажется, упущения.
Ну а по мелочи… Думаю, такие вещи как
this.topToolbar.items.items[0].items.items[1];
иvar color = 'white';
if (value == 1) {
color = 'red';
} else if (value == 2) {
color = 'green';
} else if (value == 3) {
color = 'grey';
}
иif (!$this->checkPermissions()) {
return $this->failure($this->modx->lexicon('access_denied'));
}
со временем трансформируются во что-то более совершенное. :)Согласен, коллега?
Странно, вопросы как будто к автору поста, а направлены Илье. Сергей, кому вопросы то и кто этот загадочный «коллега»?)
Каждый человек — загадка, мир, вселенная. В философском смысле. А в профессиональном Илья на высокой ступени иерархии. Тут нет никакой загадки. Для меня по крайней мере.
А всё остальное — просто брюзжание немолодого человека юных лет про то, что мне не хватает для кайфа. )
кому вопросы тоКакие вопросы? Согласен, коллега? Это риторический вопрос, не требующий ответа. ;)
А всё остальное — просто брюзжание немолодого человека юных лет про то, что мне не хватает для кайфа. )
Ясно…
1. Людям не знакомым с Collections (как я например) из описания совершенно не понятно, что и для чего. Нужно лезть в инструкцию, чтобы понять, что добавляются пункты контекстного меню.Людям, не знакомым с Collections, стоит ознакомиться с ним, раз уж заинтересовались данной темой.
2. Ну и зачем всё это писать, если можно выложить на гитхаб уже готовое решение с очень маленькой инструкцией по кастомизации — где и что изменить в заготовке под себя.Так это не готовое решение, а пример того, как можно расширить компонент Collections, не затрагивая его исходников.
А в такой простыне кода даже мне страшно разбираться.Сергей, если страшно, то не надо. :)
такие вещи как this.topToolbar.items.items[0].items.items[1]; со временем трансформируются во что-то более совершенное.Представь, как твой комментарий приобрёл бы ценность, добавив ты туда пример совершенного кода?
Признаюсь, я ошибался. Видишь как люди плюсуют твои комментарии. Значит им удобнее твой вариант — пройти все 8 шагов инструкции. А я то по старинке всё готовые дополнения делаю. )
П.С. А если серьёзно, то код-то как раз пользователя не волнует. Единицы смотрят под капот. А вот удобство настройки, мне кажется, дело первоочередное. UX forever!
П.П.С. Collections у меня нет, скажу по теме пользователей. Вот такая конструкция
Представь, как твой комментарий приобрёл бы ценность, добавив ты туда пример совершенного кода?Представь, как мне фиолетово. Судя по комментарию это мне у тебя ещё учится и учится.
П.С. А если серьёзно, то код-то как раз пользователя не волнует. Единицы смотрят под капот. А вот удобство настройки, мне кажется, дело первоочередное. UX forever!
П.П.С. Collections у меня нет, скажу по теме пользователей. Вот такая конструкция
var leftCol = this.items.items[0].items.items[0].items.items[0];
легко заменяется на такуюExt.getCmp('modx-user-active').ownerCt
И в итоге чекбокс добавить можно так:$modx->controller->addHtml("
<script type='text/javascript'>
Ext.ComponentMgr.onAvailable('modx-user-tabs', function() {
this.on('beforerender', function() {
var cb = Ext.create({
xtype: 'xcheckbox',
boxLabel: 'Тестовый чекбокс',
description: 'Тестовый чекбокс',
name: 'testCheckbox',
checked: true
});
Ext.getCmp('modx-user-active').ownerCt.insert(0, cb);
});
});
</script>
");
Collections у меня нет, скажу по теме пользователей. Вот такая конструкцияСергей, исключительно ради интереса, я тебе создам тестовый сайт и загружу туда Collections, а ты мне покажешь, как имея объект гриды и выходя вверх, можно получить объект кнопки, которая находится в верхнем тулбаре этой гриды, фактически на несколько уровней ниже. Согласен?
var leftCol = this.items.items[0].items.items[0].items.items[0]; легко превращается в такую
Ext.getCmp('modx-user-active').ownerCt
getCmp не прокатит, т.к. кнопки анонимные там
extCollections.utils.renderTVStatus = function(value = 0) {
return '<div style="width: 12px; height: 12px; line-height: 0; background: ' + ['white', 'red', 'green', 'grey'][value] + '; border-radius: 50%;"> </div>';
};
:)
Круто! И нужная вещь, спасибо!
Оффтоп. Еще не разобрался, но плюсанул заранее, пока время голосования не истекло. Все таки жалко, нахожу постоянно старые полезные посты, а автора даже уже не лайкнуть. Кому жаловаться?
Сам знаешь.
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.