State Widget ?
#1
Posted 29 October 2008 - 09:08 AM
Would it be possible to have a kind of state widget we could manually place inside a form. You would assign it a name and when it is rendered it will request its "state" information from the controller using the assigned name. So you could do something like the following in the controller
$this->saveState('stateWidgetName','attributeName', 'value')
The view would contain
<com:StateWidget name="stateWidgetName" />
When rendered the view would request the state from the controller. On a web page the state would be a hidden field with the serialized value of the state or if cache available a cacheid. The field would be named in a way that it could be identified when it was returned, so a controller could call
$this->getState('stateWidgetName','attributeName','defaultValue')
to retrieve an object from the state.
Thoughts ?
NZ
#2
Posted 29 October 2008 - 09:14 AM
#3
Posted 29 October 2008 - 09:41 AM
For example you have a page with 2 forms, one is an opinion poll (inside a form) the other is a form containing some screen to edit a database record. When the opinion poll gets submitted it does not need to know anything about the database record that is being edited so it may not have any state widget contained inside the form. But the database record form could have state information, so the developer added a state widget inside that form.
So you do not really have to add any specific logic there can only be (at most) one state inside each form. When a call is made to retrieve a value from the state the request parameters can be examined for a predefined name and the value be extracted from there, if the parameter does not exist then null is returned. Hmm so the get would actually be : $this->getState('attributeName','defaultValue')
#4
Posted 29 October 2008 - 09:53 AM
#5
Posted 29 October 2008 - 10:06 AM
You are correct in thinking that if I wanted to return to that very page then every form on the page would require the same state information and every form would need the state widget defined with the same name.
But either way (the nice thing is) it is still a choice ..
#6
Posted 29 October 2008 - 10:14 AM
#7
Posted 29 October 2008 - 10:47 AM
Maybe a better widget would be a StatefulForm widget ? Which would combine the rendering of the form and the hidden field. The basic operation of this form would be then like prado's viewstate - all StatefulForms' use a common map to persist their state information and the getState / setState would be used to access this map. You can avoid rendering the "state" information in the form by using the CHtml::form syntax.
#8
Posted 29 October 2008 - 10:52 AM
#9
Posted 29 October 2008 - 12:05 PM
The TStateFormatter is pretty much a copy from prado without the encryption or validation handling. It should also decide whether the state data should be cached or not.
<?php
class BStatefulForm extends CWidget {
private static $_dataMap = false;
const NAME='__yiiViewState';
/**
* Returns a statemap from the post or creates a new state map
*/
public static function getStateMap() {
if (self::$_dataMap===false) {
// check to see if state was sent via request
if (isset($_POST[BStatefulForm::NAME])) {
$data = TStateFormatter::unserialize($_POST[BStatefulForm::NAME]);
if ($data===false) {
self::$_dataMap = new CMap;
}
else {
self::$_dataMap = $data;
}
}
else {
self::$_dataMap = new CMap;
}
}
return self::$_dataMap;
}
/**
* Called by beginWidget
*/
public function init() {
echo CHtml::form($this->action='', 'post', $this->htmlOptions);
if (self::$_dataMap!==false)
echo CHtml::hiddenField(BStatefulForm::NAME, TStateFormatter::serialize(self::$_dataMap));
}
/**
* Called by endWidget
*/
public function run() {
echo "</form>";
}
private $action=false;
private $htmlOptions=array();
public function setAction($action) {
$this->action=$action;
}
public function setHtmlOptions($htmlOptions) {
$this->htmlOptions=$htmlOptions;
}
}
/**
This needs to be enhanced to provide more security
*/
class TStateFormatter
{
/**
* @param mixed state data
* @return string serialized data
*/
public static function serialize($data)
{
// TODO encrypt, use cache if availble
$str = serialize($data);
if(extension_loaded('zlib'))
$str=gzcompress($str);
return base64_encode($str);
}
/**
* @param TPage
* @param string serialized data
* @return mixed unserialized state data, null if data is corrupted
*/
public static function unserialize($data)
{
$str=base64_decode($data);
if(extension_loaded('zlib'))
$str=@gzuncompress($str);
if($str!==false)
{
$data = unserialize($str);
return $data;
}
return false;
}
}
?>
Usage controller
$stateMap =BStatefulForm::getStateMap();
if (!isset($stateMap['nextNumber'])) {
$stateMap['nextNumber']=0;
}
else {
$stateMap['nextNumber']+=1;
}
usage page
<com:BStatefulForm action={array()}>
<%= BStatefulForm::getStateMap()->itemAt('nextNumber') %>
<%= CHtml::submitButton("Next") %>
</com:BStatefulForm>
The result is an incrementing number, if multiple forms exist on the same page then all forms numbers' are incremented.
The encryption & validation handling is why I thought this code should be part of a controller or maybe the application
nz
#10
Posted 29 October 2008 - 12:13 PM
So basically if we want to keep state, the corresponding form has to be generated using this form widget, right? Looks good to me. Could you come up with more practical use cases?
#11
Posted 29 October 2008 - 12:56 PM
A practical case would be like a form wizard were you have multiple steps to complete the form, but all the information ends up in a single CActiveRecord. So you need to persist the data from post to post. Another role it could fill is to check for concurrent record modification.
nz
#12
Posted 30 October 2008 - 10:07 AM
#13
Posted 30 October 2008 - 10:31 AM
Or security - lets say a person is editing there own user profile and the unique identifier field value is included on a hidden field on the form - a smart hacker could simply change the value of this field to whatever and hit save overwriting maybe an admin account or something
nz
#14
Posted 30 October 2008 - 11:25 AM
Another example (of state usage) is preventing duplicate posts - this could be done using session information but it is also easily confused if the user has multiple browser windows open
nz
#15
Posted 31 October 2008 - 01:22 PM
It will be nice to have the requested ReST to compare the next page that everything is fine and not to request the same ReST again.
I think this is something important in Web 2.0.
#17
Posted 31 October 2008 - 05:02 PM
Saving that make your session state growth a lot. It's just an use case...
Sorry my bad english
Sebas
#19
Posted 31 October 2008 - 09:06 PM
Use CHtml::statefulForm() to render a form supporting persistent page state. And use CController::getPageState()/setPageState() to access persistent page state.
Let me know if you encounter any problem.

Help












