Dynamic CFormModel

Hello,

How to create a dynamic CFormModel so that attributes can be added at runtime?

I have extended the CFormModel so that I have an array of dynamic form elements. Each element has at least name, type and a boolean value representing if it is a required form element or not.

The form is rendered so that for every element in array a corresponding element in the form is created. Later when form is posted I can iterate over the POST array and fill each elements value accordingly.

But I have problem validating the form. The rules function returns the list of these element names that has value ‘required’ set to true gets included in the list.

So am i doing it wrong? Is there a better way to create dynamic forms?

The elements class:




class Element {

  public $_data = array();

  public function __construct( $_data = array() ) {

    $this->_data = $_data;

  }

  public function __get($name) {

    return $this->_data[$name];

  }

  

  public function __toString() {

    return $this->_data["name"];

  }

}



The model:




class DynamicForm extends CFormModel {

  public $elements = array();


  public function rules() {

    $required = array();

    foreach($this->elements as $e)

      if($e->required)

        $required[] = $e->name;

    

    return array(

      array(implode(', ', $required), 'required'),

    );

  }

  

  public function __get($name) {

    foreach($this->elements as $e)

      if($e->name == $name) return $this->elements[$name]->value;

    

    return $this->$name;

  }

  

  public function attributeLabels() {

    return $attributeLabels = array();

  }

}



The action in controller:




public function actionDynamicForm() {

  $form = new DynamicForm;    

  $form->elements = getFormElements();

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

    for($i = 0; $i<sizeof($form->elements); $i++){

      $e = $form->elements[$i];

      $form->attributes[$e->name] = $_POST['DynamicForm'][$e->name];

      $e->value = $_POST['DynamicForm'][$e->name];

    }

    if($form->validate()){

      // TODO

    } else $this->render('dynamicform', array('form'=>$form));

  } else {

    $this->render('dynamicform', array('form'=>$form));

  }

}



The code in view that renders elements:




foreach($form->elements as $e) {

  echo '<div class="simple">'."\n";

  echo CHtml::activeLabelEx($form, $e->name);

  switch($e->type){

    case 'textfield':

      echo CHtml::activeTextField($form, $e->name); break;

    case 'dropdown':

      echo CHtml::activeDropDownList($form, $e->name, $e->data, array('prompt'=>'Choose Item')); break;

    case 'textarea':

      echo CHtml::activeTextArea($form, $e->name, array('rows'=>6, 'cols'=>50)); break;

    case 'radiobuttonlist':

      echo '</div><div class="itemlist">';

      echo CHtml::activeRadioButtonList($form, $e->name, $e->data, array('separator' => ' ')); break;

    case 'checkboxlist':

      echo '</div><div class="itemlist">';

      echo CHtml::activeCheckBoxList($form, $e->name, $e->data, array('separator' => ' ')); break;

  }

  echo '</div>'."\n\n";

}



I think your idea is good, but it is not clear what exactly problem with validation you have?

Well the problem seems to be that i can’t get the posted values back to the model - var_dump($form->attributes) is null.

<a lot of swearing> i have a typo… i changed the $form->elements structure but forgot to change this loop:




for($i = 0; $i<sizeof($form->elements); $i++){

    $e = $form->elements[$i];

    $form->attributes[$e->name] = $_POST['DynamicForm'][$e->name];

    $e->value = $_POST['DynamicForm'][$e->name];

}



Namely the $form->array looks like this;

$form->elements = array($e->name => $e, $e->name => $e, …);

And the loop should be like this:




foreach($form->elements as $e) {

    $form->attributes[$e->name] = $_POST['DynamicForm'][$e->name];

    $attributes[$e->name] = $_POST['DynamicForm'][$e->name];

    $e->value = $_POST['DynamicForm'][$e->name];

}



So it seems to be working now…

:)

Ristiisa, you also could look at CForm class from Yii 1.1.

AFAIK 1.1 is still beta

Yes, but I believe 1.1 will be released very soon, release candidate is already here.

Also I think you could be interested in CForm class because it is alternative implementation of your idea from Qiang.