Grid.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972
  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->column('#', $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. * Prepend column to grid.
  280. *
  281. * @param string $column
  282. * @param string $label
  283. *
  284. * @return Column
  285. */
  286. protected function prependColumn($column = '', $label = '')
  287. {
  288. $column = new Column($column, $label);
  289. $column->setGrid($this);
  290. $this->columns->prepend($column);
  291. return $column;
  292. }
  293. /**
  294. * Get Grid model.
  295. *
  296. * @return Model
  297. */
  298. public function model()
  299. {
  300. return $this->model;
  301. }
  302. /**
  303. * @return array
  304. */
  305. public function getColumnNames()
  306. {
  307. return $this->columnNames;
  308. }
  309. /**
  310. * @param array $options
  311. * @return $this
  312. */
  313. public function setRowSelectorOptions(array $options = [])
  314. {
  315. if (isset($options['style'])) {
  316. $this->options['row_selector_style'] = $options['style'];
  317. }
  318. if (isset($options['circle'])) {
  319. $this->options['row_selector_circle'] = $options['circle'];
  320. }
  321. if (isset($options['clicktr'])) {
  322. $this->options['row_selector_clicktr'] = $options['clicktr'];
  323. }
  324. if (isset($options['label_name'])) {
  325. $this->options['row_selector_label_name'] = $options['label_name'];
  326. }
  327. if (isset($options['bg'])) {
  328. $this->options['row_selector_bg'] = $options['bg'];
  329. }
  330. return $this;
  331. }
  332. /**
  333. * Prepend checkbox column for grid.
  334. *
  335. * @return void
  336. */
  337. protected function prependRowSelectorColumn()
  338. {
  339. if (!$this->options['show_row_selector']) {
  340. return;
  341. }
  342. $circle = $this->options['row_selector_circle'] ? 'checkbox-circle' : '';
  343. $column = new Column(
  344. Column::SELECT_COLUMN_NAME,
  345. <<<HTML
  346. <div class="checkbox checkbox-{$this->options['row_selector_style']} $circle checkbox-grid">
  347. <input type="checkbox" class="select-all {$this->getSelectAllName()}"><label></label>
  348. </div>
  349. HTML
  350. );
  351. $column->setGrid($this);
  352. $column->displayUsing(Displayers\RowSelector::class);
  353. $this->columns->prepend($column, Column::SELECT_COLUMN_NAME);
  354. }
  355. /**
  356. * Apply column filter to grid query.
  357. */
  358. protected function applyColumnFilter()
  359. {
  360. $this->columns->each->bindFilterQuery($this->model());
  361. }
  362. /**
  363. * Build the grid.
  364. *
  365. * @return void
  366. */
  367. public function build()
  368. {
  369. if ($this->built) {
  370. return;
  371. }
  372. $this->applyQuickSearch();
  373. $this->applyColumnFilter();
  374. $collection = $this->processFilter(false);
  375. $data = $collection->toArray();
  376. $this->prependRowSelectorColumn();
  377. $this->appendActionsColumn();
  378. Column::setOriginalGridModels($collection);
  379. $this->columns->map(function (Column $column) use (&$data) {
  380. $column->fill($data);
  381. $this->columnNames[] = $column->getName();
  382. });
  383. $this->buildRows($data);
  384. $this->built = true;
  385. if ($data && $this->responsive) {
  386. $this->responsive->build();
  387. }
  388. $this->sortHeaders();
  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. $this->rows->map($value);
  405. }
  406. }
  407. }
  408. /**
  409. * Set grid row callback function.
  410. *
  411. * @param Closure $callable
  412. *
  413. * @return Collection|null
  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('%s/create%s',
  434. $this->getResource(),
  435. $queryString ? ('?'.$queryString) : ''
  436. );
  437. }
  438. /**
  439. * @param string $width
  440. * @param string $height
  441. * @return $this
  442. */
  443. public function setDialogFormDimensions(string $width, string $height)
  444. {
  445. $this->options['dialog_form_area'] = [$width, $height];
  446. return $this;
  447. }
  448. /**
  449. * Render create button for grid.
  450. *
  451. * @return string
  452. */
  453. public function renderCreateButton()
  454. {
  455. if (!$this->options['show_create_btn'] && !$this->options['show_quick_create_btn']) {
  456. return '';
  457. }
  458. return (new Tools\CreateButton($this))->render();
  459. }
  460. /**
  461. * @return $this
  462. */
  463. public function withBorder()
  464. {
  465. $this->options['show_bordered'] = true;
  466. return $this;
  467. }
  468. /**
  469. * Set grid header.
  470. *
  471. * @param Closure|string|Renderable $content
  472. *
  473. * @return $this|Closure
  474. */
  475. public function header($content = null)
  476. {
  477. if (!$content) {
  478. return $this->header;
  479. }
  480. $this->header = $content;
  481. return $this;
  482. }
  483. /**
  484. * Render grid header.
  485. *
  486. * @return string
  487. */
  488. public function renderHeader()
  489. {
  490. if (!$this->header) {
  491. return '';
  492. }
  493. $content = Helper::render($this->header, [$this->processFilter(false)]);
  494. if (empty($content)) {
  495. return '';
  496. }
  497. if ($content instanceof Renderable) {
  498. $content = $content->render();
  499. }
  500. if ($content instanceof Htmlable) {
  501. $content = $content->toHtml();
  502. }
  503. return <<<HTML
  504. <div class="box-header clearfix" style="border-top:1px solid #ebeff2">{$content}</div>
  505. HTML;
  506. }
  507. /**
  508. * Set grid footer.
  509. *
  510. * @param Closure|string|Renderable $content
  511. *
  512. * @return $this|Closure
  513. */
  514. public function footer($content = null)
  515. {
  516. if (!$content) {
  517. return $this->footer;
  518. }
  519. $this->footer = $content;
  520. return $this;
  521. }
  522. /**
  523. * Render grid footer.
  524. *
  525. * @return string
  526. */
  527. public function renderFooter()
  528. {
  529. if (!$this->footer) {
  530. return '';
  531. }
  532. $content = Helper::render($this->footer, [$this->processFilter(false)]);
  533. if (empty($content)) {
  534. return '';
  535. }
  536. if ($content instanceof Renderable) {
  537. $content = $content->render();
  538. }
  539. if ($content instanceof Htmlable) {
  540. $content = $content->toHtml();
  541. }
  542. return <<<HTML
  543. <div class="box-footer clearfix">{$content}</div>
  544. HTML;
  545. }
  546. /**
  547. * Get or set option for grid.
  548. *
  549. * @param string $key
  550. * @param mixed $value
  551. *
  552. * @return $this|mixed
  553. */
  554. public function option($key, $value = null)
  555. {
  556. if (is_null($value)) {
  557. return $this->options[$key];
  558. }
  559. $this->options[$key] = $value;
  560. return $this;
  561. }
  562. /**
  563. * Disable row selector.
  564. *
  565. * @return $this
  566. */
  567. public function disableRowSelector(bool $disable = true)
  568. {
  569. $this->tools->disableBatchActions($disable);
  570. return $this->option('show_row_selector', !$disable);
  571. }
  572. /**
  573. * Show row selector.
  574. *
  575. * @return $this
  576. */
  577. public function showRowSelector(bool $val = true)
  578. {
  579. return $this->disableRowSelector(!$val);
  580. }
  581. /**
  582. * Remove create button on grid.
  583. *
  584. * @return $this
  585. */
  586. public function disableCreateButton(bool $disable = true)
  587. {
  588. return $this->option('show_create_btn', !$disable);
  589. }
  590. /**
  591. * Show create button.
  592. *
  593. * @return $this
  594. */
  595. public function showCreateButton(bool $val = true)
  596. {
  597. return $this->disableCreateButton(!$val);
  598. }
  599. /**
  600. * @param bool $disable
  601. * @return $this
  602. */
  603. public function disableQuickCreateButton(bool $disable = true)
  604. {
  605. return $this->option('show_quick_create_btn', !$disable);
  606. }
  607. /**
  608. * @param bool $val
  609. * @return $this
  610. */
  611. public function showQuickCreateButton(bool $val = true)
  612. {
  613. return $this->disableQuickCreateButton(!$val);
  614. }
  615. /**
  616. * If allow creation.
  617. *
  618. * @return bool
  619. */
  620. public function allowCreateBtn()
  621. {
  622. return $this->options['show_create_btn'];
  623. }
  624. /**
  625. * If grid show quick create button.
  626. *
  627. * @return bool
  628. */
  629. public function allowQuickCreateBtn()
  630. {
  631. return $this->options['show_quick_create_btn'];
  632. }
  633. /**
  634. * Set resource path.
  635. *
  636. * @param string $path
  637. * @return $this
  638. */
  639. public function resource(string $path)
  640. {
  641. if (!empty($path)) {
  642. $this->resourcePath = admin_url($path);
  643. }
  644. return $this;
  645. }
  646. /**
  647. * Get resource path.
  648. *
  649. * @return string
  650. */
  651. public function getResource()
  652. {
  653. return $this->resourcePath ?: (
  654. $this->resourcePath = url(app('request')->getPathInfo())
  655. );
  656. }
  657. /**
  658. * Create a grid instance.
  659. *
  660. * @param mixed ...$params
  661. * @return $this
  662. */
  663. public static function make(...$params)
  664. {
  665. return new static(...$params);
  666. }
  667. /**
  668. * Enable responsive tables.
  669. * @see https://github.com/nadangergeo/RWD-Table-Patterns
  670. *
  671. * @return Responsive
  672. */
  673. public function responsive()
  674. {
  675. if (!$this->responsive) {
  676. $this->responsive = new Responsive($this);
  677. }
  678. return $this->responsive;
  679. }
  680. /**
  681. * @return bool
  682. */
  683. public function allowResponsive()
  684. {
  685. return $this->responsive ? true : false;
  686. }
  687. /**
  688. * @param Closure $closure
  689. * @return $this;
  690. */
  691. public function wrap(\Closure $closure)
  692. {
  693. $this->wrapper = $closure;
  694. return $this;
  695. }
  696. /**
  697. * @return bool
  698. */
  699. public function hasWrapper()
  700. {
  701. return $this->wrapper ? true : false;
  702. }
  703. /**
  704. * Add variables to grid view.
  705. *
  706. * @param array $variables
  707. *
  708. * @return $this
  709. */
  710. public function with($variables = [])
  711. {
  712. $this->variables = $variables;
  713. return $this;
  714. }
  715. /**
  716. * Get all variables will used in grid view.
  717. *
  718. * @return array
  719. */
  720. protected function variables()
  721. {
  722. $this->variables['grid'] = $this;
  723. $this->variables['tableId'] = $this->tableId;
  724. return $this->variables;
  725. }
  726. /**
  727. * Set a view to render.
  728. *
  729. * @param string $view
  730. * @param array $variables
  731. */
  732. public function setView($view, $variables = [])
  733. {
  734. if (!empty($variables)) {
  735. $this->with($variables);
  736. }
  737. $this->view = $view;
  738. }
  739. /**
  740. * Set grid title.
  741. *
  742. * @param string $title
  743. *
  744. * @return $this
  745. */
  746. public function setTitle($title)
  747. {
  748. $this->variables['title'] = $title;
  749. return $this;
  750. }
  751. /**
  752. * Set grid description.
  753. *
  754. * @param string $description
  755. *
  756. * @return $this
  757. */
  758. public function setDescription($description)
  759. {
  760. $this->variables['description'] = $description;
  761. return $this;
  762. }
  763. /**
  764. * Set resource path for grid.
  765. *
  766. * @param string $path
  767. *
  768. * @return $this
  769. */
  770. public function setResource($path)
  771. {
  772. $this->resourcePath = $path;
  773. return $this;
  774. }
  775. /**
  776. * Get the string contents of the grid view.
  777. *
  778. * @return string
  779. */
  780. public function render()
  781. {
  782. $this->handleExportRequest(true);
  783. try {
  784. $this->callComposing();
  785. $this->build();
  786. } catch (\Throwable $e) {
  787. return Admin::makeExceptionHandler()->renderException($e);
  788. }
  789. return $this->doWrap();
  790. }
  791. /**
  792. * @return string
  793. */
  794. protected function doWrap()
  795. {
  796. $view = view($this->view, $this->variables());
  797. if (!$wrapper = $this->wrapper) {
  798. return "<div class='box box-default'>{$view->render()}</div>";
  799. }
  800. return $wrapper($view);
  801. }
  802. /**
  803. * Add column to grid.
  804. *
  805. * @param string $name
  806. * @return Column|Collection
  807. */
  808. public function __get($name)
  809. {
  810. return $this->addColumn($name);
  811. }
  812. /**
  813. * Dynamically add columns to the grid view.
  814. *
  815. * @param $method
  816. * @param $arguments
  817. *
  818. * @return Column
  819. */
  820. public function __call($method, $arguments)
  821. {
  822. if (static::hasMacro($method)) {
  823. return $this->macroCall($method, $arguments);
  824. }
  825. return $this->addColumn($method, $arguments[0] ?? null);
  826. }
  827. }