Batch Create (Tabular Input) In Multimodel Form

Hello everyone,

I’ve read almost all guides and docs conserning tabular input, but I still can’t figure out how to make it work finally (a lot of work done, so I don’t really ask global and abstract questions).

Need to say, I don’t use extensions like multimodelform, because they are still little buggy and don’t fulfill all my needs (such as “AT LEAST ONE” row should contain info, for example). The only thing I use is: esaverelatedbehavior . It really makes my life easier.

So, what is allready done:

Application model:

  • id

  • last_name

  • … 50+ fields

AppGoodsServices model:

  • id

  • goods_and_services

  • percentage

  • applicant_id

Relations are application HAS_MANY goods/servcies; goods/services BELONGS_TO application (under applicant_id).

My controller:


public function actionCreate()

	{

		$model=new Application;


		$this->performAjaxValidation($model);


		if(isset($_POST['Application']))

		{

			$model->attributes=$_POST['Application'];

            

            if(isset($_POST['AppGoodsServices']))

            {

                $model->goodsservices = $_POST['AppGoodsServices']; 

            }    

            if ($model->saveWithRelated('goodsservices'))

            {

                $this->redirect(array('view', 'id' => $model->id));

            } else {$model->addError('goodsservices', 'Error occured while saving goods/services.'); }

         }       

                


		$this->render('create',array(

			'model'=>$model,

		));

	}


	public function actionUpdate($id)

	{

		$model=$this->loadModel($id);


		$this->performAjaxValidation($model);


		if(isset($_POST['Application']))

		{

			$model->attributes=$_POST['Application'];

			if(isset($_POST['AppGoodsServices']))

            {

                $model->goodsservices = $_POST['AppGoodsServices']; 

            }    else {

                

            }

            if ($model->saveWithRelated('goodsservices'))

            {

                $this->redirect(array('view', 'id' => $model->id));

            } else {$model->addError('goodsservices', 'Error occured while saving goods/services.'); }

		}


		$this->render('update',array(

			'model'=>$model,

		));

	}

_form.php:




// fields fields fields

<div class="goodsservices">

        <?php

        $index = 0;

        foreach ($model->goodsservices as $id => $goods):

            $this->renderPartial('goods/_form', array(

                'model' => $goods,

                'index' => $id,

                

            ));

            $index++;

        endforeach;

        ?>

    </div>

// fields fields fields



and goods/_form.php


<div style="margin-bottom: 20px;  width:100%; clear:left;" class="crow">

    <div class="row" style="float: left;">

        <?php echo CHtml::activeLabelEx($model, '['.$index.']goods_and_services'); ?>

        <?php echo CHtml::activeTextField($model, '['.$index.']goods_and_services', array('size' => 30, 'maxlength' => 150)); ?>

        <?php echo CHtml::error($model, '['.$index .']goods_and_services'); ?>

    </div>

 

    <div class="row" style="float: left;">

        <?php echo CHtml::activeLabelEx($model, '['.$index .']percentage'); ?>

        <?php echo CHtml::activeTextField($model, '['.$index.']percentage', array('size' => 5)); ?>

        <?php echo CHtml::error($model, '['.$index.']percentage'); ?>

    </div>

    <div style="clear: both;"></div>

    <div class="row">

        <?php echo CHtml::link('Delete', '#', array('onclick' => 'deleteGoods(this, '.$index.'); return false;'));

        ?>

    </div>

</div>

<div style="clear: both;"></div>

<?php

Yii::app()->clientScript->registerScript('deleteGoods', "

function deleteGoods(elm, index)

{

    element=$(elm).parent().parent();

    /* animate div */

    $(element).animate(

    {

        opacity: 0.25, 

        left: '+=50', 

        height: 'toggle'

    }, 500,

    function() {

        /* remove div */

        $(element).remove();

    });

}", CClientScript::POS_END);

All this stuff NOW shows me all related goods in actionUpdate (I’ve MANUALLY ADDED SOME TEST ITEMS INTO DB WITH REQUIRED applicant_id) and I can remove any be clicking DELETE.

[b]My problem is how to show the first empty row on actionCreate and a button near "ADD MORE ROW" to dynamically add some rows to save it all in a batch?

One thing also is that we need that for some "children" we need at least one row to be added. Hope for your help, thank you![/b]

MultiModelForm has worked well for me despite its limitations (and “at least one row” isn’t one of them if you check around). But, YMMV… Tabular input is notoriously hard in Yii1 so perhaps you can explore Yii2 if your project constraints allow it; it seems to be much better. Good luck.

Hello JFReyes :)

Yes, you are right here. I can say that it is the most hard thing I’ve encountered with, while working on Yii. I’m afraid I can’t change horses in midstream, cause my deadline for the project is the 10th of May and I really need to do something with my batch create.

So, my question remains actual. Any help? :unsure:

Could you please tell me more about this? I need a fallback option if I still not able to implement it all by myself. I tried to solve it, but after days and nights of struggling - rejected this solution.

I seem to remember an idea about using javascript and ajax that would do this.

Ajax to post the update/create and javascript to generate the new <input>. The naming had to be an HTML array[]

Hello jkofsky :)

Yes, but my problem is how to implement it considering that at least one row must be filled for some items. May be, somehow in beforeValidate or something like that?

I made one row mandatory in the controller:




...

if(MultiModelForm::validate($modelLine,$validLineitems,$deleteLineitems)) {

	if(empty($validLineitems)) {

		Yii::app()->user->setFlash('error', '<strong>Add a line item!</strong>');

	}

	else {

		if($modelHeader->save()) {

... rest of code



Notice that I don’t use MultiModelForm::save() but rather validate first and if everything is ok then proceed to save the models.

Hmmm. I will try to test it today. Could you please look through this topic from time to time if I had some more questions?

Thank you!

Possibility of using the javascript that generates the new row to check if previous row has a value before generation.

Quick question, how are you differentiating between creating a new record and updating an existing one? Also, how do you know which records to delete from your database if you are removing deleted elements from the DOM? Just asking because I had that kind of problem and sort of solved it by prepending stuff like "add_" and "delete_" to my array keys and checking those in my controller.

This may work, but for solving another kind of problem :) It still doesn’t do any stuff to prevent submitting form without any row filled

That’s what I said. the Java Script would have to prevent submission without row filled in. Also if you are doing two models in one form, the model rules could set errors and prevent saving of the base model as well as the related model.


function actionCreate() {

   ...

   $valid = $model1->validate();

   $valid = $model2 && $valid;

   if($valid) {

      $model1->save(false);

      $model2->model1_id = $model1->id;

      $model2->save(false);

      ...

   }

   ...

}

I guess I’ve misunderstood you in your previous post.

Since my knowledges in js are pretty poor, I’m trying to go this way atm:


	public function actionUpdate($id)

	{

		Yii::import('ext.multimodelform.MultiModelForm');

        

        $model=$this->loadModel($id);

        $goods = new AppGoodsServices;

        

        $validatedGoods = array();

	// $this->performAjaxValidation($model);


		if(isset($_POST['Application']))

		{

			$model->attributes=$_POST['Application'];

            $masterValues = array ('applicant_id'=>$model->id);

            

            if(MultiModelForm::validate($goods,$validatedGoods,$deleteGoods))

            {

                if(empty($validatedGoods)) 

                {

                    Yii::app()->user->setFlash('error', '<strong>Add a line item!</strong>');

                    echo 1;

                } else

                {

                    if(MultiModelForm::save($goods,$validatedGoods,$deleteGoods,$masterValues) && $model->save())

                    {

                        $this->redirect(array('view','id'=>$model->id));

                    }

                }

            }

		}


		$this->render('update',array(

			'model'=>$model,

            'goods'=>$goods,

            'validatedGoods' => $validatedGoods,

		));

	}

Is that what you are speaking about, mate? I tried to implement JFReyes advice. By the way, would it be possible to proceed more (about 7-10) related models which also need tabular input in this way in the same form?

P.S. Sorry for the mess in my code. Can’t make it look as it is in my post

Sure, I’ll do my best :)