NestedForm.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. <?php
  2. namespace Dcat\Admin\Form;
  3. use Dcat\Admin\Admin;
  4. use Dcat\Admin\Form;
  5. use Dcat\Admin\Form\Field\MultipleSelectTable;
  6. use Dcat\Admin\Form\Field\SelectTable;
  7. use Dcat\Admin\Support\Helper;
  8. use Dcat\Admin\Widgets\Form as WidgetForm;
  9. use Illuminate\Support\Arr;
  10. use Illuminate\Support\Collection;
  11. /**
  12. * Class Form.
  13. *
  14. * @method Field\Text text($column, $label = '')
  15. * @method Field\Checkbox checkbox($column, $label = '')
  16. * @method Field\Radio radio($column, $label = '')
  17. * @method Field\Select select($column, $label = '')
  18. * @method Field\MultipleSelect multipleSelect($column, $label = '')
  19. * @method Field\Textarea textarea($column, $label = '')
  20. * @method Field\Hidden hidden($column, $label = '')
  21. * @method Field\Id id($column, $label = '')
  22. * @method Field\Ip ip($column, $label = '')
  23. * @method Field\Url url($column, $label = '')
  24. * @method Field\Email email($column, $label = '')
  25. * @method Field\Mobile mobile($column, $label = '')
  26. * @method Field\Slider slider($column, $label = '')
  27. * @method Field\Map map($latitude, $longitude, $label = '')
  28. * @method Field\Editor editor($column, $label = '')
  29. * @method Field\Date date($column, $label = '')
  30. * @method Field\Datetime datetime($column, $label = '')
  31. * @method Field\Time time($column, $label = '')
  32. * @method Field\Year year($column, $label = '')
  33. * @method Field\Month month($column, $label = '')
  34. * @method Field\DateRange dateRange($start, $end, $label = '')
  35. * @method Field\DateTimeRange datetimeRange($start, $end, $label = '')
  36. * @method Field\TimeRange timeRange($start, $end, $label = '')
  37. * @method Field\Number number($column, $label = '')
  38. * @method Field\Currency currency($column, $label = '')
  39. * @method Field\SwitchField switch($column, $label = '')
  40. * @method Field\Display display($column, $label = '')
  41. * @method Field\Rate rate($column, $label = '')
  42. * @method Field\Divide divider()
  43. * @method Field\Password password($column, $label = '')
  44. * @method Field\Decimal decimal($column, $label = '')
  45. * @method Field\Html html($html, $label = '')
  46. * @method Field\Tags tags($column, $label = '')
  47. * @method Field\Icon icon($column, $label = '')
  48. * @method Field\Embeds embeds($column, $label = '')
  49. * @method Field\Captcha captcha()
  50. * @method Field\Listbox listbox($column, $label = '')
  51. * @method Field\File file($column, $label = '')
  52. * @method Field\Image image($column, $label = '')
  53. * @method Field\MultipleFile multipleFile($column, $label = '')
  54. * @method Field\MultipleImage multipleImage($column, $label = '')
  55. * @method Field\HasMany hasMany($column, $labelOrCallback, $callback = null)
  56. * @method Field\Tree tree($column, $label = '')
  57. * @method Field\Table table($column, $labelOrCallback, $callback = null)
  58. * @method Field\ListField list($column, $label = '')
  59. * @method Field\Timezone timezone($column, $label = '')
  60. * @method Field\KeyValue keyValue($column, $label = '')
  61. * @method Field\Tel tel($column, $label = '')
  62. * @method Field\Markdown markdown($column, $label = '')
  63. * @method Field\Range range($start, $end, $label = '')
  64. * @method Field\Color color($column, $label = '')
  65. * @method Field\SelectTable selectTable($column, $label = '')
  66. * @method Field\MultipleSelectTable multipleSelectTable($column, $label = '')
  67. */
  68. class NestedForm
  69. {
  70. const DEFAULT_KEY_NAME = '__LA_KEY__';
  71. const REMOVE_FLAG_NAME = '_remove_';
  72. const REMOVE_FLAG_CLASS = 'fom-removed';
  73. /**
  74. * @var string
  75. */
  76. protected $relationName;
  77. /**
  78. * NestedForm key.
  79. *
  80. * @var
  81. */
  82. protected $key;
  83. /**
  84. * Fields in form.
  85. *
  86. * @var Collection
  87. */
  88. protected $fields;
  89. /**
  90. * Original data for this field.
  91. *
  92. * @var array
  93. */
  94. protected $original = [];
  95. /**
  96. * @var Form|WidgetForm
  97. */
  98. protected $form;
  99. /**
  100. * Create a new NestedForm instance.
  101. *
  102. * NestedForm constructor.
  103. *
  104. * @param string $relation
  105. * @param null $key
  106. */
  107. public function __construct($relation, $key = null)
  108. {
  109. $this->relationName = $relation;
  110. $this->key = $key;
  111. $this->fields = new Collection();
  112. }
  113. /**
  114. * Set Form.
  115. *
  116. * @param Form|WidgetForm $form
  117. *
  118. * @return $this
  119. */
  120. public function setForm($form = null)
  121. {
  122. $this->form = $form;
  123. return $this;
  124. }
  125. /**
  126. * Get form.
  127. *
  128. * @return Form
  129. */
  130. public function getForm()
  131. {
  132. return $this->form;
  133. }
  134. /**
  135. * Set original values for fields.
  136. *
  137. * @param array $data
  138. * @param string $relatedKeyName
  139. *
  140. * @return $this
  141. */
  142. public function setOriginal($data, $relatedKeyName)
  143. {
  144. if (empty($data)) {
  145. return $this;
  146. }
  147. foreach ($data as $value) {
  148. if (! isset($value[$relatedKeyName])) {
  149. continue;
  150. }
  151. /*
  152. * like $this->original[30] = [ id = 30, .....]
  153. */
  154. $this->original[$value[$relatedKeyName]] = $value;
  155. }
  156. return $this;
  157. }
  158. /**
  159. * Prepare for insert or update.
  160. *
  161. * @param array $input
  162. *
  163. * @return mixed
  164. */
  165. public function prepare($input)
  166. {
  167. foreach ($input as $key => $record) {
  168. if (! array_key_exists(static::REMOVE_FLAG_NAME, $record)) {
  169. continue;
  170. }
  171. $this->setFieldOriginalValue($key);
  172. $input[$key] = $this->prepareRecord($record);
  173. }
  174. return $input;
  175. }
  176. /**
  177. * Get key for current form.
  178. *
  179. * @return string
  180. */
  181. public function getKey()
  182. {
  183. return $this->key;
  184. }
  185. /**
  186. * Set key for current form.
  187. *
  188. * @param mixed $key
  189. *
  190. * @return $this
  191. */
  192. public function setKey($key)
  193. {
  194. $this->key = $key;
  195. return $this;
  196. }
  197. /**
  198. * Set original data for each field.
  199. *
  200. * @param string $key
  201. *
  202. * @return void
  203. */
  204. protected function setFieldOriginalValue($key)
  205. {
  206. $values = [];
  207. if (array_key_exists($key, $this->original)) {
  208. $values = $this->original[$key];
  209. }
  210. $this->fields->each(function (Field $field) use ($values) {
  211. $field->setOriginal($values);
  212. });
  213. }
  214. /**
  215. * Do prepare work before store and update.
  216. *
  217. * @param array $record
  218. *
  219. * @return array
  220. */
  221. protected function prepareRecord($record)
  222. {
  223. if ($record[static::REMOVE_FLAG_NAME] == 1) {
  224. return $record;
  225. }
  226. $prepared = [];
  227. /* @var Field $field */
  228. foreach ($this->fields as $field) {
  229. $columns = $field->column();
  230. $value = $this->fetchColumnValue($record, $columns);
  231. if ($value === false) {
  232. continue;
  233. }
  234. if (method_exists($field, 'prepare')) {
  235. $value = $field->prepare($value);
  236. }
  237. if (($field instanceof Form\Field\Hidden) || ! Helper::equal($field->original(), $value)) {
  238. if (is_array($columns)) {
  239. foreach ($columns as $name => $column) {
  240. Arr::set($prepared, $column, $value[$name]);
  241. }
  242. } elseif (is_string($columns)) {
  243. Arr::set($prepared, $columns, $value);
  244. }
  245. }
  246. }
  247. $prepared[static::REMOVE_FLAG_NAME] = $record[static::REMOVE_FLAG_NAME];
  248. return $prepared;
  249. }
  250. /**
  251. * Fetch value in input data by column name.
  252. *
  253. * @param array $data
  254. * @param string|array $columns
  255. *
  256. * @return array|mixed
  257. */
  258. protected function fetchColumnValue($data, $columns)
  259. {
  260. if (is_string($columns)) {
  261. if (! Arr::has($data, $columns)) {
  262. return false;
  263. }
  264. return Arr::get($data, $columns);
  265. }
  266. if (is_array($columns)) {
  267. $value = [];
  268. foreach ($columns as $name => $column) {
  269. if (! Arr::has($data, $column)) {
  270. continue;
  271. }
  272. $value[$name] = Arr::get($data, $column);
  273. }
  274. return $value;
  275. }
  276. return false;
  277. }
  278. /**
  279. * @param Field $field
  280. *
  281. * @return $this
  282. */
  283. public function pushField(Field $field)
  284. {
  285. $this->fields->push($field);
  286. if (method_exists($this->form, 'builder')) {
  287. $this->form->builder()->fields()->push($field);
  288. $field->attribute(Builder::BUILD_IGNORE, true);
  289. }
  290. $field->setNestedFormRelation([
  291. 'relation' => $this->relationName,
  292. 'key' => $this->key,
  293. ]);
  294. $field::requireAssets();
  295. return $this;
  296. }
  297. /**
  298. * Get fields of this form.
  299. *
  300. * @return Collection
  301. */
  302. public function fields()
  303. {
  304. return $this->fields;
  305. }
  306. /**
  307. * Fill data to all fields in form.
  308. *
  309. * @param array $data
  310. *
  311. * @return $this
  312. */
  313. public function fill(array $data)
  314. {
  315. /* @var Field $field */
  316. foreach ($this->fields() as $field) {
  317. $field->fill($data);
  318. }
  319. return $this;
  320. }
  321. /**
  322. * Get the html and script of template.
  323. *
  324. * @return array
  325. */
  326. public function getTemplateHtmlAndScript()
  327. {
  328. $html = '';
  329. $scripts = [];
  330. /* @var Field $field */
  331. foreach ($this->fields() as $field) {
  332. //when field render, will push $script to Admin
  333. $html .= $field->runScript(false)->render();
  334. /*
  335. * Get and remove the last script of Admin::$script stack.
  336. */
  337. if ($script = $field->getScript()) {
  338. $scripts[] = $script;
  339. }
  340. }
  341. return [$html, implode(";\r\n", $scripts)];
  342. }
  343. /**
  344. * Set `errorKey` `elementName` `elementClass` for fields inside hasmany fields.
  345. *
  346. * @param Field $field
  347. *
  348. * @return Field
  349. */
  350. protected function formatField(Field $field)
  351. {
  352. $column = $field->column();
  353. $elementName = $elementClass = $errorKey = [];
  354. $key = $this->key ?: 'new_'.static::DEFAULT_KEY_NAME;
  355. if (is_array($column)) {
  356. foreach ($column as $k => $name) {
  357. $errorKey[$k] = sprintf('%s.%s.%s', $this->relationName, $key, $name);
  358. $elementName[$k] = sprintf('%s[%s][%s]', $this->formatName(), $key, $name);
  359. $elementClass[$k] = [$this->formatClass(), $this->formatClass($name)];
  360. }
  361. } else {
  362. $errorKey = sprintf('%s.%s.%s', $this->relationName, $key, $column);
  363. $elementName = sprintf('%s[%s][%s]', $this->formatName(), $key, $column);
  364. $elementClass = [$this->formatClass(), $this->formatClass($column)];
  365. }
  366. return $field->setErrorKey($errorKey)
  367. ->setElementName($elementName)
  368. ->setElementClass($elementClass);
  369. }
  370. protected function formatClass($name = null)
  371. {
  372. return str_replace('.', '_', $name ?: $this->relationName);
  373. }
  374. protected function formatName($name = null)
  375. {
  376. return Helper::formatElementName($name ?: $this->relationName);
  377. }
  378. /**
  379. * Add nested-form fields dynamically.
  380. *
  381. * @param string $method
  382. * @param array $arguments
  383. *
  384. * @return mixed
  385. */
  386. public function __call($method, $arguments)
  387. {
  388. if ($className = Form::findFieldClass($method)) {
  389. $column = Arr::get($arguments, 0, '');
  390. /* @var Field $field */
  391. $field = new $className($column, array_slice($arguments, 1));
  392. $field->setForm($this->form);
  393. $field = $this->formatField($field);
  394. $this->pushField($field);
  395. return $field;
  396. }
  397. return $this;
  398. }
  399. }