Builder.php 17 KB

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