Builder.php 17 KB

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