Showing Related Many_Many Grid

I have an Exam model and a Questions model. They are joined by a MANY_MANY relation.




public function relations()

{

  return array(

    'questions' => array(self::MANY_MANY, 

                         	'Questions', 

                         	'tbl_exam_rel_question(exam_id, question_id)'),

  );

}



In the Exam view, I want to show a grid with all the Exam questions (and all the grid functionality like searching, etc). I have found some solutions to do this but I think they are not the correct way of doing it, there must be a better way.

For example, one webpage says to write in the view something like this:




$config = array();

$dataProvider = new CArrayDataProvider($rawData=$model->questions, $config);



But I do not like this because:

  • data provider is being created inside the view

  • it will not work for searching the grid (I think)

I think what I need to use is something similar as the grid that the Exam admin uses:




$this->widget(...

...

    'dataProvider'=>$model->search()

...



But the $model refers to Exam (and not questions). I have a Questions model that has its own search() method, but I think that if I use that method then ALL the questions are going to be displayed and not just the ones that correspond to that specific exam.

The solution must be very simple but I have a lack of yii experience, so please help me.

Thank you

Anyone?

Dear nkd

Let us consider an example.

I have a table author : id,name

I have a table book : id,name

I have then a join table book_author : id, b_id, a_id.

An author might have written many books. One book belong to many authors.

In author’s admin page we are going to display authors grid that is followed by books grid.

This is the controller action.

AuthorController.php




public function actionAdmin()

	{

		$author=new Author('search');

		$author->unsetAttributes(); 

 

		$book=new Book('search');

		$book->unsetAttributes(); 


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

			$author->attributes=$_GET['Author'];


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

			$book->attributes=$_GET['Book'];

            

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

			'author'=>$author,

			'book'=>$book,

		));

	}



By just copying the code of CGridView widget in admin.php of book views and pasting the code below the

authors grid will render the both the grids with all the functionalities intact.

But the challenge ahead is when user selects a row in authors grid, we have to display the books wriiten by him

in books grid.

To get the author id and utilize it in Book Model, We have to declare a virtual property in Book Model.

We have to utilize the relation between book and book_author.

We have to customize the search method in Book Model.

Book.php




class Book extends CActiveRecord

{ 

   public $authKey;

   public function rules()

	{

		return array(

			array('name', 'required'),

			array('name', 'length', 'max'=>256),


			//Make the authKey safe on search


			array('name,authKey,id', 'safe', 'on'=>'search'),

		);

	}


    public function relations()

	{

		return array(

			'bookAuthors' => array(self::HAS_MANY, 'BookAuthor', 'b_id'),

		);

	}


    public function search()

	{

	


	    $criteria=new CDbCriteria;


            $criteria->with=array('bookAuthors');

            $criteria->together=true;

            $criteria->group='bookAuthors.b_id';


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

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


            //Here catched authKey is utilized for adding condition to criteria.

            //We are utilizing the book_author.a_id for filtering books for particular author.


            if(isset($this->authKey)&& $this->authKey>0)

                    $criteria->addCondition("bookAuthors.a_id=$this->authKey");


		return new CActiveDataProvider($this, array(

			'criteria'=>$criteria,

			

		));

	}

}



This is what the admin.php in authors view look like.

admin.php




<?php




$this->breadcrumbs=array(

	'Authors'=>array('index'),

	'Manage',

);


$this->menu=array(

	array('label'=>'List Author', 'url'=>array('index')),

	array('label'=>'Create Author', 'url'=>array('create')),

);


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

$('.search-button').click(function(){

	$('.search-form').toggle();

	return false;

});

$('.search-form form').submit(function(){

	$.fn.yiiGridView.update('author-grid', {

		data: $(this).serialize()

	});

	return false;

});

$('#author-grid').on('click.book',$('tr'),function(){

	var authKey= $('#author-grid').yiiGridView('getChecked','author-grid_c3');

	authKey=authKey[0];

	$('#book-grid').yiiGridView('update',{data:{'Book[authKey]':authKey}});

	   });


");

?>


<h1>Manage Authors</h1>


<p>

You may optionally enter a comparison operator (<b>&lt;</b>, <b>&lt;=</b>, <b>&gt;</b>, <b>&gt;=</b>, <b>&lt;&gt;</b>

or <b>=</b>) at the beginning of each of your search values to specify how the comparison should be done.

</p>


<?php echo CHtml::link('Advanced Search','#',array('class'=>'search-button')); ?>

<div class="search-form" style="display:none">

<?php $this->renderPartial('_search',array(

	'model'=>$author,

)); ?>

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


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

	'id'=>'author-grid',

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

	'filter'=>$author,

	'columns'=>array(

		'id',

		'name',

		array(

			'class'=>'CButtonColumn',

		),

		array(

			'class'=>'CCheckBoxColumn',

		),   

	),

)); ?>


<h2> Get the Books of an Author</h2>


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

	'id'=>'book-grid',

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

	'filter'=>$book,

	'columns'=>array(

                array(

	               'name'=>'authKey',

	               'htmlOptions'=>array('width'=>'10px'),

	            ),

		'id',

		'name',

		array(

			'class'=>'CButtonColumn',

		),

		

	),

)); ?>




When we are looking at the admin.php, we have done three important modifications.

  1. Obviously we have two grids.

  2. We have added a checkBoxColumn to first grid(Author).

  3. We have added a bit of javascript.

    The purpose of this code is that when user selects a row it parses the

    author id and then it sends this id as a get parameter to update the book-grid.

    Another important thing is When user clicks the author-grid,

    book grid is updated with author id through authKey and when user clicks the book-grid

    it is not going to get the value from author id from author grid.It gets updated with its own filters.

    This is the code."author-grid_c3" indicates that checkBoxColumn is 4th coumn in the author grid.




$('#author-grid').on('click.book',$('tr'),function(){

	var authKey= $('#author-grid').yiiGridView('getChecked','author-grid_c3');

	authKey=authKey[0];

	$('#book-grid').yiiGridView('update',{data:{'Book[authKey]':authKey}});

	   });



  1. We have created a column for authKey in book-grid .The filter cell lively displays the row checked in author-grid. This column is optional.

I hope I helped a bit.

Regards.

Thank you very much! This information will help me a lot and I am sure it will also help many others.

I can see that you used the HAS_MANY property to achieve this, but is there a way to do something similar with the MANY_MANY attribute (because it really is a many_many relationship)?

Dear nkd

This is all about tables and models in my case.

Model:Author table:author columns:id,name.

Model:Book table:book columns:id,name.

Model:BookAuthor JOINtable:book_author columns:id,a_id,b_id.

a_id references to author.id

b_id references to book.id.

Author<=>Book Many::Many

Author=>BookAuthor Has::Many

Book=>BookAuthor Has::Many

The following relation is between Book and BookAuthor not between Book and Author.




public function relations()

        {      //This is relation between table book and join table book_author.

                return array(

                        'bookAuthors' => array(self::HAS_MANY, 'BookAuthor', 'b_id'),

                );

        }



Sorry for choosing confusing terms.

Regards.