diff --git a/CHANGELOG b/CHANGELOG index 7ae031e04..4cd91841f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ Version 1.0rc to be released - Fixed ticket #19: CHtml::activeCheckBox does not work as expected when unchecked (Qiang) - Added CHtml::checkBoxList(), CHtml::radioButtonList() (Qiang) - Added CHtml::activeCheckBoxList() and CHtml::activeRadioButtonList() (Qiang) +- Added support for persistent page state (Qiang) Version 1.0b October 24, 2008 ----------------------------- diff --git a/framework/web/CClientScript.php b/framework/web/CClientScript.php index 25c3753ee..34f8c1f58 100644 --- a/framework/web/CClientScript.php +++ b/framework/web/CClientScript.php @@ -18,6 +18,14 @@ */ class CClientScript extends CComponent { + /** + * Name of the hidden field storing persistent page states. + */ + const STATE_INPUT_NAME='YII_PAGE_STATE'; + /** + * The HTML code for the hidden field storing persistent page states. + */ + const STATE_INPUT_FIELD=''; /** * @var boolean whether JavaScript should be enabled. Defaults to true. */ @@ -34,6 +42,8 @@ class CClientScript extends CComponent private $_scripts=array(); private $_bodyScriptFiles=array(); private $_bodyScripts=array(); + private $_pageStates; + /** * Constructor. @@ -96,6 +106,12 @@ class CClientScript extends CComponent $html2.=CHtml::script(implode("\n",$this->_bodyScripts))."\n"; } + if(($states=$this->savePageStates())!=='') + { + $input=''; + $output=str_replace(self::STATE_INPUT_FIELD,$input,$output); + } + if($html!=='') $output=preg_replace('/(.*?)(<\\/head\s*>)/is','$1'.$html.'$2',$output,1); @@ -312,4 +328,80 @@ class CClientScript extends CComponent { return isset($this->_bodyScripts[$id]); } + + /** + * Returns a persistent page state value. + * @param string the state name + * @param mixed the value to be returned if the named state is not found + * @return mixed the page state value + * @see setPageState + */ + public function getPageState($name,$defaultValue=null) + { + if($this->_pageStates===null) + $this->loadPageStates(); + return isset($this->_pageStates[$name])?$this->_pageStates[$name]:$defaultValue; + } + + /** + * Saves a persistent page state value. + * A page state value will remain accessible when the page is submitted + * via a post action. + * @param string the state name + * @param mixed the page state value + * @param mixed the default page state value. If this is the same as + * the given value, the state will be removed from persistent storage. + * @see getPageState + */ + public function setPageState($name,$value,$defaultValue=null) + { + if($this->_pageStates===null) + $this->loadPageStates(); + if($value===$defaultValue) + unset($this->_pageStates[$name]); + else + $this->_pageStates[$name]=$value; + + $params=func_get_args(); + $this->_controller->recordCachingAction('clientScript','setPageState',$params); + } + + /** + * Loads page states from a hidden input. + */ + protected function loadPageStates() + { + if(isset($_POST[self::STATE_INPUT_NAME]) && !empty($_POST[self::STATE_INPUT_NAME])) + { + if(($data=base64_decode($_POST[self::STATE_INPUT_NAME]))!==false) + { + if(extension_loaded('zlib')) + $data=@gzuncompress($data); + if(($data=Yii::app()->getSecurityManager()->validateData($data))!==false) + { + $this->_pageStates=unserialize($data); + return; + } + } + } + $this->_pageStates=array(); + } + + /** + * Saves page states as a base64 string. + */ + protected function savePageStates() + { + if($this->_pageStates===null) + $this->loadPageStates(); + if(empty($this->_pageStates)) + return ''; + else + { + $data=Yii::app()->getSecurityManager()->hashData(serialize($this->_pageStates)); + if(extension_loaded('zlib')) + $data=gzcompress($data); + return base64_encode($data); + } + } } diff --git a/framework/web/CController.php b/framework/web/CController.php index 2a025fc59..bf4e383ea 100644 --- a/framework/web/CController.php +++ b/framework/web/CController.php @@ -222,7 +222,7 @@ class CController extends CBaseController if($this->_dynamicOutput) { - $output=preg_replace_callback('/<###tmp(\d+)###>/',array($this,'replaceDynamicOutput'),$output); + $output=preg_replace_callback('/<###dynamic-(\d+)###>/',array($this,'replaceDynamicOutput'),$output); $this->_dynamicOutput=null; } @@ -499,7 +499,7 @@ class CController extends CBaseController public function renderDynamic($callback) { $n=count($this->_dynamicOutput); - echo "<###tmp$n###>"; + echo "<###dynamic-$n###>"; $params=func_get_args(); array_shift($params); $this->renderDynamicInternal($callback,$params); @@ -703,6 +703,35 @@ class CController extends CBaseController $filter->filter($filterChain); } + /** + * Returns a persistent page state value. + * @param string the state name + * @param mixed the value to be returned if the named state is not found + * @return mixed the page state value + * @see setPageState + * @see CHtml::statefulForm + */ + public function getPageState($name,$defaultValue=null) + { + return $this->getClientScript()->getPageState($name,$defaultValue); + } + + /** + * Saves a persistent page state value. + * A page state value will remain accessible when the page is submitted + * via a post action. It requires that the form is generated using {@link CHtml::statefulForm}. + * @param string the state name + * @param mixed the page state value + * @param mixed the default page state value. If this is the same as + * the given value, the state will be removed from persistent storage. + * @see getPageState + * @see CHtml::statefulForm + */ + public function setPageState($name,$value,$defaultValue=null) + { + $this->getClientScript()->setPageState($name,$value,$defaultValue); + } + /** * Generates pagination information. * This method can be used to generate pagination information given item count diff --git a/framework/web/helpers/CHtml.php b/framework/web/helpers/CHtml.php index 73e1cbc1b..20b5c7bc9 100644 --- a/framework/web/helpers/CHtml.php +++ b/framework/web/helpers/CHtml.php @@ -140,6 +140,22 @@ class CHtml return self::tag('form',$htmlOptions,false,false); } + /** + * Generates a stateful form tag. + * A stateful form tag is similar to {@link form} except that it renders an additional + * hidden field for storing persistent page states. You should use this method to generate + * a form tag if you want to access persistent page states when the form is submitted. + * @param mixed the form action URL (see {@link normalizeUrl} for details about this parameter.) + * @param string form method (e.g. post, get) + * @param array additional HTML attributes. + * @return string the generated form tag. + */ + public static function statefulForm($action='',$method='post',$htmlOptions=array()) + { + return self::form($action,$method,$htmlOptions)."\n" + .'
'.CClientScript::STATE_INPUT_FIELD.'
'; + } + /** * Generates a hyperlink tag. * @param string link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag.