joblo / multimodelform
处理表单中多个记录和模型扩展的 Yii 扩展
Requires
- php: >=5.2.0
This package is not auto-updated.
Last update: 2024-09-28 16:46:38 UTC
README
此扩展允许在编辑表单中与多个记录和不同模型一起工作。它处理客户端克隆和删除输入元素/分组以及服务器端批量插入/更新/删除。
使用此功能创建表单在视图和控制器中是一种“棘手”的解决方案。此小部件应该为您完成大部分工作。它对订单/注册表单等非常有用...
在 github 上找到最新版本
##功能
- 使用 jQuery 插件 http://www.andresvidal.com/labs/relcopy.html 在客户端克隆/删除表单元素/分组
- 在控制器创建/更新动作中简单处理提交的表单数据
- 同时编辑多个记录和模型(主/详细...)
- 支持每个模型/记录的验证和错误摘要
- 使用 Yii 的“表单构建器”功能自动生成输入表单元素
- tableView (= gridView)
- 在编辑表单中支持带有完整上传处理和图片预览的文件字段。
##注意
并非所有表单输入元素都受支持。
您可以使用最基本的 CFormInputElements(文本、文本区域、下拉列表...)。
一些输入小部件在客户端克隆后需要使用 js 代码进行解决方案。目前支持方法中的现成 JavaScript 代码。
- CJuiDatePicker
- datetimepicker
- EJuiComboBox 和 CJuiAutoComplete(由 Smirnov Ilya 添加)
特殊处理
- 复选框:您必须使用复选框列表(见下文和演示.3.2)
新功能 v6.0.0
- 文件字段完全处理所有上传和验证工作
不受支持。
##要求
- Yii 1.1.6+
##使用
- 将 .../protected/extensions 下的文件提取出来或使用 composer
###主/详细示例
您可以在下面的演示应用程序中找到以下实现的解释。
假设您有两个模型 'group'(id,title)和 'member'(id,groupid,firstname,lastname,membersince)。id 属性是自动递增的主键。
-
使用 gii 生成模型 'Group' 和 'Member'。为了测试错误摘要,将成员的 firstname/lastname 设置为必填项。
-
使用 gii 生成 'GroupController' 和 group/views。对于本例,您不需要创建 'MemberController' 和 member 视图。
-
将 GroupController 的默认动作Update 更改为
[php]
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,
));
}
- 将 actionCreate 的代码更改为如下
[php]
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,
));
}
- 将视图/group/create.php 和 update.php 中的 renderPartial 更改为将参数 $member 和 $validatedMembers 传输到 _form.php
[php]
echo $this->renderPartial('_form', array('model'=>$model,
'member'=>$member,'validatedMembers'=>$validatedMembers));
- 更改 GroupController 的表单视图生成的代码(views/group/_form.php)。
[php]
<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 https://yiiframework.cn/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 -->
##使用验证/保存
您可以将 Multimodelform 的 validate() 和 save() 方法分开,以便在保存之前修改项。
[php]
//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);
}
当然,您可以在没有额外验证的情况下保存()。当 $validatedItems/$deleteItems 为空时,将内部执行验证。
[php]
$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 ...
##使用 tableView
设置属性 'tableView'=>true。
[php]
$this->widget('ext.multimodelform.MultiModelForm',array(
...
'tableView' => true,
//'tableFootCells' => array('footerCol1','footerCol2'...), //optional table footer
...
));
##使用小部件表单元素
Yii 允许使用 type='AWidget' 作为表单元素。因此,在您的表单配置数组中,您可以使用
[php]
array(
'elements'=>array(
....
'lastname'=>array(
'type'=>'text',
'maxlength'=>40,
),
'dayofbirth'=>array(
'type'=>'zii.widgets.jui.CJuiDatePicker',
'language'=>'de',
'options'=>array(
'showAnim'=>'fold',
),
...
)
)
但是,在克隆日期元素时需要使用 JavaScript 解决方案。我们必须将 CJuiDatePicker 功能分配给克隆的新元素。
有 jsBeforeClone、jsAfterClone、jsBeforeNewId、jsAfterNewId 可用的属性,可以在其中实现 JavaScript 代码。使用 'this' 作为当前 jQuery 对象。
对于 CJuiDatePicker、扩展datetimepicker、CJuiAutoComplete 和 EJuiComboBox,都有预定义函数可用,因此克隆日期字段变得非常容易。
您必须将属性 'jsAfterNewId' 分配给准备好的代码。
假设您的表定义元素定义在数组 $formConfig 中。'afterNewIdDatePicker' 从指定元素读取选项,并添加 'datepicker'。
[php]
$this->widget('ext.multimodelform.MultiModelForm',array(
...
// 'jsBeforeNewId' => "alert(this.attr('id'));",
'jsAfterNewId' => MultiModelForm::afterNewIdDatePicker($formConfig['elements']['dayofbirth']),
...
));
现在 'dayofbirth' 字段的克隆应该可以工作。
支持以下功能:
- datetimepicker:使用 afterNewIdDateTimePicker()
- CJuiAutoComplete:使用 afterNewIdAutoComplete()
- EJuiCombobox:使用 afterNewIdJuiComboBox()
对于其他小部件,您需要在克隆时找到正确的 JavaScript 代码。如果您找到了其他小部件的 JavaScript 代码,请通知我。
##可排序字段集
如果您想要通过拖放手动排序项目,请将属性 'sortAttribute' 设置为您的数据库字段以进行排序(应该是整数)。使用 jQuery UI sortable,但仅在 'tableView' 为 false 时才有效。请参阅演示。
[php]
$this->widget('ext.multimodelform.MultiModelForm',array(
...
'sortAttribute' => 'position', //if assigned: sortable fieldsets is enabled
...
));
##复选框
遗憾的是,基本复选框不受支持,因为它不容易处理(请参阅论坛中的注释)。
但如果您需要复选框,可以使用 checkboxlist。如果您只需要一个复选框,可以将项目数据设置为一个包含一个元素的数组。
在视图 / formConfig 中
[php]
$memberFormConfig = array(
'elements'=>array(
...
'flags'=>array(
'type'=>'checkboxlist',
'items'=>array('1'=>'Founder','2'=>'Developer','3'=>'Marketing'), //One single checkbox: array('1'=>'Founder')
),
));
在模型中,您必须在保存/加载时在数组 <-> 字符串之间进行转换 - 请参阅演示中的 Member 模型。
[php]
//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);
}
##文件字段和文件上传
MultiModelForm 处理上传文件和图像的所有内容。分配的 images 将 显示为预览,files 将作为编辑表单中上传输入附近的 下载链接。
将类型为 'file' 的输入元素添加到表单配置中。添加 'visible'=>true 很重要,因为 Yii 默认情况下文件字段是不安全的,而不安全的属性将不可见。
[php]
$memberFormConfig = array(
'elements'=>array(
'firstname'=>array(
'type'=>'text',
'maxlength'=>40,
),
...
'image'=>array(
'type'=>'file',
'visible'=>true, //Important!
),
));
将图像字段添加到您的模型的规则中。图像是文件类型,因此将使用 CFileValidator,您可以添加允许的类型、MIME 类型、最大大小等。设置 'allowEmpty'=>true 很重要,否则,在更新记录时,用户将被强制上传新的图像。
[php]
public function rules()
{
return array(
array('firstname, lastname', 'length', 'max'=>40),
array('image', 'file', 'allowEmpty'=>true,'types'=>'jpg,gif,png'),
...
);
}
数据库中的 文件输入字段必须是字符串类型:VARCHAR(200) 或类似类型以保存相对文件路径。
###默认行为
上传的默认行为是,mmf 将上传的文件保存到公共 webroot 文件夹中的 files/modelclass。
上传文件的相对路径将被分配给文件属性(图像、...)。
请确保公共文件夹 files 可写,就像 assets 文件夹一样。
MMF 通过添加索引:filename-1.jpg、filename-2.jpg 等等来确保文件夹中的文件名唯一。
###更改默认行为
您可以通过向您的 mmf 模型添加 回调方法 来更改此默认行为。
向您的 mmf 模型(成员、...)添加一个名为 mmfFileDir() 的方法。
[php]
public function mmfFileDir($attribute,$mmf)
{
return 'media/'.strtolower(get_class($this));
}
$attribute 也可以使用,例如如果模型中有多个文件字段。$mmf 是 MultiModelForm 小部件。
也许您需要在文件上传时执行其他行为,例如您想在上传或保存文件时创建图像预设(调整大小、...)。
向 mmf 模型添加一个名为 mmfSaveUploadedFile() 的回调方法。参数 $uploadedFile 是 CUploadedFile 实例。
[php]
public function mmfSaveUploadedFile($attribute,$uploadedFile,$mmf)
{
if(!empty($uploadedFile))
{
$uploadedFile->saveAs(...);
... resize or save to db or whatever ...
$this->$attribute = ... path to the preset or other values you like ...
}
}
如果您需要在此回调方法中访问主模型(组、...),您必须在控制器操作中分配新参数 'masterModel'。
[php]
public function actionUpdate($id)
{
$model=$this->loadModel($id); //the Group model
$member = new Member;
$validatedMembers = array(); //ensure an empty array
if(isset($_POST['Group']))
{
...
//the last param $model is the masterModel 'group'.
if(MultiModelForm::save($member,$validatedMembers,$deleteMembers,$masterValues,$_POST['Member'],$model)
...
}
...
}
现在在您的回调方法中,您可以使用 $groupModel=$mmf->masterModel;
###在删除项目后删除文件
MMF在用户在编辑表单中删除条目时不会删除上传的文件。如果您想在删除条目后删除文件,您需要编写如下代码
[php]
if (MultiModelForm::save($model,$validatedItems,$deleteItems,$masterValues))
{
foreach($deleted as $deletedModel)
{
if(!empty($deletedModel->image) && is_file($deletedModel->image))
unlink($deletedModel->image);
}
}
###文件处理属性
- fileReplaceExisting=false:如果为true,则在上传时删除具有相同名称的现有文件
- fileImagePreviewHtmlOptions=array('style' => 'max-width: 100px; max-height: 100px;')编辑表单中图像预览标签的HTML选项
- fileLinkHtmlOptions=array('target' => '_blank')编辑表单中下载链接的HTML选项
##属性showAddItemOnError
关于论坛主题中的请求和解决方案
在错误模式下(模型规则未成功通过)时,用户不应能够添加/克隆条目。现在,您可以设置属性$showAddItemOnError为false以启用此行为。请参阅演示。
##属性allowAddItem和allowRemoveItem(自v3.1起)
例如,如果您只想允许管理员用户添加新条目或删除条目,则可以使用这些属性来显示添加链接和删除链接
[php]
$this->widget('ext.multimodelform.MultiModelForm',array(
...
'allowAddItem' => Yii::app()->user->isAdmin(),
'allowRemoveItem' => Yii::app()->user->hasRole('admin'),
...
));
##Bootstrap
如果您使用Twitters Bootstrap CSS或Yii的Bootstrap扩展之一,请将属性'bootstrapLayout'=true
表单元素/标签将被'control-group'、'controls'等包裹,以便多模型表单能够正确显示。
##属性jsAfterCloneCallback
如果您需要在克隆元素后修改或执行js操作,可以将js函数(newElem,sourceElem)作为回调分配。
- newElem:新的克隆jQuery对象
- sourceElem:克隆源对象
此回调将对记录行中的所有输入执行。
用法
[php]
// a js function in your view
echo CHtml::script('function alertIds(newElem,sourceElem) {
alert(newElem.attr("id"));
alert(sourceElem.attr("id"));}'
);
$this->widget('ext.multimodelform.MultiModelForm',array(
...
'jsAfterCloneCallback'=>'alertIds',
...
));
##注意
-
如果您升级MultiModelForm,不要忘记删除资产。
-
您可以在视图中使用多个MultiModelForm小部件,但mmf 模型必须属于不同的类。在添加更多multimodelform小部件时,请务必分配唯一的widget id。
-
有关小部件的更多选项,请参阅MultiModelForm.php。
-
该小部件永远不会渲染表单开始/结束标签。因此,您始终必须在视图中添加 $this->beginWidget('CActiveForm',...) ... $this->endWidget。
-
在即将发布的Yii版本中,MultiModelEmbeddedForm类的实现需要审查。在1.1.6+中,CActiveForm.init()和CActiveForm.run()的唯一输出是表单开始和结束标签。
-
该扩展也应适用于非ActiveRecord模型:CModel、CFormModel等实例。
-
使用Yii::app()->controller->action->id或$model->scenario根据您的需求生成不同的表单(更新时只读字段...)
##资源
##变更日志
-
v6.0.0 支持处理文件上传和预览的文件字段
- 方法validate()的新参数$initAttributes
- 方法save()的新参数$masterModel
- 添加了对composer的支持 / composer.json
- 内部更改和小的错误修复
-
v5.0 更好的自定义js处理,请参阅代码中的注释;更新后不要忘记清除资产
- 新的属性'removeOnClick'用于在点击删除链接时执行js代码
- 新的属性'jsAfterCloneCallback' - 使用方法:请参阅上述文档
- 新的属性'clearInputs'(=选项['clearInputs'],默认为true)在克隆值上的错误修复
- 新的属性'jsRelCopy'以使用您自己的修改过的jsrelcopy js脚本(放置在资产文件夹中)
-
v4.5
- 添加了对复合主键的支持
- 新的属性'renderForm'允许自定义'MultiModelRenderForm'
- 更改了更新行为:在更新之前从数据库中通过findByPK加载记录(类似于控制器中actionUpdate的默认行为)
-
v4.1
-
错误修复:使用Bootstrap布局渲染tableView
-
v4.0
-
错误修复:当hideCopyTemplate=true时,在tableView中有两个删除的列。非常感谢shoemaker。请参阅此论坛主题
-
支持Twitter的Bootstrap
-
新增属性'addItemAsButton',用于显示“添加项”按钮而不是链接。
-
JavaScript代码中的小错误修复
-
v3.3
-
正确支持relcopy.js中的options['limit']属性。请参阅limitText属性。
-
v3.2
-
新增属性'hideCopyTemplate'(默认为true),表示空的copyTemplate不可见
-
新增带有CheckBoxList的示例
-
v3.1
-
新增:添加了$allowAddItem和$allowRemoveItem属性
-
错误修复:Tableheader中的隐藏字段不再显示
-
错误修复:作为formconfig元素(htmlttags,CFormStringElement)的字符串没有渲染
-
v3.0
-
新增:为fieldsets添加了可排序功能(不在表格视图中)
-
新增:属性$showAddItemOnError=true;在错误模式下显示“添加项”链接和空项
-
新增:带有可排序fieldsets的示例
-
错误修复:可见/隐藏字段
-
v2.2.1 错误修复:数组元素需要额外的'allEmpty'检查
-
v2.2
-
支持表单配置中的数组元素(checkboxlist,radiolist)。这些元素仅在更新时工作,而不是在创建时工作
-
v2.1.1
-
错误修复 - 隐藏元素的标签已被渲染
-
v2.1
-
错误修复 - tableView:隐藏输入已被渲染到单元格中
-
更改了MultiModelForm::save的参数和处理方式
-
v2.0.1 错误修复:Tableheader也显示了隐藏字段。更好的内部标签处理,现在支持必需的'*'符号
-
v2.0 添加了'tableView'和克隆日期/时间小部件的支持
-
v1.0.2 错误修复:在创建主记录时创建了两次详细记录
-
v1.0.1 错误修复:当设置error_reporting(E_ALL)时出现'未定义索引'和'未定义变量'错误