Проблема с SuperBoxSelect

Всем привет. Пытаюсь в modExtra добавить поле со множественным выбором из списка товаров msProduct. Удалось прикрутить саму форму select и вывести продукты, но существует проблема с сохранением и выводом в поле сохранённых товаров.



Делаю следующим образом. В схему добавляю:

<field key="products" dbtype="text" phptype="json" null="true"/>

В combo.js у меня такой код:

modExtra.combo.Options = function(config) {
    config = config || {};
    Ext.applyIf(config,{
        xtype:'superboxselect',
        allowBlank: true,
        msgTarget: 'under',
        allowAddNewData: true,
        addNewDataOnBlur : true,
        resizable: true,
        name: 'products',
        anchor:'99%',
        minChars: 1,
        fieldLabel: _('ms2_product_name'),
        valueField: 'id',
        displayField: 'pagetitle',
        url: modExtra.config['connector_url'],
        store:new Ext.data.JsonStore({
            id: (config.name || 'products') + '-store',
            root:'results',
            autoLoad: true,
            autoSave: false,
            totalProperty:'total',
            fields: ['id', 'pagetitle', 'parents'],
            value: '{products}',
            url: modExtra.config['connector_url'],
            baseParams: {
                action: 'mgr/product/getoptions',
                key: config.name
            },
            tpl: new Ext.XTemplate('\
            <tpl for=".">\
                <div class="x-combo-list-item minishop2-product-list-item" ext:qtip="{pagetitle}">\
                    <tpl if="parents">\
                        <span class="parents">\
                            <tpl for="parents">\
                                <nobr><small>{pagetitle} / </small></nobr>\
                            </tpl>\
                        </span>
\
                    </tpl>\
                    <span><small>({id})</small> <b>{pagetitle}</b></span>\
                </div>\
            </tpl>', {compiled: true}
            ),
        }),
        mode: 'remote',
        triggerAction: 'all',
        extraItemCls: 'x-tag',
        expandBtnCls: 'x-form-trigger',
        clearBtnCls: 'x-form-trigger',
        listeners: {
            newItem: function(bs,v, f) {bs.addItem({products: v});},
        },
        renderTo: Ext.getBody()
    });
    config.name += '[]';
    modExtra.combo.Options.superclass.constructor.call(this,config);
};
Ext.extend(modExtra.combo.Options,Ext.ux.form.SuperBoxSelect);
Ext.reg('modextra-combo-options', modExtra.combo.Options);
В процессоре:

<?php

class modExtraProductGetOptionsProcessor extends modObjectGetListProcessor
{
    public $classKey = 'msProduct';
    public $defaultSortField = 'id';
    public $defaultSortDirection = 'ASC';
    protected $product_id = 0;

    /**
     * @return bool
     */
    public function initialize()
    {
        if ($this->getProperty('combo') && !$this->getProperty('limit') && $id = (int)$this->getProperty('id')) {
            $this->product_id = $id;
        }
        $this->setDefaultProperties(array(
            'parents' => 2,
            'start' => 0,
            'limit' => 20,
            'sort' => $this->defaultSortField,
            'dir' => $this->defaultSortDirection,
            'combo' => false,
            'query' => '',
        ));

        return true;
    }


    /**
     * @return array|string
     */
    public function process()
    {
        $beforeQuery = $this->beforeQuery();
        if ($beforeQuery !== true) {
            return $this->failure($beforeQuery);
        }
        $data = $this->getData();
        $list = $this->iterate($data);

        return $this->outputArray($list, $data['total']);
    }


    /**
     * @return array
     */
    public function getData()
    {
        $data = array();
        $limit = intval($this->getProperty('limit'));
        $start = intval($this->getProperty('start'));

        /* query for chunks */
        $c = $this->modx->newQuery($this->classKey);
        $c = $this->prepareQueryBeforeCount($c);
        $data['total'] = $this->modx->getCount($this->classKey, $c);
        $c = $this->prepareQueryAfterCount($c);

        $sortClassKey = $this->getSortClassKey();
        $sortKey = $this->modx->getSelectColumns($sortClassKey, $this->getProperty('sortAlias', $sortClassKey), '',
            array($this->getProperty('sort')));
        if (empty($sortKey)) {
            $sortKey = $this->getProperty('sort');
        }
        $c->sortby($sortKey, $this->getProperty('dir'));
        if ($limit > 0) {
            $c->limit($limit, $start);
        }

        if ($c->prepare() && $c->stmt->execute()) {
            $data['results'] = $c->stmt->fetchAll(PDO::FETCH_ASSOC);
        }

        return $data;
    }


    /**
     * @param array $data
     *
     * @return array
     */
    public function iterate(array $data)
    {
        $list = array();
        $list = $this->beforeIteration($list);
        $this->currentIndex = 0;
        /** @var xPDOObject|modAccessibleObject $object */
        foreach ($data['results'] as $array) {
            $objectArray = $this->prepareResult($array);
            if (!empty($objectArray) && is_array($objectArray)) {
                $list[] = $objectArray;
                $this->currentIndex++;
            }
        }
        $list = $this->afterIteration($list);

        return $list;
    }


    /**
     * @param xPDOQuery $c
     *
     * @return xPDOQuery
     */
    public function prepareQueryBeforeCount(xPDOQuery $c)
    {
        $c->select('id,parent,pagetitle,context_key');
        $c->where(array(
            'class_key' => 'msProduct',
        ));

        if ($this->product_id) {
            $c->where(array('id' => $this->product_id));
        } elseif ($query = $this->getProperty('query')) {
            $c->where(array('pagetitle:LIKE' => "%$query%"));
        }

        return $c;
    }


    /**
     * @param array $resourceArray
     *
     * @return array
     */
    public function prepareResult(array $resourceArray)
    {
        $resourceArray['parents'] = array();
        $parents = $this->modx->getParentIds($resourceArray['id'], 2,
            array('context' => $resourceArray['context_key']));
        if ($parents[count($parents) - 1] == 0) {
            unset($parents[count($parents) - 1]);
        }
        if (!empty($parents) && is_array($parents)) {
            $q = $this->modx->newQuery('msProduct', array('id:IN' => $parents));
            $q->select('id,pagetitle');
            if ($q->prepare() && $q->stmt->execute()) {
                while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
                    $key = array_search($row['id'], $parents);
                    if ($key !== false) {
                        $parents[$key] = $row;
                    }
                }
            }
            $resourceArray['parents'] = array_reverse($parents);
        }

        return $resourceArray;
    }

}

return 'modExtraProductGetOptionsProcessor';
И наконец в items.window.js такой код:

{
                    xtype: 'modextra-combo-options',
                    fieldLabel: _('modextra_products'),
                    name: 'products',
                    id: config.id + '-products',
                    anchor: '99%',
                    allowBlank: true,
                }
Здесь сам компонент github.com/SequelONE/modExtra

Помогите разобраться с сохранением и выводом при редактировании.

UPD: Теперь всё сохраняется, но не отображается выбор.
SEQUEL.ONE
12 июня 2020, 22:04
modx.pro
1
860
0

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

SEQUEL.ONE
16 июня 2020, 20:24
0
Пробовал добавить параметр
value: 'pagetitle'
не работает. Не понимаю как вывести в поле редактирования добавленные элементы. В базу они добавляются.
    Aleksandr Huz
    16 июня 2020, 21:18
    0
    В базе они сохраняются в виде списка через запятую, верно?
    Соответственно, нам нужно преобразовать список в массив обратно на выходе. Это делается в процессоре modObjectGetProcessor (get.class.php)

    public function cleanup()
        {
            $array = $this->object->toArray();
            if(!empty($array['products'])) {
                $array['products'] = explode(',', $array['products']);
            }
            return $this->success('', $array);
        }
    }
      SEQUEL.ONE
      16 июня 2020, 21:20
      0
      Нет, в базе сохраняется массив такого формата

      ["1","4","6"]
        Aleksandr Huz
        16 июня 2020, 21:23
        0
        Теперь вопрос)) Уверен, что это массив?
          SEQUEL.ONE
          16 июня 2020, 21:25
          0
          Нет, в соседней ветке про superboxselect этот формат так назвали, по мне так это на JSON больше похоже)
            Aleksandr Huz
            16 июня 2020, 21:29
            0
            В базе сохраняется такой формат как строка.

            Перед сохранением нужно преобразовать массив в список.
            create.class.php
            update.class.php

            public function beforeSet()
            {
                $this->setProperty('products', implode(',', $this->getProperty('products')));
                return !$this->hasErrors();
            }
            А для вывода, я выше написал.
              SEQUEL.ONE
              16 июня 2020, 22:10
              0
              В базу сохраняет NULL, если в базе сохранить
              1,2,3
              ничего не отображается, и поломался Grid в добавок =(
                SEQUEL.ONE
                16 июня 2020, 22:13
                0
                Вот get.class.php

                <?php
                
                class modExtraItemGetProcessor extends modObjectGetProcessor
                {
                    public $objectType = 'modExtraItem';
                    public $classKey = 'modExtraItem';
                    public $languageTopics = ['modextra:default'];
                    //public $permission = 'view';
                
                
                    /**
                     * We doing special check of permission
                     * because of our objects is not an instances of modAccessibleObject
                     *
                     * @return mixed
                     */
                    public function process()
                    {
                        if (!$this->checkPermissions()) {
                            return $this->failure($this->modx->lexicon('access_denied'));
                        }
                
                        return parent::process();
                    }
                
                    public function cleanup()
                    {
                        $array = $this->object->toArray();
                        if(!empty($array['products'])) {
                            $array['products'] = explode(',', $array['products']);
                        }
                        return $this->success('', $array);
                    }
                
                }
                
                return 'modExtraItemGetProcessor';
                Вот create.class.php

                <?php
                
                class modExtraItemCreateProcessor extends modObjectCreateProcessor
                {
                    public $objectType = 'modExtraItem';
                    public $classKey = 'modExtraItem';
                    public $languageTopics = ['modextra'];
                    //public $permission = 'create';
                
                
                    /**
                     * @return bool
                     */
                    public function beforeSet()
                    {
                        $name = trim($this->getProperty('name'));
                
                        if (empty($name)) {
                            $this->modx->error->addField('name', $this->modx->lexicon('modextra_item_err_name'));
                        } elseif ($this->modx->getCount($this->classKey, ['name' => $name])) {
                            $this->modx->error->addField('name', $this->modx->lexicon('modextra_item_err_ae'));
                        }
                
                        $this->setProperty('products', implode(',', $this->getProperty('products')));
                
                        return parent::beforeSet();
                    }
                
                }
                
                return 'modExtraItemCreateProcessor';
                и update.class.php по аналогии с create.class.php
                <?php
                
                class modExtraItemUpdateProcessor extends modObjectUpdateProcessor
                {
                    public $objectType = 'modExtraItem';
                    public $classKey = 'modExtraItem';
                    public $languageTopics = ['modextra'];
                    //public $permission = 'save';
                
                
                    /**
                     * We doing special check of permission
                     * because of our objects is not an instances of modAccessibleObject
                     *
                     * @return bool|string
                     */
                    public function beforeSave()
                    {
                        if (!$this->checkPermissions()) {
                            return $this->modx->lexicon('access_denied');
                        }
                
                        return true;
                    }
                
                
                    /**
                     * @return bool
                     */
                    public function beforeSet()
                    {
                        $id = (int)$this->getProperty('id');
                        $name = trim($this->getProperty('name'));
                        if (empty($id)) {
                            return $this->modx->lexicon('modextra_item_err_ns');
                        }
                
                        if (empty($name)) {
                            $this->modx->error->addField('name', $this->modx->lexicon('modextra_item_err_name'));
                        } elseif ($this->modx->getCount($this->classKey, ['name' => $name, 'id:!=' => $id])) {
                            $this->modx->error->addField('name', $this->modx->lexicon('modextra_item_err_ae'));
                        }
                
                        $this->setProperty('products', implode(',', $this->getProperty('products')));
                
                        return parent::beforeSet();
                    }
                }
                
                return 'modExtraItemUpdateProcessor';
                  Aleksandr Huz
                  16 июня 2020, 22:44
                  0
                  Значит ошибка в комбо

                  name: 'products[]',
                  hiddenName: 'products[]',
                  
                  newItem: function(bs,v, f) {bs.addItem({pagetitle: v});},
                    SEQUEL.ONE
                    16 июня 2020, 22:58
                    0
                    Что-то вообще перестало в это поле сохранять. Можете посмотреть что не так github.com/SequelONE/modExtra?
                      SEQUEL.ONE
                      16 июня 2020, 22:59
                      0
                      Там последний коммит без этих художеств)
                        Aleksandr Huz
                        16 июня 2020, 23:03
                        0
                        попробуй удалить эту строчку
                        config.name += '[]';
                        SEQUEL.ONE
                        16 июня 2020, 23:07
                        0
                        Не помогло =(
                      SEQUEL.ONE
                      16 июня 2020, 23:00
                      0
                      Или могу доступы дать к тестовому сайту.
      SEQUEL.ONE
      21 июня 2020, 14:57
      0
      Проблема так и не решилась. Видимо какой-то косяк с MODX.Window. Возможно проблема заключается в том, что выпадающий список у меня формируется из товаров miniShop2, но мне как раз необходим такой функционал. Может кто-то увидит этот комментарий и поможет разобраться?
        Максим
        22 июня 2020, 12:06
        0
        Я так понимаю что такая же проблема?
        Если да, то пока придумал только костыльное решение в виде создания плагина и в нем устанавливаю значение.
        <?php
        /** @var modX $modx */
        switch ($modx->event->name) {
                
            case 'OnDocFormPrerender':
                if ($mode == 'upd') {
                    $inshop = implode(',', $resource->get('inshop'));
                    $modx->controller->addHtml('<script type="text/javascript">
                        Ext.onReady(function() {
                            Ext.getCmp("modx-panel-resource").getForm().setValues({"inshop[]":[' . $inshop . ']});
                        });
                        </script>');
                }
        }
        Но у вас в окно нужно подставлять… Можно попробовать в этом плагине ловить открытие окна и подставлять.
        Другое решение пока не нашел, да и не искал, честно говоря.
          SEQUEL.ONE
          22 июня 2020, 12:12
          0
          Да, такая же. За исключением того, что я пытаюсь в компоненте modExtra это сделать в всплывающем окне при редактировании.
            Максим
            22 июня 2020, 12:38
            0
            Ну у меня сегодня лайтовый день. Можно попробовать в скайпе созвониться… Может со второго захода получится победить эту проблему… Но не уверен… Если есть желание, то давайте скайп в личку.
        Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
        18