Grid.php 19 KB

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