Изменяем форму заказа minishop2

За год работы с минишопом я понял одну вещь — никто не знает как просто и быстро кастомизировать стандартную форму заказов. Для меня это довольно частая задача, по этому соберу тут несколько приемов которыми сам пользуюсь:



Подготовка


  1. Создаем плагин с любым названием и помещаем в него код:
    switch ($modx->event->name) {
        case 'msOnManagerCustomCssJs':
            if ($page != 'orders') return;
            	$modx->controller->addLastJavascript(MODX_ASSETS_URL.'components/customField/default.js');
        break;
    }
    Выставляем ему событие msOnManagerCustomCssJs
  2. По пути /assets/components/customField/ создаем файл default.js в нем то мы и будем работать

1. Редактируемая цена доставки


Это очень частая просьба и даже есть метод который гуглится, но этот метод завязан на изменении исходников minishop2, я же покажу как сделать без изменения исходников, да еще и с кнопкой которая пересохранит заказ


Ext.ComponentMgr.onAvailable('minishop2-window-order-update', function(){
	this.fields.items[0].items[2].items[1].items[1].xtype = 'textfield'; //меняем тип поля с diaplayfield на textfield
	//добавляем кнопку "сохранить"	
	this.fields.items[0].items[2].items[1].items.push({
            	xtype: 'button',
             	name: 'no_rec', 
             	fieldLabel: '', 
             	anchor: '100%',
             	text: '<i class="icon icon-refresh"></i> Сохранить',
             	handler: function() {
             	    var url = window.location.href; //сохраняем текущий урл
             	    Ext.getCmp('minishop2-window-order-update').submit(); //сохраняем текущий заказ
             	    setTimeout(function() {
             	        window.location.href = url; //перезагружаем страницу по предыдущему урлу что откроет перед нами это же окно заказа
             	    }, 1000)
             	}
	});
});
Также если мы просто нажмем на кнопку сохранить в самом низу — все сохранится, кнопка под доставкой нужна для удобства и вы можете ее не создавать.
Это самый примитивный способ сохранения, если кому не лень поковыряться в исходниках и сказать как запустить updateOrder — скажите, мне — лень

Теперь нам нужен плагин, который будет реагировать на изменение заказа и пересчитывать общую цену, создаем его:
switch ($modx->event->name) {
    case 'msOnBeforeUpdateOrder': //событие
        $old = $modx->getObject('msOrder', $id); //старый заказ
        $oldDeliveryCost = $old->get('delivery_cost'); //старая цена доставки
        
        $newDeliveryCost = $object->get('delivery_cost'); //новая цена доставки
        
        if ($oldDeliveryCost != $newDeliveryCost) { //если были изменения - меняем cost
        	$tmp1 = $newDeliveryCost;
        	$tmp2 = $object->get('cart_cost');
        	$object->set('cost', $tmp1+$tmp2);
        }
    break;
}
Вот собственно и все, едем дальше.

2. Добавляем email заказчика в таблицу адреса




Принцип:
Т.к. в форму заказа не приходит email пользователя, а только его id — мы будем слать ajax запрос на наш контроллер и получать email посредством api modx.

По пути assets/components/customField/ создаем файл action.php, в него помещаем вот этот код:
<?php
define('MODX_API_MODE', true);
require_once dirname(dirname(dirname(dirname(__FILE__)))) . '/index.php';

$modx->getService('error','error.modError');
$modx->setLogLevel(modX::LOG_LEVEL_ERROR);
$modx->setLogTarget('FILE');
if ($_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest') {
  	$modx->sendRedirect($modx->makeUrl($modx->getOption('site_start'),'','','full')); //отфильтровываем все не ajax запросы
}

if (!$modx->hasPermission('msorder_view')) { //проверяем имеет ли текущий пользователь права на просмотр заказа
    die('Доступ запрещен!');
}

$id = (int) $_POST['id'];

$user = $modx->getObject('modUser', $id);
if (!$user) {
    die('ошибка');
}

die($user->Profile->get('email'));

В наш default.js помщаем следующий код:

Ext.ComponentMgr.onAvailable('minishop2-window-order-update', function(){
	var user_id = this.record.user_id; //получаем пользователя текущего заказа
	changeFields(this, user_id);
	function changeFields (self) {
	    self.fields.items[2].items[1].items[1].columnWidth = 0.3; //меняем размеры колонок
	    self.fields.items[2].items[1].items[0].columnWidth = 0.3;
	    self.fields.items[2].items[1].items.push({ //добавляем новый input diplayfield
	       columnWidth: 0.3,
	       border: false,
	       layout: 'form',
	       items: [
	            {
	                anchor: '100%',
	                fieldLabel: 'Email',
	                name: 'non_rec',
	                xtype: 'displayfield',
	                html: 'Еще не загружено',
	                id: 'emailFieldAjax' //создаем alias для того, чтобы в дальнейшем получить объект через getCmp
	            }    
	       ]
	    });

        Ext.Ajax.request({ //делаем ajax запрос на наш контроллер
           url: '/assets/components/customField/action.php',
           success: function(resp) {
               Ext.getCmp('emailFieldAjax').setValue(resp.responseText); //получаем то, что вернул наш контроллер
           },
           failure: function(resp) {
               Ext.Msg.alert('Внимание', 'Ошибка ajax запроса');
           },
           params: { id: user_id }
        });
        
	}
});

3. Кастомизируем вкладку адреса по полной


Это самый объемный пункт будет и его я уже описывал в статье про «ГдеПосылка» но там все же было в другом контексте, по этому повторюсь и тут. Поля будут полностью нативными и редактируемыми

База данных
Создаем нужные нам поля в базе данных, для этого открываем дополнение Console и вставляем туда этот код:
$fields = array('inn', 'fio_two', 'tel_two', 'ogrn'); //массив с именами нужных нам полей

foreach ($fields as $item) {
  $table = $modx->getTableName('msOrderAddress'); //название класса в таблицу которого будем записывать
  $sql = 'ALTER TABLE ' . $table . '  ADD `'.$item.'` VARCHAR(255) NULL;'; 
  $modx->exec($sql);
}
Теперь нам надо сделать так, чтобы miniShop2 подхватил наши поля, для этого нам надо расширить его карту, для расширения карт у minishop2 есть собственный функционал плагинов, по пути core/components/orderCustomField/ создаем index.php со следующим содержимым:
<?php
return array(
    'map' => array(
        'msOrderAddress' => require_once 'msOrderAddress.inc.php',
    ),
);
В этой же папке создаем msOrderAddress.inc.php который описывает сущности наших полей. Внимательно посмотрите на него, если вы видите массивы не в первый раз, то думаю как переделать его под себя для вас будет очевидно :)
<?php 
return array(
    'fields' => array (
        'inn' => NULL,
	'fio_two' => NULL,
        'tel_two' => NULL,
	'ogrn' => NULL,
      ),
    'fieldMeta' => array(
        'inn' => array (
          'dbtype' => 'varchar',
          'precision' => '255',
          'phptype' => 'string',
          'null' => true,
        ),
      

        'fio_two' => array (
          'dbtype' => 'varchar',
          'precision' => '255',
          'phptype' => 'string',
          'null' => true,
        ),


        'tel_two' => array (
          'dbtype' => 'varchar',
          'precision' => '255',
          'phptype' => 'string',
          'null' => true,
        ),


        'ogrn' => array (
          'dbtype' => 'varchar',
          'precision' => '255',
          'phptype' => 'string',
          'null' => true,
        ),

    ),
);
Далее открываем опять консоль и подключаем наш index.php в плагины минишопа:
if ($miniShop2 = $modx->getService('miniShop2')) {
    $miniShop2->addPlugin('customField', '{core_path}components/orderCustomField/index.php');
}
По технической части все, на данном этапе ваши поля полностью функционируют и вы можете получить/установить/обновить/сохранить их через MODX API. Для того, чтобы на странице оформления заказа у вас они заработали просто создайте инпут с name того поля, который добавляли в бд :
<inpu type="text" name="fieldName" placeholder="fieldName" />
Добавляем на вкладку адреса:
На самом деле абсолютно не важно на какую вкладку вы добавляете поля, добавленные в таблицу msOrderAddress, самое важное чтобы их name имел префикс addr_ (в противном случае сохраняться они не будут) процедура добавления ничем не отличается от пункта 2, единственное что если вы хотите чтобы поле было изменяемым — используйте xtype: 'textfield', если информативным xtype: 'displayfield', я приведу пример разметки из самого первого скриншота, а вы там думаю уже сами разберетесь что к чему:

Ext.ComponentMgr.onAvailable('minishop2-window-order-update', function(){


    var face = { //разметка типа покупателя (юр/физ/ип)
    	border: false,
    	layout: 'column',
    	items: [
    		{
    			border: false,
    			columnWidth: 0.5,
    			autoHeight: true,
    			layout: 'form',
    			items: {
    				xtype: 'displayfield',
    				name: 'types',
    				fieldLabel: 'Лицо',
    				anchor: '100%'
    			}
    		},
            {
                border: false,
                columnWidth: 0.5,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'textfield',
                    name: 'addr_gift',
                    fieldLabel: 'Подарок',
                    anchor: '100%'
                }
            }
    	],
    	autoHeight: true,
    }

    var last_adress = { //разметка второго адреса
        border: false,
        layout: 'column',
        autoHeight: true,
        items: [
            {
                border: true,
                columnWidth: 1,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'displayfield',
                    name: '',
                    fieldLabel: 'Другой получатель',
                    anchor: '100%',
                }
            }, {
                border: false,
                columnWidth: 0.4,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'textfield',
                    name: 'addr_receiver_last',
                    fieldLabel: 'Получатель',
                    anchor: '100%',
                }
            }, {
                border: false,
                columnWidth: 0.3,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'textfield',
                    name: 'addr_phone_last',
                    fieldLabel: 'Телефон',
                    anchor: '100%',
                }
            }, {
                border: false,
                columnWidth: 0.3,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'textfield',
                    name: 'addr_email_last',
                    fieldLabel: 'Email',
                    anchor: '100%'
                }
            }
        ],
    }

    var ur_data = { //разметка юр. данных
        border: false,
        layout: 'column',
        autoHeight: true,
        items: [
            {
                border: true,
                columnWidth: 1,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'displayfield',
                    name: '',
                    fieldLabel: 'Юридические данные',
                    anchor: '100%',
                }
            }, {
                border: false,
                columnWidth: 0.5,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'textfield',
                    name: 'addr_ur_name',
                    fieldLabel: 'Название организации',
                    anchor: '100%',
                }
            }, {
                border: false,
                columnWidth: 0.5,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'textfield',
                    name: 'addr_ur_adress',
                    fieldLabel: 'Адрес организации',
                    anchor: '100%',
                }
            }, {
                border: false,
                columnWidth: 0.4,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'textfield',
                    name: 'addr_ur_inn',
                    fieldLabel: 'ИНН',
                    anchor: '100%'
                }
            }, {
                border: false,
                columnWidth: 0.3,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'textfield',
                    name: 'addr_ur_kpp',
                    fieldLabel: 'КПП',
                    anchor: '100%'
                }
            }, {
                border: false,
                columnWidth: 0.3,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'textfield',
                    name: 'addr_ur_ogrnip',
                    fieldLabel: 'ОГРНИП',
                    anchor: '100%'
                }
            }
        ],
    }

    var rs_data = { //разметка расчетного счета
        border: false,
        layout: 'column',
        autoHeight: true,
        items: [
            {
                border: true,
                columnWidth: 1,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'displayfield',
                    name: '',
                    fieldLabel: 'Расчетный счет',
                    anchor: '100%',
                }
            }, {
                border: false,
                columnWidth: 0.4,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'textfield',
                    name: 'addr_ur_bank',
                    fieldLabel: 'Банк',
                    anchor: '100%',
                }
            }, {
                border: false,
                columnWidth: 0.3,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'textfield',
                    name: 'addr_ur_bik',
                    fieldLabel: 'БИК',
                    anchor: '100%',
                }
            }, {
                border: false,
                columnWidth: 0.3,
                autoHeight: true,
                layout: 'form',
                items: {
                    xtype: 'textfield',
                    name: 'addr_ur_rs',
                    fieldLabel: 'Расчетный счет',
                    anchor: '100%'
                }
            }
        ],
    }
	//если заказчик не физ лицо, добавляем разметку юр данных в начало списка
    if (this.record.types != 'Физ.лицо') {
        this.fields.items[2].items.unshift(rs_data);
        this.fields.items[2].items.unshift(ur_data);
    }

	//если присутствует хоть одно заполненное поле второго адреса - добавляем его в разметку
    if (!this.record.addr_email_last && !this.record.addr_phone_last && !this.record.addr_receiver_last) {

    } else {
        this.fields.items[2].items.unshift(last_adress);
    }
    this.fields.items[2].items.unshift(face);
});

Заключение


Таким образом (по последнему примеру) мы видим что можно очень гибко управлять полями (к примеру если покупатель не юр лицо, то блоки с юр данными не отобразятся). Я надеюсь что эта статья приоткроет тайну extjs и ms2 для многих новичков и мы навсегда закроем вопросы о том как изменять/добавлять форму заказов minishop2. А я тем временем готовлю дополнение, которое позволит управлять полями заказа minishop2 с помощью графического интерфейса :)
Pavel Zarubin
22 апреля 2018, 02:16
61
1 232
+41

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

Pavel Zarubin
22 апреля 2018, 05:49
2
+14
P.s. всегда когда подходите к расширеню разметки extjs но не знаете куда добавить те или иные поля в какую — то вкладку, просто распечатайте объект field и уже в консоле разбирайтесь что куда
console.log(this.fields);
Там достаточно все очевидно, для того, чтобы понять человеку который только-только начал постигать js
Василий Наумкин
22 апреля 2018, 09:02
+7
Отличная заметка!

Было бы неплохо отформатировать её в markdown и прислать в docs.modx.pro
Руслан Сафин
22 апреля 2018, 15:00
+1
Отличная заметка! Спасибо!
Сам как то мучился с extJs, а тут все отлично описано
Андрей
04 июля 2018, 12:00
+1
Добавлял поля по инструкции, наткнулся на ошибку, сохранялось только последнее поле, оказалось что приведенная структура массива для файла msOrderAddress.inc.php не совсем верна:

Вот так все работает:
return array(
  'fields' => array(
    'field_name_1' => NULL,
    'field_name_2' => NULL,
  ),
  'fieldMeta' => array(
    'field_name_1' => array (
      'dbtype' => 'varchar',
      'precision' => '255',
      'phptype' => 'string',
      'null' => true,
    ),
    'field_name_2' => array (
      'dbtype' => 'varchar',
      'precision' => '255',
      'phptype' => 'string',
      'null' => true,
    )
  )
);
    Pavel Zarubin
    09 июля 2018, 00:16
    0
    Если вас не затруднит, напишите пожалуйста в чем отличие вашей структуры от моей:)
    Pavel Zarubin
    09 июля 2018, 00:17
    0
    А, точно, увидел ошибку, извиняюсь, сейчас поправлю
Алексей
04 июля 2018, 13:10
+1
Павел, спасибо!
    Pavel Zarubin
    09 июля 2018, 00:15
    0
    Всегда пожалуйста! :)