Cform Not Submitting (Empty $_Post)

Hi there,

I am developping an admin interface in my yii app for managing translations.

I have defined the following Cform configuration array :


return array(


	'title'=>t('Form_Translations_Title'),


	'elements'=>array(),

	

	'buttons'=>array(

		'save'=>array(

			'type'=>'submit',

			'label'=>t('Form_Translations_Submit'),

		),

	),

);

And I populate it at runtime by browsing my sourceMessages DB table, and my available languages in my translatedMessages table.

This works since I am able to display the source messages and their translations in this Form_Translations_Title of mine.

The problem, is when i submit, the $_post var is empty.

Here’s the action code :


$translationsForm = $this->controller->loadTranslationsForm();

		

		var_dump($_POST);

		

		// check if translationsForm submitted some source messages and / or translations and save them

		// note that there is no validation because the form doesn't have a model

		// no validation neither in the rows because the TranslationsRows instances are not ActiveRecords

		// this is why we enable validation in the save calls

		if($translationsForm->submitted('save'))

		{

			foreach($translationsForm->elements as $translationRow)

			{

				$translationRow['source']->model->save();

				

				foreach($translationRow['translations']->elements as $translation)

					$translation->model->save();

			}			

			$this->controller->redirect(array('translations'));

		}



I suspect that my issue is that I didn’t define a model for the translationsForm. It doesn’t seem relevant.

Also, here’s the loadTranslationsForm() operations below.

Any ideas why i get a null post ?

If it’s for the reason i’m suspecting, then i believe it’s a bad behaviour from the submit logic, because I only want to populate models associated with subforms.

As you can see below, I already had to declare a useless model in my TranslationsRowForm to get the code working.

Maybe this could be part of the issue…





	public function loadTranslationsForm()

	{

		$translations = TranslationsRow::getTranslationsRows();

		if($translations===null)

			throw new CHttpException(404,'The requested page does not exist.');

		return new TranslationsGridForm($translations);

	}




/*

 * TranslationsGridForm is the CForm meant to update every source message and its translations in every target language.

 * For each sourceMessage, it includes a TranslationsRowForm element

 */

	 

class TranslationsGridForm extends GTForm

{

	private $_translations;

		

	public function __construct($translations)

	{

		parent::__construct('application.views.admin.forms.translationsGridForm');


		$this->_translations = TranslationsRow::getTranslationsRows();

		

		$this->model = $this->_translations[0];

		

		// create translationsRowForm for each available sourceMessage

		foreach($this->_translations as $translationsRow)

			$this[$translationsRow->sourceModel->id] = new TranslationsRowForm($translationsRow);

	

	}

	

}


class TranslationsRowForm extends GTForm

{

	public function __construct(/*TranslationsRow*/ $translationsRow)

	{

		parent::__construct('application.views.admin.forms.translationsRowForm');

		

		$this->model=$translationsRow;

		

		// Add source message element

		$this['source']=new CForm('application.views.admin.forms.translationsSourceMiniForm',$translationsRow->sourceModel);

		

		// Add translations elements for each supported language

		foreach($translationsRow->translationsModels as $translation)

		{

			$this['translations'][$translation->language] = new CForm('application.views.admin.forms.translationsTargetForm',$translation);

		}

	}

}






First, I didn’t print the _$POST at the right place. Nevermind that.

What I understood from further testing and diving into yii core code, is that you just can’t use multiple instances of the same model in a Nested CForm.

That is because the name attribute of the fields will be duplicated (name of the model + name of the field).

I’m currently trying to change the name attribute. I’ll let you informed.

Right, easiest way is to override both element rendering and data loading methods.

Making extended use of these techniques on my app. $_POST and models are populated now.

If anyone needs samples, please let me know.

Hi I’m running into a similar issue as you. Would you be able to further describe how you fixed it?

Thanks

Sure.

Below is how I handled the main issues one meets when using a CForm with multiple instances of a same model.

Note that it doesn’t work natively in yii because the default generated html code will not differentiate your subforms fields between your different models.

Thus, when you submit, the post method will find different fields with exactly the same name, and it will succesively replace the data of the previous models. At the end, $_POST will only store the values of your last model. (hope this is clear, but the interesting part is next anyway)

  1. If you need to populate the form at runtime, you have to make sure both Sons CForm->parent property as well as Parent CForm->elements property are properly initialized.

To do so, you’ll have to create a CForm and specify the parent CForm (initializes ‘parent’), and then explicitly attach the created CForm to its parent (initializes ‘elements’).

Sample :




// create DivisionClans' forms and fill with existing model

for ($i = 0;$i < $this->_clanCount; $i++)

{

	// Instanciate the DivisionClan Model, depending on create or update scenarios

	$clanModel = $i < count($this['division']->model->clans) ? 

		CompetitionsDivisionsClans::model()->findByPK(array('Division_ID'=>$this['division']->model->Division_ID, 'Clan_ID'=>$this['division']->model->clans[$i]->Clan_ID))

		: new CompetitionsDivisionsClans();

			

	// Instanciate Clan Form and add it as sub-form (setting parent in constructor isn't sufficient)

	$clanForm = new GTForm('application.views.management.forms.divisionClanForm',$clanModel,$this['clans']);

	$this['clans'][$i] = $clanForm;

			

}



  1. Override the render method to make sure the models fields don’t have the same name in the HTML output :



public /*overrides*/ function render()

{

	$output=$this->renderBegin();

		

	$output.="<legend>".$this->title."</legend>\n";

		


	// render season

	$output.=$this['division']->render();

		


	// render clans

	$output.="<legend>".$this['clans']->title."</legend>\n";

	

	

	foreach($this['clans']->elements as $i => $clanForm)

	{

		$output.="<fieldset>\n";

		$output.=strtr($clanForm['Clan_ID']->render(),array($clanForm['Clan_ID']->name=>"{$i}"));

		$output.="</fieldset>\n";

	}	

	

	// render button

	$output.=$this->buttons['save']->render();

	

	return $output . $this->renderEnd();	

}




  1. Override the loadData method to make sure Yii fills the right models with the right $_POST elements :



public /*overrides*/ function loadData()

{

	$this['division']->loadData();

	

	for($i=0; $i<$this->_clanCount; $i++)

	{

		$this['clans'][$i]->model->Clan_ID=$_POST['CompetitionsDivisionsClans'][$i];

	}

}



This works and can be used for more complex scenarios. In my translations management scenario, each cell of the table is actually a subForm with its own model. In that more specific case, you just have to make your fields names bi-dimensional so that you get things like $_POST[sourceID][languageID].

Hope this helps. It can save days if it does.