Grid.php 19 KB

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