CActiveForm.checkBoxList

How to default select the option in a CActiveForm.checkBoxList? INFO: I’ve a table items with a MANY_MANY relation to the model category

So when an item belongs to the category 1 and 2, it should select the category 1 and 2 by default

Item model:


public function relations()

{

return array(

	'categories' => array(self::MANY_MANY, 'Category', 'item_category(item_id, category_id)'),

);

}

View:


<?php echo $form->checkBoxList($model, 'categories', CHtml::listData(Category::model()->findAll(), 'id_category', 'category')); ?>

nobody?

You can use getter/setter to access/set the relation, for example getItemCategories()/setItemCategories(). The methods should only return/set an array of category IDs. So in the getter you loop over $this->categories and collect the IDs in an array. In the setter you receive an array as $value. So you need to write some logic to create/delete the links to Categories. Then you can use itemCategories just like a usual attribute for your checkBoxList.

you have a working example?

Try this (not tested):


public function getItemCategories()

{

    $ids=array();

    foreach($this->categories as $c)

        $ids[]=$c->id;

    return $ids;

}


public function setItemCategories($values)

{

    // 1. delete all related rows in item_category (use a AR for this table)


    // 2. create new links:

    foreach($values as $id)

    {

        $r=new ItemCategory;

        $r->category_id=$id;

        $r->item_id=$this->id;

    }

}


// in your view:

echo $form->checkBoxList($model,'itemCategories',

    CHtml::listData(Category::model()->findAll(), 'id_category', 'category')

);

Finally i found some time to look into this problem, but i still dont get you solution…

where to put those getter and setter? and why is this so complicated… is it so weird that you want to select the related items?

Put the getter/setter into your model class. Yii can not know, how to convert related models to values for a checkbox list and vice versa. You have to do that manually.

Hi Mike,

This post has been extremely helpful so far. The checkboxes populate correctly with already saved data, which is just wonderful! But I can’t get the form to save the changes I make to a record. I tweaked your code to work with my DB, with tables: order, disclaimer, orderdisclaimer (joining table). Here is the code that I used:




	public function getOrderDisclaimers()

	{

		$ids=array();

		foreach($this->disclaimers as $c)

			$ids[]=$c->id;

		return $ids;

	}

	

	public function setOrderDisclaimers($values)

	{

		// 1. delete all related rows in item_category (use a AR for this table)

		

	

		// 2. create new links:

		foreach($values as $id)

		{

			$r=new Orderdisclaimer;

			$r->disclaimerId=$id;

			$r->orderId=$this->id;

		}

	}



and in the form view:




	<div class="checkboxgroup">

	<table>

		<?php  

		// Calls getOrderDisclaimers in Order Model

		echo $form->checkBoxList($model,'orderDisclaimers',	CHtml::listData(Disclaimer::model()->findAll(), 'id', 'disclaimerText'), array(

            'separator'=>'',

            'template'=>'<tr><td>{input}</td><td>{label}</td></tr>'

            ) 	);

		?>

	</table>

	</div>

Am I missing something? How can I get the checkboxes to save into the orderdisclaimer table?

Thanks so much for your help!

Did you declar ‘orderDisclaimers’ as safe attribue?

And a hint: Please use the "< >" button on top of the editor to markup your code. It will be much more readable.

Thanks for the tip. My form still isn’t saving the checked items. Also, what would the syntax be for deleting the the related orderdisclaimer records and where would it go? Your previous post says “(use a AR for this table),” but I’m afraid I just started playing with Yii a couple days ago and I don’t know what you mean.

I know I’m not the only one out there who has been looking for a simple solution to this, and it’s been very difficult to find information that a beginner can understand. I’d like to post my entire working solution once things are working so others can see how this all works.

Here is my entire Order model. Do you notice anything else I should change?





<?php


/**

 * This is the model class for table "order".

 *

 * The followings are the available columns in table 'order':

 * @property integer $id

 * @property integer $statustypeid

 * @property integer $memberid

 * @property integer $masterisciid

 * @property string $fullisci

 * @property integer $brandid

 * @property string $market

 * @property string $adcoordinator

 * @property string $jobnumber

 * @property string $voiceoverchanges

 * @property string $graphicchanges

 * @property string $notes

 * @property string $customdisclaimer

 * @property string $customdisclaimerdesc

 * @property string $customattribute

 * @property string $customattributedesc

 * @property string $phone

 * @property string $phonedesc

 * @property string $url

 * @property string $urldesc

 * @property integer $statecodeid

 * @property string $confirmationsignature

 * @property string $confirmationipaddress

 * @property string $orderdateandtime

 * @property string $uploadedspotdateandtime

 * @property string $approvalspoturl

 * @property string $approvalchangenotes

 * @property string $approvalsignature

 * @property string $approveddateandtime

 * @property string $approvedipaddress

 * @property string $adaccreditorchangenotes

 * @property string $adaccreditorsignature

 * @property string $adaccreditordateandtime

 * @property string $adaccreditoripaddress

 * @property string $delivereddateandtime

 * @property string $billeddateandtime

 * @property integer $iscoop

 * @property string $coopnotes

 * @property integer $islocked

 * @property integer $isarchived

 *

 * The followings are the available model relations:

 * @property Statustype $statusType

 * @property Member $member

 * @property Masterisci $masterIsci

 * @property Brand $brand

 * @property Statecode $stateCode

 * @property Ordercampus[] $ordercampuses

 * @property Orderdisclaimer[] $orderdisclaimers

 */

class Order extends CActiveRecord

{

	//-------------------------- Added by JJ --------------------------------

	// Thanks to Mike on the following forum post.

	// http://www.yiiframework.com/forum/index.php?/topic/22726-cactiveformcheckboxlist/page__p__110969__hl__checkBoxLi+t+Model#entry110969

	

	public function getOrderDisclaimers()

	{

		$ids=array();

		foreach($this->disclaimers as $c)

			$ids[]=$c->id;

		return $ids;

	}

	

	public function setOrderDisclaimers($values)

	{

		// 1. delete all related rows in orderdisclaimer (use a AR for this table)

		

		

		// 2. create new links:

		foreach($values as $id)

		{

			$r=new Orderdisclaimer;

			$r->disclaimerid=$id;

			$r->orderid=$this->id;

		}

	}

	

	public function getOrderAttributes()

	{

		$ids=array();

		foreach($this->attributes as $c)

			$ids[]=$c->id;

		return $ids;

	}

	

	public function setOrderAttributes($values)

	{

		// 1. delete all related rows in orderdisclaimer (use a AR for this table)

		

		

		// 2. create new links:

		foreach($values as $id)

		{

			$r=new Orderattribute;

			$r->attributeid=$id;

			$r->orderid=$this->id;

		}

	}

	

	//-----------------------------------------------------------------------




	/**

	 * Returns the static model of the specified AR class.

	 * @return Order the static model class

	 */

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	/**

	 * @return string the associated database table name

	 */

	public function tableName()

	{

		return 'order';

	}


	/**

	 * @return array validation rules for model attributes.

	 */

	public function rules()

	{

		// NOTE: you should only define rules for those attributes that

		// will receive user inputs.

		return array(

			array('memberid, masterisciid, fullisci' , 'required'),

			array('statustypeid, memberid, masterisciid, brandid, statecodeid, iscoop, islocked, isarchived', 'numerical', 'integerOnly'=>true),

			array('fullisci, market, adcoordinator, jobnumber, customdisclaimerdesc, customattributedesc, phonedesc, url, urldesc, confirmationsignature, confirmationipaddress, approvalsignature, adaccreditorsignature', 'length', 'max'=>255),

			array('phone', 'length', 'max'=>30),

			array('approvedipaddress, adaccreditoripaddress', 'length', 'max'=>50),

			// The following rule is used by search().

			// Please remove those attributes that should not be searched.

			array('orderDisclaimers, orderAttributes, id, statustypeid, memberid, masterisciid, fullisci, brandid, market, adcoordinator, jobnumber, voiceoverchanges, graphicchanges, notes, customdisclaimer, customdisclaimerdesc, customattribute, customattributedesc, phone, phonedesc, url, urldesc, statecodeid, confirmationsignature, confirmationipaddress, orderdateandtime, uploadedspotdateandtime, approvalspoturl, approvalchangenotes, approvalsignature, approveddateandtime, approvedipaddress, adaccreditorchangenotes, adaccreditorsignature, adaccreditordateandtime, adaccreditoripaddress, delivereddateandtime, billeddateandtime, iscoop, coopnotes, islocked, isarchived', 'safe', 'on'=>'search'),

		);

	}


	/**

	 * @return array relational rules.

	 */

	public function relations()

	{

		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

		return array(

			'statusType' => array(self::BELONGS_TO, 'Statustype', 'statusTypeId'),

			'member' => array(self::BELONGS_TO, 'Member', 'memberId'),

			'masterIsci' => array(self::BELONGS_TO, 'Masterisci', 'masterIsciId'),

			'brand' => array(self::BELONGS_TO, 'Brand', 'brandId'),

			'stateCode' => array(self::BELONGS_TO, 'Statecode', 'stateCodeId'),

			'ordercampuses' => array(self::HAS_MANY, 'Ordercampus', 'orderId'),

			// -- Original --

			//'orderdisclaimers' => array(self::HAS_MANY, 'Orderdisclaimer', 'orderId'),

			//---- JJ Edits -------------------------------

			'disclaimers' => array( self::MANY_MANY, 'Disclaimer', 'orderdisclaimer(orderid, disclaimerid)'),

			'attributes' => array( self::MANY_MANY, 'Attribute', 'orderattribute(orderid, attributeid)'),

		);

	}


	/**

	 * @return array customized attribute labels (name=>label)

	 */

	public function attributeLabels()

	{

		return array(

			// ------------------------------

			'disclaimers' => 'Disclaimers',

			'attributes' => 'Attributes',

			

			

			

			// -------------------------------

			'id' => 'ID',

			'statustypeid' => 'Statustypeid',

			'memberid' => 'Memberid',

			'masterisciid' => 'Masterisciid',

			'fullisci' => 'Fullisci',

			'brandid' => 'Brandid',

			'market' => 'Market',

			'adcoordinator' => 'Adcoordinator',

			'jobnumber' => 'Jobnumber',

			'voiceoverchanges' => 'Voiceoverchanges',

			'graphicchanges' => 'Graphicchanges',

			'notes' => 'Notes',

			'customdisclaimer' => 'Customdisclaimer',

			'customdisclaimerdesc' => 'Customdisclaimerdesc',

			'customattribute' => 'Customattribute',

			'customattributedesc' => 'Customattributedesc',

			'phone' => 'Phone',

			'phonedesc' => 'Phonedesc',

			'url' => 'Url',

			'urldesc' => 'Urldesc',

			'statecodeid' => 'Statecodeid',

			'confirmationsignature' => 'Confirmationsignature',

			'confirmationipaddress' => 'Confirmationipaddress',

			'orderdateandtime' => 'Orderdateandtime',

			'uploadedspotdateandtime' => 'Uploadedspotdateandtime',

			'approvalspoturl' => 'Approvalspoturl',

			'approvalchangenotes' => 'Approvalchangenotes',

			'approvalsignature' => 'Approvalsignature',

			'approveddateandtime' => 'Approveddateandtime',

			'approvedipaddress' => 'Approvedipaddress',

			'adaccreditorchangenotes' => 'Adaccreditorchangenotes',

			'adaccreditorsignature' => 'Adaccreditorsignature',

			'adaccreditordateandtime' => 'Adaccreditordateandtime',

			'adaccreditoripaddress' => 'Adaccreditoripaddress',

			'delivereddateandtime' => 'Delivereddateandtime',

			'billeddateandtime' => 'Billeddateandtime',

			'iscoop' => 'Iscoop',

			'coopnotes' => 'Coopnotes',

			'islocked' => 'Islocked',

			'isarchived' => 'Isarchived',

		);

	}


	/**

	 * Retrieves a list of models based on the current search/filter conditions.

	 * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.

	 */

	public function search()

	{

		// Warning: Please modify the following code to remove attributes that

		// should not be searched.


		$criteria=new CDbCriteria;


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


		return new CActiveDataProvider($this, array(

			'criteria'=>$criteria,

		));

	}

}




Usually i would say “Read the fine manual”, as we can’t take you by then hand here to learn the basics and every case is a little different. But let’s make an exception as your case is easy: :)

You did not define orderAttributes a safe attribute. You must add a rule for it. In the simplest case this means, add a rule called ‘safe’ for it. Read more here:

http://www.yiiframework.com/doc/guide/1.1/en/form.model#securing-attribute-assignments

And by “use a AR” i meant: You need an ActiveRecord class for your link table. Which you already seem to have: Orderattribute. So just call Orderattribute::model()->deleteAll(‘orderId=’.$this->orderId); to delete all old records before you create the new ones. http://www.yiiframework.com/doc/guide/1.1/en/database.ar#deleting-record

You can even use a more sophisticated logic to only delete/create changed relations. But maybe i should not have said this as i know your next question now … ;)

You only declared it safe in scenario [b]search /b. If you don’t use other scenarios, you can add a rule ‘safe’ (wihtout ‘on’ !) to declare it as safe in all scenarios:




array('orderAttributes','safe'),



As this will be your next question: Consider a scenario as a nicer name for a set of rules. It’s answering the question: “In which situation can i change which attributes and which rules should apply?”. You should read this section (again):

http://www.yiiframew…1/en/form.model

All information is there. Even if it may be a bit hard to grasp first, scenarios are not really complicated.

Regarding the more sophisticated solution: This is left as practice for you, after you got the basic solution working ;). I would start by looking at PHP’s array_diff() function, then compare the current set of ids (getOrderAttributes()) with the new ones ($values).

EDIT: Looks like you removed your last reply, so guess you already found out what the problem was.

Yes, I did end up figuring out the “scenario” part and the setOrderDisclaimers is now being executed. I removed my last post shortly after posting it because I figured it out and I didn’t want to bug anyone.

Only now within the setOrderDisclaimers function, the $values always contains “1”, and no more ids no matter how many check boxes I select. I’ve tried several things to see if it would do any good, but to no avail. Any ideas?

Thank you!

Can you add a print_r($_POST); exit; on top of your controller action before you submit the form to find out what is actually posted?

I finally got my code to work! I have an order table, a disclaimer table, and a many to many relationship between the two, which means I need an orderdisclaimer table. I wanted to be able to display all the possible disclaimers with a checkbox next to them so the user could pick and choose which disclaimers were applicable to the order. I set up my tables with the appropriate comments in mysql that automatically assign relations in the models (based on Larry Ullman’s tutorial on how to set up tables, found here). Then I added the following code to the order model:





public function getOrderDisclaimers()

{

	$ids=array();

        foreach($this->disclaimers as $c)

                $ids[]=$c->id;

        return $ids;

}

        

public function setOrderDisclaimers($values)

{			

        // 1. delete all related rows in item_category (use a AR for this table)

        Orderdisclaimer::model()->deleteAll('orderid='.$this->id);

				

        // 2. create new links: 

        if(!empty($values)){

	  foreach($values as $id)

          {

                $r=new Orderdisclaimer;

                $r->disclaimerid=$id;

                $r->orderid=$this->id;

		$r->save(FALSE);

          }		

        }		

}

	



and to the order form view, I added the following code:





<div class="checkboxgroup">

<table>

  <?php  

  // Calls getOrderDisclaimers in Order Model

  echo $form->checkBoxList($model,'orderDisclaimers',     

    CHtml::listData(Disclaimer::model()->findAll(), 'id', 'disclaimerText'), 

    array('separator'=>'','template'=>'<tr><td>{input}</td><td>{label}</td></tr>'

  ) );

  ?>

</table>

</div>



Many thanks to Mike for his direction and help for such a newbie like me. I added an if statement to handle the form if no check boxes are selected. Mike, I also added the following line to your code:

$r->save(FALSE);

This line is what made things seem to work. Is this good coding? Or is there a better way to do it?

If it doesn’t work without, it means you have a validation error in that link model. So maybe it would be cleaner if you verify your rules in that model? To find the error you can try this:




if(!$r->save())

    print_r($r->errors);



You can also add a rule in your order model which verifies that the supplied list of ids are all valid. If you don’t, then evil users can create links to disclaimers which don’t exist. It may not do real harm to your app - but it’s creating inconsistency in your DB.

Thank you for this great thread. I am also a Yii beginner and faced with the exact same challenge. I was able to implement a working solution based on the information contained in this thread. Thank you again.

please follow this

http://www.yiiframework.com/extension/save-relations-ar-behavior

download demo

it works well for me

it automatically update delete relational records

Thanks for this great thread. Wondering whether it would be better to refactor the suggested solution of updating database tables in the setter methods. I’m new to Yii but in other systems I’ve worked on you wouldn’t want to do such a heavy weight operation in a setter method.

What do you think of adding an array property, $orderDisclamers, to the class and then overriding the save() method of the model class to properly handle saving this attribute? This would make the behavior more inline with existing attributes because if you set other attributes they do not immediately impact the database.