Yii Framework Forum: State Widget ? - Yii Framework Forum

Jump to content

  • (2 Pages)
  • +
  • 1
  • 2
  • You cannot start a new topic
  • You cannot reply to this topic

State Widget ? Rate Topic: -----

#1 User is offline   notzippy 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 201
  • Joined: 06-October 08

Posted 29 October 2008 - 09:08 AM

Hi

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
0

#2 User is offline   qiang 

  • Yii Project Lead
  • Yii
  • Group: Yii Dev Team
  • Posts: 5,902
  • Joined: 04-October 08
  • Location:DC, USA

Posted 29 October 2008 - 09:14 AM

This would be like Prado's viewstate. However, the implementation is not trivial. The main reason is that a page could contain many forms. Using hidden field to store state information would mean placing the same hidden field in each form.

0

#3 User is offline   notzippy 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 201
  • Joined: 06-October 08

Posted 29 October 2008 - 09:41 AM

Kind of - I was thinking each state widget would store specific information according to the containing forms' purpose

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')

0

#4 User is offline   qiang 

  • Yii Project Lead
  • Yii
  • Group: Yii Dev Team
  • Posts: 5,902
  • Joined: 04-October 08
  • Location:DC, USA

Posted 29 October 2008 - 09:53 AM

When submitting the poll form, how would you render the database form correctly, assuming the database form's rendering depends on some state information?
0

#5 User is offline   notzippy 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 201
  • Joined: 06-October 08

Posted 29 October 2008 - 10:06 AM

I was thinking the poll form would be submitted via ajax so the data record form would not have to be rendered, or on the submit of the "poll" the user is prompted that there changes will be lost and upon confirmation they are taken to the "poll results" page.

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 ..
0

#6 User is offline   qiang 

  • Yii Project Lead
  • Yii
  • Group: Yii Dev Team
  • Posts: 5,902
  • Joined: 04-October 08
  • Location:DC, USA

Posted 29 October 2008 - 10:14 AM

if the state is just for a single form, then explicitly using a hidden field rather than using getState/setState perhaps is easier and more intuitive.


0

#7 User is offline   notzippy 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 201
  • Joined: 06-October 08

Posted 29 October 2008 - 10:47 AM

Some of the logic to decide on how to serialize or how to store the state in cache and the cacheid is stored in the hidden field would be nice to have in a single spot.

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.


0

#8 User is offline   qiang 

  • Yii Project Lead
  • Yii
  • Group: Yii Dev Team
  • Posts: 5,902
  • Joined: 04-October 08
  • Location:DC, USA

Posted 29 October 2008 - 10:52 AM

I'm still not quite clear yet on how to do this. Maybe you can think more about it and come up with some prototype so that we can continue to discuss?
0

#9 User is offline   notzippy 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 201
  • Joined: 06-October 08

Posted 29 October 2008 - 12:05 PM

Prototype 0.00001 ;D

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
0

#10 User is offline   qiang 

  • Yii Project Lead
  • Yii
  • Group: Yii Dev Team
  • Posts: 5,902
  • Joined: 04-October 08
  • Location:DC, USA

Posted 29 October 2008 - 12:13 PM

This looks interesting!
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?
0

#11 User is offline   notzippy 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 201
  • Joined: 06-October 08

Posted 29 October 2008 - 12:56 PM

That is the idea.

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
0

#12 User is offline   qiang 

  • Yii Project Lead
  • Yii
  • Group: Yii Dev Team
  • Posts: 5,902
  • Joined: 04-October 08
  • Location:DC, USA

Posted 30 October 2008 - 10:07 AM

The implementation of this feature seems clear to me, but I'm not quite convinced by the use cases. In the use cases you gave, the persistent data is better kept in session rather than using hidden fields. So could you give more use cases?
0

#13 User is offline   notzippy 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 201
  • Joined: 06-October 08

Posted 30 October 2008 - 10:31 AM

The worst thing about storing information in a session is when a user opens a new window, now you have 2 windows open lets say the user navigates to one item and edits it on one window and then switches to the second window and edits a different record then they switch back and click save on the first window....
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
0

#14 User is offline   notzippy 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 201
  • Joined: 06-October 08

Posted 30 October 2008 - 11:25 AM

Session data can easily become corrupted  when used to persist state information. Having a piece of information that is secure on the form being submitted is the only way to ensure that a proper response is made.

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
0

#15 User is offline   sebas 

  • Advanced Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 498
  • Joined: 28-October 08
  • Location:Buenos Aires, Argentina

Posted 31 October 2008 - 01:22 PM

If you make a contact grabber from yahoo, hotmail or other service, when you get the list, you should show it to the user before the user accept / refuse each user.

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.
0

#16 User is offline   qiang 

  • Yii Project Lead
  • Yii
  • Group: Yii Dev Team
  • Posts: 5,902
  • Joined: 04-October 08
  • Location:DC, USA

Posted 31 October 2008 - 01:27 PM

What does this have to do with state widget?
0

#17 User is offline   sebas 

  • Advanced Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 498
  • Joined: 28-October 08
  • Location:Buenos Aires, Argentina

Posted 31 October 2008 - 05:02 PM

They should be big, so you should save the data in state widget and not in the session.

Saving  that make your session state growth a lot. It's just an use case...

Sorry my bad english

Sebas

0

#18 User is offline   notzippy 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 201
  • Joined: 06-October 08

Posted 31 October 2008 - 05:11 PM

That is a good point, session space can be limited !
0

#19 User is offline   qiang 

  • Yii Project Lead
  • Yii
  • Group: Yii Dev Team
  • Posts: 5,902
  • Joined: 04-October 08
  • Location:DC, USA

Posted 31 October 2008 - 09:06 PM

Just checked in the implementation of this feature.

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.
0

#20 User is offline   notzippy 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 201
  • Joined: 06-October 08

Posted 31 October 2008 - 11:24 PM

Excellent !
thanks qiang
++
0

Share this topic:


  • (2 Pages)
  • +
  • 1
  • 2
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users