Grid.php 19 KB

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