Introduction ¶
this extension allows us to create dynamic tabular inputs using partial views and adding more of it via Ajax/JS this also allows each row to have its own widgets like CJui. i will also teach how to save 1 header model + multiple related model as details in this article
Installation ¶
extract the folder in protected/extensions/dynamictabularform
add this in your import array in the main.php
'ext.dynamictabularform.*',
- done!
Screenshot ¶
Requirements ¶
Yii 1.1 or above
Usage ¶
This extension uses a CAction to load each row of the tabular input via ajax. basic usage with controller
we need to add my custom action in the actions() method don't forget to allow the action in the accessRules()
DynamicTabularForm widget parameters: ¶
- rowUrl: the url of the ajax renderer
- defaultRowView: the default view file for the row
DynamicTabularForm widget methods: ¶
public function rowForm($models = array(), $rowView=null, $htmlOptions = array())
Parameters
- $models: is the array of CModels to be used
- $rowView: the view file to be used for initial rendering if $models contains data already
public function deleteRowButton($row_id, $key, $label='X', $htmlOptions = array())
Parameters
- $row_id: the id of the div of the current row(see usage)
$key: the key of the current row
$label: label of the delete button
public function updateTypeField($model, $key, $attribute, $htmlOptions = array())
for example i have created 3 rows and saved it in my database, then i wanted to update it and i want to delete 1 row of entry. this is important to determine what fields are to be deleted. this is usually useful to determine what rows are for new entries, for deletion and for updating
Parameters
- $model: model of the row
- $key: the key of the current row
- $attribute: to what attribute we will bind on what kind of update we will do if creation, deletion of updating
GetRowForm action parameters ¶
view: the view file that it will renderPartial
modelClass: the class of the model that will be initialized and be passed to the partial view defined in view. this is passed as the variabe $model
Variables available for the partial view ¶
$model: the initialized modelClass
$form: just an initialized DynamicTabularForm
$key: the counter that you can use in naming the inputs and the row
Example ¶
Controller - SlaController.php ¶
class SlaController extends Controller {
public function actions() {
return array(
'getRowForm' => array(
'class' => 'ext.dynamictabularform.actions.GetRowForm',
'view' => '_rowForm',
'modelClass' => 'SlaDetail'
),
);
}
/**
* without relation extension
*/
public function actionCreate() {
/**
* a typical setup... SLA is my header and its details is the SlaDetail model
* this i like a regular receipt
*/
$sla = new Sla();
$sladetails = array(new SlaDetail);
if (isset($_POST['Sla'])) {
$sla->attributes = $_POST['Sla'];
/**
* creating an array of sladetail objects
*/
if (isset($_POST['SlaDetail'])) {
$sladetails = array();
foreach ($_POST['SlaDetail'] as $key => $value) {
/*
* sladetail needs a scenario wherein the fk sla_id
* is not required because the ID can only be
* linked after the sla has been saved
*/
$sladetail = new SlaDetail('batchSave');
$sladetail->attributes = $value;
$sladetails[] = $sladetail;
}
}
/**
* validating the sla and array of sladetail
*/
$valid = $sla->validate();
foreach ($sladetails as $sladetail) {
$valid = $sladetail->validate() & $valid;
}
if ($valid) {
$transaction = $sla->getDbConnection()->beginTransaction();
try {
$sla->save();
$sla->refresh();
foreach ($sladetails as $sladetail) {
$sladetail->sla_id = $sla->id;
$sladetail->save();
}
$transaction->commit();
} catch (Exception $e) {
$transaction->rollback();
}
$this->redirect(array('view', 'id' => $sla->id));
}
}
$this->render('create', array(
'sla' => $sla,
'sladetails' => $sladetails
));
}
public function actionUpdate($id) {
$sla = $this->loadModel($id);
$sladetails = $sla->slaDetails;
if (isset($_POST['Sla'])) {
$sla->attributes = $_POST['Sla'];
if (isset($_POST['SlaDetail'])) {
$sladetails = array();
foreach ($_POST['SlaDetail'] as $key => $value) {
/**
* here we will take advantage of the updateType attribute so
* that we will be able to determine what we want to do
* to a specific row
*/
if ($value['updateType'] == DynamicTabularForm::UPDATE_TYPE_CREATE)
$sladetails[$key] = new SlaDetail();
else if ($value['updateType'] == DynamicTabularForm::UPDATE_TYPE_UPDATE)
$sladetails[$key] = SlaDetail::model()->findByPk($value['id']);
else if ($value['updateType'] == DynamicTabularForm::UPDATE_TYPE_DELETE) {
$delete = SlaDetail::model()->findByPk($value['id']);
if ($delete->delete()) {
unset($sladetails[$key]);
continue;
}
}
$sladetails[$key]->attributes = $value;
}
}
$valid = $sla->validate();
foreach ($sladetails as $sladetail) {
$valid = $sladetail->validate() & $valid;
}
if ($valid) {
$transaction = $sla->getDbConnection()->beginTransaction();
try {
$sla->save();
$sla->refresh();
foreach ($sladetails as $sladetail) {
$sladetail->sla_id = $sla->id;
$sladetail->save();
}
$transaction->commit();
} catch (Exception $e) {
$transaction->rollback();
}
$this->redirect(array('view', 'id' => $sla->id));
}
}
$this->render('create', array(
'sla' => $sla,
'sladetails' => $sladetails
));
}
}
create.php ¶
<div class="content">
<?php
/* @var $this SlaController */
?>
<p>
Example form of a one to many models with dynamic inputs!
</p>
<?php
$form = $this->beginWidget('DynamicTabularForm', array(
'defaultRowView'=>'_rowForm',
));
echo "<h3>Header</h3>";
echo $form->errorSummary($sla);
?>
<div class="row-fluid">
<div class="span4">
<?php
echo $form->labelEx($sla, 'name');
echo $form->textField($sla, 'name');
echo $form->error($sla, 'name');
?>
</div>
<div class="span4">
<?php
echo $form->labelEx($sla, 'customer_id');
echo $form->dropDownList($sla, 'customer_id', Customer::getList());
echo $form->error($sla, 'customer_id');
?>
</div>
<div class="span4">
<?php
echo $form->labelEx($sla, 'owner_id');
echo $form->dropDownList($sla, 'owner_id', User::getList());
echo $form->error($sla, 'owner_id');
?>
</div>
</div>
<h3>Details</h3>
<?php
/**
* this is the main feature!!
*/
echo $form->rowForm($sladetails);
echo CHtml::submitButton('create');
$this->endWidget();
?>
</div>
_rowForm.php ¶
<?php $row_id = "sladetail-" . $key ?>
<div class='row-fluid' id="<?php echo $row_id ?>">
<?php
echo $form->hiddenField($model, "[$key]id");
echo $form->updateTypeField($model, $key, "updateType", array('key' => $key));
?>
<div class="span3">
<?php
echo $form->labelEx($model, "[$key]location");
echo $form->textField($model, "[$key]location");
echo $form->error($model, "[$key]location");
?>
</div>
<div class="span3">
<?php
echo $form->labelEx($model, "[$key]remarks");
echo $form->textField($model, "[$key]remarks");
echo $form->error($model, "[$key]remarks");
?>
</div>
<div class="span3">
<?php
echo $form->labelEx($model, "[$key]schedule");
$this->widget('zii.widgets.jui.CJuiDatePicker', array(
'model' => $model,
'attribute' => "[$key]schedule",
'options' => array(
'showAnim' => 'fold',
),
'htmlOptions' => array(
'style' => 'height:20px;'
),
));
echo $form->error($model, "[$key]schedule");
?>
</div>
<div class="span2">
<?php echo $form->deleteRowButton($row_id, $key); ?>
</div>
</div>
Add the following to the model that is used in the rows
public $updateType;
To Do ¶
- Add actionUpdate tutorial
- Improve Documentation
Imrpovements!
I hope you guys can evaluate and comment for bugs or what still can be done to improve it! thanks
Update v2!
i have released a bug fix on the DynamicTabularForm::updateTypeField(), now it uses the key unlike the first release thanks!
Troubles...
Interesting ...
You can place an instance, database and app to see the actual performance?
I tried to run but I can add columns.
regarding adding "public UpdateType," I miss you $ ...
regards
@Sepiroth
what troubles are you having exactly?
Still Get Error when duplicate Row
preview image on this link
error when click create button..
what have i been missing?
Re: Still Get Error when duplicate Row
maybe you can show us what exactly is the error?
look at yii's response message
image of the error message
Internal Server Error.. can you see that error?
RE:image of the error message
try going to the URL directly so that you will be able to see the yii debug message... error 500 is more likely your syntax error
Cannot create and zero detail
Hi,
I need more info on how to create the rules for sla_detail
public function rules() { // NOTE: you should only define rules for those attributes that // will receive user inputs. return array( array('sla_id, name, date', 'required'), array('name, date', 'required', 'on' => 'batchSave'), array('sla_id', 'numerical', 'integerOnly' => true), array('name', 'length', 'max' => 100), // The following rule is used by search(). // @todo Please remove those attributes that should not be searched. array('id, sla_id, name, date', 'safe', 'on' => 'search'), ); }
Using above rules will not create the sla + sla_detail and no error messages shown. Somehow, it worked when I removed the sla_id numerical rule. I did not understand why.
In addition, my client just want to reserve the invoice number and do not want to add the detail yet. using this setup, it will not be able to save the sla since it requires minimum of one detail.
Thank you in advance.
RE:Cannot create and zero detail
Hi Daniel,
the bathSave scenario is there to EXEMPT the required sla_id since we are validating the slaDetail without sla_id.
array('sla_id','required','except'=>'batchSave')
on the other hand your question about zero details,
i will have to add that functionality.
initially DynamicTabularForm requires atleast 1 $slaDetail to register its scripts...
thanks for the comments... i will have to add some features to support that properly in my third release
Big Changes for v3
Hi guys, i need your opinion about the usage. it seems that making the form to extend to CActiveForm makes the implementation of rows cumbersome
in my next release i would make it as a CWidget
this is the example for the next release:
$this->widget('dynamictabularform',array( /** * ajaxUpdate and rowUrl are for the ajax updating */ 'ajaxUpdate'=>true, 'rowUrl'=>array('row'), /** * these next variables are for the initial data * that will not be populated via ajax */ 'form'=>$form, // the form that the widget is nested into 'rowView'=>'_rowForm', // the view file of the initial view 'keyedViewVariables'=>array('models'=>$models), 'viewVariables'=>array('user'=>Yii::app()->user) ));
also, in the first 2 releases the Dynamictabularform::rowForm() is a model specific method. i want to get away with that. what i wanted is to make the users be able to use different multiple models for each widgets.
any more comments would be appreciated
click does not work to add new details
Hello people.
I have a problem.
I run the example but when I press + does not add a new row. Will have any idea?
thanks
Fixing the '+' button trouble and other documentation troubles.
First of all,
Many thanks @ezekielnoob by save long time although I had to understanding the code (thank goodness that is short xD).
Here talk about problems I encountered and resolved to make it work.
@Claudio Kerekes the same thing happened to me and to fixing it I did the following :
instead it:
Following complete code:
public function actions ( ) { return array ( ' getRowForm ' = > array ( ' class' = > ' ext.DynamicTabularForm.actions.GetRowForm ' 'view' = > ' _row_form ' ' modelClass ' = > ' VisitLanguage ' ) ) ; }
And the same with the definition in row main.php "'import' = > array ( ..." from, where instead of:
' ext.dynamictabularform . * '
we write :
' ext.DynamicTabularForm . * '
2 . At the view to rendering "_rowForm", in the row "updateTypeField" we must change "updateType " by the id ( key) of our model on which we work.
Example: if my $model is Visit and this has "id_visit" key, we would type the following:
<? php echo $ form-> updateTypeField ( $ model , $ key , " id_visit " , array (' key' = > $ key) ) >
How did I notice this? When checking the GET params that come from the javascript code by DynamicTabularForm.php class:
$ cs- > registerScript ( " DynamicForm " " var counter = " sizeof ($ models) . . " ; addRow function () { counter = counter + 1; $ . ajax ( { url : ' " . $ this -> rowUrl . " ' data: { key : counter, } , success : function ( data) { appendRow (data) } , } ) ; }
, this returned the following exception 500:
<h1> CException < / h1 > <p> Alias " ext.DynamicTabularForm.GetRowForm " is invalid . Make sure it points to an existing PHP file and the file is readable . ( / var / www / yii / framework / YiiBase.php : 322) < / p> <pre> # 0 / var / www / yii / framework /
Thanks,
hope it helps
Totto.
Improvements
sorry about it guys, i have been very busy for a couple of months already and haven't had a change to revisit the code...
Thanks
Thank you for this extension! It saves me a lot of time.
The records aren't being saved to the database
I have modified the code u posted to suit my app but when I click create nothing happens. The data isn't being stored in the database. Can I post the code and you tell me where I went wrong? I removed the header.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.