123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- <?php
- namespace Dcat\Admin\Form\Field;
- use Dcat\Admin\Form;
- use Dcat\Admin\Form\Field;
- use Dcat\Admin\Form\NestedForm;
- use Dcat\Admin\Support\Helper;
- use Illuminate\Support\Arr;
- use Illuminate\Support\Facades\Validator;
- use Illuminate\Support\Str;
- /**
- * Class HasMany.
- */
- class HasMany extends Field
- {
- /**
- * Relation name.
- *
- * @var string
- */
- protected $relationName = '';
- /**
- * Relation key name.
- *
- * @var string
- */
- protected $relationKeyName = 'id';
- /**
- * Form builder.
- *
- * @var \Closure
- */
- protected $builder = null;
- /**
- * Form data.
- *
- * @var array
- */
- protected $value = [];
- /**
- * View Mode.
- *
- * Supports `default` and `tab` currently.
- *
- * @var string
- */
- protected $viewMode = 'default';
- /**
- * Available views for HasMany field.
- *
- * @var array
- */
- protected $views = [
- 'default' => 'admin::form.hasmany',
- 'tab' => 'admin::form.hasmanytab',
- 'table' => 'admin::form.hasmanytable',
- ];
- /**
- * Options for template.
- *
- * @var array
- */
- protected $options = [
- 'allowCreate' => true,
- 'allowDelete' => true,
- ];
- protected $columnClass;
- /**
- * Create a new HasMany field instance.
- *
- * @param $relationName
- * @param array $arguments
- */
- public function __construct($relationName, $arguments = [])
- {
- $this->relationName = $relationName;
- $this->column = $relationName;
- $this->columnClass = $this->formatClass($relationName);
- if (count($arguments) == 1) {
- $this->label = $this->formatLabel();
- $this->builder = $arguments[0];
- }
- if (count($arguments) == 2) {
- [$this->label, $this->builder] = $arguments;
- }
- }
- protected function formatClass(string $column)
- {
- return str_replace('.', '-', $column);
- }
- /**
- * Get validator for this field.
- *
- * @param array $input
- *
- * @return bool|Validator
- */
- public function getValidator(array $input)
- {
- if (! Arr::has($input, $this->column)) {
- return false;
- }
- $form = $this->buildNestedForm();
- $rules = $attributes = $messages = [];
- /* @var Field $field */
- foreach ($form->fields() as $field) {
- if (! $fieldRules = $field->getRules()) {
- continue;
- }
- if ($field instanceof File) {
- $fieldRules = is_string($fieldRules) ? explode('|', $fieldRules) : $fieldRules;
- Helper::deleteByValue($fieldRules, ['image', 'file']);
- }
- $column = $field->column();
- $this->normalizeValidatorInput($field, $input);
- if (is_array($column)) {
- foreach ($column as $key => $name) {
- $rules[$name.$key] = $fieldRules;
- }
- $this->resetInputKey($input, $column);
- } else {
- $rules[$column] = $fieldRules;
- }
- $attributes = array_merge(
- $attributes,
- $this->formatValidationAttribute($input, $field->label(), $column)
- );
- $messages = array_merge(
- $messages,
- $this->formatValidationMessages($input, $field->getValidationMessages())
- );
- }
- Arr::forget($rules, NestedForm::REMOVE_FLAG_NAME);
- if (empty($rules)) {
- return false;
- }
- $newRules = [];
- $newInput = [];
- foreach ($rules as $column => $rule) {
- foreach (array_keys(Arr::get($input, $this->column)) as $key) {
- if (Arr::get($input, "{$this->column}.{$key}.".NestedForm::REMOVE_FLAG_NAME)) {
- continue;
- }
- $subKey = "{$this->column}.{$key}.{$column}";
- $newRules[$subKey] = $rule;
- $newInput[$subKey] = $ruleValue = Arr::get($input, "{$this->column}.$key.$column");
- if (is_array($ruleValue)) {
- foreach ($ruleValue as $vkey => $value) {
- $newInput["{$subKey}.{$vkey}"] = $value;
- }
- }
- }
- }
- $newInput += $input;
- return Validator::make($newInput, $newRules, array_merge($this->getValidationMessages(), $messages), $attributes);
- }
- protected function normalizeValidatorInput(Field $field, array &$input)
- {
- if (
- $field instanceof MultipleSelect
- || $field instanceof Checkbox
- || $field instanceof Tags
- ) {
- foreach (array_keys(Arr::get($input, $this->column)) as $key) {
- $value = $input[$this->column][$key][$field->column()];
- $input[$this->column][$key][$field->column()] = array_filter($value, function ($v) {
- return $v !== null;
- });
- }
- }
- }
- /**
- * Format validation messages.
- *
- * @param array $input
- * @param array $messages
- *
- * @return array
- */
- protected function formatValidationMessages(array $input, array $messages)
- {
- $result = [];
- foreach (Arr::get($input, $this->column) as $key => &$value) {
- $newKey = $this->column.'.'.$key;
- foreach ($messages as $k => $message) {
- $result[$newKey.'.'.$k] = $message;
- }
- }
- return $result;
- }
- /**
- * Format validation attributes.
- *
- * @param array $input
- * @param string $label
- * @param string $column
- *
- * @return array
- */
- protected function formatValidationAttribute($input, $label, $column)
- {
- $new = $attributes = [];
- if (is_array($column)) {
- foreach ($column as $index => $col) {
- $new[$col.$index] = $col;
- }
- }
- foreach (array_keys(Arr::dot($input)) as $key) {
- if (is_string($column)) {
- if (Str::endsWith($key, ".$column")) {
- $attributes[$key] = $label;
- }
- } else {
- foreach ($new as $k => $val) {
- if (Str::endsWith($key, ".$k")) {
- $attributes[$key] = $label."[$val]";
- }
- }
- }
- }
- return $attributes;
- }
- /**
- * Reset input key for validation.
- *
- * @param array $input
- * @param array $column $column is the column name array set
- *
- * @return void.
- */
- protected function resetInputKey(array &$input, array $column)
- {
- /**
- * flip the column name array set.
- *
- * for example, for the DateRange, the column like as below
- *
- * ["start" => "created_at", "end" => "updated_at"]
- *
- * to:
- *
- * [ "created_at" => "start", "updated_at" => "end" ]
- */
- $column = array_flip($column);
- /**
- * $this->column is the inputs array's node name, default is the relation name.
- *
- * So... $input[$this->column] is the data of this column's inputs data
- *
- * in the HasMany relation, has many data/field set, $set is field set in the below
- */
- foreach (Arr::get($input, $this->column) as $index => $set) {
- /*
- * foreach the field set to find the corresponding $column
- */
- foreach ($set as $name => $value) {
- /*
- * if doesn't have column name, continue to the next loop
- */
- if (! array_key_exists($name, $column)) {
- continue;
- }
- /**
- * example: $newKey = created_atstart.
- *
- * Σ( ° △ °|||)︴
- *
- * I don't know why a form need range input? Only can imagine is for range search....
- */
- $newKey = $name.$column[$name];
- /*
- * set new key
- */
- Arr::set($input, "{$this->column}.$index.$newKey", $value);
- /*
- * forget the old key and value
- */
- Arr::forget($input, "{$this->column}.$index.$name");
- }
- }
- }
- /**
- * Prepare input data for insert or update.
- *
- * @param array $input
- *
- * @return array
- */
- protected function prepareInputValue($input)
- {
- $form = $this->buildNestedForm();
- return array_values(
- $form->setOriginal($this->original, $this->getKeyName())->prepare($input)
- );
- }
- /**
- * Build a Nested form.
- *
- * @param null $key
- *
- * @return NestedForm
- */
- public function buildNestedForm($key = null)
- {
- $form = new Form\NestedForm($this->column, $key);
- $form->setForm($this->form);
- call_user_func($this->builder, $form);
- $form->hidden($this->getKeyName());
- $form->hidden(NestedForm::REMOVE_FLAG_NAME)->default(0)->addElementClass(NestedForm::REMOVE_FLAG_CLASS);
- return $form;
- }
- /**
- * Get the HasMany relation key name.
- *
- * @return string
- */
- public function getKeyName()
- {
- if (is_null($this->form)) {
- return;
- }
- return $this->relationKeyName;
- }
- /**
- * @param string $relationKeyName
- */
- public function setRelationKeyName(?string $relationKeyName)
- {
- $this->relationKeyName = $relationKeyName;
- return $this;
- }
- /**
- * Set view mode.
- *
- * @param string $mode currently support `tab` mode.
- *
- * @return $this
- *
- * @author Edwin Hui
- */
- public function mode($mode)
- {
- $this->viewMode = $mode;
- return $this;
- }
- /**
- * Use tab mode to showing hasmany field.
- *
- * @return HasMany
- */
- public function useTab()
- {
- return $this->mode('tab');
- }
- /**
- * Use table mode to showing hasmany field.
- *
- * @return HasMany
- */
- public function useTable()
- {
- return $this->mode('table');
- }
- /**
- * Build Nested form for related data.
- *
- * @throws \Exception
- *
- * @return array
- */
- protected function buildRelatedForms()
- {
- if (is_null($this->form)) {
- return [];
- }
- $forms = [];
- /*
- * If redirect from `exception` or `validation error` page.
- *
- * Then get form data from session flash.
- *
- * Else get data from database.
- */
- foreach (Helper::array($this->value()) as $idx => $data) {
- $key = Arr::get($data, $this->getKeyName(), $idx);
- $forms[$key] = $this->buildNestedForm($key)
- ->fill($data);
- }
- return $forms;
- }
- /**
- * Disable create button.
- *
- * @return $this
- */
- public function disableCreate()
- {
- $this->options['allowCreate'] = false;
- return $this;
- }
- /**
- * Disable delete button.
- *
- * @return $this
- */
- public function disableDelete()
- {
- $this->options['allowDelete'] = false;
- return $this;
- }
- public function value($value = null)
- {
- if ($value === null) {
- return Helper::array(parent::value($value));
- }
- return parent::value($value);
- }
- /**
- * Render the `HasMany` field.
- *
- * @throws \Exception
- *
- * @return \Illuminate\View\View|string
- */
- public function render()
- {
- if (! $this->shouldRender()) {
- return '';
- }
- if ($this->viewMode == 'table') {
- return $this->renderTable();
- }
- // specify a view to render.
- $this->view = $this->views[$this->viewMode];
- $this->addVariables([
- 'forms' => $this->buildRelatedForms(),
- 'template' => $this->buildNestedForm()->render(),
- 'relationName' => $this->relationName,
- 'options' => $this->options,
- 'count' => count($this->value()),
- 'columnClass' => $this->columnClass,
- ]);
- return parent::render();
- }
- /**
- * Render the `HasMany` field for table style.
- *
- * @throws \Exception
- *
- * @return mixed
- */
- protected function renderTable()
- {
- $headers = [];
- $fields = [];
- $hidden = [];
- /* @var Field $field */
- foreach ($this->buildNestedForm()->fields() as $field) {
- if (is_a($field, Hidden::class)) {
- $hidden[] = $field->render();
- } else {
- /* Hide label and set field width 100% */
- $field->setLabelClass(['hidden']);
- $field->width(12, 0);
- $fields[] = $field->render();
- $headers[] = $field->label();
- }
- }
- /* Build row elements */
- $template = array_reduce($fields, function ($all, $field) {
- $all .= "<td>{$field}</td>";
- return $all;
- }, '');
- /* Build cell with hidden elements */
- $template .= '<td class="hidden">'.implode('', $hidden).'</td>';
- // specify a view to render.
- $this->view = $this->views[$this->viewMode];
- $this->addVariables([
- 'headers' => $headers,
- 'forms' => $this->buildRelatedForms(),
- 'template' => $template,
- 'relationName' => $this->relationName,
- 'options' => $this->options,
- 'count' => count($this->value()),
- 'columnClass' => $this->columnClass,
- ]);
- return parent::render();
- }
- }
|