This extension allows to work with multiple records and different models in a edit form. It handles clientside cloning and removing input elements/fieldsets and serverside batchInsert/Update/Delete.
Creating forms with this functionality is a 'tricky' workaround in views and controllers. This widget should do the main part of the work for you. It can be useful for order/registration forms ...
See:
'multimodelform' could be an updated version of my extension jqrelcopy, but because of the different approach jqrelcopy will stay as lightweight extension for other needs. If you don't have to work with models take a look at jqrelcopy.
Not every form input element is supported.
You can use the a few basic CFormInputElement (text, textarea, dropdownlist, ...) and some widgets (CJuiDatePicker, see below) but at the moment
are not supported.
New since v2.0
Imporant note when upgrading to v2.1
MultiModelForm::save($model,$validatedItems,$deleteItems,$masterValues)
Extract the files under .../protected/extensions
Master/Detail example:
Assume you have two models 'group' (id, title) and 'member' (id, groupid, firstname,lastname,membersince). The id attribute is the autoincrement primary key.
Generate the models 'Group' and 'Member' with gii. For testing the error summary set the members firstname/lastname as required in the rules.
Generate the 'GroupController' and the group/views with gii. You don't need to create a 'MemberController' and the member views for this example.
Change the default actionUpdate of the GroupController to
public function actionUpdate($id) { Yii::import('ext.multimodelform.MultiModelForm'); $model=$this->loadModel($id); //the Group model $member = new Member; $validatedMembers = array(); //ensure an empty array if(isset($_POST['Group'])) { $model->attributes=$_POST['Group']; //the value for the foreign key 'groupid' $masterValues = array ('groupid'=>$model->id); if( //Save the master model after saving valid members MultiModelForm::save($member,$validatedMembers,$deleteMembers,$masterValues) && $model->save() ) $this->redirect(array('view','id'=>$model->id)); } $this->render('update',array( 'model'=>$model, //submit the member and validatedItems to the widget in the edit form 'member'=>$member, 'validatedMembers' => $validatedMembers, )); }
public function actionCreate() { Yii::import('ext.multimodelform.MultiModelForm'); $model = new Group; $member = new Member; $validatedMembers = array(); //ensure an empty array if(isset($_POST['Group'])) { $model->attributes=$_POST['Group']; if( //validate detail before saving the master MultiModelForm::validate($member,$validatedMembers,$deleteItems) && $model->save() ) { //the value for the foreign key 'groupid' $masterValues = array ('groupid'=>$model->id); if (MultiModelForm::save($member,$validatedMembers,$deleteMembers,$masterValues)) $this->redirect(array('view','id'=>$model->id)); } } $this->render('create',array( 'model'=>$model, //submit the member and validatedItems to the widget in the edit form 'member'=>$member, 'validatedMembers' => $validatedMembers, )); }
echo $this->renderPartial('_form', array('model'=>$model, 'member'=>$member,'validatedMembers'=>$validatedMembers));
<div class="form wide"> <?php $form=$this->beginWidget('CActiveForm', array( 'id'=>'group-form', 'enableAjaxValidation'=>false, )); <p class="note">Fields with <span class="required">*</span> are required.</p> <?php //show errorsummary at the top for all models //build an array of all models to check echo $form->errorSummary(array_merge(array($model),$validatedMembers)); <div class="row"> <?php echo $form->labelEx($model,'title'); <?php echo $form->textField($model,'title'); <?php echo $form->error($model,'title'); </div> <?php // see http://www.yiiframework.com/doc/guide/1.1/en/form.table // Note: Can be a route to a config file too, // or create a method 'getMultiModelForm()' in the member model $memberFormConfig = array( 'elements'=>array( 'firstname'=>array( 'type'=>'text', 'maxlength'=>40, ), 'lastname'=>array( 'type'=>'text', 'maxlength'=>40, ), 'membersince'=>array( 'type'=>'dropdownlist', //it is important to add an empty item because of new records 'items'=>array(''=>'-',2009=>2009,2010=>2010,2011=>2011,), ), )); $this->widget('ext.multimodelform.MultiModelForm',array( 'id' => 'id_member', //the unique widget id 'formConfig' => $memberFormConfig, //the form configuration array 'model' => $member, //instance of the form model //if submitted not empty from the controller, //the form will be rendered with validation errors 'validatedItems' => $validatedMembers, //array of member instances loaded from db 'data' => $member->findAll('groupid=:groupId', array(':groupId'=>$model->id)), )); <div class="row buttons"> <?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); </div> <?php $this->endWidget(); </div><!-- form -->
The behavior has changed in v2.1. In older versions 'save' always exceuted 'validate' internally. 'validate' means to recreate all items from the form data. So there was no chance to alter the attributes of the models in $validatedItems between calling 'validate' and 'save'.
No you can do something like this.
//validate formdata and populate $validatedItems/$deleteItems if (MultiModelForm::validate($model,$validatedItems,$deleteItems,$masterValues)) { //... alter the model attributes of $validatedItems if you need ... //will not execute internally validate again, because $validatedItems/$deleteItems are not empty MultiModelForm::save($model,$validatedItems,$deleteItems,$masterValues); }
But you can still use 'save' without extra validate before. Validation will be internally done when $validatedItems/$deleteItems are empty.
$validatedItems=array(); if(isset($_POST['FORMDATA']) && MultiModelForm::save($model,$validatedItems,$deleteItems,$masterValues)) { //... validation and saving is ok ... //redirect ... } //No POST data or validation error on save $this->render ...
Set the property 'tableView'=>true.
$this->widget('ext.multimodelform.MultiModelForm',array( ... 'tableView' => true, //'tableFootCells' => array('footerCol1','footerCol2'...), //optional table footer ... ));
Yii allows type='AWidget' as form element. So in your form config array you can use:
array( 'elements'=>array( .... 'lastname'=>array( 'type'=>'text', 'maxlength'=>40, ), 'dayofbirth'=>array( 'type'=>'zii.widgets.jui.CJuiDatePicker', 'language'=>'de', 'options'=>array( 'showAnim'=>'fold', ), ... ) )
This however needs a javascript workaround on cloning the date element. We have to assign the CJuiDatePicker functionality to the cloned new element.
Since v2.0 there are the properties jsBeforeClone,jsAfterClone,jsBeforeNewId,jsAfterNewId available where javascript code can be implemented. Use 'this' as the current jQuery object.
For CJuiDatePicker and the extension extension datetimepicker there are predefined functions available, so it's easy to make cloning date fields work.
You have to assign the property 'jsAfterNewId' to the prepared code.
Assume your form definition elements are defined in the array $formConfig. 'afterNewIdDatePicker' reads the options from the specified element and adds the 'datepicker':
$this->widget('ext.multimodelform.MultiModelForm',array( ... // 'jsBeforeNewId' => "alert(this.attr('id'));", 'jsAfterNewId' => MultiModelForm::afterNewIdDatePicker($formConfig['elements']['dayofbirth']), ... ));
Now cloning of the field 'dayofbirth' should work.
Support for 'datetimepicker' is analog (afterNewIdDateTimePicker).
For other widgets you have to find out the correct javascript code on cloning. Please let me know if you have found a javascript code for other widgets.
Note: I couldn't get Autocomplete working. Maybe one of you has an idea (see afterNewIdAutoComplete in MultiModelForm.php).
Set the property 'sortAttribute' to your db-field for sorting (should be an integer) if you want to order the items by drag/drop manually. Uses jQuery UI sortable, but works only when 'tableView' is false. See the demo.
$this->widget('ext.multimodelform.MultiModelForm',array( ... 'sortAttribute' => 'position', //if assigned: sortable fieldsets is enabled ... ));
Regarding to requests and workarounds in the forum topic:
A user should not be able to add/clone items, when in error mode (model rules not passed successfully). Now you can set the property $showAddItemOnError to false to enable this behavior. See the demo.
For example, if you only want to allow an admin user to add new items or remove items, you can use these properties to display the addlink and the removelinks
$this->widget('ext.multimodelform.MultiModelForm',array( ... 'allowAddItem' => Yii::app()->user->isAdmin(), 'allowRemoveItem' => Yii::app()->user->hasRole('admin'), ... ));
Unfortunatly the basic checkbox is not supported, because it's not so easy to handle (see the comments in the forum).
But if you need checkboxes, you can use the checkboxlist - see the demo.3.2 If you only need a single checkbox you can set the item-data to an array with one item.
In the view / formConfig:
$memberFormConfig = array( 'elements'=>array( ... 'flags'=>array( 'type'=>'checkboxlist', 'items'=>array('1'=>'Founder','2'=>'Developer','3'=>'Marketing'), //One single checkbox: array('1'=>'Founder') ), ));
In the model you have to convert array <-> string on saving/loading - see the Member model in the demo
/* * Convert the flags array to string */ public function beforeSave() { if(parent::beforeSave()) { if(!empty($this->flags) && is_array($this->flags)) $this->flags = implode(',',$this->flags); return true; } return false; } /* * Convert the flags string to array */ public function afterFind() { $this->flags = empty($this->flags) ? array() : explode(',',$this->flags); }
Take a look at MultiModelForm.php for more options of the widget.
The widget never will render a form begin/end tag. So you have always to add $this->beginWidget('CActiveForm',...) ... $this->endWidget in the view.
The implementation of the class MultiModelEmbeddedForm needs review in upcoming Yii releases. In 1.1.6+ the only output of CActiveForm.init() and CActiveForm.run() is the form begin and end tag.
The extension should work for non activerecord models too: instances of CModel, CFormModel...
You can add more widgets for rendering other models in the form view. Take care to assign a unique widget id when adding more multimodelform widgets.
Use Yii::app()->controller->action->id or $model->scenario to generate different forms for your needs (readonly fields on update ...)
Forum topic multimodelform/jqrelcopy
Tutorial Using Form Builder
jQuery plugin RelCopy
v3.0
v2.2.1 Bugfix: Array elements need extra 'allEmpty' check
Total 20 comments
to add this to your config file:
instead of using:
in your controller? Yii will handle the autoloading as needed, so using the actual import line seems redundant / leads to messier controllers ...
Didn't know there was one..
This is an issue of how to create forms with the formbuilder. Please post it in the forum, or in the multimodelform topic
In this case, I would first work with the form builder to generate a form you like and afterwards integrate this detailform in the multimodelform.
I have radio button in the widget, but I would like them displayed horizontally. I tried 'display'=>'inline', but it didn't work:
Any Suggestions?
Hi.
This extension really rocks. I have a question. How to embed the detail into a fieldset?
Regards!
Hi Joe, danke für die super Externsion. Gibt es eine Anleitung wie man die das Aussehen / die Form der Extension anpassen kann? Ich würde gerne meine eigene Tabelle verwenden und komme mit der Datei nicht klar. Wünschenswert wäre auch das jede Klasse eine eigene Datei hätte und es eine Datei für die Ausgabe der Extension gäbe. Ich benutze die Version 3.0 thx!
Please see my response in the forum
Thank you for the nice extension. I met two issues when using this, I have master class 'Team' like the 'Group' in demo and 'User' like the 'Member' describe in demo. issue 1: my backend database is sqlserver, where the User table has id column which is auto increment identity,
with the extension, I can update user within a team,but when add a new user, the error occurs as: CDbCommand failed to execute the SQL statement: SQLSTATE[23000]: [Microsoft][SQL Server Native Client 10.0][SQL Server]Cannot insert explicit value for identity column in table 'user' when IDENTITY_INSERT is set to OFF.. The SQL statement executed was: INSERT INTO [dbo].[user] ([username], [email], [password], [phone], [mobile], [id], [team_id]) VALUES (:yp0, :yp1, :yp2, :yp3, :yp4, :yp5, :yp6)
issue 2: How can I only show some attributes (e.g, only reuired column) in detail form instead of currently, it shows all columns of Users table
Looking forward to helping on this
It would be nice if you upload the full file into the forum topic (see resource) so that I can integrate the fileupload feature in the next release. Maybe you can add a simple full example too.
I have extend the MultimodelForm to support File upload using CUploadedFile.
Let's use a model to store our file.
And in your view, use the CForm element like this.
With validate and save separated, I can modify the validated items with correct data. I'll try with fileField, and report back asap. Thanks again. This extension really awesome.
feby_lho:
Now you should be able to alter the data between 'validate' and 'save'. See usage validate/save above.
dinhtrung/feby_lho: Sorry, I don't have the time yet to investigate the javascript stuff for cloning autocomplete (I have tried a few hours without a result) or filefields.
You have to play around with jsAfterNewId ... to hook into the js-code on cloning. Take a look at the code of MultiModelForm::afterNewIdDatePicker that returns the necessary js-code. Maybe it can be easy done with filefields, not easy with autocomplete :-(
Still can't get it right at line 335, since you reset the array to null, and get error if I uncomment this line I think this validatedItems don't have to be as parameter in your function, am I correct? you set the attributes from the form anyway,
and you change the data type of validatedItems from array to modelclass in validate(), any solutions to set value like my comment #3925 below from controller? I'm stuck.
thanks
I've tried your extension and it works great for post data and all. But how can I get multimodelform to work with file field? I can only render the view with file field, but could not make my model understand to process the upload file correctly. Any suggestion?
it'll be great idea and I'm looking forward for the autocomplete feature too
What you want to do cannot work, because your $validatedDetail is an out parameter.
Take a look at line 335 - method initItems: I have added a line to reset the $validatedItems as a bugfix v1.0.2, because detailrecords where created twice if I call 'MultiModelForm::validate' and 'MultiModelForm::save' in the usage comment above on 'actionCreate'.
So you can comment this line an see what happens.
Maybe I find another workaround like adding onBeforeSave ... to alter attribute values before saving.
hi, I have some problems,
and get error
CDbCommand failed to execute the SQL statement: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'subtotal' cannot be null. The SQL statement executed was: INSERT INTO
item_usage_detail(item_id,quantity,detail_id,usage_id,subtotal) VALUES (:yp0, :yp1, :yp2, :yp3, :yp4)what I'm missing here? thanks
The issue of showing hidden fields in the table header is fixed in v2.0.1.
And also fixed: Now the '*' will be added to the label of required attributes.
one more question :D, I tried tableView and like example above, my purchase_detail has some columns that I don't want to show/don't need user input, but tableView seems to show all the columns, its' showed only as table header and don't have input area like others that I set in formConfig, how can I remove this?
thanks again
When you call MultiModelForm::validate the 'validatedMembers' will be populated with all detailmodels submitted by the form. Some of this models can have errors (checked by the rules). So additionally you can check if validatedMembers is an empty array.
You have to display the users flash messages in the view too.
Leave a comment
Please login to leave your comment.