i-avatar777 / yii2-service-form-ajax
用于yii2的表单验证和执行AJAX的服务。代替提交并刷新页面,使用AJAX请求和调用JS方法success
。
Requires
- php: >=5.5.0
- yiisoft/yii2: *
Requires (Dev)
README
用于yii2的表单验证和执行AJAX的服务。代替提交并刷新页面,使用AJAX请求和调用JS方法 success
。
概念
为了通过AJAX传递表单。
返回后显示错误。
例外情况是,无法通过AJAX传递文件,或者比较困难,因此应用在线上传小部件,其中仅通过AJAX传递文件。
\iAvatar777\services\FormAjax\ActiveRecord::attributeWidgets - 这里指定了小部件及其设置,用于在表单中输出
这样在页面上指定表单
<?php $model = new \avatar\models\validate\CabinetSchoolFilesCloudSave(); ?> <?php $form = \iAvatar777\services\FormAjax\ActiveForm::begin([ 'model' => $model, 'formUrl' => '/cabinet-school-files/add', 'success' => <<<JS function (ret) { $('#modalInfo').on('hidden.bs.modal', function() { }).modal(); } JS ]) ?> <?= $form->field($model, 'url') ?> <?= $form->field($model, 'key')->passwordInput() ?> <?php \iAvatar777\services\FormAjax\ActiveForm::end(['label' => 'Сохранить']) ?>
如果您使用这个 ActiveForm
,则必须使用这个模型 ActiveRecord
或这个 Model
。在其中定义了返回验证错误的函数 getErrors102
。或者您可以使用自己的,但请将此函数复制到您的模型中。
作为表单处理程序,主要考虑的是 DefaultFormAjax
,它在控制器中定义如下
class CabinetBlogController extends CabinetBaseController { public function actions() { return [ 'add' => [ 'class' => '\iAvatar777\services\FormAjax\DefaultFormAjax', 'model' => '\avatar\models\forms\BlogItem', ], ]; } }
其中 model
是表单模型,而 add
是操作标识符 (Action::id
)。
为了使表单发送检查此处理程序,必须在表单中指定
与YII2标准AJAX处理的区别
安装
显示小部件
在函数 attributeWidgets
中指定字段和小部件的对应关系。如果表单中指定了,则此小部件将自动绘制
<?= $form->field($model, 'image') ?>
class ProductImage extends \iAvatar777\services\FormAjax\ActiveRecord { public function attributeWidgets() { return [ 'image' => [ 'class' => '\iAvatar777\widgets\FileUpload7\FileUpload', 'update' => \avatar\controllers\CabinetSchoolPagesConstructorController::getUpdate(), 'settings' => \avatar\controllers\CabinetSchoolPagesConstructorController::getSettingsLibrary($this->_school_id, $this->_type_id), 'events' => [ 'onDelete' => function ($item) { $r = new \cs\services\Url($item->image); $d = pathinfo($r->path); $start = $d['dirname'] . '/' . $d['filename']; File::deleteAll(['like', 'file', $start]); }, ], ], ]; } }
禁用按钮
如果需要不显示按钮并执行子处理,则可以这样
<?php \iAvatar777\services\FormAjax\ActiveForm::end(['isHide' => true]) ?>
ajaxJson({ url: '/...', data: $('{$formSelector}').serializeArray(), success: function(ret) { // }, errorScript: function(ret) { if (ret.id == 102) { for (var key in ret.data.errors) { if (ret.data.errors.hasOwnProperty(key)) { var name = key; var value = ret.data.errors[key]; var id; for (var key2 in ret.data.fields) { if (ret.data.fields.hasOwnProperty(key2)) { var name2 = key2; var value2 = ret.data.fields[key2]; if (name == name2) { id = 'field-' + value2; } } } var g = $('.' + id); g.addClass('has-error'); g.find('p.help-block-error').html(value.join('<br>')).show(); } } } } });
特点
提交事件在Enter键上被触发两次,但实际上只触发一次。没有找到原因。因此,为了解决这个问题,我设置了参数 delta
= 1000毫秒,在此期间不能再次调用事件 我的提交
。
$('#formc2ff52cf').submit(function(ret) { form1.isStart = true; form1.thisStart = (new Date()).getTime(); if (form1.lastStart == -1) { form1.lastStart = form1.thisStart; } else { if (form1.lastStart + form1.delta > form1.thisStart) { form1.isStart = false; } } if (form1.isStart) { // AJAX } return false; });
在控制器中添加或更新时使用
如果模型类是 \yii\db\ActiveRecord
或 \yii\base\Model
,则控制器中保存的代码如下
if (Yii::$app->request->isPost) { if ($model->load(Yii::$app->request->post()) && $model->validate()) { $s = $model->save(); return self::jsonSuccess($s); } else { $fields = []; foreach ($model->attributes as $k => $v) { $fields[$k] = Html::getInputId($model, $k); } return self::jsonErrorId(102, [ 'errors' => $model->errors, 'fields' => $fields, ]); } }
如果模型类是 \iAvatar777\services\FormAjax\ActiveRecord
或 \iAvatar777\services\FormAjax\Model
,则它们存在函数 getErrors102()
,因此代码更简洁,可以写成这样
if (Yii::$app->request->isPost) { if ($model->load(Yii::$app->request->post()) && $model->validate()) { $s = $model->save(); return self::jsonSuccess($s); } else { return self::jsonErrorId(102, $model->getErrors102()); } }
使用示例
如果保存是在Model中调用,则parent::将调用onBeforeUpdate和onAfterUpdate
标准操作
为了简化标准操作,库中存在标准操作处理器集
\iAvatar777\services\FormAjax\DefaultFormAjax
- 用于处理AJAX表单处理\iAvatar777\services\FormAjax\DefaultFormAdd
- 用于在页面上绘制表单添加,页面传递变量$model
\iAvatar777\services\FormAjax\DefaultFormEdit
- 用于在页面上绘制表单编辑,页面传递变量$model
。记录标识符通过GET方法的方法传递参数id
。\iAvatar777\services\FormAjax\DefaultFormDelete
- 用于通过AJAX删除记录。记录标识符通过POST方法传递参数id
。
如果页面上有表单添加
在控制器中,在函数 actions()
中指定
class CabinetBlogController extends CabinetBaseController { public function actions() { return [ 'add' => [ 'class' => '\common\services\FormAjax\DefaultFormAdd', 'model' => '\avatar\models\forms\BlogItem', 'view' => '@avatar/views/blog/add', ], ]; } }
参数 view
不是必需的,如果未指定,则使用操作标识符 (action)。
\iAvatar777\services\FormAjax\ActiveRecord
的事件模型
Widget:onBeforeLoad выполняется до $model->load()
Widget:onAfterLoad выполняется после $model->load()
Widget:onAfterLoadDb выполняется после $model->findOne()
Widget:onBeforeInsert выполняется до $model->save()
Widget:onAfterInsert выполняется после $model->save()
Widget:onBeforeUpdate выполняется до $model->save()
Widget:onAfterUpdate выполняется после $model->save()
Widget:onBeforeValidate выполняется до $model->validate()
Widget:onAfterValidate выполняется после $model->validate()
Widget:onBeforeDelete выполняется до $model->delete()
Widget:onAfterDelete выполняется после $model->delete()
它们如何定义?传递什么内容?
function onBeforeUpdate() {}
模型启动事件
例如 delete
- attributeWidgets()
['events']['onBeforeDelete']
- 组件:onBeforeDelete
- \yii\db\ActiveRecord::delete()
- 组件:onAfterDelete
- attributeWidgets()
['events']['onAfterDelete']
也就是说,如果需要,可以编写自己的处理器,并且同时在该组件中使用额外的处理。
例如,如果表单中显示的值与数据库中存储的值格式不同,可以在组件处理器中指定类型转换。
或者例如,如果需要在删除记录后实际删除图片。
三种显示表单的场景
- 总共有三种显示表单的场景
1
$model->findOne()
2 - если прошла валидация
$model->findOne()
$model->load()
$model->validate()
$model->save()
3 - если не прошла валидация
$model->findOne()
$model->load()
$model->validate()
如果表单中更改了字段的存储格式,则需要注意,在绘制表单时,字段应存储相同的格式。
如果进行格式转换,则最好这样做
我可以在 onAfterLoadDb
中进行转换,从 YYYY-mm-dd 转换为 dd.mm.YYYY,在 onBeforeUpdate
事件中从 dd.mm.YYYY 转换为 YYYY-mm-dd,这样在验证字段时将格式为 dd.mm.YYYY,在显示表单时将格式为 dd.mm.YYYY,在加载表单时值将以 dd.mm.YYYY 的格式加载,因此不需要进行任何更改。
如果我想要以 DateTime
格式保存(保持值在 DateTime
格式)怎么办?如何进行验证?如何在表单中显示值?例如:我可以在 onAfterLoadDb
中进行转换,从 YYYY-mm-dd 转换为 DateTime
,在 onAfterLoad
事件中从 dd.mm.YYYY 转换为 DateTime
,验证用户输入的值格式为 DateTime
。在 onBeforeUpdate
事件中将 DateTime
转换为 YYYY-mm-dd。绘制组件时将使用 DateTime
格式。
由于保存表单后操作结束,因此保存表单后表单不再绘制。
组件
基本类: \iAvatar777\services\FormAjax\Widget
组件的目的是可以在其中调用事件以处理收到的数据。
例如,在日期组件中指定日期格式为 dd.mm.yyyy,为了进行验证,它应该是 dd.mm.yyyy 格式,在保存时它应该是 yyyy-mm-dd 格式。
组件中的函数示例
public function onAfterLoadDb($field) { }
在简单 HTML 输出后初始化表单
有时需要在输出简单 HTML 后处理或附加表单处理器。在这种情况下,为了使表单正常工作,需要调用该函数
iAvatar777_ActiveForm.init(formId, formSelector, formUrl, functionSuccess, type);
获取表单字段值
有时需要以非标准方式获取字段值。例如,从文本编辑器。在这种情况下,组件中指定获取字段值的函数
class Widget extends \yii\base\Widget { public function get_field_value() { $id = 'field-' . Html::getInputId($this->model, $this->attribute); $name = Html::getInputName($this->model, $this->attribute); return <<<JS function (fields) { // очищенный результат var rows; var serializeArray = $(formSelector).serializeArray(); // функция зачистки, учитывая что значений может быть много то алгоритм такой, прохожусь по всему массиву, если втретилось это поле то не вклчаю его в результат, остальное включаю. for (var i=0; i < serializeArray.length; i++) { if (serializeArray[i].name == '{name}') { // делаю замену rows.push({name: 'name', value: '1'}); } else { // добавляю rows.push(serializeArray[i]); } } return rows; } JS; } }
如果没有函数,则将通过字段的 ID(INPUT
)选择字段值。仅收集表中列出的字段(INPUT
)。
从 serrializeArray 清除值的脚本示例
// очищенный результат var rows; var serializeArray = $(formSelector).serializeArray(); // функция зачистки, учитывая что значений может быть много то алгоритм такой, прохожусь по всему массиву, если втретилось это поле то не вклчаю его в результат, остальное включаю. for (var i=0; i < serializeArray.length; i++) { if (serializeArray[i].name == '{name}') { // ничего не делаю } else { // добавляю rows.push(serializeArray[i]); } }
替换简单字段的脚本示例
// очищенный результат var rows; var serializeArray = $(formSelector).serializeArray(); // функция зачистки, учитывая что значений может быть много то алгоритм такой, прохожусь по всему массиву, если втретилось это поле то не вклчаю его в результат, остальное включаю. for (var i=0; i < serializeArray.length; i++) { if (serializeArray[i].name == '{name}') { // делаю замену rows.push({name: 'name', value: '1'}); } else { // добавляю rows.push(serializeArray[i]); } }