add模式:为空 * > edit模式:当前编辑数据ID * > delete模式:当前删除的数据ID集合 * @method Form|array|integer|string itemId($value = null) * * 当前数据 * > add模式:为空 * > edit模式:当前编辑数据 * > delete模式:当前删除的数据集合 * * @method Form|Model|stdClass|Collection item($value = null) * * @method Form|mixed engine($value = null) * * Hook 数据已提交 * $value = function (Form $form) { $form->item()->each(function ($item) { }); } * @method Form|mixed hookSubmitted($value = null) * * Hook 数据正在保存 * $value = function(Form $form){ return Response::generateError('error'); } * @method Form|mixed hookSaving($value = null) * * Hook 已保存 * $value = function (Form $form) { $form->item(); } * @method Form|mixed hookSaved($value = null) * * Hook 正在删除,会运行在事务中,如果返回标准错误,会阻止删除 * $value = function (Form $form) { $form->item()->each(function ($item) { }); } * @method Form|mixed hookDeleting($value = null) * * Hook 已删除 * $value = function (Form $form) { $form->item()->each(function ($item) { }); } * @method Form|mixed hookDeleted($value = null) * * Hook 数据已更改(增加、修改、删除、排序) * $value = function (Form $form) { RepositoryUtil::makeItems($form->item())->map(function ($item) { });} * @method Form|mixed hookChanged($value = null) * * @method Form|mixed dataSubmitted($value = null) * @method Form|mixed dataForming($value = null) * @method Form|mixed dataAdding($value = null) * @method Form|mixed dataEditing($value = null) * * @method Form|mixed canAdd($value = null) * @method Form|mixed canEdit($value = null) * @method Form|mixed canDelete($value = null) * @method Form|mixed formClass($value = null) * @method Form|mixed treeMaxLevel($value = null) * @method Form|mixed treeRootPid($value = null) * @method Form|mixed formUrl($value = null) * @method Form|mixed ajax($value = null) * @method Form|mixed formAttr($value = null) * */ class Form implements Renderable { use HasFields, HasBuilder, HasFluentAttribute, HasSort, HasCascadeFields, HasRepositoryFilter; /** * @var string */ private $id; /** * @var Repository */ private $repository; /** * @var string */ private $view = 'modstart::core.form.index'; protected $fluentAttributes = [ 'engine', 'builder', 'mode', 'title', 'showSubmit', 'showReset', 'itemId', 'item', 'hookSubmitted', 'hookSaving', 'hookSaved', 'hookDeleting', 'hookDeleted', 'hookChanged', 'dataSubmitted', 'dataForming', 'dataAdding', 'dataEditing', 'canAdd', 'canEdit', 'canDelete', 'canSort', 'formClass', 'treeMaxLevel', 'treeRootPid', 'formUrl', 'ajax', 'formAttr', ]; /** * 运行引擎 @see FormEngine * @var string */ private $engine = 'basic'; /** * 表单处理模式 @see FormMode * @var string */ private $mode = 'form'; /** * 标题 * @var string */ private $title; /** * 是否显示提交 * @var bool */ private $showSubmit = true; /** * 是否显示重置 * @var bool */ private $showReset = true; /** * 表单编辑、删除的记录ID * @var integer|string|array|mixed */ private $itemId = null; /** * @var Model|stdClass */ private $item; /** * @var Closure */ private $hookSubmitted; /** * @var Closure */ private $hookSaving; /** * @var Closure */ private $hookSaved; /** * @var Closure */ private $hookDeleting; /** * @var Closure */ private $hookDeleted; /** * @var Closure */ private $hookChanged; /** * @var array */ private $dataSubmitted; /** * @var array */ private $dataForming; /** * @var array */ private $dataAdding; /** * @var array */ private $dataEditing; private $canAdd = true; private $canEdit = true; private $canDelete = true; private $canSort = false; private $formClass = ''; private $treeMaxLevel = 99; private $treeRootPid = 0; private $formUrl = null; private $ajax = true; private $formAttr = ''; /** * Form constructor. * @param Model|\Illuminate\Database\Eloquent\Builder|Repository $model */ public function __construct($repository, \Closure $builder = null) { $this->id = IdUtil::generate('Grid'); $this->repository = Repository::instance($repository); $this->setupFields(); $this->fieldDefaultRenderMode(FieldRenderMode::FORM); $this->setupRepositoryFilter(); $this->builder($builder); } public static function make($model, \Closure $builder = null) { if (class_exists($model) && is_subclass_of($model, Model::class)) { return new Form($model, $builder); } return new Form(DynamicModel::make($model), $builder); } public function asTree($keyName = 'id', $pidColumn = 'pid', $sortColumn = 'sort', $titleColumn = 'title') { $this->repository->setKeyName($keyName); $this->repository->setTreePidColumn($pidColumn); $this->repository->setSortColumn($sortColumn); $this->repository->setTreeTitleColumn($titleColumn); $this->engine = FormEngine::TREE; $this->canSort(true); return $this; } public function asTreeMass($rootPid = 0, $keyName = 'id', $pidColumn = 'pid', $sortColumn = 'sort', $titleColumn = 'title') { $this->repository->setKeyName($keyName); $this->repository->setTreePidColumn($pidColumn); $this->repository->setSortColumn($sortColumn); $this->repository->setTreeTitleColumn($titleColumn); $this->engine = FormEngine::TREE_MASS; $this->canSort(true); return $this; } private function build() { $this->runBuilder(); if ($this->engine == FormEngine::TREE) { /** @var Select $field */ if ($this->treeMaxLevel > 1) { $field = FieldManager::make($this, 'select', $this->repository->getTreePidColumn(), L('Parent')); $field->optionRepositoryTreeItems($this->repository, $this->treeMaxLevel); } else { $field = FieldManager::make($this, 'hidden', $this->repository->getTreePidColumn(), L('Parent')); $field->value(0); } $this->prependField($field); } else if ($this->engine == FormEngine::TREE_MASS) { /** @var Display $field */ $field = FieldManager::make($this, 'display', $this->repository->getTreePidColumn(), L('Parent')); $field->addable(true)->editable(true)->listable(false); $field->hookRendering(function (AbstractField $field, $item, $index) { if (empty($item)) { $pid = InputPackage::buildFromInput()->get('_pid', $this->treeRootPid); } else { $pid = $item->{$this->repository->getTreePidColumn()}; } $this->repository()->setArgument('treePid', $pid); $ancestors = $this->repository->getTreeAncestorItems(); $html = []; $html[] = ' ' . L('Root'); foreach ($ancestors as $ancestor) { $html[] = ' ' . htmlspecialchars($ancestor->{$this->repository->getTreeTitleColumn()}); } $html[] = ''; return AutoRenderedFieldValue::make(join('', $html)); }); $this->prependField($field); } } /** * @return Repository|null */ public function repository() { return $this->repository; } private function fieldValidateMessages($fields, $input) { $failedValidators = []; foreach ($fields as $field) { if (!$validator = $field->getValidator($input)) { continue; } if (($validator instanceof Validator) && !$validator->passes()) { $failedValidators[] = $validator; } } $msgs = []; foreach ($failedValidators as $validator) { foreach ($validator->messages()->getMessages() as $column => $messages) { $msgs[$column] = $messages; } } if (empty($msgs)) { return false; } return $msgs; } private function validateFields($fields, $data) { $msgsList = []; if ($validationMessages = $this->fieldValidateMessages($this->editableFields(), $this->dataSubmitted)) { $msgsList = array_merge($msgsList, $validationMessages); } foreach ($msgsList as $column => $msgs) { foreach ($msgs as $msg) { return Response::generateError($msg); } } return Response::generateSuccess(); } /** * 忽略保留字段,如 `id` `created_at` `updated_at` * * @return void */ private function removeReservedFields() { $reservedColumns = [ $this->repository->getKeyName(), $this->repository->getCreatedAtColumn(), $this->repository->getUpdatedAtColumn(), ]; $reject = function (AbstractField $field) use (&$reservedColumns) { return in_array($field->column(), $reservedColumns, true) && $field instanceof \ModStart\Field\Display; }; $this->fields = $this->fields()->reject($reject); } public function hookCall($callback) { if ($callback instanceof Closure) { $ret = call_user_func($callback, $this); if (null !== $ret) { return $ret; } } return Response::generateSuccess(); } public function isModeForm() { return $this->mode === FormMode::FORM; } public function isModeAdd() { return $this->mode === FormMode::ADD; } public function isModeEdit() { return $this->mode === FormMode::EDIT; } public function isModeDelete() { return $this->mode === FormMode::DELETE; } /** * @param $callback = function(Form $form){ $data = $form->dataForming(); return Response::generateSuccess(); } * @param array|null $data * @return mixed * * @example * * @ */ public function formRequest($callback, array $data = null) { $this->mode(FormMode::FORM); $this->build(); $this->dataSubmitted = $data ? $data : Input::all(); try { ResultException::throwsIfFail($this->validateFields($this->addableFields(), $this->dataSubmitted)); ResultException::throwsIfFail($this->hookCall($this->hookSubmitted)); $this->dataForming = []; $this->removeReservedFields(); foreach ($this->addableFields() as $field) { if ($field->isLayoutField()) { continue; } $value = isset($this->dataSubmitted[$field->column()]) ? $this->dataSubmitted[$field->column()] : null; $value = $field->prepareInput($value, $this->dataSubmitted); $value = $field->serializeValue($value, $field); if ($field->hookValueSerialize()) { $value = call_user_func($field->hookValueSerialize(), $value, $field); } $this->dataForming[$field->column()] = $value; } ResultException::throwsIfFail($this->hookCall($this->hookSaving)); $ret = call_user_func($callback, $this); if (null !== $ret) { if (Response::isError($ret)) { return Response::jsonFromGenerate($ret); } } ResultException::throwsIfFail($this->hookCall($this->hookSaved)); ResultException::throwsIfFail($this->hookCall($this->hookChanged)); if (null !== $ret) { return Response::jsonFromGenerate($ret); } if (!empty($this->dataSubmitted['_redirect'])) { return Response::json(0, null, null, $this->dataSubmitted['_redirect']); } return Response::jsonSuccess(L('Save Success')); } catch (BizException $e) { return Response::jsonError($e->getMessage()); } catch (ResultException $e) { return Response::jsonError($e->getMessage()); } } /** * 增加记录 * @return $this */ public function add() { $this->mode(FormMode::ADD); $this->build(); return $this; } /** * 增加记录提交 * @param array|null $data * @return mixed */ public function addRequest(array $data = null) { if (!$this->canAdd) return Response::pagePermissionDenied(); $this->mode(FormMode::ADD); $this->build(); $this->dataSubmitted = $data ? $data : Input::all(); try { ResultException::throwsIfFail($this->validateFields($this->addableFields(), $this->dataSubmitted)); ResultException::throwsIfFail($this->hookCall($this->hookSubmitted)); $this->dataAdding = []; $this->removeReservedFields(); foreach ($this->addableFields() as $field) { if ($field->isLayoutField() || $field->isCustomField()) { continue; } $value = isset($this->dataSubmitted[$field->column()]) ? $this->dataSubmitted[$field->column()] : null; $value = $field->prepareInput($value, $this->dataSubmitted); $value = $field->serializeValue($value, $field); if ($field->hookValueSerialize()) { $value = call_user_func($field->hookValueSerialize(), $value, $field); } $this->dataAdding[$field->column()] = $value; } // exit(print_r($this->dataAdding)); $id = $this->repository->add($this); if (!empty($this->dataSubmitted['_redirect'])) { return Response::json(0, null, null, $this->dataSubmitted['_redirect']); } return Response::jsonSuccess(L('Add Success')); } catch (BizException $e) { return Response::jsonError($e->getMessage()); } catch (ResultException $e) { return Response::jsonError($e->getMessage()); } } /** * 编辑记录 * @return $this */ public function edit($id) { try { $this->mode(FormMode::EDIT); $this->itemId($id); $this->item($this->repository()->editing($this)); BizException::throwsIfEmpty(L('Record Not Exists'), $this->item); $this->build(); $this->fillFields(); return $this; } catch (BizException $e) { return Response::sendError($e->getMessage()); } } /** * 编辑记录提交 * * @param $id * @return mixed * @throws BizException * @throws ResultException */ public function editRequest($id, array $data = null) { if (!$this->canEdit) return Response::pagePermissionDenied(); $this->dataSubmitted = $data ? $data : Input::all(); try { $this->edit($id); ResultException::throwsIfFail($this->validateFields($this->editableFields(), $this->dataSubmitted)); ResultException::throwsIfFail($this->hookCall($this->hookSubmitted)); $this->dataEditing = []; $this->removeReservedFields(); $action = isset($this->dataSubmitted['_action']) ? $this->dataSubmitted['_action'] : null; if ('itemCellEdit' == $action) { $column = isset($this->dataSubmitted['column']) ? $this->dataSubmitted['column'] : null; $value = isset($this->dataSubmitted['value']) ? $this->dataSubmitted['value'] : null; if ($column) { foreach ($this->editableFields() as $field) { if ($field->isLayoutField() || $field->isCustomField()) { continue; } if ($field->column() == $column) { $this->dataEditing[$field->column()] = $value; break; } } } BizException::throwsIfEmpty('Data Error', $this->dataEditing); } else { foreach ($this->editableFields() as $field) { if ($field->isLayoutField() || $field->isCustomField()) { continue; } $value = isset($this->dataSubmitted[$field->column()]) ? $this->dataSubmitted[$field->column()] : null; $value = $field->prepareInput($value, $this->dataSubmitted); $value = $field->serializeValue($value, $field); if ($field->hookValueSerialize()) { $value = call_user_func($field->hookValueSerialize(), $value, $field); } $this->dataEditing[$field->column()] = $value; } } $this->repository()->edit($this); if (!empty($this->dataSubmitted['_redirect'])) { return Response::json(0, null, null, $this->dataSubmitted['_redirect']); } return Response::jsonSuccess(L('Edit Success')); } catch (BizException $e) { return Response::jsonError($e->getMessage()); } catch (ResultException $e) { return Response::jsonError($e->getMessage()); } } /** * 删除数据请求 * @param array $ids * @return mixed * @throws BizException * @throws ResultException */ public function deleteRequest($ids) { if (!$this->canDelete) return Response::pagePermissionDenied(); $this->mode(FormMode::DELETE); $this->itemId($ids); $this->build(); try { $data = $this->repository->deleting($this); $this->item($data); $this->itemId(collect($data)->map(function ($o) { return $o->{$this->repository()->getKeyName()}; })->toArray()); $result = $this->repository->delete($this, $data); return Response::jsonSuccess(L('Delete Success')); } catch (BizException $e) { return Response::jsonError($e->getMessage()); } catch (ResultException $e) { return Response::jsonError($e->getMessage()); } } public function sortRequest($ids) { if (!$this->canSort) return Response::pagePermissionDenied(); $this->mode(FormMode::SORT); $this->itemId($ids); $input = InputPackage::buildFromInput(); $this->repository->setArgument('direction', $input->getType('direction', SortDirection::class)); $this->build(); try { $result = $this->repository->sortEdit($this); ResultException::throwsIfFail($this->hookCall($this->hookChanged)); return Response::jsonSuccess(L('Operate Success')); } catch (BizException $e) { return Response::jsonError($e->getMessage()); } catch (ResultException $e) { return Response::jsonError($e->getMessage()); } } /** * 渲染增加、编辑页面 * @return string * @throws \Throwable */ public function render() { $data = []; switch ($this->mode) { case FormMode::FORM: $data['fields'] = $this->addableFields(); break; case FormMode::ADD: if (!$this->canAdd) return Response::pagePermissionDenied(); $data['fields'] = $this->addableFields(); break; case FormMode::EDIT; if (!$this->canEdit) return Response::pagePermissionDenied(); $data['fields'] = $this->editableFields(); break; default: return Response::sendError('Form.render mode error : ' . $this->mode); } $data = array_merge($this->fluentAttributeVariables(), $data); return view($this->view, $data)->render(); } /** * Generate a Field object and add to form builder if Field exists. * * @param string $method * @param array $arguments * * @return AbstractField|void|$this * @throws \Exception */ public function __call($method, $arguments) { switch ($method) { case 'enablePagination': case 'defaultOrder': case 'canShow': case 'canBatchDelete': case 'canMultiSelectItem': case 'addBlankPage': case 'editBlankPage': case 'disableCUD': case 'hookItemOperateRendering': case 'addDialogSize': case 'editDialogSize': case 'dialogSizeSmall': case 'gridFilter': case 'gridOperateAppend': return $this; } if ($this->isFluentAttribute($method)) { return $this->fluentAttribute($method, $arguments); } return FieldManager::call($this, $method, $arguments); } }