NestedForm.php 12 KB

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