Alex

Alex

Был в сети 18 января 2022, 19:44
Заказы принимаю
Да, на самом деле так и есть. Практически одинаковый результат можно получить что за 10к, что за 2к. Таковы суровые реалии. Действительно, 95% заказчиков, а то и все 99% вообще не интересует, что там внутри и как это сделано, главное чтобы работало и, снова таки, они в чем-то правы. Если у них не сложный и не большой магазин, то разницы они практически не почувствуют, а вот финансовая разница ощутима. А те заки, которым важно как это работает и у них большие сложные сайты не делают магазины на MODX и сомневаюсь что пользуются услугами фрилансеров. Так что дешевле не всегда хуже. Некоторые местные гуру-модекс-программисты такую лажу продают в модсторе, что просто жесть.
На счет двух вызовов не знаю, не сталкивался. Вам подойдет для разделения на десктоп и мобильный дополнение mobileDetect.
Еще, если мне не изменяет память, дополнение msMinicartDynamic не пересчитывает динамически полную сумму корзины, поэтому я для пересчета итоговой суммы оставил логику стандартной корзины. Вот код как у меня выглядит миникорзина.
<div id="msCart" class="modal-wrap">
    <div class="full-cart-msg not_empty">
        {$_modx->runSnippet('!msMiniCartDynamic', [
            'tplOuter' => 'minicartModalOuter.tpl',
            'tpl' => 'minicartModalRow.tpl',
            'img' => 'small'
        ])}
    </div>
    <div class="minicart-full-info">
        {'ms2_minicart_goods' | lexicon} <strong class="ms2_total_count">{$total_count}</strong> {'ms2_frontend_count_unit' | lexicon},
        {'ms2_minicart_cost' | lexicon} <strong class="ms2_total_cost">{$total_cost}</strong> грн.
    </div>
    <div class="clr"></div>
</div>
Скорее всего вы что-то не правильно сделали. Попробуйте сделать точно по документации. Прямо один в один.
Всунуть куда-то в код шаблона вот это
<script src="/assets/components/msminicartdynamic/js/web/msminicartdynamic.js"></script>
И миникорзина должна обновляться как положено.
UPD. Читайте внимательно описание компонента, там этот момент указан. Видимо по каким-то причинам автоматическая подгрузка скрипта не была реализована.
Здравствуйте. Есть небольшой баг в результате подсчета просмотренных товаров. Например, заходим с чистой сессией, смотрим один товар, потом другой, возвращаемся в предыдущий товар и вот тут «просмотренных товаров (2)», хотя выводится один, так как текущий товар исключается из результатов сниппета looked.
При таком количестве товаров остро встает вопрос по управлению всем этим делом в админке. Также в дальнейшем проекту может понадобиться какой-то дополнительный функционал и также встанет вопрос о его внедрении. Считаю что над этими вопросами уже стоит задуматься и прикинуть подходит ли MODX.
Спасибо за совет. Я уже «вкурил» что есть основная таблица defaul.grid.js а от нее наследуется order.grid.js. Сложно так вот сразу понять схему работы всего этого дела. Также понял как работает вот это из отдельного файла:

miniShop2.utils.renderActions = function (value, props, row) {
    var res = [];
    var cls, icon, title, action, item = '';
    for (var i in row.data.actions) {
        if (!row.data.actions.hasOwnProperty(i)) {
            continue;
        }
        var a = row.data.actions[i];
        if (!a['button']) {
            continue;
        }

        icon = a['icon'] ? a['icon'] : '';
        if (typeof(a['cls']) == 'object') {
            if (typeof(a['cls']['button']) != 'undefined') {
                icon += ' ' + a['cls']['button'];
            }
        }
        else {
            cls = a['cls'] ? a['cls'] : '';
        }
        action = a['action'] ? a['action'] : '';
        title = a['title'] ? a['title'] : '';

        item = String.format(
            '<li class="{0}"><button class="btn btn-default {1}" action="{2}" title="{3}"></button></li>',
            cls, icon, action, title
        );

        res.push(item);
    }

    return String.format(
        '<ul class="minishop2-row-actions">{0}</ul>',
        res.join('')
    );
};
То есть надо написать так (ну это я так понял, может и не так):

Ext.namespace('Scrapx.utils'); // вот это я так понимаю указывает что мы расширяем пространство имен

// и тут уже можно писать рендерер
Scrapx.utils.renderActions = function (value, props, row) {
    var res = [];
    var cls, icon, title, action, item = '';
    for (var i in row.data.actions) {
        if (!row.data.actions.hasOwnProperty(i)) {
            continue;
        }
        var a = row.data.actions[i];
        if (!a['button']) {
            continue;
        }

        icon = a['icon'] ? a['icon'] : '';
        if (typeof(a['cls']) == 'object') {
            if (typeof(a['cls']['button']) != 'undefined') {
                icon += ' ' + a['cls']['button'];
            }
        }
        else {
            cls = a['cls'] ? a['cls'] : '';
        }
        action = a['action'] ? a['action'] : '';
        title = a['title'] ? a['title'] : '';

        item = String.format(
            '<li class="{0}"><button class="btn btn-default {1}" action="{2}" title="{3}"></button></li>',
            cls, icon, action, title
        );

        res.push(item);
    }

    return String.format(
        '<ul class="minishop2-row-actions">{0}</ul>',
        res.join('')
    );
};
Добавляем в файле core/components/scrapx/controllers/default/scrapxpresets.class.php:

$this->addJavascript($this->scrapx->config['jsUrl'].'mgr/widgets/utils.js');
Теперь я вызываю у себя в файле assets/components/scrapx/js/mgr/widgets/scrapxpresets.grid.js рендерер вот так:

{
	header:_('scrapx.header_actions')
	,dataIndex: 'actions'
	,sortable: true
	,renderer: Scrapx.utils.renderActions
				
}
И он работает.

В вашем файле core/componenets/minishop2/processors/mgr/orders/getlist.php есть такая функция, которая как мне показалось отвечает за вызов собственно кнопок действий:

public function prepareArray(array $data)
    {
        if (empty($data['customer'])) {
            $data['customer'] = $data['customer_username'];
        }

        $data['status'] = '<span style="color:#' . $data['color'] . ';">' . $data['status'] . '</span>';
        unset($data['color']);

        if (isset($data['cost'])) {
            $data['cost'] = $this->ms2->formatPrice($data['cost']);
        }
        if (isset($data['cart_cost'])) {
            $data['cart_cost'] = $this->ms2->formatPrice($data['cart_cost']);
        }
        if (isset($data['delivery_cost'])) {
            $data['delivery_cost'] = $this->ms2->formatPrice($data['delivery_cost']);
        }
        if (isset($data['weight'])) {
            $data['weight'] = $this->ms2->formatWeight($data['weight']);
        }

        $data['actions'] = array(
            array(
                'cls' => '',
                'icon' => 'icon icon-edit',
                'title' => $this->modx->lexicon('ms2_menu_update'),
                'action' => 'updateOrder',
                'button' => true,
                'menu' => true,
            ),
            array(
                'cls' => array(
                    'menu' => 'red',
                    'button' => 'red',
                ),
                'icon' => 'icon icon-trash-o',
                'title' => $this->modx->lexicon('ms2_menu_remove'),
                'multiple' => $this->modx->lexicon('ms2_menu_remove_multiple'),
                'action' => 'removeOrder',
                'button' => true,
                'menu' => true,
            ),
            /*
            array(
                'cls' => '',
                'icon' => 'icon icon-cog actions-menu',
                'menu' => false,
                'button' => true,
                'action' => 'showMenu',
                'type' => 'menu',
            ),
            */
        );

        return $data;
    }
Я у себя в файле core/components/scrapx/processors/mgr/scrapxpresets/getlist.class.php добавил вот так:

<?php
class ScrapxPresetsGetListProcessor extends modObjectGetListProcessor {
    public $languageTopics = array('scrapx:scrapxpresets');
    public $classKey = 'ScrapxPresets';
    public $defaultSortField = 'id';
    public $defaultSortDirection = 'ASC';
    public $checkListPermission = true;
    public function prepareQueryBeforeCount(xPDOQuery $c) {
        $query = $this->getProperty('query');
        if (!empty($query)) {
            $c->where(array('name:LIKE' => '%'.$query.'%'));
        }
        return $c;
    }

    /**
     * @param array $data
     *
     * @return array
     */
    public function prepareArray(array $data){
        $data['actions'] = array(
            array(
                'cls' => '',
                'icon' => 'icon icon-edit',
                'title' => $this->modx->lexicon('scrapx.scrapping'),
                'action' => 'scrapping',
                'button' => true,
                'menu' => true,
            ),
        );

        return $data;
    }
}
return 'ScrapxPresetsGetListProcessor';
Но никакие кнопки в таблице пока не появились. Пока не разобрался что делать дальше.
В месте где должны быть кнопки в таблице у меня выводится обертка

<ul class="minishop2-row-actions"></ul>
Но пунктов никаких нет. Вот теперь думаю как передать правильно туда данные… вообще не пойму пока что делать дальше. Изучаю ваши исходники минишопа и экспериментирую методом тыка…
У меня что-то рендер не хочет работать если его поместить в отдельный файл как в минишопе, сделал так.
Scrapx.grid.ScrapxPresets = function(config) {
    config = config || {};
    Ext.applyIf(config,{
        id: 'scrapx-grid-scrapxpresets'
        ,url: Scrapx.config.connectorUrl
        ,baseParams: { action: 'mgr/scrapxpresets/getList' }
        ,fields: ['id','home_url','scrap_url','resource_class','category_id','actions']
        ,paging: true
        ,remoteSort: true
        ,anchor: '97%'
        ,autoExpandColumn: 'name'
        ,save_action: 'mgr/scrapxpresets/updateFromGrid'
        ,autosave: true
        ,columns: [{
				header:_('scrapx.header_id')
				,dataIndex: 'id'
				,sortable: true
				,hidden: true
			},{
				header:_('scrapx.header_home_url')
				,dataIndex: 'home_url'
				,sortable: true
				,editor: {
					xtype: 'textfield'
				}
			},{
				header:_('scrapx.header_scrap_url')
				,dataIndex: 'scrap_url'
				,sortable: true
				,editor: {
					xtype: 'textfield'
				}
			},{
				header:_('scrapx.header_resource_class')
				,dataIndex: 'resource_class'
				,sortable: true
				,editor: {
					xtype: 'modx-combo-class-derivatives'
				}
                ,fieldLabel: _('resource_type')
                ,description: '<b>[[*class_key]]</b><br />'
                ,name: 'class_key'
                ,hiddenName: 'class_key'
                ,id: 'modx-resource-class-key'
                ,anchor: '100%'
			},{
				header:_('scrapx.header_category_id')
				,dataIndex: 'category_id'
				,sortable: true
				,editor: {
					xtype: 'numberfield'
				}
			},{
				//header:_('scrapx.header_actions')
				
                dataIndex: 'actions',
                id: 'actions',
                width: 195,
                sortable: false,
                fixed: true,
                width: 150,
                renderer: this.renderActions

			}],tbar:[{
             text: '<i class="icon icon-clipboard"></i>  ' + _('scrapx.btn_create')


            ,handler: { xtype: 'scrapx-window-scrapxpresets-create' ,blankValues: true }
            },'->',{
            xtype: 'textfield'
            ,name: 'search'
            ,id: 'scrapx-search-filter'
            ,emptyText: _('scrapx.search')+'...'
            ,listeners: {
                'change': {fn:this.search,scope:this}
                ,'render': {fn: function(cmp) {
                    new Ext.KeyMap(cmp.getEl(), {
                        key: Ext.EventObject.ENTER
                        ,fn: function() {
                            this.fireEvent('change',this);
                            this.blur();
                            return true;
                        }
                        ,scope: cmp
                    });
                },scope:this}
            }
        },{
            xtype: 'button'
            ,id: 'scrapx-filter-clear'
            ,text: _('scrapx.filter_clear')
            ,listeners: {
                'click': {fn: this.clearFilter, scope: this}
            }
        }]


        ,getMenu: function() {
            return [{
                text: _('scrapx.menu.edit')
                ,handler: this.editScrapxPresets
            },'-',{
                text: _('scrapx.menu.remove')
                ,handler: this.removeScrapxPresets
            }];
        },editScrapxPresets: function(btn,e) {
            if (!this.editScrapxPresetsWindow) {
                this.editScrapxPresetsWindow = MODx.load({
                    xtype: 'scrapx-window-scrapxpresets-edit'
                    ,record: this.menu.record
                    ,listeners: {
                        'success': {fn:this.refresh,scope:this}
                    }
                });
            }
            this.editScrapxPresetsWindow.setValues(this.menu.record);
            this.editScrapxPresetsWindow.show(e.target);
        },removeScrapxPresets: function() {
            MODx.msg.confirm({
                title: _('scrapx.title.win_remove')
                ,text: _('scrapx.confirm.remove')
                ,url: this.config.url
                ,params: {
                    action: 'mgr/scrapxpresets/remove'
                    ,id: this.menu.record.id
                }
                ,listeners: {
                    'success': {fn:this.refresh,scope:this}
                }
            });
        }

    });
    Scrapx.grid.ScrapxPresets.superclass.constructor.call(this,config)
};
Ext.extend(Scrapx.grid.ScrapxPresets,MODx.grid.Grid,{
    search: function(tf,nv,ov) {
        var s = this.getStore();
        s.baseParams.query = tf.getValue();
        this.getBottomToolbar().changePage(1);
        this.refresh();
    }
    ,clearFilter: function() {
        var s = this.getStore();
        s.baseParams.search = '';
        Ext.getCmp('scrapx-search-filter').reset();
        this.getBottomToolbar().changePage(1);
        this.refresh();
    },
    renderActions:  function (value, props, row) {
    var res = [];
    var cls, icon, title, action, item;
    if (typeof(value) == 'object') {
        for (var i in value) {
            if (!value.hasOwnProperty(i)) {
                continue;
            }
            var a = value[i];
            if (!a['button']) {
                continue;
            }

            icon = a['icon'] ? a['icon'] : '';
            if (typeof(a['cls']) == 'object') {
                if (typeof(a['cls']['button']) != 'undefined') {
                    icon += ' ' + a['cls']['button'];
                }
            } else {
                cls = a['cls'] ? a['cls'] : '';
            }
            action = a['action'] ? a['action'] : '';
            title = a['title'] ? a['title'] : '';

            item = String.format(
                '<li class="{0}"><button class="btn btn-default {1}" action="{2}" title="{3}"></button></li>',
                cls, icon, action, title
            );

            res.push(item);
        }
    }

    return String.format(
        '<ul class="xp-row-actions">{0}</ul>',
        res.join('')
    );
}
});
Ext.reg('scrapx-grid-scrapxpresets',Scrapx.grid.ScrapxPresets);

Scrapx.window.EditScrapxPresets = function(config) {
    config = config || {};
    var self = this;
    Ext.applyIf(config,{
        title: _('scrapx.title.win_edit')
        ,url: Scrapx.config.connectorUrl
        ,autoHeight: true
        ,modal: true
        ,baseParams: {
            action: 'mgr/scrapxpresets/update'
        }
        ,fields: [{
				xtype: 'hidden'
				,name: 'id'
			},{
				xtype: 'textarea'
				,fieldLabel: _('scrapx.label_home_url')
				,description: '<b>[[*home_url]]</b><br />'+_('scrapx.label_home_url_help')
				,name: 'home_url'
				,allowBlank:false
				,anchor: '100%'
			},{
				xtype: 'textarea'
				,fieldLabel: _('scrapx.label_scrap_url')
				,description: '<b>[[*scrap_url]]</b><br />'+_('scrapx.label_scrap_url_help')
				,name: 'scrap_url'
				,allowBlank:false
				,anchor: '100%'
			},{
				xtype: 'textfield'
				,fieldLabel: _('scrapx.label_resource_class')
				,description: '<b>[[*resource_class]]</b><br />'+_('scrapx.label_resource_class_help')
				,name: 'resource_class'
				,allowBlank:false
				,anchor: '100%'
			},{
				xtype: 'numberfield'
				,fieldLabel: _('scrapx.label_category_id')
				,description: '<b>[[*category_id]]</b><br />'+_('scrapx.label_category_id_help')
				,name: 'category_id'
				,allowBlank:false
				,anchor: '100%'
			}]
    });
    Scrapx.window.EditScrapxPresets.superclass.constructor.call(this,config);
};
Ext.extend(Scrapx.window.EditScrapxPresets,MODx.Window,{});
Ext.reg('scrapx-window-scrapxpresets-edit',Scrapx.window.EditScrapxPresets);


Scrapx.window.CreateScrapxPresets = function(config) {
    config = config || {};
    var self = this;
    Ext.applyIf(config,{
        title: _('scrapx.title.win_create')
        ,url: Scrapx.config.connectorUrl
        ,autoHeight:true
        ,modal: true
        ,baseParams: {
            action: 'mgr/scrapxpresets/create'
        }
        ,fields: [{
				xtype: 'hidden'
				,name: 'id'
			},{
				xtype: 'textarea'
				,fieldLabel: _('scrapx.label_home_url')
				,description: '<b>[[*home_url]]</b><br />'+_('scrapx.label_home_url_help')
				,name: 'home_url'
				,allowBlank:false
				,anchor: '100%'
			},{
				xtype: 'textarea'
				,fieldLabel: _('scrapx.label_scrap_url')
				,description: '<b>[[*scrap_url]]</b><br />'+_('scrapx.label_scrap_url_help')
				,name: 'scrap_url'
				,allowBlank:false
				,anchor: '100%'
			},{
				xtype: 'textfield'
				,fieldLabel: _('scrapx.label_resource_class')
				,description: '<b>[[*resource_class]]</b><br />'+_('scrapx.label_resource_class_help')
				,name: 'resource_class'
				,allowBlank:false
				,anchor: '100%'
			},{
				xtype: 'numberfield'
				,fieldLabel: _('scrapx.label_category_id')
				,description: '<b>[[*category_id]]</b><br />'+_('scrapx.label_category_id_help')
				,name: 'category_id'
				,allowBlank:false
				,anchor: '100%'
			}]
    });
    Scrapx.window.CreateScrapxPresets.superclass.constructor.call(this,config);
};
Ext.extend(Scrapx.window.CreateScrapxPresets,MODx.Window);
Ext.reg('scrapx-window-scrapxpresets-create',Scrapx.window.CreateScrapxPresets);
Обертка для кнопок появляется в разметке, а вот кнопок нет. Значит их надо откуда-то передать. Смотрим getList процессор минишопа, там есть вот такая функция:

/**
     * @param array $data
     *
     * @return array
     */
    public function prepareArray(array $data)
    {
        if (empty($data['customer'])) {
            $data['customer'] = $data['customer_username'];
        }

        $data['status'] = '<span style="color:#' . $data['color'] . ';">' . $data['status'] . '</span>';
        unset($data['color']);

        if (isset($data['cost'])) {
            $data['cost'] = $this->ms2->formatPrice($data['cost']);
        }
        if (isset($data['cart_cost'])) {
            $data['cart_cost'] = $this->ms2->formatPrice($data['cart_cost']);
        }
        if (isset($data['delivery_cost'])) {
            $data['delivery_cost'] = $this->ms2->formatPrice($data['delivery_cost']);
        }
        if (isset($data['weight'])) {
            $data['weight'] = $this->ms2->formatWeight($data['weight']);
        }

        $data['actions'] = array(
            array(
                'cls' => '',
                'icon' => 'icon icon-edit',
                'title' => $this->modx->lexicon('ms2_menu_update'),
                'action' => 'updateOrder',
                'button' => true,
                'menu' => true,
            ),
            array(
                'cls' => array(
                    'menu' => 'red',
                    'button' => 'red',
                ),
                'icon' => 'icon icon-trash-o',
                'title' => $this->modx->lexicon('ms2_menu_remove'),
                'multiple' => $this->modx->lexicon('ms2_menu_remove_multiple'),
                'action' => 'removeOrder',
                'button' => true,
                'menu' => true,
            ),
            /*
            array(
                'cls' => '',
                'icon' => 'icon icon-cog actions-menu',
                'menu' => false,
                'button' => true,
                'action' => 'showMenu',
                'type' => 'menu',
            ),
            */
        );

        return $data;
    }
Вот это нас не интересует:

if (empty($data['customer'])) {
            $data['customer'] = $data['customer_username'];
        }

        $data['status'] = '<span style="color:#' . $data['color'] . ';">' . $data['status'] . '</span>';
        unset($data['color']);

        if (isset($data['cost'])) {
            $data['cost'] = $this->ms2->formatPrice($data['cost']);
        }
        if (isset($data['cart_cost'])) {
            $data['cart_cost'] = $this->ms2->formatPrice($data['cart_cost']);
        }
        if (isset($data['delivery_cost'])) {
            $data['delivery_cost'] = $this->ms2->formatPrice($data['delivery_cost']);
        }
        if (isset($data['weight'])) {
            $data['weight'] = $this->ms2->formatWeight($data['weight']);
        }
Значит оставляем примерно так:
/**
     * @param array $data
     *
     * @return array
     */
    public function prepareArray(array $data){
        $data['actions'] = array(
            array(
                'cls' => '',
                'icon' => 'icon icon-edit',
                'title' => $this->modx->lexicon('scrapx.action_parse'),
                'action' => 'getScraping',
                'button' => true,
                'menu' => true,
            ),
        );

        return $data;
    }
Но вот это $data['actions'] тоже надо откуда то взять в процессоре… Блин как все запутано. Может я вообще нет так делаю?:)
Изучая исходники минишопа я понял что рендер для кнопок находится в js/mgr/misc/ms2.utils.js
Вот он:
miniShop2.utils.renderActions = function (value, props, row) {
    var res = [];
    var cls, icon, title, action, item = '';
    for (var i in row.data.actions) {
        if (!row.data.actions.hasOwnProperty(i)) {
            continue;
        }
        var a = row.data.actions[i];
        if (!a['button']) {
            continue;
        }

        icon = a['icon'] ? a['icon'] : '';
        if (typeof(a['cls']) == 'object') {
            if (typeof(a['cls']['button']) != 'undefined') {
                icon += ' ' + a['cls']['button'];
            }
        }
        else {
            cls = a['cls'] ? a['cls'] : '';
        }
        action = a['action'] ? a['action'] : '';
        title = a['title'] ? a['title'] : '';

        item = String.format(
            '<li class="{0}"><button class="btn btn-default {1}" action="{2}" title="{3}"></button></li>',
            cls, icon, action, title
        );

        res.push(item);
    }

    return String.format(
        '<ul class="minishop2-row-actions">{0}</ul>',
        res.join('')
    );
};
Но что с ним дальше делать непонятно…
Да, вы совершенно правильно поняли. У вас сейчас вывод товара оформлен в чанке tpl.msProducts.row. Для того чтобы вам вывести товары с другим html оформлением вам нужно создать, например, чанк tpl.msProducts.row2 и там все оформить так как нужно. В сниппете пишете
[[!msProducts?
&tpl = `tpl.msProducts.row2`
]]
Ну и остальные параметры сниппета как вам нужно.
Можно конечно по какому-то признаку на феноме через
{if $...} {/if}
Сделать, но это уже немного другая история и думаю оно вам не надо.