Form builder
#1
Posted 02 March 2010 - 10:56 AM
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.
#2
Posted 02 March 2010 - 11:09 AM
#3
Posted 02 March 2010 - 04:26 PM
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.
#4
Posted 02 March 2010 - 08:38 PM
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 -->
#5
Posted 02 March 2010 - 09:01 PM
#6
Posted 02 March 2010 - 09:08 PM
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...
#7
Posted 02 March 2010 - 09:30 PM
$this->findAll()
as you will already be in the context of the FavoriteFood model.
#8
Posted 03 March 2010 - 09:45 AM
$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.
#9
Posted 03 March 2010 - 11:24 AM
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.
#10
Posted 03 March 2010 - 11:28 AM
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
#11
Posted 03 March 2010 - 11:34 AM
jsoo, on 03 March 2010 - 11:24 AM, said:
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.
#12
Posted 03 March 2010 - 11:52 AM
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 ;-)
jsoo, on 03 March 2010 - 11:24 AM, said:
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.
#13
Posted 03 March 2010 - 12:07 PM
backwardselvis, on 03 March 2010 - 11:28 AM, said:
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.
#14
Posted 03 March 2010 - 12:10 PM
intel352, on 03 March 2010 - 11:52 AM, said:
Got it from [SOLVED] How to associate model with sub-form?.
#15
Posted 04 March 2010 - 11:55 AM
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));
}

Help













