Связи объектов в CustomExtra

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



Для начала представим гипотетическую ситуацию, в которой у нас на сайте есть некие «Предметы» и с каждым предметом можно проводить некие «Операции». Попробуем связать Предметы и Операции.

После установки CustomExtra включим 2 вкладки — у предметов 0 числовых, строковых и текстовых полей, а у операций — 1 дополнительное числовое поле (в него мы будем сохранять id предмета)



Добавляем ComboBox


Для начала изменим тип поля у операций на выпадающий список. Для этого переходим в файл, ответственный за построение окон — assets/components/customextra/js/mgr/widgets/operations.windows.js, находим часть, которая перебирает числовые поля и добавляем условие, чтобы конкретное поле id1 имело особый xtype:

...
    ,items: [{
        // У первого поля особый тип
        xtype: i == 1 ? 'customextra-combo-items' : 'textfield',
        fieldLabel: _('customextra_operation_id' + i),
        name: 'id' + i,
        id: config.id + '-id' + i,
        anchor: '99%',
        allowBlank: true,
    }]
...


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

Теперь, в конце файла добавляем такой код (тут создаётся особый тип поля, который будет получать предметы и подставлять их в выпадающий список):

customExtra.combo.items = function(config) {
    config = config || {};
    Ext.applyIf(config,{
        name: 'id1'
        ,fieldLabel: 'Предмет'
        ,hiddenName: 'id1'
        ,displayField: 'name'
        ,valueField: 'id'
        ,anchor: '99%'
        ,fields: ['id','name']
        ,pageSize: 20
        // Если будете использовать не в CustomExtra, не забудьте проверить правильность
        // написание параметра connector_url - у вас он может отличатсья.
        ,url: customExtra.config.connector_url
        ,editable: true
        ,allowBlank: true
        ,emptyText: 'Выберите Предмет'
        ,baseParams: {
            action: 'mgr/item/getlist'
            ,combo: 1
        }
        ,tpl: new Ext.XTemplate(
            '<tpl for=".">\
                <div class="x-combo-list-item">\
                    <strong>{name}</strong> <sup>({id})</sup>\
                </div>\
            </tpl>'
            ,{compiled: true}
        )
    });
    customExtra.combo.items.superclass.constructor.call(this,config);
};
Ext.extend(customExtra.combo.items,MODx.combo.ComboBox);
Ext.reg('customextra-combo-items',customExtra.combo.items);


Теперь, если вы обновите страницу CustomExtra (Ctrl + R), окно создания операций будет выглядеть так:



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



За формирование списка операций отвечает процессор core/components/customextra/processors/mgr/operation/getlist.class.php
Добавим в него выборку предметов и подстановку в ячейку нужного значения:

public function prepareQueryBeforeCount(xPDOQuery $c) {
    
    // Добавляем в выборку предметы
    $c->leftJoin('customExtraItem', 'customExtraItem', '`customExtraItem`.`id` = `'.$this->classKey.'`.`id1`');
    $c->select($this->modx->getSelectColumns($this->classKey, $this->classKey));
    $c->select('`customExtraItem`.`name` as `item_name`');
    $c->groupby($this->classKey . '.id');
    // конец
    
    $query = trim($this->getProperty('query'));
    if ($query) {
        $c->where(array(
            'name:LIKE' => "%{$query}%",
            'OR:description:LIKE' => "%{$query}%",
        ));
    }

    return $c;
}


И в файле, ответственном за построение таблицы, укажем, что показывать надо имя, а не id

getFields: function (config) {
    // Добавляем поле item_name в список
    var fields = ['id', 'name', 'description', 'item_name', 'active', 'actions', 'deleted', 'published', 'paid', 'new', 'hit', 'favorite'];
    //...
},

getColumns: function (config) {
    // ...
    // А в колонке вместо первого дополнительного id показываем имя
    for(var i = 1; i < ids; i++) {
        columns.push({
            header: _('customextra_operation_id' + i),
            dataIndex: i == 1 ? 'item_name' : 'id' + i,
            sortable: true,
            width: partWidth,
        });
    }
    // ...
},


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



Выводим вложенную табличку в окне


Чтобы вывести табличку нужно добавить такой код в метод окна редактирования (перед return):

Ext.extend(customExtra.window.UpdateItem, MODx.Window, {
    getFields: function (config) {
        // ...
        fields.push({
            xtype: 'customextra-grid-operations',
            fieldLabel: _('customextra_operations'),
            id: config.id + '-operations',
            record: config.record,
            anchor: '99%'
        });
        return fields;
    },
    // ...
});


Табличка выводится, но она выводится вся, а нам нужен список только связанных операций. Для этого добавим 2 строчки в файл, отвечающий за табличку операций assets/components/customextra/js/mgr/widgets/operations.grid.js

customExtra.grid.Operations = function (config) {
    config = config || {};
    // Чтобы табличка отдельно работала, создадим нулевой объект
    config.record = config.record || {object: {id: 0}}
    if (!config.id) {
        config.id = 'customextra-grid-operations';
    }
    Ext.applyIf(config, {
        // ...
        baseParams: {
            action: 'mgr/operation/getlist'
            // В процессор будем передавать id предмета
            ,item_id: config.record.object.id
        },


А в процессор добавим выборку по id предмета:

public function prepareQueryBeforeCount(xPDOQuery $c) {
    
    // Добавляем в выборку предметы
    $c->leftJoin('customExtraItem', 'customExtraItem', '`customExtraItem`.`id` = `'.$this->classKey.'`.`id1`');
    $c->select($this->modx->getSelectColumns($this->classKey, $this->classKey));
    $c->select('`customExtraItem`.`name` as `item_name`');
    $c->groupby($this->classKey . '.id');
    // конец
    
    // Добавляем выборку по id предмета
    $item_id = trim($this->getProperty('item_id'));
    if ($item_id) {
        $c->where(array(
            'id1' => $item_id
        ));
    }
    // конец
    
    $query = trim($this->getProperty('query'));
    if ($query) {
        $c->where(array(
            'name:LIKE' => "%{$query}%",
            'OR:description:LIKE' => "%{$query}%",
        ));
    }

    return $c;
}

Та-дам!



Операции можно создавать, редактировать, удалять. Даже поиск работает. Но есть неудобство — при клике на кнопку «Создать операцию», поле «Предмет» будет пустым. Хочется, чтобы там уже был указан текущий предмет. Для этого добавим id в список устанавливаемых значений в файле operations.grid.js:

createOperation: function (btn, e) {
    // ...
    w.setValues({active: true, published: true, id1: this.record.object.id});
    w.show(e.target);
},

Вот теперь всё удобно и красиво



Шпаргалка


Итак, чтобы добавить свой combobox к полю, нужно:
  • У нужного поля указать свой тип xtype
  • Описать этот xtype в том же файле
  • В процессор getlist добавить выборку нужных объектов
  • В табличке (grid) у нужной ячейки указать нужное поле для отображения
А чтобы вывести вложенную табличку в окне:
  • Добавить эту таблицу в список полей окна
  • В самой таблице добавить передачу нужного id (config.record.object.id)
  • В процессор getlist добавить выборку по соответствующему полю
  • Для вложенного окна создания указать значение по умолчанию
Илья Уткин
30 июня 2016, 14:22
modx.pro
17
3 719
+4

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

Семён Лобачевский
30 июня 2016, 17:30
0
Спасибо! Очень полезная инструкция.
    Василий Столейков
    06 сентября 2017, 13:39
    0
    И в файле, ответственном за построение таблицы, укажем, что показывать надо имя, а не id
    Вы не уточнили, какой именно файл ответственен за построение таблицы. Такие кусочки кода есть в разных файлах customExtra, например в media.grid.js
      Илья Уткин
      06 сентября 2017, 13:46
      +1
      Каждый файл отвечает за свою вкладку. Для вкладки «Предметов» вносим изменения в items.grid.js
        Василий Столейков
        06 сентября 2017, 13:57
        0
        Ок, спасибо! Похоже я тоже его нашел.
        Просто есть много похожих файлов, например файлов с названием getlist.class.php есть в каждой из папок (item, link, media, operation, order). Я ещё не совсем понял для чего каждая из этих папок отвечает.
          Илья Уткин
          06 сентября 2017, 14:02
          +1
          Каждая папка отвечает за свою вкладку
          • item — Предметы
          • order — Заказы
          • operation — Операции
          • media — Медиа
          • link — Ссылки
            Василий Столейков
            06 сентября 2017, 14:02
            0
            А, точно, спасибо! Очевидно же! У меня уже глаза замылились, пора развеятся… )))
            Спасибо за подсказки!
      Василий Столейков
      07 сентября 2017, 11:55
      0
      Мне нужно вывести список ресурсов в combobox.
      Описанный же метод в статье выводит список добавленных предметов в customExtra.
      Попытался внедриться в getlist.class.php, но не получилось сделать выборку ресурсов.
      Создал отдельный файл процессора getproducts.class.php и указал к нему путь в combobox-е, но почему-то combobox упорно не хочет видеть указанный ему файл процессора и обращается к старому.

      Вот регистрация комбобокса:
      customExtra.combo.items = function(config) {
          config = config || {};
          Ext.applyIf(config,{
              name: 'id1'
              ,fieldLabel: 'Товар'
              ,hiddenName: 'id1'
              ,displayField: 'pagetitle'
              ,valueField: 'id'
              ,anchor: '99%'
              ,fields: ['id', 'pagetitle']
              ,pageSize: 20
              ,hideMode: 'offsets'
              // Если будете использовать не в CustomExtra, не забудьте проверить правильность
              // написание параметра connector_url - у вас он может отличатсья.
              ,url: customExtra.config.connector_url
              ,editable: true
              ,allowBlank: false
              ,emptyText: 'Выберите товар'
              ,baseParams: {
                  action: 'mgr/item/getproducts' // ПУТЬ К НОВОМУ ФАЙЛУ ПРОЦЕССОРА
                  ,combo: true
              }
              /*
              ,tpl: new Ext.XTemplate(
                  '<tpl for=".">\
                      <div class="x-combo-list-item">\
                          <strong>{resource_pagetitle}</strong> <sup>({resource_id})</sup>\
                      </div>\
                  </tpl>'
                  ,{compiled: true}
              )
              */
          });
          customExtra.combo.items.superclass.constructor.call(this,config);
      };
      Ext.extend(customExtra.combo.items,MODx.combo.ComboBox);
      Ext.reg('customextra-combo-items',customExtra.combo.items);

      А вот и код самого процессора getproducts.class.php:
      <?php
      class msResourceGetListProcessor extends modObjectGetListProcessor
      {
          public $classKey = 'modResource';
          public $languageTopics = array('resource');
          public $defaultSortField = 'pagetitle';
      
      
          /**
           * @param xPDOQuery $c
           *
           * @return xPDOQuery
           */
          public function prepareQueryBeforeCount(xPDOQuery $c)
          {
              if ($this->getProperty('combo')) {
                  $c->select('id,pagetitle');
              }
              if ($id = (int)$this->getProperty('id')) {
                  $c->where(array('id' => $id));
              }
              if ($query = trim($this->getProperty('query'))) {
                  $c->where(array('pagetitle:LIKE' => "%{$query}%"));
              }
      
              return $c;
          }
      
      
          /**
           * @param xPDOObject $object
           *
           * @return array
           */
          public function prepareRow(xPDOObject $object)
          {
              if ($this->getProperty('combo')) {
                  $array = array(
                      'id' => $object->get('id'),
                      'pagetitle' => '(' . $object->get('id') . ') ' . $object->get('pagetitle'),
                  );
              } else {
                  $array = $object->toArray();
              }
      
              return $array;
          }
      
      }
      
      return 'msResourceGetListProcessor';

      Почему combobox не видит файл нового процессора? Или как по-другому подсунуть комбобоксу список ресурсов?
        Володя
        07 сентября 2017, 14:44
        +1
        почему-то combobox упорно не хочет видеть указанный ему файл процессора и обращается к старому.
        таких чудес не бывает…
        — либо ты чтото не там прописываешь…
        — либо кеш
        — либо твой комбо гдето ниже еще переопределяется
          Василий Столейков
          07 сентября 2017, 14:50
          0
          — либо твой комбо гдето ниже еще переопределяется
          Блестяще! Спасибо за наводку! Поменял название комбо и всё заработало!
          Кстати, спасибо тебе, Володя, и Василию, за классные компоненты, по коду которых я сейчас и делаю всё это…
            atrox
            06 сентября 2018, 10:09
            0
            можете подсказать, я тоже вывел через ресурсы, только у меня постоянно появляются цифры, какое поле указать тут что выводился pagetitle?


            за основу я взял ваш процессор

            Спасибо заранее за ответ
            for(var i = 1; i < ids; i++) {
            			columns.push({
            				header: _('customextra_item_id' + i),
            				dataIndex: i == 1 ? 'pagetitle' : 'id' + i,
            				sortable: true,
            				width: partWidth,
            			});
            }
        Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
        10