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); }
Set the property 'bootstrapLayout'=true if you use Twitters Bootstrap CSS or one of the Yii bootstrap extensions.
The formelements/labels will be wrapped by 'control-group', 'controls', ... so that the multimodelform should be displayed correct.
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
v4.0
v3.3
v3.0
v2.2.1 Bugfix: Array elements need extra 'allEmpty' check
Total 20 comments
I have tried this comment above. but no luck for me. i have add the add item button to the bottom. But i am very much new to yii and the MultiModelForm looks complex for my understanding.
And also i have same requirement to add items as asked by kunbolat Inline form items and a link to remove that but i am unable to achieve it with out some experts help...
I would be grateful if anybody can help me out in this..
Thanks
I have slightly changed the code of Multimodelform extension in order to render the links for adding records on the bottom of the form what is much more logical position. For this purpose I simply used the trick to store the value of the generated link in a variable instead of echoing it to the output and then I render it within 'tableFootCells' property. Here is the code for changed function renderTableBegin which is part of the Multimodelform extension. In MultiModelForm.php file replace the renderTableBegin function with the following:
I found that the ESelect2 extension was ideal for the requirements on the recent project. It supports key=>value pair integration. However, there was problem when ESelect2 widget was copied with relcopy. So after getting some great advices from the author of the combobox support (see below post), I was able to get things working.
Here is the code which should be included in MultiModelForm extension (MultiModelFormp.php)
And in the _form.php file the definitions are like the following:
If you need more than one ESelect2 widget per model and if they would need different configuration then you should parametrize the options for ESelect2 widget which are used in the afterNewIdSelect2 function. These options are required to properly initialize the widget after copying.
The multimodelform has property to either show or hide so called 'copytemplate' record. This property is named 'hideCopyTemplate'. When one is using a dropdownlist or combobox widget which is configured for a non-empty default value (like 'P' instead of '') for this field then the multimodel form will raise validation error on required fields for the coyptemplate record because the whole 'copytemplate' record is not completely empty. This problem is occurring when the existing record is updated. The multimodelform always ads a copytemplate record which can be either visible or hidden. In order to prevent validation errors one can hack the $_POST array before it is processed in the controller's 'actionUpdate' and set the value of problematic field to ''.
In my recent project the requirement was that whenever a new record is added some of the fields which were using combobox should already have a predefined default value which can be later changed by the user.
Here is the code in which we first check that all other fields are not set and then we set the value to ''. We need to check values for all other fields in this model in order to be sure that we are setting value on the 'empty copytemplate' record.
Fields, which are not displayed on the form, but need to be initialized should be initialized in the beforeValidate function. In the model one should add the follwoing code:
Don't forget to call parent::beforeValidate() at the end of your beforeValidate function.
hi Joblo,
Thanks for nice extension, works like charm. However, I am facing a problem with dependent dropdown type of scenario. I thought may be you can help. the scenario is like this: I have one master form like group in example and other sub form(which will be repeated) for one group. now, this sub form has four fields: items, costp, salep. I get the list of items in a dropdown from db no problem, but now to get the costp of selected item in dropdown, I write an Ajax call and get the data. However, it works only when the sub form is generated once, if I try to create second sub form and do the same, nothing happens due to conflicted id of sub form element. Can you please help? I will appreciate any help.
Regards, VB
Hello again.
I'm also added a combobox widget support. ( http://www.yiiframework.com/extension/combobox )
In my application I use a Toph's fork of combobox: https://github.com/kanyuga/yii-combobox (because this fork supports key=>value separation).
But it shoud work with main branch too (not tested).
Combobox plugin needs a small bugfix to work with multimodelform ( see my comment here: http://www.yiiframework.com/extension/combobox#c12257 )
Here is the code:
Good news, everyone!
Yeah, I made it! :)
The problem was an incorrect element copying: you shoud copy "autocomplete" element without data and events. (more information here: http://api.jquery.com/clone/ )
Now, if you want to use autocomplete elements in your form - your code shoud look like this:
Please update the extension.
When using a dropdownlist in a column of the multimodelform, then the header of that column is not displayed in tableview.
I found the solution: The element's property 'visible'=>true must be explicitly set for an element of type dropdownlist.
Greetings from Austria, Ferdinand
Thank you for the great extension. I'm using a highly modified version of this in my project based on v4.5. One thing I've changed that made it a lot easier to work with is to change all the "echo" statements to compile into variables and return variables instead of echoing them directly. Then at the end of the MultiModelForm.run() function echo out the variable one time.
I think you have two models: Employee and Task But if you only want to display the tasks of a employee/user you can set the masterValue(s) before save in the controller.
Please use this forum topic for more diskussion.
Hi, i would like to know if i can use this extension with just one model. I have Employees that register their tasks daily and i need to save all of them in a batch. The form looks like a table where the user can clone the form elements depending on the amount of tasks he needs to load. I´m using yii boostrap. I would appreciate any comment. Thanks!
I found a bug in this exstension. Demo works fine, but I found a problem in my application: copy template and table header (when using table view) is empty by default. Clicking "add item" link adds only remove link and nothing more.
I found a solution by forcing "visible" attribute on every element in form:
Extension ver. 4.1
Hi Joblo,
I posted a workaround for the autocomplete feature on the extension forum. I resorted to binding a javascript code to the new id of the autocomplete element. It worked on all subsequent clones except... the 1st one. Strange!!
Hi Joblo,
I am implementing your extension in my application. Something I do not understand: where are you defining $deleteItems and $deleteMembers?
Thanks
Thanks very much for your prompt reply. Will work on it and let you know if I succeed :)
I have just released the cascadedropdown but didn't try it in a multimodelform.
Only an idea, have no time for testing:
Add two dropdownlists in the formconfig, add the code to cascade these inputs (master->setDependent) in the view. I don't know what happens after cloning. I think you have to add js-code to assign the cascade for the new ids again. You can try if this is possible with the 'jsAfterClone' or 'jsAfterNewId' property for that ... Take a look at the source of the MultiModelForm::afterNewIdDatePicker.
Thanks for this great extension. Before starting to modify the views according to my needs, I would like to know if it is possible to have among the member fields a dependent dropdown list with values which will depend on value input in a previous field among the member fields? If so, can you just give me a hint?
Many thanks
Just loved it! Many Thanks.
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 ...
Leave a comment
Please login to leave your comment.