CForm

Building HTML form is often time-consuming and repetitive. Currently, we usually call CHtml methods to generate labels and input fields, and embed them in HTML code. If we need to different a new form, we would repeat the whole process again. The proposal for CForm is to alleviate this problem.

The main idea is that we use a CForm object to represent the collection of the input fields. The CForm object is backed by a model object which is responsible to perform data validation and other biz logic. The CForm object itself doesn't have any logic and only serves as a container of the input field specifications. For example, a CForm object could be built as:



$model=new LoginFormModel;


$form=new CForm($model, 'path/to/specFile');


where the spec file looks like



return array(


	'username'=>array(


		'label'=>'Username', // optional, would be determined by model


		'required'=>true,    // optional, would be determined by model


		'type'=>'CTextField',// optional, CTextField


		'maxlength'=>80,


	),


	'password'=>array(


		'label'=>'Password', // optional, would be determined by model


		'required'=>true,    // optional, would be determined by model


		'type'=>'CPasswordField',


	),


);


Because CForm is just a container, in order to render the form, we need to a view (template). The simplest view template could look like the following:



$form->begin();


foreach($form->elements as $element)


{


	echo $element->label."<br/>".$element->render()."<br/>";


	echo $element->error;


}


$form->end();


You may think such rendering is too rigid. If so, you may write another view template, or simplest render the form element one by one in a very complex form layout.

So what are the benefits of this additional abstraction? First, the same CForm object can be rendered using different view templates, which means reusing the form. Second, the same view template can be used to render different CForm objects, which means reusing the view template. Third, when developing a form, developers can focus on writing the form spec while designers focus on views or view templates.

The following is the typical code needed in a controller action:



$model=new LoginFormModel;


$form=new CForm($model, 'path/to/spec');


if($form->submitted())


{


	$form->loadData();


	if($model->validate())


		$this->redirect(...);


}


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


The new code doesn't look any more complex than our existing one. It is actually a bit more readable.

There are still some problems regarding this form design:

  1. How to represent a form involving several models?

  2. How to deal with tabular data input?

  3. How to group form elements?

Your questions and suggestions are welcome!

Good idea, Qiang!

Quote

1. How to represent a form involving several models?

Do you mean other models related to current one? Maybe, something like embedded forms. We can see them in Symfony, for example.

Quote

3. How to group form elements?

Maybe something like this:

return array('personal_info_group', 'Personal Info', array(... elements here...));

I think:

CForm can have properties like action, etc.

For 1. may be,

$form=new CForm(array('model1'=>$model1, 'model2'=>$model2), 'path/to/specFile');

or may be something like:

$form=new CForm(

  array(

      'model1' => array('model'=>$model1, 'spec'=>'path/to/specFile1'),

      'model2' => array('model'=>$model1, 'spec'=>'path/to/specFile2'),

);

Etc…

  1. Thinking… no idea yet…

  2. may be defining the group in the specFile:

return array(

'group1'=>array(

&#039;username&#039;=&gt;array(


	&#039;label&#039;=&gt;&#039;Username&#039;, // optional, would be determined by model


	&#039;required&#039;=&gt;true,&nbsp; &nbsp; // optional, would be determined by model


	&#039;type&#039;=&gt;&#039;CTextField&#039;,// optional, CTextField


	&#039;maxlength&#039;=&gt;80,


),


&#039;password&#039;=&gt;array(


	&#039;label&#039;=&gt;&#039;Password&#039;, // optional, would be determined by model


	&#039;required&#039;=&gt;true,&nbsp; &nbsp; // optional, would be determined by model


	&#039;type&#039;=&gt;&#039;CPasswordField&#039;,


),

),

'group2'=>array('someField1'=>…, etc),

);

I have experience with Zend Framework and I can say is Yii shall get inspiration from it.

1. How to represent a form involving several models?

Like form and subforms: http://framework.zen…form.forms.html

Take a look how is simple in zend framework:

This is FORM (base)

<?php





	require SITE_FORMS . 'SubFormulario_Inscreva.php';


	require SITE_FORMS . 'SubFormulario_MaisPessoas.php';





	class Formulario_Inscreva extends Zend_Form


	{


		public function init()


		{


			$this->setAction('')


			     ->setMethod('post');





			$inscreva    = new SubFormulario_Inscreva();


			$maispessoas = new SubFormulario_MaisPessoas();





			$this->addSubForm($inscreva   , 'inscreva');


			$this->addSubForm($maispessoas, 'maispessoas');





			$submit = new Zend_Form_Element_Submit('submit');


			$submit->setLabel('Enviar')


					->setDecorators(array(


							array(


								'decorator' => 'ViewHelper',


								'options' => array('helper' => 'formSubmit')),


							array(


								'decorator' => array('td' => 'HtmlTag'),


								'options' => array('tag' => 'td', 'colspan' => 2)),


							array(


								'decorator' => array('tr' => 'HtmlTag'),


								'options' => array('tag' => 'tr')),


							array(


								'decorator' => array('table' => 'HtmlTag'),


								'options' => array('tag' => 'table')),


						    ));





			$this->setDecorators(


				array('FormElements'


					  , array('Form')


				)


			);





	        $this->addElements(


						array($submit


				        )


				    );





		}


	}

This is one of sub-form attached in base form

<?php





	/**


	*   Nome


    *   Empresa


    *   CPF/CNPJ


    *   Endereço


    *   Bairro


    *   Cidade


    *   Estado


    *   Telefone


    *   E-mail


    *   Site


    *   Forma de pagamento


    *   Observação


    *


    *   Sub-Formulário


    *       Nome Completo


    *       Nome para Crachá


    *       E-mail


	*/





	require SITE_FORMS . 'Form_Element_Radio_Formapagemento.php';


	require SITE_FORMS . 'Form_Element_Select_Estado.php';


	require SITE_FORMS . 'Form_Element_Text_Bairro.php';


	require SITE_FORMS . 'Form_Element_Text_CPFCNPJ.php';


	require SITE_FORMS . 'Form_Element_Text_Cidade.php';


	require SITE_FORMS . 'Form_Element_Text_Email.php';


	require SITE_FORMS . 'Form_Element_Text_Empresa.php';


	require SITE_FORMS . 'Form_Element_Text_Endereco.php';


	require SITE_FORMS . 'Form_Element_Text_Nome.php';


	require SITE_FORMS . 'Form_Element_Text_Site.php';


	require SITE_FORMS . 'Form_Element_Text_Telefone.php';


	require SITE_FORMS . 'Form_Element_Textarea_Observacao.php';





	class SubFormulario_Inscreva extends Zend_Form_SubForm


	{


		public function init()


		{


			// Nome


			$nome = new Form_Element_Text_Nome('nome');


			$nome->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Empresa


			$empresa = new Form_Element_Text_Empresa('empresa');


			$empresa->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// CPF/CNPJ


			$cpfcnpj = new Form_Element_Text_CPFCNPJ('cpfcnpj');


			$cpfcnpj->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Endereço


			$endereco = new Form_Element_Text_Endereco('endereco');


			$endereco->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Bairro


			$bairro = new Form_Element_Text_Bairro('bairro');


			$bairro->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Cidade


			$cidade = new Form_Element_Text_Cidade('cidade');


			$cidade->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Estado


			$estado = new Form_Element_Select_Estado('estado');


			$estado->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Telefone


			$telefone = new Form_Element_Text_Telefone('telefone');


			$telefone->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// E-mail


			$email = new Form_Element_Text_Email('email');


			$email->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Site


			$site = new Form_Element_Text_Site('site');


			$site->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Site


			$formaPagamento = new Form_Element_Radio_Formapagamento('formaPagamento');


			$formaPagamento->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr', 'valign' => 'top'))


					  )


			);





			// Observação


			$observacao = new Form_Element_Textarea_Observacao('observacao');


			$observacao->setDecorators(array(


						    'ViewHelper'


						  , 'Errors'


						  , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


						  , array('Label', array('tag' => 'th'))


						  , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr', 'valign' => 'top'))


				       ));





			$this->setDecorators(


				array('FormElements', array('HtmlTag', array('tag' => 'table', 'cellspacing' => 1)))


			);





	        $this->addElements(


						array($nome


			                 ,$empresa


			                 ,$cpfcnpj


			                 ,$endereco


			                 ,$bairro


			                 ,$cidade


			                 ,$estado


			                 ,$telefone


			                 ,$email


			                 ,$site


			                 ,$formaPagamento


			                 ,$observacao


				        )


				    );





		}


	}

Quote

2. How to deal with tabular data input?

This is a great nightmare in Zend Framework world…

You must google for an entire day and find only one article explaining…

You can have 2 options.

1 - Let the object render their selves with Decorators,  subforms and they element (text, radio…) with pre-set html wrappers.

Good for simple form.

Bad for a little complex form.

2 - You can do a viewscript: http://framework.zen…tors.viewScript

You make a template and renders each html element in they respective position.

Quote

3. How to group form elements?

I think this was answered in 1.

Zend Framework is a mass of code, this example requires nearly 20 files, which is insane for a single form.

Tabular data should be collected from multiple CForms. What about CTabularForm, which would be a wrapper for many CForms to decrease the latter’s complexity?

Is it possible to call CForm’s constructor with an array instead of a path to the specification?

Thanks for your fantastic piece of work!

Quote

I have experience with Zend Framework and I can say is Yii shall get inspiration from it.
1. How to represent a form involving several models?

Like form and subforms: http://framework.zen…form.forms.html

Take a look how is simple in zend framework:

This is FORM (base)

<?php





	require SITE_FORMS . 'SubFormulario_Inscreva.php';


	require SITE_FORMS . 'SubFormulario_MaisPessoas.php';





	class Formulario_Inscreva extends Zend_Form


	{


		public function init()


		{


			$this->setAction('')


			     ->setMethod('post');





			$inscreva    = new SubFormulario_Inscreva();


			$maispessoas = new SubFormulario_MaisPessoas();





			$this->addSubForm($inscreva   , 'inscreva');


			$this->addSubForm($maispessoas, 'maispessoas');





			$submit = new Zend_Form_Element_Submit('submit');


			$submit->setLabel('Enviar')


					->setDecorators(array(


							array(


								'decorator' => 'ViewHelper',


								'options' => array('helper' => 'formSubmit')),


							array(


								'decorator' => array('td' => 'HtmlTag'),


								'options' => array('tag' => 'td', 'colspan' => 2)),


							array(


								'decorator' => array('tr' => 'HtmlTag'),


								'options' => array('tag' => 'tr')),


							array(


								'decorator' => array('table' => 'HtmlTag'),


								'options' => array('tag' => 'table')),


						    ));





			$this->setDecorators(


				array('FormElements'


					  , array('Form')


				)


			);





	        $this->addElements(


						array($submit


				        )


				    );





		}


	}

This is one of sub-form attached in base form

<?php





	/**


	*   Nome


    *   Empresa


    *   CPF/CNPJ


    *   Endereço


    *   Bairro


    *   Cidade


    *   Estado


    *   Telefone


    *   E-mail


    *   Site


    *   Forma de pagamento


    *   Observação


    *


    *   Sub-Formulário


    *       Nome Completo


    *       Nome para Crachá


    *       E-mail


	*/





	require SITE_FORMS . 'Form_Element_Radio_Formapagemento.php';


	require SITE_FORMS . 'Form_Element_Select_Estado.php';


	require SITE_FORMS . 'Form_Element_Text_Bairro.php';


	require SITE_FORMS . 'Form_Element_Text_CPFCNPJ.php';


	require SITE_FORMS . 'Form_Element_Text_Cidade.php';


	require SITE_FORMS . 'Form_Element_Text_Email.php';


	require SITE_FORMS . 'Form_Element_Text_Empresa.php';


	require SITE_FORMS . 'Form_Element_Text_Endereco.php';


	require SITE_FORMS . 'Form_Element_Text_Nome.php';


	require SITE_FORMS . 'Form_Element_Text_Site.php';


	require SITE_FORMS . 'Form_Element_Text_Telefone.php';


	require SITE_FORMS . 'Form_Element_Textarea_Observacao.php';





	class SubFormulario_Inscreva extends Zend_Form_SubForm


	{


		public function init()


		{


			// Nome


			$nome = new Form_Element_Text_Nome('nome');


			$nome->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Empresa


			$empresa = new Form_Element_Text_Empresa('empresa');


			$empresa->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// CPF/CNPJ


			$cpfcnpj = new Form_Element_Text_CPFCNPJ('cpfcnpj');


			$cpfcnpj->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Endereço


			$endereco = new Form_Element_Text_Endereco('endereco');


			$endereco->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Bairro


			$bairro = new Form_Element_Text_Bairro('bairro');


			$bairro->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Cidade


			$cidade = new Form_Element_Text_Cidade('cidade');


			$cidade->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Estado


			$estado = new Form_Element_Select_Estado('estado');


			$estado->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Telefone


			$telefone = new Form_Element_Text_Telefone('telefone');


			$telefone->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// E-mail


			$email = new Form_Element_Text_Email('email');


			$email->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Site


			$site = new Form_Element_Text_Site('site');


			$site->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr'))


					  )


			);





			// Site


			$formaPagamento = new Form_Element_Radio_Formapagamento('formaPagamento');


			$formaPagamento->setDecorators(array(


				         'ViewHelper'


			           , 'Errors'


					   , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


					   , array('Label', array('tag' => 'th'))


					   , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr', 'valign' => 'top'))


					  )


			);





			// Observação


			$observacao = new Form_Element_Textarea_Observacao('observacao');


			$observacao->setDecorators(array(


						    'ViewHelper'


						  , 'Errors'


						  , array('decorator' => array('td' => 'HtmlTag'), 'options' => array('tag' => 'td'))


						  , array('Label', array('tag' => 'th'))


						  , array('decorator' => array('tr' => 'HtmlTag'), 'options' => array('tag' => 'tr', 'valign' => 'top'))


				       ));





			$this->setDecorators(


				array('FormElements', array('HtmlTag', array('tag' => 'table', 'cellspacing' => 1)))


			);





	        $this->addElements(


						array($nome


			                 ,$empresa


			                 ,$cpfcnpj


			                 ,$endereco


			                 ,$bairro


			                 ,$cidade


			                 ,$estado


			                 ,$telefone


			                 ,$email


			                 ,$site


			                 ,$formaPagamento


			                 ,$observacao


				        )


				    );





		}


	}

Quote

2. How to deal with tabular data input?

This is a great nightmare in Zend Framework world…

You must google for an entire day and find only one article explaining…

You can have 2 options.

1 - Let the object render their selves with Decorators,  subforms and they element (text, radio…) with pre-set html wrappers.

Good for simple form.

Bad for a little complex form.

2 - You can do a viewscript: http://framework.zen…tors.viewScript

You make a template and renders each html element in they respective position.

Quote

3. How to group form elements?

I think this was answered in 1.

The CForm idea from qiang is nice.

But please don't do the form thing as in the zend framework it is a nightmare!! I left zend framework mainly for its form management. It is impossibile to customize the html in the form without becoming crazy with decorators  etc.

Thank you for the reference to Zend_Form. Yes, I did study it a little bit and also similar feature in other frameworks. I agree that we should not put too much logic and presentation in the form object. While Zend_Form does offer maximum flexibility, its usage is overly complicated and may not be practical as well.

The idea of Yii form is very simple: the form only contains specification of input fields. We let data models to do biz logic, let views to do presentation, and controller to be responsible for binding them all.

Will be there any cases when one has to check whether the $form->submitted() without loading the data into it?

Yes, you can do that. The two lines are equivalent to:



if(isset($_POST['submitButton']))


    $model->attributes=$_POST['ModelClass'];


Sorry if I was misunderstable. Could $form->submitted() be implemented in such way so that automatically loads data?

I like it. I have thought about a form class for Yii for a while but am way to busy with other things to spend any time doing php.

Anyway, might it also be possible to pass an object instead of specfile so as to facilitate easy dynamic forms. For example



$model=new LoginFormModel;


$formspec=new CFormSpec;


$formspec->addTextField('username',array('maxlength'=>80));


$formspec->addPasswordField('username',array('maxlength'=>80));


$form=new CForm($model, $formspec);





dalip

I wanted to do the same, but only with arrays. Why add additional levels of abstraction?

Right now the main problem I am considering is how to reuse CHtml in CForm. My original proposal uses widgets as input types, which would require us to implement a widget for every active* method in CHtml.

Quote

Sorry if I was misunderstable. Could $form->submitted() be implemented in such way so that automatically loads data?

Yes, we could add a parameter to submitted() that may indicate loading data or not.

Quote

Right now the main problem I am considering is how to reuse CHtml in CForm. My original proposal uses widgets as input types, which would require us to implement a widget for every active* method in CHtml.

The fiddling I did a while ago with form generation gave me the same impression. Any idea on how to handle labels?

label is not a problem because we can get it from the model behind. We still provide the 'label' property for each element which can be used to override the label obtained from model.

Quote

Right now the main problem I am considering is how to reuse CHtml in CForm. My original proposal uses widgets as input types, which would require us to implement a widget for every active* method in CHtml.

I think this is the right way to go.

The final user should only know how to use it and don't concerd if it has thousand widgets.

In the end, it will be severial widgets using helpers. They would have minimal line of codes.

Quote

Thank you for the reference to Zend_Form. Yes, I did study it a little bit and also similar feature in other frameworks. I agree that we should not put too much logic and presentation in the form object. While Zend_Form does offer maximum flexibility, its usage is overly complicated and may not be practical as well.

The idea of Yii form is very simple: the form only contains specification of input fields. We let data models to do biz logic, let views to do presentation, and controller to be responsible for binding them all.

exactly. For me this way will be perfect for CForm ;)

Surely proper use of partials will reduce duplication in form design. Is this proposal not complicating things slightly :-\

This is the way I could imagine it:

A widget class is made for every type of field.  There will be a CPasswordField, CTextField etc.  These classes will inherit from CField which in turn inherits from CFieldBaseCFieldBase will contain any shared logic between the field classes.  CField will be a dummy class which the user can override in his app in order to extend all field classes.

CFieldBase should contain an internal method getLabel() that gets the label from the model.  It is used if the label is not overridden in the form config array.  This way the user could override this method in CField, for instance to add a period at the end of the label, or make it uppercase.  Edit:  Actually maybe the labels would be better to be modified in the view?

CHtml active*Field() methods would simply call the corresponding widget class (they would basically be wrappers).

Spec (form config) files should be able to have arbitrary options like so:

return array(


   'username'=>array(


      'label'=>'Username', // optional, would be determined by model


      'required'=>true,    // optional, would be determined by model


      'type'=>'CTextField',// optional, CTextField


      'tooltip'=>'tooltip info here',


      'maxlength'=>80,


   ),


);

So that in the view file you can reference this:

<?php


$form->begin();


foreach($form->elements as $element)


{


   echo $element->label."<br/>".$element->render()."<br/>";


   if ($element->tooltip) echo "<div class="tooltip">$element->tooltip</div>";   //<--


   echo $element->error;


}


$form->end();