Grid.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954
  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' => true,
  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' => true,
  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. * @param Repository $model
  158. * @param Closure $builder
  159. */
  160. public function __construct(Repository $repository = null, $builder = null)
  161. {
  162. if ($repository) {
  163. $this->keyName = $repository->getKeyName();
  164. }
  165. $this->model = new Model($repository);
  166. $this->columns = new Collection();
  167. $this->rows = new Collection();
  168. $this->builder = $builder;
  169. $this->tableId = 'grid-'.Str::random(8);
  170. $this->model()->setGrid($this);
  171. $this->setupTools();
  172. $this->setupFilter();
  173. $this->callResolving();
  174. }
  175. /**
  176. * Get table ID.
  177. *
  178. * @return string
  179. */
  180. public function getTableId()
  181. {
  182. return $this->tableId;
  183. }
  184. /**
  185. * Get primary key name of model.
  186. *
  187. * @return string
  188. */
  189. public function getKeyName()
  190. {
  191. return $this->keyName ?: 'id';
  192. }
  193. /**
  194. * Set primary key name.
  195. *
  196. * @param string $name
  197. */
  198. public function setKeyName(string $name)
  199. {
  200. $this->keyName = $name;
  201. }
  202. /**
  203. * Add column to Grid.
  204. *
  205. * @param string $name
  206. * @param string $label
  207. *
  208. * @return Column|Collection
  209. */
  210. public function column($name, $label = '')
  211. {
  212. if (strpos($name, '.') !== false) {
  213. list($relationName, $relationColumn) = explode('.', $name);
  214. $label = empty($label) ? admin_trans_field($relationColumn) : $label;
  215. $name = Str::snake($relationName).'.'.$relationColumn;
  216. }
  217. $column = $this->addColumn($name, $label);
  218. return $column;
  219. }
  220. /**
  221. * Add number column.
  222. *
  223. * @param null|string $label
  224. * @return Column
  225. */
  226. public function number(?string $label = null)
  227. {
  228. return $this->addColumn('#', $label ?: '#')->bold();
  229. }
  230. /**
  231. * Batch add column to grid.
  232. *
  233. * @example
  234. * 1.$grid->columns(['name' => 'Name', 'email' => 'Email' ...]);
  235. * 2.$grid->columns('name', 'email' ...)
  236. *
  237. * @param array $columns
  238. *
  239. * @return null
  240. */
  241. public function columns($columns = [])
  242. {
  243. if (func_num_args() == 0) {
  244. return $this->columns;
  245. }
  246. if (func_num_args() == 1 && is_array($columns)) {
  247. foreach ($columns as $column => $label) {
  248. $this->column($column, $label);
  249. }
  250. return;
  251. }
  252. foreach (func_get_args() as $column) {
  253. $this->column($column);
  254. }
  255. }
  256. /**
  257. * @return Collection
  258. */
  259. public function getColumns()
  260. {
  261. return $this->columns;
  262. }
  263. /**
  264. * Add column to grid.
  265. *
  266. * @param string $field
  267. * @param string $label
  268. *
  269. * @return Column
  270. */
  271. protected function addColumn($field = '', $label = '')
  272. {
  273. $column = new Column($field, $label);
  274. $column->setGrid($this);
  275. $this->columns->put($field, $column);
  276. return $column;
  277. }
  278. /**
  279. * Get Grid model.
  280. *
  281. * @return Model
  282. */
  283. public function model()
  284. {
  285. return $this->model;
  286. }
  287. /**
  288. * @return array
  289. */
  290. public function getColumnNames()
  291. {
  292. return $this->columnNames;
  293. }
  294. /**
  295. * @param array $options
  296. * @return $this
  297. */
  298. public function setRowSelectorOptions(array $options = [])
  299. {
  300. if (isset($options['style'])) {
  301. $this->options['row_selector_style'] = $options['style'];
  302. }
  303. if (isset($options['circle'])) {
  304. $this->options['row_selector_circle'] = $options['circle'];
  305. }
  306. if (isset($options['clicktr'])) {
  307. $this->options['row_selector_clicktr'] = $options['clicktr'];
  308. }
  309. if (isset($options['label_name'])) {
  310. $this->options['row_selector_label_name'] = $options['label_name'];
  311. }
  312. if (isset($options['bg'])) {
  313. $this->options['row_selector_bg'] = $options['bg'];
  314. }
  315. return $this;
  316. }
  317. /**
  318. * Prepend checkbox column for grid.
  319. *
  320. * @return void
  321. */
  322. protected function prependRowSelectorColumn()
  323. {
  324. if (!$this->options['show_row_selector']) {
  325. return;
  326. }
  327. $circle = $this->options['row_selector_circle'] ? 'checkbox-circle' : '';
  328. $column = new Column(
  329. Column::SELECT_COLUMN_NAME,
  330. <<<HTML
  331. <div class="checkbox checkbox-{$this->options['row_selector_style']} $circle checkbox-grid">
  332. <input type="checkbox" class="select-all {$this->getSelectAllName()}"><label></label>
  333. </div>
  334. HTML
  335. );
  336. $column->setGrid($this);
  337. $column->displayUsing(Displayers\RowSelector::class);
  338. $this->columns->prepend($column, Column::SELECT_COLUMN_NAME);
  339. }
  340. /**
  341. * Apply column filter to grid query.
  342. */
  343. protected function applyColumnFilter()
  344. {
  345. $this->columns->each->bindFilterQuery($this->model());
  346. }
  347. /**
  348. * Build the grid.
  349. *
  350. * @return void
  351. */
  352. public function build()
  353. {
  354. if ($this->built) {
  355. return;
  356. }
  357. $this->applyQuickSearch();
  358. $this->applyColumnFilter();
  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 setDialogFormDimensions(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 "<div class='box box-default'>{$view->render()}</div>";
  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. }