Builder.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833
  1. <?php
  2. namespace Dcat\Admin\Form;
  3. use Dcat\Admin\Admin;
  4. use Dcat\Admin\Form;
  5. use Dcat\Admin\Form\Field\Hidden;
  6. use Illuminate\Contracts\Support\Renderable;
  7. use Illuminate\Support\Arr;
  8. use Illuminate\Support\Collection;
  9. use Illuminate\Support\Facades\URL;
  10. use Illuminate\Support\Str;
  11. /**
  12. * Class Builder.
  13. */
  14. class Builder
  15. {
  16. /**
  17. * Previous url key.
  18. */
  19. const PREVIOUS_URL_KEY = '_previous_';
  20. /**
  21. * @var mixed
  22. */
  23. protected $id;
  24. /**
  25. * @var Form
  26. */
  27. protected $form;
  28. /**
  29. * @var
  30. */
  31. protected $action;
  32. /**
  33. * @var Collection
  34. */
  35. protected $fields;
  36. /**
  37. * @var array
  38. */
  39. protected $options = [];
  40. /**
  41. * Modes constants.
  42. */
  43. const MODE_EDIT = 'edit';
  44. const MODE_CREATE = 'create';
  45. const MODE_DELETE = 'delete';
  46. /**
  47. * Form action mode, could be create|view|edit.
  48. *
  49. * @var string
  50. */
  51. protected $mode = self::MODE_CREATE;
  52. /**
  53. * @var array
  54. */
  55. protected $hiddenFields = [];
  56. /**
  57. * @var Tools
  58. */
  59. protected $tools;
  60. /**
  61. * @var Footer
  62. */
  63. protected $footer;
  64. /**
  65. * Width for label and field.
  66. *
  67. * @var array
  68. */
  69. protected $width = [
  70. 'label' => 2,
  71. 'field' => 8,
  72. ];
  73. /**
  74. * View for this form.
  75. *
  76. * @var string
  77. */
  78. protected $view = 'admin::form';
  79. /**
  80. * Form title.
  81. *
  82. * @var string
  83. */
  84. protected $title;
  85. /**
  86. * @var MultipleForm[]
  87. */
  88. protected $multipleForms = [];
  89. /**
  90. * @var Layout
  91. */
  92. protected $layout;
  93. /**
  94. * @var int
  95. */
  96. protected $defaultBlockWidth = 12;
  97. /**
  98. * @var string
  99. */
  100. protected $formId;
  101. /**
  102. * @var \Closure
  103. */
  104. protected $wrapper;
  105. /**
  106. * @var bool
  107. */
  108. protected $showHeader = true;
  109. /**
  110. * @var bool
  111. */
  112. protected $showFooter = true;
  113. /**
  114. * Builder constructor.
  115. *
  116. * @param Form $form
  117. */
  118. public function __construct(Form $form)
  119. {
  120. $this->form = $form;
  121. $this->fields = new Collection();
  122. $this->layout = new Layout($form);
  123. $this->tools = new Tools($this);
  124. $this->footer = new Footer($this);
  125. }
  126. /**
  127. * @param \Closure $closure
  128. * @return Layout
  129. */
  130. public function layout($closure = null)
  131. {
  132. if ($closure) {
  133. $closure($this->layout);
  134. }
  135. return $this->layout;
  136. }
  137. /**
  138. * @param \Closure $closure
  139. * @return $this;
  140. */
  141. public function wrap(\Closure $closure)
  142. {
  143. $this->wrapper = $closure;
  144. return $this;
  145. }
  146. /**
  147. * @return bool
  148. */
  149. public function hasWrapper()
  150. {
  151. return $this->wrapper ? true : false;
  152. }
  153. /**
  154. * @param int $width
  155. * @return $this
  156. */
  157. public function setDefaultBlockWidth(int $width)
  158. {
  159. $this->defaultBlockWidth = $width;
  160. return $this;
  161. }
  162. /**
  163. * @param MultipleForm $form
  164. */
  165. public function addForm(MultipleForm $form)
  166. {
  167. $this->multipleForms[] = $form;
  168. $form->disableResetButton();
  169. $form->disableSubmitButton();
  170. $form->disableFormTag();
  171. return $this;
  172. }
  173. /**
  174. * Get form tools instance.
  175. *
  176. * @return Tools
  177. */
  178. public function getTools()
  179. {
  180. return $this->tools;
  181. }
  182. /**
  183. * Get form footer instance.
  184. *
  185. * @return Footer
  186. */
  187. public function getFooter()
  188. {
  189. return $this->footer;
  190. }
  191. /**
  192. * Set the builder mode.
  193. *
  194. * @param string $mode
  195. *
  196. * @return void
  197. */
  198. public function setMode(string $mode = self::MODE_CREATE)
  199. {
  200. $this->mode = $mode;
  201. }
  202. /**
  203. * @return string
  204. */
  205. public function getMode()
  206. {
  207. return $this->mode;
  208. }
  209. /**
  210. * Returns builder is $mode.
  211. *
  212. * @param $mode
  213. *
  214. * @return bool
  215. */
  216. public function isMode($mode)
  217. {
  218. return $this->mode == $mode;
  219. }
  220. /**
  221. * Check if is creating resource.
  222. *
  223. * @return bool
  224. */
  225. public function isCreating()
  226. {
  227. return $this->isMode(static::MODE_CREATE);
  228. }
  229. /**
  230. * Check if is editing resource.
  231. *
  232. * @return bool
  233. */
  234. public function isEditing()
  235. {
  236. return $this->isMode(static::MODE_EDIT);
  237. }
  238. /**
  239. * Check if is deleting resource.
  240. *
  241. * @return bool
  242. */
  243. public function isDeleting()
  244. {
  245. return $this->isMode(static::MODE_DELETE);
  246. }
  247. /**
  248. * Set resource Id.
  249. *
  250. * @param $id
  251. *
  252. * @return void
  253. */
  254. public function setResourceId($id)
  255. {
  256. $this->id = $id;
  257. }
  258. /**
  259. * Get Resource id.
  260. *
  261. * @return mixed
  262. */
  263. public function getResourceId()
  264. {
  265. return $this->id;
  266. }
  267. /**
  268. * @return string
  269. */
  270. public function getResource($slice = null)
  271. {
  272. if ($this->mode == self::MODE_CREATE) {
  273. return $this->form->getResource(-1);
  274. }
  275. if ($slice !== null) {
  276. return $this->form->getResource($slice);
  277. }
  278. return $this->form->getResource();
  279. }
  280. /**
  281. * @param int $field
  282. * @param int $label
  283. *
  284. * @return $this
  285. */
  286. public function setWidth($field = 8, $label = 2)
  287. {
  288. $this->width = [
  289. 'label' => $label,
  290. 'field' => $field,
  291. ];
  292. return $this;
  293. }
  294. /**
  295. * Get label and field width.
  296. *
  297. * @return array
  298. */
  299. public function getWidth()
  300. {
  301. return $this->width;
  302. }
  303. /**
  304. * Set form action.
  305. *
  306. * @param string $action
  307. */
  308. public function setAction($action)
  309. {
  310. $this->action = $action;
  311. }
  312. /**
  313. * Get Form action.
  314. *
  315. * @return string
  316. */
  317. public function getAction()
  318. {
  319. if ($this->action) {
  320. return $this->action;
  321. }
  322. if ($this->isMode(static::MODE_EDIT)) {
  323. return $this->form->getResource().'/'.$this->id;
  324. }
  325. if ($this->isMode(static::MODE_CREATE)) {
  326. return $this->form->getResource(-1);
  327. }
  328. return '';
  329. }
  330. /**
  331. * Set view for this form.
  332. *
  333. * @param string $view
  334. *
  335. * @return $this
  336. */
  337. public function setView($view)
  338. {
  339. $this->view = $view;
  340. return $this;
  341. }
  342. /**
  343. * Set title for form.
  344. *
  345. * @param string $title
  346. *
  347. * @return $this
  348. */
  349. public function setTitle($title)
  350. {
  351. $this->title = $title;
  352. return $this;
  353. }
  354. /**
  355. * Get fields of this builder.
  356. *
  357. * @return Collection
  358. */
  359. public function fields()
  360. {
  361. return $this->fields;
  362. }
  363. /**
  364. * Get specify field.
  365. *
  366. * @param string $name
  367. *
  368. * @return mixed
  369. */
  370. public function field($name)
  371. {
  372. return $this->fields->first(function (Field $field) use ($name) {
  373. return $field->column() == $name;
  374. });
  375. }
  376. /**
  377. * @param $column
  378. * @return $this
  379. */
  380. public function removeField($column)
  381. {
  382. $this->fields = $this->fields->filter(function (Field $field) use ($column) {
  383. return $field->column() != $column;
  384. });
  385. return $this;
  386. }
  387. /**
  388. * If the parant form has rows.
  389. *
  390. * @return bool
  391. */
  392. public function hasRows()
  393. {
  394. return !empty($this->form->rows);
  395. }
  396. /**
  397. * Get field rows of form.
  398. *
  399. * @return array
  400. */
  401. public function getRows()
  402. {
  403. return $this->form->rows;
  404. }
  405. /**
  406. * @return array
  407. */
  408. public function getHiddenFields()
  409. {
  410. return $this->hiddenFields;
  411. }
  412. /**
  413. * @param Field $field
  414. *
  415. * @return void
  416. */
  417. public function addHiddenField(Field $field)
  418. {
  419. $this->hiddenFields[] = $field;
  420. }
  421. /**
  422. * Add or get options.
  423. *
  424. * @param array $options
  425. *
  426. * @return array|null
  427. */
  428. public function options($options = [])
  429. {
  430. if (empty($options)) {
  431. return $this->options;
  432. }
  433. $this->options = array_merge($this->options, $options);
  434. }
  435. /**
  436. * Get or set option.
  437. *
  438. * @param string $option
  439. * @param mixed $value
  440. *
  441. * @return $this
  442. */
  443. public function option($option, $value = null)
  444. {
  445. if (func_num_args() == 1) {
  446. return Arr::get($this->options, $option);
  447. }
  448. $this->options[$option] = $value;
  449. return $this;
  450. }
  451. /**
  452. * @param bool $disable
  453. * @return $this
  454. */
  455. public function disableHeader(bool $disable = true)
  456. {
  457. $this->showHeader = !$disable;
  458. return $this;
  459. }
  460. /**
  461. * @param bool $disable
  462. * @return $this
  463. */
  464. public function disableFooter(bool $disable = true)
  465. {
  466. $this->showFooter = !$disable;
  467. return $this;
  468. }
  469. /**
  470. * @param $id
  471. * @return $this
  472. */
  473. public function setFormId($id)
  474. {
  475. $this->formId = $id;
  476. return $this;
  477. }
  478. /**
  479. * @return string
  480. */
  481. public function getFormId()
  482. {
  483. return $this->formId ?: ($this->formId = 'form-'.Str::random(8));
  484. }
  485. /**
  486. * @return string
  487. */
  488. public function title()
  489. {
  490. if ($this->title) {
  491. return $this->title;
  492. }
  493. if ($this->mode == static::MODE_CREATE) {
  494. return trans('admin.create');
  495. }
  496. if ($this->mode == static::MODE_EDIT) {
  497. return trans('admin.edit');
  498. }
  499. return '';
  500. }
  501. /**
  502. * Determine if form fields has files.
  503. *
  504. * @return bool
  505. */
  506. public function hasFile()
  507. {
  508. foreach ($this->fields() as $field) {
  509. if (
  510. $field instanceof Field\File
  511. || $field instanceof Form\Field\BootstrapFile
  512. ) {
  513. return true;
  514. }
  515. }
  516. return false;
  517. }
  518. /**
  519. * Add field for store redirect url after update or store.
  520. *
  521. * @return void
  522. */
  523. protected function addRedirectUrlField()
  524. {
  525. $previous = URL::previous();
  526. if (!$previous || $previous == URL::current()) {
  527. return;
  528. }
  529. if (Str::contains($previous, url($this->getResource()))) {
  530. $this->addHiddenField((new Hidden(static::PREVIOUS_URL_KEY))->value($previous));
  531. }
  532. }
  533. /**
  534. * Open up a new HTML form.
  535. *
  536. * @param array $options
  537. *
  538. * @return string
  539. */
  540. public function open($options = [])
  541. {
  542. $attributes = [];
  543. if ($this->isMode(self::MODE_EDIT)) {
  544. $this->addHiddenField((new Hidden('_method'))->value('PUT'));
  545. }
  546. $this->addRedirectUrlField();
  547. $attributes['id'] = $this->getFormId();
  548. $attributes['action'] = $this->getAction();
  549. $attributes['method'] = Arr::get($options, 'method', 'post');
  550. $attributes['accept-charset'] = 'UTF-8';
  551. $attributes['data-toggle'] = 'validator';
  552. $attributes['class'] = Arr::get($options, 'class');
  553. if ($this->hasFile()) {
  554. $attributes['enctype'] = 'multipart/form-data';
  555. }
  556. $html = [];
  557. foreach ($attributes as $name => $value) {
  558. $html[] = "$name=\"$value\"";
  559. }
  560. return '<form '.implode(' ', $html).' pjax-container>';
  561. }
  562. /**
  563. * Close the current form.
  564. *
  565. * @return string
  566. */
  567. public function close()
  568. {
  569. $this->form = null;
  570. $this->fields = null;
  571. return '</form>';
  572. }
  573. /**
  574. * Remove reserved fields like `id` `created_at` `updated_at` in form fields.
  575. *
  576. * @return void
  577. */
  578. protected function removeReservedFields()
  579. {
  580. if (! $this->isMode(static::MODE_CREATE)) {
  581. return;
  582. }
  583. $reservedColumns = [
  584. $this->form->getKeyName(),
  585. $this->form->getRepository()->getCreatedAtColumn(),
  586. $this->form->getRepository()->getUpdatedAtColumn(),
  587. ];
  588. $this->fields = $this->fields()->reject(function (Field $field) use (&$reservedColumns) {
  589. return in_array($field->column(), $reservedColumns)
  590. && $field instanceof Form\Field\Display;
  591. });
  592. }
  593. /**
  594. * Render form header tools.
  595. *
  596. * @return string
  597. */
  598. public function renderTools()
  599. {
  600. return $this->tools->render();
  601. }
  602. /**
  603. * Render form footer.
  604. *
  605. * @return string
  606. */
  607. public function renderFooter()
  608. {
  609. if (!$this->showFooter) {
  610. return;
  611. }
  612. return $this->footer->render();
  613. }
  614. /**
  615. * Render form.
  616. *
  617. * @return string
  618. */
  619. public function render()
  620. {
  621. $this->removeReservedFields();
  622. $tabObj = $this->form->getTab();
  623. if (!$tabObj->isEmpty()) {
  624. $this->setupTabScript();
  625. }
  626. $this->form->allowAjaxSubmit() && $this->setupSubmitScript();
  627. $open = $this->open(['class' => 'form-horizontal']);
  628. $data = [
  629. 'form' => $this,
  630. 'tabObj' => $tabObj,
  631. 'width' => $this->width,
  632. 'formId' => $this->getFormId(),
  633. 'showHeader' => $this->showHeader,
  634. ];
  635. $this->layout->prepend(
  636. $this->defaultBlockWidth,
  637. $this->doWrap(view($this->view, $data))
  638. );
  639. return <<<EOF
  640. {$open} {$this->layout->build()} {$this->close()}
  641. EOF;
  642. }
  643. /**
  644. * @param Renderable $view
  645. * @return string
  646. */
  647. protected function doWrap(Renderable $view)
  648. {
  649. if ($wrapper = $this->wrapper) {
  650. return $wrapper($view);
  651. }
  652. return "<div class='card material'>{$view->render()}</div>";
  653. }
  654. protected function setupSubmitScript()
  655. {
  656. Admin::script(
  657. <<<JS
  658. var f = $('#{$this->getFormId()}');
  659. f.find('[type="submit"]').click(function () {
  660. var t = $(this);
  661. LA.Form({
  662. \$form: f,
  663. before: function () {
  664. f.validator('validate');
  665. if (f.find('.has-error').length > 0) {
  666. return false;
  667. }
  668. t.button('loading');
  669. },
  670. after: function () {
  671. t.button('reset');
  672. }
  673. });
  674. return false;
  675. });
  676. JS
  677. );
  678. }
  679. protected function setupTabScript()
  680. {
  681. $formId = $this->getFormId();
  682. $script = <<<JS
  683. var hash = document.location.hash;
  684. if (hash) {
  685. $('#$formId .nav-tabs a[href="' + hash + '"]').tab('show');
  686. }
  687. // Change hash for page-reload
  688. $('#$formId .nav-tabs a').on('shown.bs.tab', function (e) {
  689. history.pushState(null,null, e.target.hash);
  690. });
  691. if ($('#$formId .has-error').length) {
  692. $('#$formId .has-error').each(function () {
  693. var tabId = '#'+$(this).closest('.tab-pane').attr('id');
  694. $('li a[href="'+tabId+'"] i').removeClass('hide');
  695. });
  696. var first = $('#$formId .has-error:first').closest('.tab-pane').attr('id');
  697. $('li a[href="#'+first+'"]').tab('show');
  698. }
  699. JS;
  700. Admin::script($script);
  701. }
  702. }