Grid.php 19 KB

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