Связи объектов в 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 june 2016, 17:22    Илья Уткин   G+  
10    637 +4

Comments (1)

  1. Семён Лобачевский 30 june 2016, 17:30 # 0
    Спасибо! Очень полезная инструкция.
    You need to login to create comments.