[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
5 505
+16
Поблагодарить автора Отправить деньги

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

Андрей Сухомозгий
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. Извиняюсь, если не понятно объяснил)
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
48