[customExtra] Создание кастомных объектов

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

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

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

До тех пор, пока мне не понадобилось иметь два таких кастомных объекта...

Поэтому сегодня я представляю вашему вниманию обновлённый customExtra.

Описание



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

Основные возможности



Все параметры компонента доступны в Настройках системы.

Первая настройка — Отображать табы. В этой настройке можно указать, какие табы вам необходимы. Всего в компоненте доступно 5 табов, различающихся только называниями: Предметы, Заказы, Операции, Медиа и Ссылки.

Чтобы отобразить все доступные табы, необходимо указать следующий список:
item,order,operation,media,link

Табы будут выведены в админке в том порядке, в котором они следуют в списке. Поэтому вы можете менять расположение табов, просто поменяв порядок в этой настройке.

Каждый таб — это отдельная табличка со своими настройками. У каждого объекта (предмета, заказа и т.д.) есть ID, название, описание и флаг активности (active).

Кроме этих полей в настройках системы можно указать, сколько и каких полей вам нужно добавить — для каждого таба в отдельности. Например, мы можем добавить Заказам два числовых поля и одну текстовую область, а для Ссылок вывести только одно текстовое поле.

Любой объект можно создать с помощью сниппета или плагина — где вам это необходимо:
<?php
$modx->addPackage('customextra', $modx->getOption('core_path').'components/customextra/model/');
$request = $modx->newObject('customExtraOperation');
$request->set('name', $_POST['name']);
$request->set('string1', $_POST['contact']);
$request->set('description', $_POST['text']);
$request->save();
return true;

и созданный объект сразу же появится в табличке. Таким же образом объекты можно и редактировать.

Выводятся объекты с помощью pdoTools
[[pdoResources?
  &class=`customExtraMedia`
  &loadModels=`customextra`
  &sortby=`id`
  &tpl=`tpl.media`
]]

Дополнительный функционал



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

Список чекбоксов: active,published,paid,new,hit,favorite

Два из них по умолчанию отмечены галочкой — Активный (active) и Опубликованный (published).

При выводе на фронтенд объекты можно фильтровать по этим чекбоксам (впрочем, как и по любым другим полям):
[[pdoResources?
  &class=`customExtraItem`
  &loadModels=`customextra`
  &sortby=`id`
  &where=`{"active":1}`
  &tpl=`tpl.items`
]]

Массовые операции — продвинутое использование



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

При нажатии на кнопку сниппет будет запущен. Если вам необходимо обработать в этом сниппете множество объектов, необходимо разделять обработку на порции. Для этого сниппет должен «сообщить» компоненту о том, что обработка не завершена. Если сниппет вернет false, компонент запустит его еще раз — и так до тех пор, пока не будет получен ответ true.

Например, мы хотим убрать у всех заказов галочку «Новый». Наш сниппет будет выглядеть так:
<?php
$modx->addPackage('customextra', $modx->getOption('core_path').'components/customextra/model/');
// Укажем, по сколько объектов обрабатывать за раз
$step = 5;
// Формируем запрос
$q = $modx->newQuery('customExtraOrder', array('new' => 1));
// Посчитаем, сколько всего объектов с отмеченной галочкой в базе
$count = $modx->getCount('customExtraOrder', $q);
// Если «Новых» объектов не найдено, останавливаем выполнение
if ($count == 0) return true;
// Установим лимит
$q->limit($step);
// И получим порцию объектов для обработки
$orders = $modx->getCollection('customExtraOrder', $q);
// Теперь можно обработать полученные объекты
foreach ($orders as $order) {
  $order->set('new', 0);
  $order->save();
}
// Если всего в базе объектов меньше, чем мы обрабатываем за один шаг
if ($count <= $step) {
  // то мы останавливаем обработку
  return true;
} else {
  // если нет - указываем, что обработка не закончена
  return false;
}

Теперь компонент послушно выполнит сниппет столько раз, сколько надо — и выполнение не оборвётся по таймауту.

Пример использования


Конечно, без примера сложно представить себе функционал, поэтому давайте попробуем представить, что в ТЗ на разработку сайта имеется что-то типа такого:

4.3. Размещение пользователями контента
Необходимо реализовать форму добавления контента. Пользователь должен получить возможность добавлять в общую ленту сущность «Цитата». Добавленная цитата должна быть неопубликованной до тех пор, пока менеджер не одобрит её в админке. После одобрения пользователю отправляется уведомление, а цитата появляется в ленте.

Давайте, реализуем такой функционал.

Устанавливаем customExtra, и в настройках проставляем нули у дополнительных полей (нам достаточно только name и description), а из галочек оставляем только active и new




Ну и в лексиконах заменим слово «Предмет» на «Цитата»

Табличка теперь выглядит подходящим образом




Выведем на сайте форму добавления цитаты:
[[!AjaxForm?
  &form=`form.addQuote`
  &hooks=`saveQuote`
  &validate=`quote:required`
]]

<form action="" method="post" class="ajax_form af_example form-horizontal">
  <div class="control-group">
    <label class="control-label" for="af_quote">[[%af_label_message]]</label>
    <div class="controls">
        <textarea id="af_quote" name="quote" class="span4" rows="5">[[+fi.quote]]</textarea>
        <span class="error_quote">[[+fi.error.message]]</span>
    </div>
  </div>
  <div class="control-group">
    <div class="controls">
      <button type="reset" class="btn btn-default">[[%af_reset]]</button>
      <button type="submit" class="btn btn-primary">[[%af_submit]]</button>
    </div>
  </div>
</form>

И создадим сниппет saveQuote — который будет сохранять цитату
<?php
$modx->addPackage('customextra', $modx->getOption('core_path').'components/customextra/model/');
$quote = $modx->newObject('customExtraItem');
$quote->set('description', strip_tags($_POST['quote']));
$quote->save();
$quote->set('name', 'Цитата № '.$quote->id);
$quote->save();
return true;

Кстати, можно сразу сохранить id пользователя и время создания цитаты:
$quote->set('id1', $modx->user->id);
$quote->set('id2', time());

Кроме того, новые цитаты надо помечать как неактивные (чтобы менеджер их публиковал) и новые (чтобы высылать уведомления)
$quote->set('active', 0);
$quote->set('new', 1);

Можно пробовать добавлять цитаты. Они сразу появятся в табличке:




Чтобы отправлять уведомления, будем использовать дополнительную кнопку. Для этого создаём сниппет sendNotifications и указываем его в системной настройке.
<?php
$modx->addPackage('customextra', $modx->getOption('core_path').'components/customextra/model/');
$step = 1;
// Нам нужны новые опубликованные цитаты
$q = $modx->newQuery('customExtraItem', array('active' => 1, 'new' => 1));
$count = $modx->getCount('customExtraItem', $q);
if ($count == 0) return true;
$q->limit($step);
$quotes = $modx->getCollection('customExtraItem', $q);
foreach ($quotes as $quote) {
  // Реализацию отправки письма вынесем в отдельный сниппет
  $modx->runSnippet('sendEmail', array(
        'toUser' => $quote->id1,
        'quote' => $quote->description
      ));
  // Убираем галочку "Новая" - чтобы не отправить повторно
  $quote->set('new', 0);
  $quote->save();
}
if ($count <= $step) {
  return true;
} else {
  return false;
}

Еще можно поменять надпись на кнопке:




Когда будем выводить цитаты на сайт, главное не забыть заменить все квадратные и фигурные скобки в цитате на соответствующие HTML-коды

Теперь можно показывать заказчику и считать, что функционал реализован.

Где скачать


Первая версия компонента была доступна в репозитории MODX.

Но сейчас функционал стал шире и теперь новая, расширенная и улучшенная версия, доступна в магазине modstore.pro на платной основе.

customExtra 2.0.2-beta
Илья Уткин
22 декабря 2015, 01:21
modx.pro
19
7 334
+16

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

Андрей Сухомозгий
22 декабря 2015, 09:09
+1
Илья, это просто замечательное дополнение. Но есть один вопрос — можно ли все таки создавать одновременно несколько таких объектов...? Допустим, это «Цитаты» и «Цитаты2»
Вопрос в связи с этим:
До тех пор, пока мне не понадобилось иметь два таких кастомных объекта...
    Илья Уткин
    22 декабря 2015, 10:00
    0
    Да, можно создавать до пяти объектов:

      Андрей Сухомозгий
      22 декабря 2015, 10:05
      0
      видел эту картинку, но чет подумал, что «разные объекты» будут как бы на разных страницах, а эти табы — для связки двух объектов в рамках одного компонента.

      можно же в общем-то, переименовав компонент, поставить его еще раз)
    Илья Уткин
    22 декабря 2015, 10:00
    0
    Промахнулся, сорри
      Василий Столейков
      22 декабря 2015, 10:08
      0
      Только вчера вечером вчитывался в твою прошлую статью о первой версии. Спасибо за наглядные примеры!
      Ждём новую версию в магазине!
      Павел Гвоздь
      22 декабря 2015, 23:36
      0
      Будет ли возможность выставлять полям разный рендер для грида? Ну например полю user_id выставить render такой, шоб вместо ИДа в гриде отображался его username/fullname со ссылкой на редактирование профиля?
        Илья Уткин
        23 декабря 2015, 10:01
        0
        Возможно, что-то такое и будет, но я пока не знаю, как это всё должно настраиваться…
        Сейчас можно, например, внести изменения в процессор getlist — добавить в него присоединение таблицы пользователей и заменить значение нужного поля на требуемый HTML
          Илья Уткин
          23 декабря 2015, 10:06
          0
          Жаль, что файл может перезаписаться при обновлении — придется всегда держать где-нибудь копию.
          Может, добавить в настройки возможность выбрать класс для каждого процессора — чтобы его можно было расширить…
        Fedor Gorozhanko
        23 декабря 2015, 01:19
        0
        Обычно использую расширение Collections и настраиваю шаблоны через редактор форм.
        В чем особое преимущество этого решения?
          Илья Уткин
          23 декабря 2015, 10:04
          0
          Ни в чем. customExtra другую задачу решает.
          Рустам С
          23 декабря 2015, 07:54
          0
          а можно сделать поле с выпадающим списком? и заполнять список из ресурсов, например у меня должен быть список авторов
            Илья Уткин
            23 декабря 2015, 10:03
            0
            Поля в формах никак не настраиваются… Единственное — можно внести изменения в JS-файл, отвечающий за нужное окно, поменяв xtype у поля.

            Но придется всегда помнить об этом и восстанавливать эти изменения, если пакет придется обновлять.
            Tanya
            23 декабря 2015, 12:36
            0
            О! появилась дополнительная кнопка! (а она может быть только одна?) Я смотрю теперь компонент стал платным, в нем что то изменилось с прошлой бесплатной? Вот бы ещё была возможность делать много разных кастомных объектов с одним компонентом!
              Илья Уткин
              23 декабря 2015, 12:41
              0
              Да, в том числе и из-за функционала этой кнопки — она позволяет выполнять массовые операции не боясь, что процесс оборвется из-за таймаута. Плюс теперь можно использовать до пяти типов объектов — у каждого свои настройки.

              Я его вообще сразу хотел делать платным, но первая версия получилось уж очень незаконченной.
                Илья Уткин
                23 декабря 2015, 12:55
                +3
                Мы тут подумали и решили снизить цену до 390 руб. Все таки функционал не совсем коммерческий…
                  Tanya
                  23 декабря 2015, 14:41
                  0
                  Здорово!
                  Плюс теперь можно использовать до пяти типов объектов
                  а сделаете пример, чтобы не запутаться?
                    Илья Уткин
                    23 декабря 2015, 15:10
                    0
                    В настройках теперь для каждого объекта свой подраздел и каждому можно указывать сколько каких полей показывать.
                    Только и всего.

                    Объекты называются так:
                    customExtraItem
                    customExtraOrder
                    customExtraOperation
                    customExtraMedia
                    customExtraLink
                      Tanya
                      23 декабря 2015, 19:15
                      0
                      а можно вопрос о покупке, если я куплю и поставлю на локалке потестировать, то потом как я перенесу его на хостинг? Снова покупать надо? Я не очень представляю как в магазине это устроено.
                        Воеводский Михаил
                        23 декабря 2015, 19:52
                        +1
                        Один раз самостоятельно можно изменить хост, к которому привязана покупка.
                  Илья Уткин
                  23 декабря 2015, 20:40
                  0
                  Не заметил вопрос про кнопку)

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

                  Только если будет обновление, она опять появится.
                  Леви Ким
                  23 декабря 2015, 18:09
                  0
                  я правильно понимаю что «area_customextra_items» в лексиконе отвечает за название «items» в табах? В лексиконе поменял название, но в табе ни чего не произошло.
                  И еще заметил, что на скришотелексикон называется «area_customextra2_items» а у меня просто area_customextra_items. хотя вроде только щас закачал с репозитория…
                    Илья Уткин
                    23 декабря 2015, 18:25
                    0
                    area — это название раздела настроек

                    В лексиконе можно ввести, например, item в поле поиска — будет удобнее искать нужную запись
                      Илья Уткин
                      23 декабря 2015, 18:28
                      0
                      За название вкладки отвечает запись customextra_items

                      А 2 это у меня в разработке было.
                    Андрей Копп
                    30 декабря 2015, 23:21
                    0
                    Было бы здорово ещё пакеты при нажатии на кнопку собирать как тут modstore.pro/packages/utilities/cmpgeneratorpro ) Вроде всё очень круто, но табы автоматом не создаются, а при добавлении руками, кнопки начинают туда сюда прыгать(((
                      Zazh
                      12 июля 2016, 00:59
                      0
                      Здравствуйте, есть ли видеоуроки для чайников? И возможно ли записи привязать к тв полям?
                        Илья Уткин
                        12 июля 2016, 10:19
                        2
                        +1
                        Видео-уроков нет. А что значит «привязать к тв-полям»? ТВ-поля использовать вряд ли получится, но интерфейс можно переделать, отредактировав JS-файлы. Вот пара советов:
                          Zazh
                          12 июля 2016, 11:20
                          0
                          Например я делаю словарь, в нем есть фильтр по буквам, возможно ли это реализовать на customExtra?
                            Илья Уткин
                            12 июля 2016, 11:24
                            1
                            +1
                            Это можно реализовать при выводе с помощью pdoResources. Вот, например, как вывести все объекты на S
                            [[pdoResources?
                              &class=`customExtraItem`
                              &loadModels=`customextra`
                              &sortby=`name`
                              &where=`{"name:LIKE":"s%"}`
                              &tpl=`@INLINE <p>{$name} — {$description}</p>`
                            ]]
                            Bluetenstadt
                            12 июля 2016, 15:12
                            0
                            хотелось бы знать как Добавляем загрузку файла из фронтенда.
                        Александр
                        20 января 2017, 22:58
                        +1
                        Здравствуйте.
                        Создал вкладки «Дата» и «Время» (помимо остальных табов), всё работает. Но есть одна проблема: невозможно правильно сортировать объекты по значениям в этой вкладке, так как значения сравниваются как строки.


                        Добавьте, пожалуйста, вкладку «Дата», в которой будет указана дата и время создания объекта. Ну и чтобы сортировка была правильная. Если Вы не планируете вносить такие изменения в компонент, то не могли бы подсказать, что и в каком файле нужно править?
                          Илья Уткин
                          23 января 2017, 10:23
                          +2
                          Готового решения у меня нет. Но смысл такой: дату нужно хранить в одном из числовых полей. Для удобства заполнения нужно в форме поменять xtype у нужного поля, а в табличке (с помощью процессора getlist.class.php) выводить дату в форматированном виде. Вот статья как связывать объекты — там есть и подстановка xtype, и работа с процессором.
                            Александр
                            24 января 2017, 15:54
                            0
                            Дата и время хранятся в id1 (в формате unix timestamp)
                            xtype поля не менял, так как заполнять или редактировать время заказа не нужно.
                            В getlist.class.php по аналогии с Вашей ссылкой добавил:
                            $c->leftJoin('customExtraItem', 'customExtraItem', '`customExtraItem`.`id` = `'.$this->classKey.'`.`id1`');
                            $c->select($this->modx->getSelectColumns($this->classKey, $this->classKey));
                            $c->select('`customExtraOrder`.`id1` as `order_date`');
                            В orders.grid.js добавил поле order_date:
                            var fields = ['id', 'name', 'description', 'active', 'actions', 'deleted', 'published', 'paid', 'new', 'hit', 'favorite', 'order_date'];
                            //...
                            dataIndex: i == 1 ? 'order_date' : 'id' + i,
                            Но я так и не понял как форматировать дату. Можете подсказать, что нужно ещё дописать в getlist.class.php?
                              Илья Уткин
                              24 января 2017, 16:24
                              +1
                              В такой ситуации править orders.grid.js не нужно. Только внести такие изменения в getlist.class.php:
                              // ...
                              	public function prepareRow(xPDOObject $object) {
                              		$array = $object->toArray();
                              		// Форматируем дату
                              		$array['id1'] = date('d.m.Y г. H:i:s', $array['id1']);
                              		// Конец
                              		$array['actions'] = array();
                              		// ...
                              	}
                              // ...
                              Василий Столейков
                              06 сентября 2017, 15:34
                              0
                              Дата и время хранятся в id1 (в формате unix timestamp)
                              Как у тебя время хранится в формате unix timestamp?
                              У меня не получается сохранить его в такой формат.
                              Например при выборе даты 05.09.2017 он сохраняет в базе только 5. А при дате 18.09.2013 сохраняет 18. То есть только день, цифры до точки.

                              P.S. xtype у меня datefield
                                Василий Столейков
                                06 сентября 2017, 16:05
                                0
                                Если использую xtype xdatetime, то в базу сохраняется только 4 цифры года…
                                Как сохранить дату в timestamp?
                                  Алексей Ерохин
                                  06 сентября 2017, 18:25
                                  +1
                                  В процессоре create/update подготавливать дату
                                  Например так делают в modx
                                  $scriptProperties['pub_date'] = strtotime($scriptProperties['pub_date']);
                                    Василий Столейков
                                    06 сентября 2017, 18:57
                                    0
                                    Ок, спасибо!
                                    Я похоже тоже до этого дошел понемножку…
                                      Василий Столейков
                                      07 сентября 2017, 11:34
                                      0
                                      В процессоре не срабатывает $scriptProperties, не срабатывает там и логирование MODX, чтобы выяснить что попадает в какие переменные.
                                      Сделал следующим образом:
                                      if(!empty($this->getProperty('id3'))) {
                                          $this->setProperty('id3',strtotime($this->getProperty('id3')));
                                      }
                                      Спасибо за наводку!
                                        Алексей Ерохин
                                        07 сентября 2017, 11:42
                                        +1
                                        $scriptProperties = $this->getProperties();
                                        А вывод для тестов можно было сделать через return
                                          Василий Столейков
                                          07 сентября 2017, 11:45
                                          0
                                          Ок, спасибо, очень кстати!
                                          Сейчас как раз пробую сделать вывод в combobox список ресурсов, и для этого мне как раз нужно знать что творится в классе процессора…
                            Vlad
                            09 мая 2018, 14:56
                            0
                            почему-то в поле description записывается значение 0 и не изменяется при редактировании объекта, не знаете в чем может быть дело?
                            Vlad
                            21 мая 2018, 12:59
                            0
                            подскажите пожалуйста, каким образом можно организовать выгрузку в Эксель через сниппет «обработать объекты», например через phpExcel, можно ли через него как-то отдать файл в браузер?
                              beka
                              10 сентября 2018, 01:21
                              0
                              Подскажите пожалуйста

                              Сделал вывод данных на страницу в списка, теперь мне нужно чтобы при нажатии (на строку) открывалась новая страница, и внутри отображалась только та инфа которую я выбрал.

                              То есть, мне нужно как то передать id в другую страницу.

                              P.S. Извиняюсь, если не понятно объяснил)
                                Юрий Ханжин
                                15 ноября 2021, 18:27
                                0
                                Илья, добрый день. Компонент отлично работает. Мне требуется помощь в моей доработке с ExtJS, я с ним не взаимодействовал до этого момента. У меня получилась огромная форма и не вылезает скролл. Добавил 'autoScroll: true,', но это не помогло. Подскажите пожалуйста — как мне добавить скролл?

                                Кусок кода с обновлением окна:
                                customExtra.window.UpdateItem = function (config) {
                                config = config || {};
                                if (!config.id) {
                                config.id = 'customextra-item-window-update';
                                }
                                Ext.applyIf(config, {
                                title: _('customextra_item_update'),
                                width: 100 + '%',
                                autoHeight: true,
                                autoScroll: true,
                                url: customExtra.config.connector_url,
                                action: 'mgr/item/update',
                                fields: this.getFields(config),
                                keys: [{
                                key: Ext.EventObject.ENTER, shift: true, fn: function () {
                                this.submit()
                                }, scope: this
                                }]
                                });
                                customExtra.window.UpdateItem.superclass.constructor.call(this, config);
                                };
                                  Юрий Ханжин
                                  15 ноября 2021, 18:45
                                  0
                                  Вопрос снимаю, разобрался.

                                  height: 500,
                                  autoScroll: true,
                                  Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                                  50