Form builder

New to Yii.

Following the example of the login page that is installed with the Testdrive app, I was at first a bit confused by the combination of CHtml and CForm code on the login.php view (in the 1.1.0 release) – browsing the svn repo I realized that this part of the Testdrive app is in transition, the latest development version making use of the new CActiveForm widget with AJAX validation.

So just wondering, vis-a-vis form builder, how the CActiveForm widget fits in with the notion, put forth in the linked page (and the CForm doc), of abstracting form specification away from presentation. (Noted that the latest CForm defaults to using CActiveForm for rendering.) Considered best practice to specify models for forms, as opposed to just calling the widget? If I’m understanding correctly, the blog demo in the development version does the latter.

Sorry if too vague – just trying to get a feel for best practices and the "Yii way" of doing things.

Well, maybe I’ve already answered my own questions. For the project I’m working on I can definitely see the value of specifying subform models, so that I can reuse them across the various CActiveRecord models that are tied to other models via foreign key constraints. Any examples of this kind of CForm use?

OK, getting it, thanks to the example code in the form builder article linked above.

Next thing I’ll work on is figuring out how to use CForm to get a dropdown list of items from the database. Suggestions welcome…

Edit: My current idea is to use CActiveDataProvider to get the items for the dropdown list. Question is how to get them into the form specification. I guess I could just create the CActiveDataProvider object in the form spec., but seems wrong to put that into a view file.

Open to suggestions, this is just how I did it. So Lets say that I have two tables, Person and a FavoriteFood.

Create a method inside Person.php to access the FavoriteFood.php model.

Person.php




...

	public function relations()

	{

		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

		return array(

                        'favorite_food' => array(self::BELONGS_TO,'FavoriteFood','favoriteFoodId'),

		);

	}


	/**

	 * @return array customized attribute labels (name=>label)

	 */

	public function attributeLabels()

	{

		return array(

			'id' => 'Id',

			'firstName' => 'First Name',

			'lastName' => 'Last Name',

			'favoriteFoodId' => 'Favorite Food',

		);

	}

        public function getFavoriteFoodList()

        {

                $food = new FavoriteFood;

                $foodAll = $food->findAll(); //query database to return all records

                return $listFoodAll = CHtml::listData($foodAll,'id','name'); // listData() returns an object for use with drop downs, $id = key, name = $value

        }


...



PersonController.php




...

        public function actionCreate()

	{

		$model=new Person;

                $listFoodAll = Person::model()->getFavoriteFoodList();


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

		{

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

			if($model->save())

				$this->redirect(array('view','id'=>$model->id));

		}


		$this->render('create',array(

			'model'=>$model,

                        'listFoodAll'=>$listFoodAll,

		));

	}

...



../views/person/create.php




<?php

$this->breadcrumbs=array(

	'Person'=>array('index'),

	'Create',

);

?>

<h1>Create Person</h1>


<ul class="actions">

	<li><?php echo CHtml::link('List Person',array('index')); ?></li>

	<li><?php echo CHtml::link('Manage Person',array('admin')); ?></li>

</ul><!-- actions -->


<?php echo $this->renderPartial('_form', array('model'=>$model,'listFoodAll'=>$listFoodAll)); ?>



../views/person/_form.php




<div class="form">


<?php echo CHtml::beginForm(); ?>


	<p class="note">Fields with <span class="required">*</span> are required.</p>


	<?php echo CHtml::errorSummary($model); ?>


	<div class="row">

		<?php echo CHtml::activeLabelEx($model,'firstName'); ?>

		<?php echo CHtml::activeTextField($model,'firstName',array('size'=>60,'maxlength'=>100)); ?>

		<?php echo CHtml::error($model,'firstName'); ?>

	</div>


	<div class="row">

		<?php echo CHtml::activeLabelEx($model,'lastName'); ?>

		<?php echo CHtml::activeTextField($model,'lastName',array('size'=>60,'maxlength'=>100)); ?>

		<?php echo CHtml::error($model,'lastName'); ?>

	</div>


	<div class="row">

		<?php echo CHtml::activeLabelEx($model,'favoriteFoodId'); ?>

		<?php echo CHtml::activeDropDownList($model,'favoriteFoodId',$listFoodAll); ?>

		<?php echo CHtml::error($model,'favoriteFoodId'); ?>

	</div>


	<div class="row buttons">

		<?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>

	</div>


<?php echo CHtml::endForm(); ?>


</div><!-- form -->



Yeah, it’s that combination of using CHtml::listData() and passing the return value to CHtml::activeDropDownList() as the data parameter http://www.yiiframework.com/doc/api/CHtml#activeDropDownList-detail.

Thanks for the replies, @backwardselvis and @charris66, and for the complete example. I may well end up going that route, at least for now. Was thinking of using the new form builder way of specifying a form instead of using CHtml; still trying to work out a sensible arrangement for this particular case.

Not only am I new to Yii, but also to reasonably strict application of MVC. In your example I would tend to put getFavoriteFoodList() in the FavoriteFood model. Just for the sake of discussion…

No probs. Glad to be able to help. And yeah that’s another way to do it for sure. In that case you might even be able to simply call:


$this->findAll()

as you will already be in the context of the FavoriteFood model.

To create a dropdown, the method I used w/ CForm is:




$form['users']['type'] = 'dropdownlist';

$form['users']['size'] = 1;

$form['users']['items'] = array();

$results = $userModel->findAll();

if($results) {

    $form['users']['items'] = CHtml::listData($results, 'user_id', 'username');

}



Note that the ‘size’ option is important, if you want a regular dropdown, as opposed to having a selectable list.

@intel352: thanks so much. (And greetings from Durham NC.) Your example got me over the hump (and I had been banging my head over this one for a while). I had to make some changes from your example, perhaps due to the initial CForm specification I’m using. Here’s what I got to work:

Form specification:




return array(

    'title' => 'Add Foobar',

    'elements' => array(

        'foo' => array('type' => 'text'),

        'bar' => array(

            'type' => 'form',

            'elements' => array(

                'foobar' => array('type' => 'dropdownlist'),

            ),

        ),

    ),

    'buttons' => array(

        'add' => array('type' => 'submit', 'label' => 'Add'),

    ),

);



Controller action:




    $model = new Foo;

    $form = new CForm('application.views.foo._form', $model);

    $barModel = new Bar;

    $form['bar']->model = $barModel;

    $form['bar']['foobar']->items = CHtml::listData($barModel->findAll(), 'id', 'foobar');




I did not find it necessary to specify the size option.

I don’t know if this is an appropriate use of sub-forms. I guess that is the reason for the changes I had to make from your example.

@jsoo

Hope the example helped, and getFavoriteFoodList() is in the Person model. Are you saying you would move it to the FavoriteFood model?

-take care

elvis

Interesting, this is a lot cleaner. Thanks.

Greetings from Southport :slight_smile:

I like how you access $form after passing the config to CForm. I must’ve missed that tidbit somewhere, but I wasn’t aware you could work with the form after initializing it’s config. Nice!

And yeah, my implementation of CForm is a bit different, as I’m building an auto-form extension based on the model, so it’s not the pretty printed array that you see in the view examples :wink:

Right; makes more sense to me that way because it is a simple findAll(), really associated with the Food model rather than Person. Then I was also looking to see if there is any existing way to do something like findAllRelated(), but haven’t come across it. Was considering extending CActiveRecord to add it, but don’t want to start extending things left and right till I have a better feel for Yii’s core capabilities, which are extensive.

Anyway, thanks to @intel352 I am just using form builder as you see.

Got it from [SOLVED] How to associate model with sub-form?.

FYI, my current version:

Goal is a form that works for either adding or updating model Foo. Foo is a simple model with only three fields, ‘id’, ‘name’, and ‘barId’ (a foreign key on Bar->id). ‘id’ is automatically assigned, so the form only needs two elements, a text input for ‘name’ and a dropdown list of Bar models for ‘barId’. (Bar->name is the display text.)

Form specification:




return array(

    'elements' => array(

        'name' => array('type' => 'text'),

        'barId' => array('type' => 'dropdownlist'),

    ),

    'buttons' => array(

        'submit' => array('type' => 'submit'),

        'cancel' => array('type' => 'submit', 'label' => 'Cancel'),

    ),

);



Because the add and update actions are so similar I made a private utility function to process either action


public function actionAdd()

{

    $this->_addOrUpdate(new Foo, 'add', 'Add');

}


public function actionUpdate()

{

    $this->_addOrUpdate($this->loadModel(), 'update', 'Update');

}


private function _addOrUpdate($model, $viewFile, $submitLabel)

{

    $form = new CForm('application.views.foo._form', $model);

    $form->buttons['submit']->label = $submitLabel;

    

    $form['barId']->label = Bar::model()->getAttributeLabel('name');

    $form['barId']->items = CHtml::listData(Bar::model()->findAll(), 'id', 'name');

    

    if ( ( $form->submitted() && $form->validate() && $model->save() ) || $form->submitted('cancel', false) )

        $this->redirect(array('index'));

    

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

}