NestedForm.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. <?php
  2. namespace Dcat\Admin\Form;
  3. use Dcat\Admin\Form;
  4. use Dcat\Admin\Support\Helper;
  5. use Dcat\Admin\Widgets\Form as WidgetForm;
  6. use Illuminate\Support\Arr;
  7. use Illuminate\Support\Collection;
  8. class NestedForm extends WidgetForm
  9. {
  10. use Form\Concerns\HandleCascadeFields;
  11. use Form\Concerns\HasRows;
  12. use Form\Concerns\HasTabs;
  13. use Form\Concerns\HasLayout;
  14. const DEFAULT_KEY_NAME = '__LA_KEY__';
  15. const REMOVE_FLAG_NAME = '_remove_';
  16. const REMOVE_FLAG_CLASS = 'fom-removed';
  17. /**
  18. * @var string
  19. */
  20. protected $relationName;
  21. /**
  22. * NestedForm key.
  23. *
  24. * @var
  25. */
  26. protected $key;
  27. /**
  28. * Fields in form.
  29. *
  30. * @var Collection
  31. */
  32. protected $fields;
  33. /**
  34. * Original data for this field.
  35. *
  36. * @var array
  37. */
  38. protected $original = [];
  39. /**
  40. * @var Form|WidgetForm
  41. */
  42. protected $form;
  43. /**
  44. * Create a new NestedForm instance.
  45. *
  46. * NestedForm constructor.
  47. *
  48. * @param string $relation
  49. * @param null $key
  50. */
  51. public function __construct($relation = null, $key = null)
  52. {
  53. $this->relationName = $relation;
  54. $this->key = $key;
  55. $this->disableResetButton();
  56. $this->disableSubmitButton();
  57. $this->ajax(false);
  58. $this->useFormTag(false);
  59. parent::__construct();
  60. }
  61. /**
  62. * Set Form.
  63. *
  64. * @param Form|WidgetForm $form
  65. *
  66. * @return $this
  67. */
  68. public function setForm($form = null)
  69. {
  70. $this->form = $form;
  71. return $this;
  72. }
  73. /**
  74. * Get form.
  75. *
  76. * @return Form
  77. */
  78. public function form()
  79. {
  80. return $this->form;
  81. }
  82. public function model()
  83. {
  84. return $this->form->model();
  85. }
  86. /**
  87. * Set original values for fields.
  88. *
  89. * @param array $data
  90. * @param string $relatedKeyName
  91. *
  92. * @return $this
  93. */
  94. public function setOriginal($data, $relatedKeyName)
  95. {
  96. if (empty($data)) {
  97. return $this;
  98. }
  99. foreach ($data as $value) {
  100. if (! isset($value[$relatedKeyName])) {
  101. continue;
  102. }
  103. /*
  104. * like $this->original[30] = [ id = 30, .....]
  105. */
  106. $this->original[$value[$relatedKeyName]] = $value;
  107. }
  108. return $this;
  109. }
  110. /**
  111. * Prepare for insert or update.
  112. *
  113. * @param array $input
  114. *
  115. * @return mixed
  116. */
  117. public function prepare($input)
  118. {
  119. foreach ($input as $key => $record) {
  120. if (! array_key_exists(static::REMOVE_FLAG_NAME, $record)) {
  121. continue;
  122. }
  123. $this->setFieldOriginalValue($key);
  124. $input[$key] = $this->prepareRecord($record);
  125. }
  126. return $input;
  127. }
  128. /**
  129. * Get key for current form.
  130. *
  131. * @return string
  132. */
  133. public function getKey()
  134. {
  135. return $this->key;
  136. }
  137. /**
  138. * Set key for current form.
  139. *
  140. * @param mixed $key
  141. *
  142. * @return $this
  143. */
  144. public function setKey($key)
  145. {
  146. $this->key = $key;
  147. return $this;
  148. }
  149. /**
  150. * Set original data for each field.
  151. *
  152. * @param string $key
  153. *
  154. * @return void
  155. */
  156. protected function setFieldOriginalValue($key)
  157. {
  158. $values = [];
  159. if (array_key_exists($key, $this->original)) {
  160. $values = $this->original[$key];
  161. }
  162. $this->fields->each(function (Field $field) use ($values) {
  163. $field->setOriginal($values);
  164. });
  165. }
  166. /**
  167. * Do prepare work before store and update.
  168. *
  169. * @param array $record
  170. *
  171. * @return array
  172. */
  173. protected function prepareRecord($record)
  174. {
  175. if ($record[static::REMOVE_FLAG_NAME] == 1) {
  176. return $record;
  177. }
  178. $prepared = [];
  179. /* @var Field $field */
  180. foreach ($this->fields as $field) {
  181. $columns = $field->column();
  182. $value = $this->fetchColumnValue($record, $columns);
  183. if ($value === false) {
  184. continue;
  185. }
  186. if (method_exists($field, 'prepare')) {
  187. $value = $field->prepare($value);
  188. }
  189. if (($field instanceof Form\Field\Hidden) || ! Helper::equal($field->original(), $value)) {
  190. if (is_array($columns)) {
  191. foreach ($columns as $name => $column) {
  192. Arr::set($prepared, $column, $value[$name]);
  193. }
  194. } elseif (is_string($columns)) {
  195. Arr::set($prepared, $columns, $value);
  196. }
  197. }
  198. }
  199. $prepared[static::REMOVE_FLAG_NAME] = $record[static::REMOVE_FLAG_NAME];
  200. return $prepared;
  201. }
  202. /**
  203. * Fetch value in input data by column name.
  204. *
  205. * @param array $data
  206. * @param string|array $columns
  207. *
  208. * @return array|mixed
  209. */
  210. protected function fetchColumnValue($data, $columns)
  211. {
  212. if (is_string($columns)) {
  213. if (! Arr::has($data, $columns)) {
  214. return false;
  215. }
  216. return Arr::get($data, $columns);
  217. }
  218. if (is_array($columns)) {
  219. $value = [];
  220. foreach ($columns as $name => $column) {
  221. if (! Arr::has($data, $column)) {
  222. continue;
  223. }
  224. $value[$name] = Arr::get($data, $column);
  225. }
  226. return $value;
  227. }
  228. return false;
  229. }
  230. /**
  231. * @param Field $field
  232. *
  233. * @return $this
  234. */
  235. public function pushField(Field $field)
  236. {
  237. $this->fields->push($field);
  238. if ($this->layout()->hasColumns()) {
  239. $this->layout()->addField($field);
  240. }
  241. if (method_exists($this->form, 'builder')) {
  242. $this->form->builder()->fields()->push($field);
  243. $field->attribute(Builder::BUILD_IGNORE, true);
  244. }
  245. $field->setNestedFormRelation([
  246. 'relation' => $this->relationName,
  247. 'key' => $this->key,
  248. ]);
  249. $field::requireAssets();
  250. $field->width($this->width['field'], $this->width['label']);
  251. return $this;
  252. }
  253. /**
  254. * Get fields of this form.
  255. *
  256. * @return Collection
  257. */
  258. public function fields()
  259. {
  260. return $this->fields;
  261. }
  262. /**
  263. * Fill data to all fields in form.
  264. *
  265. * @param array $data
  266. *
  267. * @return $this
  268. */
  269. public function fill($data)
  270. {
  271. /* @var Field $field */
  272. foreach ($this->fields() as $field) {
  273. $field->fill($data);
  274. }
  275. return $this;
  276. }
  277. /**
  278. * Set `errorKey` `elementName` `elementClass` for fields inside hasmany fields.
  279. *
  280. * @param Field $field
  281. *
  282. * @return Field
  283. */
  284. protected function formatField(Field $field)
  285. {
  286. $column = $field->column();
  287. $elementName = $elementClass = $errorKey = [];
  288. $key = $this->key ?: 'new_'.static::DEFAULT_KEY_NAME;
  289. if (is_array($column)) {
  290. foreach ($column as $k => $name) {
  291. $errorKey[$k] = sprintf('%s.%s.%s', $this->relationName, $key, $name);
  292. $elementName[$k] = sprintf('%s[%s][%s]', $this->formatName(), $key, $name);
  293. $elementClass[$k] = [$this->formatClass(), $this->formatClass($name)];
  294. }
  295. } else {
  296. $errorKey = sprintf('%s.%s.%s', $this->relationName, $key, $column);
  297. $elementName = sprintf('%s[%s][%s]', $this->formatName(), $key, $column);
  298. $elementClass = [$this->formatClass(), $this->formatClass($column)];
  299. }
  300. return $field->setErrorKey($errorKey)
  301. ->setElementName($elementName)
  302. ->setElementClass($elementClass);
  303. }
  304. protected function formatClass($name = null)
  305. {
  306. return str_replace('.', '_', $name ?: $this->relationName);
  307. }
  308. protected function formatName($name = null)
  309. {
  310. return Helper::formatElementName($name ?: $this->relationName);
  311. }
  312. protected function resolveField($method, $arguments)
  313. {
  314. if ($className = Form::findFieldClass($method)) {
  315. $column = Arr::get($arguments, 0, '');
  316. /* @var Field $field */
  317. $field = new $className($column, array_slice($arguments, 1));
  318. $field->setForm($this->form);
  319. $field = $this->formatField($field);
  320. return $field;
  321. }
  322. }
  323. /**
  324. * Add nested-form fields dynamically.
  325. *
  326. * @param string $method
  327. * @param array $arguments
  328. *
  329. * @return mixed
  330. */
  331. public function __call($method, $arguments)
  332. {
  333. if ($field = $this->resolveField($method, $arguments)) {
  334. $this->pushField($field);
  335. return $field;
  336. }
  337. return $this;
  338. }
  339. }