Grid.php 20 KB

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