Tuesday, November 9, 2010

Posting Multiple of the Same model within a form

Recently, I was asked about posting multiple models of the same class within a form and how that can be accomplished.

The solution is to override the default name values for the form items to allow them to be presented in an array format, then in the controller, process each array item for the Class.

Lets say that I have a set of user bookmarks (Bookmark), and I want them to be able to update all the bookmarks from a single administrative page. This is a little rough, but it should explain the concept clearly:



In my actionGroupUpdate, I will pull in all the existing book marks and pass them to a general update view:
public function actionGroupUpdate()
{
    if ( isset($_POST['Bookmark']))
    {
       // process bookmarks 
       // They will be received as $_POST['Bookmark'][0] etc)
       foreach( $_POST['Bookmark'] as $posted_mark )
       { 
          // Add in appropriate logic and error checking, this is very very basic
          $model = Bookmark::model()->findByPk( $posted_mark['id'] );
          // or if we're adding new or deleted all the old , depends on your needs
          if ( $model === null )
          {
             $model = new Bookmark;
          }
          $model->attributes = $posted_mark;
          $model->save();
       }
       $this->redirect(array('bookmark/index'));
    }
    $models = Bookmark::model()->findByAttributes(array(
                 'owner_id'=>Yii::app()->user->id
                 ));
    $this->render('_groupUpdate', array('bookmarks'=>$models));
}

Then, in my _groupUpdate view, I tell it how to loop through the models and draw the forms, pulling in each model through a single shared _bookmarkForm view for clarity

<h1>Update your bookmarks</h1>
<?php 
  $i = 0;
  
  $form = $this->beginWidget('CActiveForm'); // add appropriate form options   

  foreach( $bookmarks as $mark ){ ?>
     $this->renderPartial('_bookmarkForm', array(
           'model'=>$mark,
           'form'=>$form, 
           'i'=>++$i);
  } 

  $this->endWidget();
?>

With the actual form in the _bookmarkForm view, we're going to display the form for one particular model, but override the name param of the htmlOptions array so that we can tell it that this is an array of those model types, not simply a single model of that type.

   <?php 
      // a little help keeping the name consistent
      $modelId = 'Bookmark['.$i.']';
      echo $form->hiddenField( $model, 'id', array('name'=>$modelId."[id]"));
   ?>
   
<?php echo $form->labelEx($model,'bookmark_name'); ?> <?php echo $form->textField($model,'bookmark_name',array( 'size'=>60, 'maxlength'=>256, 'name'=>$modelId."[bookmark_name]")); ?> <?php echo $form->error($model,'bookmark_name'); ?>
<?php echo $form->labelEx($model,'sortOrder'); ?> <?php echo $form->textField($model,'sortOrder',array( 'size'=>10, 'maxlength'=>10, 'name'=>$modelId."[sortOrder]")); ?> <?php echo $form->error($model,'sortOrder'); ?>

Hope that is helpful!

2 comments:

  1. For CJuiWidgets it only works in v1.1.5+

    Spent a few hours bashing my brain on that one before I found a hack solution and submitted a bug ticket.

    ReplyDelete
  2. Just read that it's possible to simplify things by adding the [$i] into the parameter name rather than explicitly setting the name, but I've not tried it yet.

    http://www.yiiframework.com/doc/guide/1.1/en/form.table

    ReplyDelete