checkBox affecting GET URL parameters

Hi,

Presume a basic CRUD list view.

I want to add a simple CHtml::checkBox or equivalent, that when changed (checked/unchecked) reloads the current URL, but with the addition of a parameter: withArchive=>$withArchive. If the parameter already exists it should be set to the new value $withArchive, and $withArchive is given to the view by the controller’s action.

The purpose of this is to present a simple checkBox that lets the user affect whether archived items in the database are to be displayed in the list or not, while at the same time indicating the current status of this option, by being a checkBox.

I could make it a simple link, but then I’d loose the indication. A checkBox is simply a very suitable control for this purpose.

I did try the blow code, but that submits a POST instead of reloads with a GET, which is expected. I haven’t found another way to do what I want easily.

<?php echo CHtml::checkBox(‘withArchive’, $withArchive, array(‘submit’=>’’,‘params’=>array(‘withArchive’=>$withArchive))); ?>

Alright, I’m past this issue now. I think I ended up with a really nice solution :)

In the view I put the following, which makes a nice checkBox that both indicates state and lets the user change the state of whether or not archived posts should be displayed:




<?php echo CHtml::checkBox('withArchive', $withArchive, array('id'=>'withArchive', 'submit'=>'', 'params'=>array('withArchive'=>!$withArchive))); ?>

<?php echo CHtml::label('Include archived posts', 'withArchive'); ?>



I then made a behavior that stores the state of archive viewing in a session variable, and provides methods for updating the value when we’re POSTed to, and retrieving the value easily:




class ArchiveAware extends CBehavior {

	private $sessionKey;

	

	public function attach($owner) {

		parent::attach($owner);


		$this->sessionKey = $this->owner->id . '.withArchive';

	}


	public function processWithArchive() {

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

			$withArchive = $_POST['withArchive'];

			$withArchive = (!$withArchive || $withArchive == 'false' ? false : true);


			$session = Yii::app()->session;

			$session[$this->sessionKey] = $withArchive;


			$this->owner->redirect(array($owner->route));

		}

	}


	public function getWithArchive() {

		$session = Yii::app()->session;

		return (isset($session[$this->sessionKey]) ? $session[$this->sessionKey] : false);

	}

}



(The check for ‘false’ in the POSTed value is because we get this via jQuery/JSON, which uses ‘true’ and ‘false’.)

Then in the controller, I attached this behavior so that it’s available for use in the controller’s actions:




	public function behaviors() {

		return array(

			'archiveAware'=>array(

			    'class' => 'application.behaviors.ArchiveAware'

			)

		);

	}



Then in each of the actions that I wanted this “archive toggling”, I added calls to processWithArchive() and getWithArchive() where needed, and also made sure to give the current $withArchive state to the view. Here’s an example of from the list action of a standard controller created by the Yii scaffolding:




	public function actionList()

	{

		$this->processWithArchive();


		$withArchive = $this->getWithArchive();

		$criteria=new CDbCriteria;


		$pages=new CPagination(Card::model()->withArchive($withArchive)->count($criteria));

		$pages->pageSize=self::PAGE_SIZE;

		$pages->applyLimit($criteria);


		$models=Card::model()->withArchive($withArchive)->findAll($criteria);


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

			'models'=>$models,

			'pages'=>$pages,

			'withArchive'=>$withArchive

		));

	}



I only had to add this to the admin and list actions since the other ones (create, show, update, delete) deals directly with one specific record, and they should handle both archived and non-archived posts.

You’ll note that in order for the withArchive state to be effective it needs to be applied to the database queries as well. I do this by using a parameterized scope named withArchive, which is set in the model in question. It looks like this:




	public function withArchive($archived = true) {

		if(!$archived) {

			$this->getDbCriteria()->mergeWith(array(

				'condition' => 'archive is FALSE'

			));

		}

		return $this;

	}



The cool thing about this approach is that the withArchive state is saved per controller (by the $sessionKey variable in the behavior), so if you do the above for multiple controllers (say one for Customers and one for Orders), they will have their own state correctly used as you move between the controllers’ views.

Also, since each controllers withArchive state is saved in the session, you can jump between the list and admin views, and they both share the same state. You don’t have to check “Include archived posts” every time you switch views.

BTW, would this be useful as a recipie in the cookbook?