Сохранение связных данных (one-to-many) в одной форме

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
pioneer
Сообщения: 136
Зарегистрирован: 2013.03.10, 23:27

Сохранение связных данных (one-to-many) в одной форме

Сообщение pioneer »

Всем привет!
Задачка довольно простая, уверен многие из вас уже сталкивались с подобной проблемой, интересует поиск наиболее удачного и простого решения.
В общем, есть 2 таблицы с отношением "один ко многим", необходимо в рамках одной формы создания/редактирования сохранить данные для обоих моделей.
Раньше в Yii 1 я делал это так:
- в контроллере:

Код: Выделить всё

public function actionCreate() {
    $model = new Order();
	
    // пришли данные из основной модели
    if ($orderData = Yii::app()->request->getPost('Order')) {
        $model->setAttributes($orderData);
        
        $orderItems = Yii::app()->request->getPost('orderItems', array());
		
	if ($model->save()) {
	    // если пришли данные из связной модели - сохраняем их
	    if ($orderItems)
		$model->saveOrderItems($orderItems);
	    //...
	}
    }
	
    // ...
    $this->render('create', ['model'=>$model]);
}

public function actionUpdate($id) {
    $model = $this->loadModel($id);
	
    // пришли данные из основной модели
    if ($orderData = Yii::app()->request->getPost('Order')) {
        $model->setAttributes($orderData);
        
	$orderItems = Yii::app()->request->getPost('orderItems', array());
		
	if ($model->save()) {
	    // если пришли данные из связной модели - сохраняем их
	    if ($orderItems)
		$model->saveOrderItems($orderItems);
	    //...
	}
    }
	
    // ...
    $this->render('update', ['model'=>$model]);
}
- в модели:

Код: Выделить всё

public function relations()
{
    return array(
        'orderItems' => array(self::HAS_MANY, 'OrderItem', 'order_id'),
        //...other relations
    );
}

public function saveOrderItems($orderItems) {
    if (!$orderItems)
        return false;

    $transaction = Yii::app()->getDb()->beginTransaction();

    try {
        foreach ($orderItems as $item) {
            $decodedArray = json_decode($item, true);
            if (isset($decodedArray['id']) && !empty($decodedArray['id']))
                $orderItem = OrderItem::model()->findByPk($decodedArray['id']);
            else
                $orderItem = new OrderItem();

            $orderItem->position_title = $decodedArray['position_title'];
            $orderItem->position_price = $decodedArray['position_price'];
            //...
            $orderItem->order_id = $this->id;
            $orderItem->save();
        }

        $transaction->commit();
        return true;
    } catch (Exception $e) {
        $transaction->rollback();
        return false;
    }
}
- в представлении:

Код: Выделить всё

<?php $form = $this->beginWidget('bootstrap.widgets.TbActiveForm', array('id'=>'order-form')); ?>

<!-- some code here -->

<?php if(!$model->isNewRecord && $model->orderItems): ?>
    <?php $i=1; ?>
    <?php foreach($model->orderItems as $orderItem): ?>
        <div id="order-item-group" style="width: 50%; border: 1px solid #ccc; border-radius: 3px; padding: 10px; margin-bottom: 10px;" data-tnum="<?=$i?>">
            <?= CHtml::hiddenField('orderItemId', $orderItem->id) ?>
            <div class="row-fluid control-group <?= $orderItem->hasErrors('position_title') ? 'error' : ''; ?>">
                <?= CHtml::activeLabelEx($orderItem, 'position_title') ?>
                <?= CHtml::textField('position_title', $orderItem->position_title, ['class'=>'span5', 'maxlength'=>13]) ?>
            </div>
            <div class="row-fluid control-group <?= $orderItem->hasErrors('position_price') ? 'error' : ''; ?>">
                <?= CHtml::activeLabelEx($orderItem, 'position_price') ?>
                <?= CHtml::textField('position_price', $orderItem->position_price, ['class'=>'span5', 'maxlength'=>13]) ?>
            </div>
            <!-- other attributes... -->
            <div style="text-align: right;"><?= CHtml::button('Удалить', ['id'=>'delete_order_item_group', 'class'=>'btn btn-danger btn-sm', 'data-id'=>$orderItem->id]) ?></div>
         </div>
         <?php $i++; ?>
     <?php endforeach; ?>
<?php else: ?>
    <?php $orderItem = OrderItem::model() ?>
    <div id="order-item-group" style="width: 50%; border: 1px solid #ccc; border-radius: 3px; padding: 10px; margin-bottom: 10px;" data-tnum="0">
        <?= CHtml::hiddenField('orderItemId'); ?>
        <div class="row-fluid control-group">
            <?= CHtml::activeLabelEx($orderItem, 'position_title') ?>
            <?= CHtml::textField('position_title', '', ['class'=>'span7', 'maxlength'=>13]) ?>
        </div>
        <div class="row-fluid control-group">
            <?= CHtml::activeLabelEx($orderItem, 'position_price') ?>
            <?= CHtml::textField('position_price', '', ['class'=>'span7', 'maxlength'=>13]) ?>
        </div>
        <!-- other attributes... -->
        <div style="text-align: right;"><?= CHtml::button('Удалить', ['id'=>'delete_order_item_group', 'class'=>'btn btn-danger btn-sm', 'data-id'=>'']) ?></div>
    </div>
<?php endif; ?>
<?= CHtml::button('Добавить еще одну позицию', ['id'=>'add_order_item_group', 'class'=>'btn']) ?>

<!-- some code here -->

<a id="save-btn" class="btn btn-primary"><?= $model->isNewRecord ? 'Добавить : 'Сохранить'; ?></a>

<?php $this->endWidget(); ?>

<script type="text/javascript">
    $(function() {
        $('#save-btn').on('click', function(e) {
            e.preventDefault();
            setOrderItems();
            $('form#order-form').submit();
        });
            
        function setOrderItems() {
            $('div#order-item-group').each(function() {
                let id = $(this).find('#orderItemId').val(),
                     position_title = $(this).find('#position_title').val(),
                     position_price = $(this).find('#position_price').val(),
                     obj;

                if (position_title && position_price) {
                    obj = JSON.stringify({
                        'id': id,
                        'position_title': position_title,
                        'position_price': position_price
                    });
                    
                    $(this).append('<input id="orderItems" type="hidden" name="orderItems[]" value="">');
                    $(this).find('#orderItems').val(obj);
                }
            });
        }
        
        $('#add_order_item_group').on('click', function() {
            let i = $(this).prev().attr('data-tnum'),
                 orderItemHtml = $(this).prev('#order-item-group[data-tnum=\"'+i+'\"]').clone();
            orderItemHtml.find('#orderItemId').val('');
            orderItemHtml.find('#position_title').val('');
            orderItemHtml.find('#position_price').val('');
            orderItemHtml.attr('data-tnum', i+1);
            $('#order-item-group[data-tnum=\"'+i+'\"]').after(orderItemHtml);
        });

        $(document).delegate('#delete_order_item_group', 'click', function() {
            let orderItemGroup = $(this).parents('#order-item-group'), id = ordeItemGroup.find('#orderItemId').val();
            if (id) {
                $.ajax({
                    type: 'post',
                    url: '/ajax/deleteOrderItem',
                    data: {id: id, '<?= $csrfTokenName; ?>': '<?= $csrfToken; ?>'},
                    success: function() {
                        orderItemGroup.hide('slow', function() {
                            $(this).remove();
                        });
                    }
                });
            } else {
                orderItemGroup.hide('slow', function() {
                    $(this).remove();
                });
            }
        });
}
</script>
В общем, если вкратце, то решение сводилось к парсингу средствами JS строчек со связными данными (1 строка = 1 модель = 1 запись в БД), их упорядочиванием в JSON и передачу на сервер с последующей обработкой и сохранением. Я думаю, что это не лучший вариант, хотя и рабочий.
Пожалуйста, поделитесь хорошим и элегантным решением как это у себя в проекте реализовали вы (желательно на примере Yii2).
Спасибо!
unknownby
Сообщения: 747
Зарегистрирован: 2019.11.05, 16:34
Контактная информация:

Re: Сохранение связных данных (one-to-many) в одной форме

Сообщение unknownby »

pioneer писал(а): 2022.04.15, 16:58 Пожалуйста, поделитесь хорошим и элегантным решением как это у себя в проекте реализовали вы (желательно на примере Yii2).
Спасибо!
Посмотри это :D
https://github.com/unclead/yii2-multiple-input
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Сохранение связных данных (one-to-many) в одной форме

Сообщение SiZE »

Дмитрий Елисеев написал давно уже статью про композитные формы, можете почитать, осмыслить https://elisdn.ru/blog/111/yii2-composite-forms
Ответить