NestedForm.php 11 KB

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