Many to Many

Hi,

This is one of the life savers.

Is it possible to extend this solution for cgridview?

I want to display more than one columns. And allow user to mark check boxes from filtered list.

Any help is highly appreciated.

Dear Friend

At the outset, I want to stress that in this thread people have used checkbox list to choose data from the related table.That means that they are deaing with minimal set of records.

In the following implementation, I used gridview to pick the records. I also disabled pagination.

With pagination, I am finding difficult to get the things done.That means that it is useful in situations where

we have minimal set of relational records.

The scenario:

I have three models representing many_many relation.

1.Meeting(table:meeting fields:id,name)

2.Member(table:member fields:id,name,age,sex,qulification)

3.MeetingMember(table:meeting_member fields:id,meeting_id,member_id).

Each meeting is attended by many members. We are taking the attendance and storing it in join table(meeting_member).

MeetingController.php




public function actionCreate()

	{

		$model=new Meeting;

                $member=new Member('search');

		$member->unsetAttributes();  

		if(isset($_GET['Member']))

			$member->attributes=$_GET['Member'];

		

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

		{

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

			

			if($model->save())

			{

				if(isset($_POST['member']) && $_POST['member']!=="" )

				{	$members=explode(",",$_POST['member']);

					

					foreach($members as $member)

					{

						$meme=new MeetingMember;

						$meme->meeting_id=$model->id;

						$meme->member_id=$member;

						$meme->save(false);

					}

				}				

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

			}

		}


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

			'model'=>$model,

			'member'=>$member,

		));

	}




public function actionUpdate($id)

	{

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

		$member=new Member('search');

		$member->unsetAttributes();  

		if(isset($_GET['Member']))

			$member->attributes=$_GET['Member'];


		

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

		{

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

			if($model->save())

			{	if(isset($_POST['member']) && $_POST['member']!=="" )

				{	$members=explode(",",$_POST['member']);


				MeetingMember::model()->deleteAll("meeting_id=:mid",array(":mid"=>$model->id));                                              

					foreach($members as $member)

					{

						$meme=new MeetingMember;

						$meme->meeting_id=$model->id;

						$meme->member_id=$member;

						$meme->save(false);

					}

				}		

			

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

			}

		}


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

			'model'=>$model,

			'member'=>$member,

		));

	}




in both create.php and update.php the following modifications done.




php echo $this->renderPartial('_form', array('model'=>$model,'member'=>$member));//$model and $member is carried to the view. 



In the view we are placing the grid inside the form,the grid has checkbox column.when user checks the rows,

primary key values of member records are placed as a string inside a text field. For that we have registered a script.When we are submitting the form, string of ids is converted to array in controller.

views/meeting/_form.php




<div class="form">


<?php $form=$this->beginWidget('CActiveForm', array(

	'id'=>'meeting-form',

	'enableAjaxValidation'=>false,

)); ?>


	<p class="note">Fields with <span class="required">*</span> are required.</p>


	<?php echo $form->errorSummary($model); ?>


	<div class="row">

		<?php echo $form->labelEx($model,'name'); ?>

		<?php echo $form->textField($model,'name',array('size'=>60,'maxlength'=>64)); ?>

		<?php echo $form->error($model,'name'); ?>

	</div>

<?php $this->widget('zii.widgets.grid.CGridView', array(

	'id'=>'member-grid',

	'dataProvider'=>$member->search(),

	'filter'=>$member,

	'enablePagination'=>false,

	'columns'=>array(

		'id',

		'name',

		'age',

		'email',

		'qualification',

		array(

		'class'=>'CCheckBoxColumn',

		'selectableRows'=>2, //enabling multiselect option

		'checked'=>function($data,$row)use($model){return in_array($data->id,$model->participants);},

		),//this ensures that during update corresponding records get checked.

		array(

			'class'=>'CButtonColumn',

		),

	),

)); ?>


<!--placing a textfield to collect the ids from member-grid-->

<?php echo CHtml::encode('members');echo "</br>";?>

<?php echo CHtml::textField('member','',array('id'=>'member'));?>

	<div class="row buttons">

		<?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>

	</div>


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


</div><!-- form -->

<?php

Yii::app()->clientScript->registerScript('collectMember','

$("#member").val('.CJavaScript::encode($model->participants).');

$("body").on("change","input[type=\"checkbox\"]",function(){


	var value=$("#member-grid").yiiGridView("getChecked","member-grid_c5");

	

	$("#member").val(value);

	});




');

/**

 *member-grid_c5 indicates that the checkBoxColumn is in the sixth column of the grid.

 */



The following things in the model, ensures that during the update the virtual property participant gets filled up.It helps in updating the checkboxes in the member grid.

Model(Meeting.php)




public $participants=array();


public function afterfind()

	{

		$this->participants=Yii::app()->db->createCommand()

			->select("member_id")

			->from("meeting_member")

			->where("meeting_id=".$this->id)

			->queryColumn();

			

		return parent::afterFind();

	}



Model(Member.php)




public function search()

{

	$criteria=new CDbCriteria;

	$criteria->compare('id',$this->id);

	$criteria->compare('name',$this->name,true);

	$criteria->compare('age',$this->age);

	$criteria->compare('email',$this->email,true);

	$criteria->compare('qualification',$this->qualification,true);


	return new CActiveDataProvider($this, array(

			'criteria'=>$criteria,

			'pagination'=>false,// disabled the pagination.

		));

}



I hope this would help you a bit.

The problem is here is that by disabling pagination we can not deal with large set of data.

Regards.

Dear Seenivsan,

Thank you very much for such a detailed guide. It contains tricks to use in some other places also.

Isn’t this solution work for php 5.2?

The line,


'checked'=>function($data,$row)use($model){return in_array($data->id,$model->participants);},

gave me an error.

Netbeans says;

"Language feature not compatible with PHP version indicated in project settings

Variable $row seems to be unused in its scope "

Thank you again for the help.

Dear Friend

That is because of the fact that anonymous functions are not supported by the php version you are currently using.

Then we can do the following.

Declare a method in the Model Meeting.php




public function checkParticipation($data,$row)

{

	return in_array($data->id,$this->participants);		

}	



Then the configuration of checkBoxColumn look like this.




    array(

		'class'=>'CCheckBoxColumn',

		'selectableRows'=>2,

		'checked'=>array($model,'checkParticipation'),

	),




I hopt it would solve the issue.

Regards.

Dear Seenivasan,

Thank you very much for the quick response. I spent whole day before bothering you again. But still I am not succeeded.

I have tables Activity, Family and FamilyHasActivity. I need to link the family to activities.

I just amend your codes as follows.

01.Controller (ActivityController.php)




public function actionFamilyallocation($id) {

        $model = $this->loadModel($id, 'Activity');

        $family = new Family('search');

        $family->unsetAttributes();

        if (isset($_GET['Family']))

            $family->attributes = $_GET['Family'];




        if (isset($_POST['Activity'])) {

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

            if ($model->save()) {

                if (isset($_POST['family']) && $_POST['family'] !== "") {

                    $families = explode(",", $_POST['family']);


                    FamilyHasActivity::model()->deleteAll("activity_id=:mid", array(":mid" => $model->id));

                    foreach ($families as $family) {

                        $meme = new FamilyHasActivity;

                        $meme->activity_id = $model->id;

                        $meme->family_id = $family;

                        $meme->save(false);

                    }

                }


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

            }

        }


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

            'model' => $model,

            'family' => $family,

        ));

    }



  1. View (familyallocation.php)



<?php echo $this->renderPartial('_formfamilyallocation', array('model'=>$model,'family'=>$family));

//$model and $member is carried to the view.  ?>



  1. View (_formfamilyallocation.php)



<div class = "form">


<?php $form = $this->beginWidget('CActiveForm', array(

'id' => 'activiy-form',

 'enableAjaxValidation' => false,

));

?>


<p class="note">Fields with <span class="required">*</span> are required.</p>


    <?php echo $form->errorSummary($model); ?>


<div class="row">

    <?php echo $form->labelEx($model, 'name'); ?>

<?php echo $form->textField($model, 'name', array('size' => 60, 'maxlength' => 64)); ?>

<?php echo $form->error($model, 'name'); ?>

</div>

<?php

$this->widget('zii.widgets.grid.CGridView', array(

'id' => 'family-grid',

 'dataProvider' => $family->search(),

 'filter' => $family,

 'enablePagination' => false,

 'columns' => array(

'children_id',

 'relationship',

 'name',

 array(

'class' => 'CCheckBoxColumn',

 'selectableRows' => 2, //enabling multiselect option

'checked'=>array($model,'checkFamilyallocations'),


 ), //this ensures that during update corresponding records get checked.

array(

'class' => 'CButtonColumn',

 ),

 ),

));

?>


<!--placing a textfield to collect the ids from member-grid-->

<?php echo CHtml::encode('families');

echo "</br>"; ?>

<?php echo CHtml::textField('family', '', array('id' => 'family')); ?>

<div class="row buttons">

<?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>

</div>


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


</div><!-- form -->

<?php

Yii::app()->clientScript->registerScript('collectFamily', '

$("#member").val('.CJavaScript::encode($model->familyallocations).');

$("body").on("change","input[type=\"checkbox\"]",function(){


        var value=$("#family-grid").yiiGridView("getChecked","family-grid_c3");

        

        $("#family").val(value);

        });




');

/**

 * member-grid_c5 indicates that the checkBoxColumn is in the sixth column of the grid.

 */



  1. Model (Activity.php)



public function checkFamilyallocations($data,$row)

{

        return in_array($data->id,$this->familyallocations);         

}  


    public $familyallocations = array();


    public function afterfind() {

        $this->familyallocations = Yii::app()->db->createCommand()

                ->select("family_id")

                ->from("family_has_activity")

                ->where("activity_id=".$this->id)

                ->queryColumn();


        return parent::afterFind();

    }



  1. Model (Family.php)



 public function search() {

        $criteria = new CDbCriteria;


        $criteria->compare('id', $this->id);

        $criteria->compare('children_id', $this->children_id);

        $criteria->compare('name', $this->name, true);

        $criteria->compare('photo', $this->photo, true);

        $criteria->compare('relationship', $this->relationship);

        $criteria->compare('head', $this->head);

        $criteria->compare('sex', $this->sex, true);

        $criteria->compare('birthday', $this->birthday, true);

        $criteria->compare('mstatus', $this->mstatus, true);

        $criteria->compare('occupation', $this->occupation);

        $criteria->compare('education', $this->education);

        $criteria->compare('living_status', $this->living_status);

        $criteria->compare('monthly_income', $this->monthly_income);


        return new CActiveDataProvider($this, array(

                    'criteria' => $criteria,

            'pagination'=>false,

                ));

    }



The view loaded well but after save it redirected to following error.

PHP warning

mb_strlen() expects parameter 1 to be string, array given

C:\xampp\htdocs\mis\framework\validators\CStringValidator.php(85)


85             $length=mb_strlen($value, $this->encoding ? $this->encoding : Yii::app()->charset);

Can you kindly check what I did wrong here?

Dear Friend

Did you attach the property familyAllocations with any Validation Rules?

Dear Friend,

Problem solved.

The problem caused by an extension used to record the change log. It’s setup for objects instead of array.

Thank you very much for the great support and guidance.

There is a extension called ‘selgridview’ and I am trying to use it for the pagination problem. I’ll update the result here.

I use this code, but there is a issue when I save. The action not produce any effect.

I missing anything?