Builder.php 17 KB

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