joblo/multimodelform

处理表单中多个记录和模型扩展的 Yii 扩展

安装: 87

依赖: 0

建议者: 0

安全: 0

星标: 13

关注者: 6

分支: 9

公开问题: 7

类型:yii-extension

dev-master 2014-09-24 07:03 UTC

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)时出现'未定义索引'和'未定义变量'错误