forms with db normalization and dynamic fields.

I’m making an job application form. In the db I have a number of fields normalized out ( previous employers, education, references, etc ). It’s InnoDB with lots of foreign keys.

They need to be able to update the application as well.

They are entering a lot items which I’m going to split between lot of tabs, each that does a ajax update to the database before going to the next tab, but I need to get the multiple entries first.

I know how to make a jQuery “Add Another Employer” button next to the employers, but I’m not sure how to make it all work within Yii.

There are fields from multiple tables, and multiple entries from individual tables, and I don’t know how to do the multiple entries.

How can I use activeTextField to work with the multiple employers? A single entry would be easy enough with

field names like employers[name], but I need something like employers[1][name] so I can just use a foreach.

Sorry for my lack of clarity here, but I’m not altogether certain how to put it together and am on a rather short time table.

Many thanks,

Adam

EDIT:

Ok, that was way broad. It’s mainly the activeTextField field names for the tables with multiple entries on the same form that I need the specific help with. Thanks.

Does this help you?

It does, I’ve been reading the Guide and I now halfway through Caching. I found that just a little while ago actually.

While it talks about multiple entries for a single table, it’s only half the problem. What about multiple tables/models?

I’m guessing I just need to create new instances of each one, and then validate them one at a time, yes?

Since they are all related, can I use the relations to say, pull the main application, which has lots of one to many relationships, fill in all the data for its related tables, and then save it? Or will ->save() not affect the children?

I also am going to be splitting the form up into many pieces and was planning on creating scenarios in the rules and safe attributes for each piece. I’m guessing I’m going to have to load the model using findByPk for each save, but if I use $app->attribute = $POST[‘app’] and the form only has a couple pieces items, will it overwrite the items it doesn’t have with empty values?

Thanks

I keep getting the error "get_class() expects parameter 1 to be object, null given"

In my controller I have this:




<<removed for space, working code posted below>>



I’m only attempting to get it working with apps for now, I’ll add the rest when it’s working.

1)If I go to the form for the first time, it displays correctly, but there are a couple issues:

If I submit with all the $model stuff correct, it saves $model, but when I $model->apps->save()

I get the error:

CDbCommand failed to execute the SQL statement: SQLSTATE[HY000]: General error: 1452 Cannot add or update a child row: a foreign key constraint fails (gw_emp.apps, CONSTRAINT fk_Apps_Profile FOREIGN KEY (Profile_id) REFERENCES profile (id) ON DELETE NO ACTION ON UPDATE NO ACTION)

Isn’t apps supposed to get the id from it’s parent?

2)If the session user_id is set and it pulls the info with findbyPk, it gives the error:

Fatal error: Call to a member function getErrors() on a non-object in C:\xampp\htdocs\yii\framework\web\helpers\CHtml.php on line 1425

when I try the error summary, and if I remove that line, I get this error:

"get_class() expects parameter 1 to be object, null given"

once it hits the first:

<?php echo CHtml::activeLabelEx($model->apps,‘name’); ?>

But only if there is no corresponding entry for $model->apps

I’ve tried it using ->with(‘apps’), and ->together(), with no change.

Do I need to always check if there is a matching value for every relation?

So I’ll always need to go $model->apps = new apps; and then manually tell it what the ID is?

The relation in profile is:

‘apps’ => array(self::HAS_ONE, ‘Apps’, ‘Profile_id’, ‘joinType’=>‘INNER JOIN’),

And the relation in apps is:

‘profile’ => array(self::BELONGS_TO, ‘Profile’, ‘Profile_id’),

Solution (i hope)!

I ended up doing this:




	public function actionForm()

	{

		$session = Yii::app()->session;

		

		$model=$this->getProfile();


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

		{

			//main model

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

			$valid = $model->validate();

			

			$profileRelations = $model->getRelations(); //retrieve array of all relation aliases

			

			//cycle through all assigning attributes from post and validating

			foreach ( $profileRelations as $relation ) {

				if (is_array($model->$relation)) {

					foreach ($model->$relation as $key => $subRelation) {

						if (isset($_POST[$relation][$key]))

							$subRelation->attributes = $_POST[$relation][$key];

							$valid = $subRelation->validate() && $valid; // valid must be second or it will skip validation after a single false

					}

				} else {

					$model->$relation->attributes = $_POST[$relation];

					$valid = $model->$relation->validate() && $valid;

				}

			}

			//*

			if ($valid) {

				$model->save();

//*

				foreach ( $profileRelations as $relation ) {

					if (is_array($model->$relation)) {

						foreach ($model->$relation as $key => $subRelation) {

							$subRelation->Profile_id = $model->id;

							$subRelation->save();

						}

					} else {

						$model->$relation->Profile_id = $model->id;

						$model->$relation->save();

					}

				}

//*/

				$session['user_id'] = $model->id;

			}

			//*/

		}

		$this->render('form',array('model'=>$model, 'jobType'=>$session['jobType']));

		

	}

	

	public function getProfile() {

		

		if ($this->_model === null) {

			$session = Yii::app()->session;

			unset($session['user_id']);

			if(isset($session['user_id'])) {

				$this->_model=profile::model()->findbyPk($session['user_id']);

				//*

				if (empty($this->_model->apps)) {

					$this->_model->apps = new apps;

				}

				$profileRelations = $this->_model->getRelations();

				//removing 'apps' since it's set manually since it's the only 1:1

				unset($profileRelations[array_search('apps',$profileRelations)]);

				

				foreach ($profileRelations as $relation) {

					if (empty($this->_model->$relation)) {

						$this->_model->$relation = array(new $relation);

						$this->_model->{$relation}[0]->Profile_id = $this->_model->id;

					}

				}

				//*/

			} else {

				$this->_model = new profile;

				//*

				$this->_model->apps = new apps; //1:1

				$this->_model->references = array(new references); //1:n

				$this->_model->prevjobs = array(new prevjobs); //1:n

				$this->_model->education = array(new education); //1:n

				$this->_model->skills = array(new skills); //1:n

				$this->_model->socialnet = array(new socialnet); //1:n

				$this->_model->languages = array(new languages); //1:n

				//*/

			}

		}

		return $this->_model;

	}




actionForm is fairly similar to the tabular data page. I kept trying to work around the problem, and then I noticed my solution was incredibly similar to the sample on that page, I guess it sunk into my head after looking at it hours ago. I cleaned it up and made it match the example best I could.

I did what I needed to with getProfile to make it work. It might just be me but it seems clunky, I need to check every single relation after loading the model to see if it exists or not and create a new one and manually set the id for each one. At least it works :)

I created $model->getRelations() that just returns a string array of the relation names. Perhaps it could be more verbose stating the type of relation to know if it’ll be an array or a model.

Well, it works, and it’s a good example of a more complicated tabular form with AR.

Is there any better way to do it?

Now I’ll go finish making the view and hope it stays solid.

-Adam