Grid.php 20 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  1. <?php
  2. namespace Dcat\Admin;
  3. use Closure;
  4. use Dcat\Admin\Contracts\Repository;
  5. use Dcat\Admin\Grid\Column;
  6. use Dcat\Admin\Grid\Concerns;
  7. use Dcat\Admin\Grid\Model;
  8. use Dcat\Admin\Grid\Responsive;
  9. use Dcat\Admin\Grid\Row;
  10. use Dcat\Admin\Grid\Tools;
  11. use Dcat\Admin\Support\Helper;
  12. use Dcat\Admin\Traits\HasBuilderEvents;
  13. use Illuminate\Contracts\Support\Renderable;
  14. use Illuminate\Database\Eloquent\Builder;
  15. use Illuminate\Support\Collection;
  16. use Illuminate\Support\Str;
  17. use Illuminate\Support\Traits\Macroable;
  18. class Grid
  19. {
  20. use HasBuilderEvents,
  21. Concerns\HasNames,
  22. Concerns\HasFilter,
  23. Concerns\HasTools,
  24. Concerns\HasActions,
  25. Concerns\HasPaginator,
  26. Concerns\HasExporter,
  27. Concerns\HasComplexHeaders,
  28. Concerns\HasSelector,
  29. Concerns\HasQuickCreate,
  30. Concerns\HasQuickSearch,
  31. Concerns\CanFixColumns,
  32. Macroable {
  33. __call as macroCall;
  34. }
  35. const CREATE_MODE_DEFAULT = 'default';
  36. const CREATE_MODE_DIALOG = 'dialog';
  37. /**
  38. * The grid data model instance.
  39. *
  40. * @var \Dcat\Admin\Grid\Model
  41. */
  42. protected $model;
  43. /**
  44. * Collection of grid columns.
  45. *
  46. * @var \Illuminate\Support\Collection
  47. */
  48. protected $columns;
  49. /**
  50. * Collection of all grid columns.
  51. *
  52. * @var \Illuminate\Support\Collection
  53. */
  54. protected $allColumns;
  55. /**
  56. * Collection of all data rows.
  57. *
  58. * @var \Illuminate\Support\Collection
  59. */
  60. protected $rows;
  61. /**
  62. * Rows callable fucntion.
  63. *
  64. * @var \Closure[]
  65. */
  66. protected $rowsCallback = [];
  67. /**
  68. * All column names of the grid.
  69. *
  70. * @var array
  71. */
  72. protected $columnNames = [];
  73. /**
  74. * Grid builder.
  75. *
  76. * @var \Closure
  77. */
  78. protected $builder;
  79. /**
  80. * Mark if the grid is built.
  81. *
  82. * @var bool
  83. */
  84. protected $built = false;
  85. /**
  86. * All variables in grid view.
  87. *
  88. * @var array
  89. */
  90. protected $variables = [];
  91. /**
  92. * Resource path of the grid.
  93. *
  94. * @var
  95. */
  96. protected $resourcePath;
  97. /**
  98. * Default primary key name.
  99. *
  100. * @var string
  101. */
  102. protected $keyName = 'id';
  103. /**
  104. * View for grid to render.
  105. *
  106. * @var string
  107. */
  108. protected $view = 'admin::grid.data-table';
  109. /**
  110. * @var Closure
  111. */
  112. protected $header;
  113. /**
  114. * @var Closure
  115. */
  116. protected $footer;
  117. /**
  118. * @var Closure
  119. */
  120. protected $wrapper;
  121. /**
  122. * @var Responsive
  123. */
  124. protected $responsive;
  125. /**
  126. * @var bool
  127. */
  128. protected $addNumberColumn = false;
  129. /**
  130. * @var string
  131. */
  132. protected $tableId = 'grid-table';
  133. /**
  134. * @var Grid\Tools\RowSelector
  135. */
  136. protected $rowSelector;
  137. /**
  138. * Options for grid.
  139. *
  140. * @var array
  141. */
  142. protected $options = [
  143. 'show_pagination' => true,
  144. 'show_filter' => true,
  145. 'show_actions' => true,
  146. 'show_quick_edit_button' => false,
  147. 'show_edit_button' => true,
  148. 'show_view_button' => true,
  149. 'show_delete_button' => true,
  150. 'show_row_selector' => true,
  151. 'show_create_button' => true,
  152. 'show_bordered' => false,
  153. 'table_collapse' => true,
  154. 'show_toolbar' => true,
  155. 'create_mode' => self::CREATE_MODE_DEFAULT,
  156. 'dialog_form_area' => ['700px', '670px'],
  157. 'table_class' => ['table', 'dt-checkboxes-select'],
  158. ];
  159. /**
  160. * Create a new grid instance.
  161. *
  162. * Grid constructor.
  163. *
  164. * @param Repository|\Illuminate\Database\Eloquent\Model|Builder|null $repository
  165. * @param null|\Closure $builder
  166. */
  167. public function __construct($repository = null, ?\Closure $builder = null)
  168. {
  169. $this->model = new Model(request(), $repository);
  170. $this->columns = new Collection();
  171. $this->allColumns = new Collection();
  172. $this->rows = new Collection();
  173. $this->builder = $builder;
  174. if ($repository = $this->model->repository()) {
  175. $this->setKeyName($repository->getKeyName());
  176. }
  177. $this->model->setGrid($this);
  178. $this->setupTools();
  179. $this->setupFilter();
  180. $this->callResolving();
  181. }
  182. /**
  183. * Get table ID.
  184. *
  185. * @return string
  186. */
  187. public function getTableId()
  188. {
  189. return $this->tableId;
  190. }
  191. /**
  192. * Set primary key name.
  193. *
  194. * @param string $name
  195. *
  196. * @return $this
  197. */
  198. public function setKeyName(string $name)
  199. {
  200. $this->keyName = $name;
  201. return $this;
  202. }
  203. /**
  204. * Get or set primary key name.
  205. *
  206. * @return string|void
  207. */
  208. public function getKeyName()
  209. {
  210. return $this->keyName ?: 'id';
  211. }
  212. /**
  213. * Add column to Grid.
  214. *
  215. * @param string $name
  216. * @param string $label
  217. *
  218. * @return Column
  219. */
  220. public function column($name, $label = '')
  221. {
  222. if (mb_strpos($name, '.') !== false) {
  223. [$relationName, $relationColumn] = explode('.', $name);
  224. $name = Str::snake($relationName).'.'.$relationColumn;
  225. }
  226. return $this->addColumn($name, $label);
  227. }
  228. /**
  229. * Add number column.
  230. *
  231. * @param null|string $label
  232. *
  233. * @return Column
  234. */
  235. public function number(?string $label = null)
  236. {
  237. return $this->addColumn('#', $label ?: '#');
  238. }
  239. /**
  240. * Batch add column to grid.
  241. *
  242. * @example
  243. * 1.$grid->columns(['name' => 'Name', 'email' => 'Email' ...]);
  244. * 2.$grid->columns('name', 'email' ...)
  245. *
  246. * @param array $columns
  247. *
  248. * @return Collection|Column[]|void
  249. */
  250. public function columns($columns = null)
  251. {
  252. if ($columns === null) {
  253. return $this->columns;
  254. }
  255. if (func_num_args() == 1 && is_array($columns)) {
  256. foreach ($columns as $column => $label) {
  257. $this->column($column, $label);
  258. }
  259. return;
  260. }
  261. foreach (func_get_args() as $column) {
  262. $this->column($column);
  263. }
  264. }
  265. /**
  266. * @return Collection|Column[]
  267. */
  268. public function allColumns()
  269. {
  270. return $this->allColumns;
  271. }
  272. /**
  273. * Add column to grid.
  274. *
  275. * @param string $field
  276. * @param string $label
  277. *
  278. * @return Column
  279. */
  280. protected function addColumn($field = '', $label = '')
  281. {
  282. $column = $this->newColumn($field, $label);
  283. $this->columns->put($field, $column);
  284. $this->allColumns->put($field, $column);
  285. return $column;
  286. }
  287. /**
  288. * @param string $field
  289. * @param string $label
  290. *
  291. * @return Column
  292. */
  293. public function prependColumn($field = '', $label = '')
  294. {
  295. $column = $this->newColumn($field, $label);
  296. $this->columns->prepend($column, $field);
  297. $this->allColumns->prepend($column, $field);
  298. return $column;
  299. }
  300. /**
  301. * @param string $field
  302. * @param string $label
  303. *
  304. * @return Column
  305. */
  306. public function newColumn($field = '', $label = '')
  307. {
  308. $column = new Column($field, $label);
  309. $column->setGrid($this);
  310. return $column;
  311. }
  312. /**
  313. * Get Grid model.
  314. *
  315. * @return Model
  316. */
  317. public function model()
  318. {
  319. return $this->model;
  320. }
  321. /**
  322. * @return array
  323. */
  324. public function getColumnNames()
  325. {
  326. return $this->columnNames;
  327. }
  328. /**
  329. * Apply column filter to grid query.
  330. */
  331. protected function applyColumnFilter()
  332. {
  333. $this->columns->each->bindFilterQuery($this->model());
  334. }
  335. /**
  336. * @param string|array $class
  337. *
  338. * @return $this
  339. */
  340. public function addTableClass($class)
  341. {
  342. $this->options['table_class'] = array_merge((array) $this->options['table_class'], (array) $class);
  343. return $this;
  344. }
  345. public function formatTableClass()
  346. {
  347. if ($this->options['show_bordered']) {
  348. $this->addTableClass(['table-bordered', 'complex-headers', 'dataTable']);
  349. }
  350. if ($this->getComplexHeaders()) {
  351. $this->addTableClass('table-text-center');
  352. }
  353. return implode(' ', array_unique((array) $this->options['table_class']));
  354. }
  355. /**
  356. * Build the grid.
  357. *
  358. * @return void
  359. */
  360. public function build()
  361. {
  362. if ($this->built) {
  363. return;
  364. }
  365. $collection = $this->processFilter(false);
  366. $data = $collection->toArray();
  367. $this->prependRowSelectorColumn();
  368. $this->appendActionsColumn();
  369. Column::setOriginalGridModels($collection);
  370. $this->columns->map(function (Column $column) use (&$data) {
  371. $column->fill($data);
  372. $this->columnNames[] = $column->getName();
  373. });
  374. $this->buildRows($data);
  375. if ($data && $this->responsive) {
  376. $this->responsive->build();
  377. }
  378. $this->sortHeaders();
  379. }
  380. /**
  381. * @return void
  382. */
  383. public function callBuilder()
  384. {
  385. if ($this->builder && ! $this->built) {
  386. call_user_func($this->builder, $this);
  387. }
  388. $this->built = true;
  389. }
  390. /**
  391. * Build the grid rows.
  392. *
  393. * @param array $data
  394. *
  395. * @return void
  396. */
  397. protected function buildRows(array $data)
  398. {
  399. $this->rows = collect($data)->map(function ($model) {
  400. return new Row($this, $model);
  401. });
  402. if ($this->rowsCallback) {
  403. foreach ($this->rowsCallback as $value) {
  404. $value($this->rows);
  405. }
  406. }
  407. }
  408. /**
  409. * Set grid row callback function.
  410. *
  411. * @param Closure $callable
  412. *
  413. * @return Collection|void
  414. */
  415. public function rows(Closure $callable = null)
  416. {
  417. if (is_null($callable)) {
  418. return $this->rows;
  419. }
  420. $this->rowsCallback[] = $callable;
  421. }
  422. /**
  423. * Get create url.
  424. *
  425. * @return string
  426. */
  427. public function getCreateUrl()
  428. {
  429. $queryString = '';
  430. if ($constraints = $this->model()->getConstraints()) {
  431. $queryString = http_build_query($constraints);
  432. }
  433. return sprintf(
  434. '%s/create%s',
  435. $this->resource(),
  436. $queryString ? ('?'.$queryString) : ''
  437. );
  438. }
  439. /**
  440. * @param \Closure $closure
  441. *
  442. * @return Grid\Tools\RowSelector
  443. */
  444. public function rowSelector()
  445. {
  446. return $this->rowSelector ?: ($this->rowSelector = new Grid\Tools\RowSelector($this));
  447. }
  448. /**
  449. * Prepend checkbox column for grid.
  450. *
  451. * @return void
  452. */
  453. protected function prependRowSelectorColumn()
  454. {
  455. if (! $this->options['show_row_selector']) {
  456. return;
  457. }
  458. $rowSelector = $this->rowSelector();
  459. $keyName = $this->getKeyName();
  460. $this->prependColumn(
  461. Grid\Column::SELECT_COLUMN_NAME,
  462. $rowSelector->renderHeader()
  463. )->display(function () use ($rowSelector, $keyName) {
  464. return $rowSelector->renderColumn($this, $this->{$keyName});
  465. });
  466. }
  467. /**
  468. * @param string $width
  469. * @param string $height
  470. *
  471. * @return $this
  472. */
  473. public function setDialogFormDimensions(string $width, string $height)
  474. {
  475. $this->options['dialog_form_area'] = [$width, $height];
  476. return $this;
  477. }
  478. /**
  479. * Render create button for grid.
  480. *
  481. * @return string
  482. */
  483. public function renderCreateButton()
  484. {
  485. if (! $this->options['show_create_button']) {
  486. return '';
  487. }
  488. return (new Tools\CreateButton($this))->render();
  489. }
  490. /**
  491. * @param bool $value
  492. *
  493. * @return $this
  494. */
  495. public function withBorder(bool $value = true)
  496. {
  497. $this->options['show_bordered'] = $value;
  498. return $this;
  499. }
  500. /**
  501. * @param bool $value
  502. *
  503. * @return $this
  504. */
  505. public function tableCollapse(bool $value = true)
  506. {
  507. $this->options['table_collapse'] = $value;
  508. return $this;
  509. }
  510. /**
  511. * Set grid header.
  512. *
  513. * @param Closure|string|Renderable $content
  514. *
  515. * @return $this|Closure
  516. */
  517. public function header($content = null)
  518. {
  519. if (! $content) {
  520. return $this->header;
  521. }
  522. $this->header = $content;
  523. return $this;
  524. }
  525. /**
  526. * Render grid header.
  527. *
  528. * @return string
  529. */
  530. public function renderHeader()
  531. {
  532. if (! $this->header) {
  533. return '';
  534. }
  535. $content = Helper::render($this->header, [$this->processFilter(false)]);
  536. if (empty($content)) {
  537. return '';
  538. }
  539. return <<<HTML
  540. <div class="card-header clearfix" style="border-bottom: 0;background: transparent;padding: 0">{$content}</div>
  541. HTML;
  542. }
  543. /**
  544. * Set grid footer.
  545. *
  546. * @param Closure|string|Renderable $content
  547. *
  548. * @return $this|Closure
  549. */
  550. public function footer($content = null)
  551. {
  552. if (! $content) {
  553. return $this->footer;
  554. }
  555. $this->footer = $content;
  556. return $this;
  557. }
  558. /**
  559. * Render grid footer.
  560. *
  561. * @return string
  562. */
  563. public function renderFooter()
  564. {
  565. if (! $this->footer) {
  566. return '';
  567. }
  568. $content = Helper::render($this->footer, [$this->processFilter(false)]);
  569. if (empty($content)) {
  570. return '';
  571. }
  572. return <<<HTML
  573. <div class="box-footer clearfix">{$content}</div>
  574. HTML;
  575. }
  576. /**
  577. * Get or set option for grid.
  578. *
  579. * @param string $key
  580. * @param mixed $value
  581. *
  582. * @return $this|mixed
  583. */
  584. public function option($key, $value = null)
  585. {
  586. if (is_null($value)) {
  587. return $this->options[$key] ?? null;
  588. }
  589. $this->options[$key] = $value;
  590. return $this;
  591. }
  592. protected function setUpOptions()
  593. {
  594. if ($this->options['show_bordered']) {
  595. $this->tableCollapse(false);
  596. }
  597. }
  598. /**
  599. * Disable row selector.
  600. *
  601. * @return $this
  602. */
  603. public function disableRowSelector(bool $disable = true)
  604. {
  605. $this->tools->disableBatchActions($disable);
  606. return $this->option('show_row_selector', ! $disable);
  607. }
  608. /**
  609. * Show row selector.
  610. *
  611. * @return $this
  612. */
  613. public function showRowSelector(bool $val = true)
  614. {
  615. return $this->disableRowSelector(! $val);
  616. }
  617. /**
  618. * Remove create button on grid.
  619. *
  620. * @return $this
  621. */
  622. public function disableCreateButton(bool $disable = true)
  623. {
  624. return $this->option('show_create_button', ! $disable);
  625. }
  626. /**
  627. * Show create button.
  628. *
  629. * @return $this
  630. */
  631. public function showCreateButton(bool $val = true)
  632. {
  633. return $this->disableCreateButton(! $val);
  634. }
  635. /**
  636. * If allow creation.
  637. *
  638. * @return bool
  639. */
  640. public function allowCreateButton()
  641. {
  642. return $this->options['show_create_button'];
  643. }
  644. /**
  645. * @param string $mode
  646. *
  647. * @return $this
  648. */
  649. public function createMode(string $mode)
  650. {
  651. return $this->option('create_mode', $mode);
  652. }
  653. /**
  654. * @return $this
  655. */
  656. public function enableDialogCreate()
  657. {
  658. return $this->createMode(self::CREATE_MODE_DIALOG);
  659. }
  660. /**
  661. * Get or set resource path.
  662. *
  663. * @param string $path
  664. *
  665. * @return $this|string
  666. */
  667. public function resource(string $path = null)
  668. {
  669. if ($path === null) {
  670. return $this->resourcePath ?: (
  671. $this->resourcePath = url(app('request')->getPathInfo())
  672. );
  673. }
  674. if (! empty($path)) {
  675. $this->resourcePath = admin_url($path);
  676. }
  677. return $this;
  678. }
  679. /**
  680. * Create a grid instance.
  681. *
  682. * @param mixed ...$params
  683. *
  684. * @return $this
  685. */
  686. public static function make(...$params)
  687. {
  688. return new static(...$params);
  689. }
  690. /**
  691. * Enable responsive tables.
  692. *
  693. * @see https://github.com/nadangergeo/RWD-Table-Patterns
  694. *
  695. * @return Responsive
  696. */
  697. public function responsive()
  698. {
  699. if (! $this->responsive) {
  700. $this->responsive = new Responsive($this);
  701. }
  702. return $this->responsive;
  703. }
  704. /**
  705. * @return bool
  706. */
  707. public function allowResponsive()
  708. {
  709. return $this->responsive ? true : false;
  710. }
  711. /**
  712. * @param Closure $closure
  713. *
  714. * @return $this;
  715. */
  716. public function wrap(\Closure $closure)
  717. {
  718. $this->wrapper = $closure;
  719. return $this;
  720. }
  721. /**
  722. * @return bool
  723. */
  724. public function hasWrapper()
  725. {
  726. return $this->wrapper ? true : false;
  727. }
  728. /**
  729. * Add variables to grid view.
  730. *
  731. * @param array $variables
  732. *
  733. * @return $this
  734. */
  735. public function with($variables = [])
  736. {
  737. $this->variables = $variables;
  738. return $this;
  739. }
  740. /**
  741. * Get all variables will used in grid view.
  742. *
  743. * @return array
  744. */
  745. protected function variables()
  746. {
  747. $this->variables['grid'] = $this;
  748. $this->variables['tableId'] = $this->getTableId();
  749. return $this->variables;
  750. }
  751. /**
  752. * Set a view to render.
  753. *
  754. * @param string $view
  755. * @param array $variables
  756. */
  757. public function view($view, $variables = [])
  758. {
  759. if (! empty($variables)) {
  760. $this->with($variables);
  761. }
  762. $this->view = $view;
  763. }
  764. /**
  765. * Set grid title.
  766. *
  767. * @param string $title
  768. *
  769. * @return $this
  770. */
  771. public function title($title)
  772. {
  773. $this->variables['title'] = $title;
  774. return $this;
  775. }
  776. /**
  777. * Set grid description.
  778. *
  779. * @param string $description
  780. *
  781. * @return $this
  782. */
  783. public function description($description)
  784. {
  785. $this->variables['description'] = $description;
  786. return $this;
  787. }
  788. /**
  789. * Set resource path for grid.
  790. *
  791. * @param string $path
  792. *
  793. * @return $this
  794. */
  795. public function setResource($path)
  796. {
  797. $this->resourcePath = $path;
  798. return $this;
  799. }
  800. /**
  801. * Get the string contents of the grid view.
  802. *
  803. * @return string
  804. */
  805. public function render()
  806. {
  807. $this->handleExportRequest();
  808. try {
  809. $this->callComposing();
  810. $this->build();
  811. $this->applyFixColumns();
  812. $this->setUpOptions();
  813. } catch (\Throwable $e) {
  814. return Admin::makeExceptionHandler()->handle($e);
  815. }
  816. return $this->doWrap();
  817. }
  818. /**
  819. * @return string
  820. */
  821. protected function doWrap()
  822. {
  823. $view = view($this->view, $this->variables());
  824. if (! $wrapper = $this->wrapper) {
  825. return $view->render();
  826. }
  827. return $wrapper($view);
  828. }
  829. /**
  830. * Add column to grid.
  831. *
  832. * @param string $name
  833. *
  834. * @return Column
  835. */
  836. public function __get($name)
  837. {
  838. return $this->addColumn($name);
  839. }
  840. /**
  841. * Dynamically add columns to the grid view.
  842. *
  843. * @param $method
  844. * @param $arguments
  845. *
  846. * @return Column
  847. */
  848. public function __call($method, $arguments)
  849. {
  850. if (static::hasMacro($method)) {
  851. return $this->macroCall($method, $arguments);
  852. }
  853. return $this->addColumn($method, $arguments[0] ?? null);
  854. }
  855. }