Column.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. <?php
  2. namespace Dcat\Admin\Grid;
  3. use Closure;
  4. use Dcat\Admin\Grid;
  5. use Dcat\Admin\Grid\Displayers\AbstractDisplayer;
  6. use Dcat\Admin\Traits\HasBuilderEvents;
  7. use Dcat\Admin\Traits\HasDefinitions;
  8. use Illuminate\Contracts\Support\Arrayable;
  9. use Illuminate\Database\Eloquent\Model;
  10. use Illuminate\Support\Arr;
  11. use Illuminate\Support\Collection;
  12. use Illuminate\Support\Fluent;
  13. use Illuminate\Support\Str;
  14. /**
  15. * @method $this switch(string $color = '')
  16. * @method $this switchGroup($columns = [], string $color = '')
  17. * @method $this editable($method = null, array $options = []);
  18. * @method $this image($server = '', int $width = 200, int $height = 200);
  19. * @method $this label($style = 'success', int $max = null);
  20. * @method $this button($style = 'success');
  21. * @method $this link($href = '', $target = '_blank');
  22. * @method $this badge($style = 'red');
  23. * @method $this progressBar($style = 'primary', $size = 'sm', $max = 100)
  24. * @method $this checkbox($options = [])
  25. * @method $this radio($options = [])
  26. * @method $this expand($callbackOrButton = null)
  27. * @method $this table($titles = [])
  28. * @method $this select($options = [])
  29. * @method $this modal($title = '', \Closure $callback = null)
  30. * @method $this tree($callbackOrNodes = null)
  31. * @method $this qrcode($formatter = null, $width = 150, $height = 150)
  32. * @method $this downloadable($server = '', $disk = null)
  33. * @method $this copyable()
  34. * @method $this orderable()
  35. *
  36. * @method $this limit($limit = 100, $end = '...')
  37. * @method $this ascii()
  38. * @method $this camel()
  39. * @method $this finish($cap)
  40. * @method $this lower()
  41. * @method $this words($words = 100, $end = '...')
  42. * @method $this upper()
  43. * @method $this title()
  44. * @method $this slug($separator = '-')
  45. * @method $this snake($delimiter = '_')
  46. * @method $this studly()
  47. * @method $this substr($start, $length = null)
  48. * @method $this ucfirst()
  49. */
  50. class Column
  51. {
  52. use HasBuilderEvents,
  53. HasDefinitions,
  54. Grid\Column\HasHeader,
  55. Grid\Column\HasDisplayers;
  56. const SELECT_COLUMN_NAME = '__row_selector__';
  57. /**
  58. * Displayers for grid column.
  59. *
  60. * @var array
  61. */
  62. protected static $displayers = [
  63. 'editable' => Displayers\Editable::class,
  64. 'switch' => Displayers\SwitchDisplay::class,
  65. 'switchGroup' => Displayers\SwitchGroup::class,
  66. 'select' => Displayers\Select::class,
  67. 'image' => Displayers\Image::class,
  68. 'label' => Displayers\Label::class,
  69. 'button' => Displayers\Button::class,
  70. 'link' => Displayers\Link::class,
  71. 'badge' => Displayers\Badge::class,
  72. 'progressBar' => Displayers\ProgressBar::class,
  73. 'radio' => Displayers\Radio::class,
  74. 'checkbox' => Displayers\Checkbox::class,
  75. 'table' => Displayers\Table::class,
  76. 'expand' => Displayers\Expand::class,
  77. 'modal' => Displayers\Modal::class,
  78. 'tree' => Displayers\Tree::class,
  79. 'qrcode' => Displayers\QRCode::class,
  80. 'downloadable' => Displayers\Downloadable::class,
  81. 'copyable' => Displayers\Copyable::class,
  82. 'orderable' => Displayers\Orderable::class,
  83. ];
  84. /**
  85. * Original grid data.
  86. *
  87. * @var Collection
  88. */
  89. protected static $originalGridModels;
  90. /**
  91. * @var Grid
  92. */
  93. protected $grid;
  94. /**
  95. * Name of column.
  96. *
  97. * @var string
  98. */
  99. protected $name;
  100. /**
  101. * @var array
  102. */
  103. protected $htmlAttributes = [];
  104. /**
  105. * Label of column.
  106. *
  107. * @var string
  108. */
  109. protected $label;
  110. /**
  111. * Original value of column.
  112. *
  113. * @var mixed
  114. */
  115. protected $original;
  116. /**
  117. * @var mixed
  118. */
  119. protected $value;
  120. /**
  121. * Sort arguments.
  122. *
  123. * @var array
  124. */
  125. protected $sort;
  126. /**
  127. * @var string
  128. */
  129. protected $width;
  130. /**
  131. * Attributes of column.
  132. *
  133. * @var array
  134. */
  135. protected $attributes = [];
  136. /**
  137. * @var []Closure
  138. */
  139. protected $displayCallbacks = [];
  140. /**
  141. * @var array
  142. */
  143. protected $titleHtmlAttributes = [];
  144. /**
  145. * @var Model
  146. */
  147. protected static $model;
  148. /**
  149. * @param string $name
  150. * @param string $label
  151. */
  152. public function __construct($name, $label)
  153. {
  154. $this->name = $name;
  155. $this->label = $this->formatLabel($label);
  156. $this->callResolving();
  157. }
  158. /**
  159. * Extend column displayer.
  160. *
  161. * @param $name
  162. * @param $displayer
  163. */
  164. public static function extend($name, $displayer)
  165. {
  166. static::$displayers[$name] = $displayer;
  167. }
  168. /**
  169. * @return array
  170. */
  171. public static function getExtensions()
  172. {
  173. return static::$displayers;
  174. }
  175. /**
  176. * Set grid instance for column.
  177. *
  178. * @param Grid $grid
  179. */
  180. public function setGrid(Grid $grid)
  181. {
  182. $this->grid = $grid;
  183. }
  184. /**
  185. * @return Grid
  186. */
  187. public function grid()
  188. {
  189. return $this->grid;
  190. }
  191. /**
  192. * Set original data for column.
  193. *
  194. * @param Collection $collection
  195. */
  196. public static function setOriginalGridModels(Collection $collection)
  197. {
  198. static::$originalGridModels = $collection;
  199. }
  200. /**
  201. * Set width for column.
  202. *
  203. * @param string $width
  204. * @return $this|string
  205. */
  206. public function width(?string $width)
  207. {
  208. $this->titleHtmlAttributes['width'] = $width;
  209. return $this;
  210. }
  211. /**
  212. * Set column attributes.
  213. *
  214. * @param array $attributes
  215. *
  216. * @return $this
  217. */
  218. public function setAttributes(array $attributes = [])
  219. {
  220. $this->htmlAttributes = array_merge($this->htmlAttributes, $attributes);
  221. return $this;
  222. }
  223. /**
  224. * Get column attributes.
  225. *
  226. * @param string $name
  227. *
  228. * @return mixed
  229. */
  230. public function getAttributes()
  231. {
  232. return $this->htmlAttributes;
  233. }
  234. /**
  235. *
  236. * @return $this
  237. */
  238. public function hide()
  239. {
  240. return $this->responsive(0);
  241. }
  242. /**
  243. * responsive
  244. *
  245. * data-priority=”1″ 保持可见,但可以在下拉列表筛选隐藏。
  246. * data-priority=”2″ 480px 分辨率以下可见
  247. * data-priority=”3″ 640px 以下可见
  248. * data-priority=”4″ 800px 以下可见
  249. * data-priority=”5″ 960px 以下可见
  250. * data-priority=”6″ 1120px 以下可见
  251. *
  252. * @param int $priority
  253. * @return $this
  254. */
  255. public function responsive(?int $priority = 1)
  256. {
  257. $this->grid->responsive();
  258. return $this->setHeaderAttributes(['data-priority' => $priority]);
  259. }
  260. /**
  261. * @return int|null
  262. */
  263. public function getDataPriority()
  264. {
  265. return isset($this->titleHtmlAttributes['data-priority']) ? $this->titleHtmlAttributes['data-priority'] : null;
  266. }
  267. /**
  268. * Set style of this column.
  269. *
  270. * @param string $style
  271. *
  272. * @return Column
  273. */
  274. public function style($style)
  275. {
  276. return $this->setAttributes(compact('style'));
  277. }
  278. /**
  279. * Get name of this column.
  280. *
  281. * @return mixed
  282. */
  283. public function getName()
  284. {
  285. return $this->name;
  286. }
  287. /**
  288. * @return mixed
  289. */
  290. public function getOriginal()
  291. {
  292. return $this->original;
  293. }
  294. /**
  295. * @return mixed
  296. */
  297. public function getValue()
  298. {
  299. return $this->value;
  300. }
  301. /**
  302. * Format label.
  303. *
  304. * @param $label
  305. *
  306. * @return mixed
  307. */
  308. protected function formatLabel($label)
  309. {
  310. if (!$label) $label = admin_trans_field($this->name);
  311. $label = $label ?: ucfirst($this->name);
  312. return str_replace(['.', '_'], ' ', $label);
  313. }
  314. /**
  315. * Get label of the column.
  316. *
  317. * @return mixed
  318. */
  319. public function getLabel()
  320. {
  321. return $this->label;
  322. }
  323. public function setLabel($label)
  324. {
  325. $this->label = $label;
  326. return $this;
  327. }
  328. /**
  329. * Mark this column as sortable.
  330. *
  331. * @param string $cast
  332. * @return Column|string
  333. */
  334. public function sortable($cast = null)
  335. {
  336. return $this->addSorter($cast);
  337. }
  338. /**
  339. * Set help message for column.
  340. *
  341. * @param string|\Closure $help
  342. * @param null|string $style 'green', 'blue', 'red', 'purple'
  343. * @param null|string $placement 'bottom', 'left', 'right', 'top'
  344. *
  345. * @return $this
  346. */
  347. public function help($help = '', ?string $style = null, ?string $placement = 'bottom')
  348. {
  349. return $this->addHelp($help, $style, $placement);
  350. }
  351. /**
  352. * Set column filter.
  353. *
  354. * @example
  355. * $grid->username()->filter(
  356. * Grid\Column\Filter\StartWith::make(__('admin.username'))
  357. * );
  358. *
  359. * $grid->created_at()->filter(
  360. * Grid\Column\Filter\Equal::make(__('admin.created_at'))->date()
  361. * );
  362. *
  363. * @param Grid\Column\Filter $builder
  364. *
  365. * @return $this
  366. */
  367. public function filter(Grid\Column\Filter $filter)
  368. {
  369. return $this->addFilter($filter);
  370. }
  371. /**
  372. * Add a display callback.
  373. *
  374. * @param $callback
  375. * @param array $params
  376. * @return $this
  377. */
  378. public function display($callback, ...$params)
  379. {
  380. $this->displayCallbacks[] = [&$callback, &$params];
  381. return $this;
  382. }
  383. /**
  384. * If has display callbacks.
  385. *
  386. * @return bool
  387. */
  388. protected function hasDisplayCallbacks()
  389. {
  390. return !empty($this->displayCallbacks);
  391. }
  392. /**
  393. * Call all of the "display" callbacks column.
  394. *
  395. * @param mixed $value
  396. * @param int $key
  397. *
  398. * @return mixed
  399. */
  400. protected function callDisplayCallbacks($value, $key)
  401. {
  402. foreach ($this->displayCallbacks as $callback) {
  403. list($callback, $params) = $callback;
  404. if (!$callback instanceof \Closure) {
  405. $value = $callback;
  406. continue;
  407. }
  408. $previous = $value;
  409. $callback = $this->bindOriginalRowModel($callback, $key);
  410. $value = $callback($value, $this, ...$params);
  411. if (($value instanceof static) &&
  412. ($last = array_pop($this->displayCallbacks))
  413. ) {
  414. list($last, $params) = $last;
  415. $last = $this->bindOriginalRowModel($last, $key);
  416. $value = call_user_func($last, $previous, $this, ...$params);
  417. }
  418. }
  419. return $value;
  420. }
  421. /**
  422. * Set original grid data to column.
  423. *
  424. * @param Closure $callback
  425. * @param int $key
  426. *
  427. * @return Closure
  428. */
  429. protected function bindOriginalRowModel(Closure $callback, $key)
  430. {
  431. $rowModel = static::$originalGridModels[$key];
  432. if (is_array($rowModel)) {
  433. $rowModel = new Fluent($rowModel);
  434. }
  435. return $callback->bindTo($rowModel);
  436. }
  437. /**
  438. * Fill all data to every column.
  439. *
  440. * @param array $data
  441. */
  442. public function fill(array &$data)
  443. {
  444. if (static::hasDefinition($this->name)) {
  445. $this->useDefinedColumn();
  446. }
  447. $i = 0;
  448. foreach ($data as $key => &$row) {
  449. $i++;
  450. if (!isset($row['#'])) {
  451. $row['#'] = $i;
  452. }
  453. $this->original = $value = Arr::get($row, $this->name);
  454. $this->value = $value = $this->htmlEntityEncode($value);
  455. Arr::set($row, $this->name, $value);
  456. if ($this->hasDisplayCallbacks()) {
  457. $value = $this->callDisplayCallbacks($this->original, $key);
  458. Arr::set($row, $this->name, $value);
  459. }
  460. }
  461. $this->value = $value ?? null;
  462. }
  463. /**
  464. * Use a defined column.
  465. *
  466. * @throws \Exception
  467. */
  468. protected function useDefinedColumn()
  469. {
  470. $class = static::$definitions[$this->name];
  471. if ($class instanceof Closure) {
  472. $this->display($class);
  473. return;
  474. }
  475. if (!class_exists($class) || !is_subclass_of($class, AbstractDisplayer::class)) {
  476. throw new \Exception("Invalid column definition [$class]");
  477. }
  478. $this->displayUsing($class);
  479. }
  480. /**
  481. * Convert characters to HTML entities recursively.
  482. *
  483. * @param array|string $item
  484. *
  485. * @return mixed
  486. */
  487. protected function htmlEntityEncode($item)
  488. {
  489. if (is_array($item)) {
  490. array_walk_recursive($item, function (&$value) {
  491. $value = htmlentities($value);
  492. });
  493. } else {
  494. $item = htmlentities($item);
  495. }
  496. return $item;
  497. }
  498. /**
  499. * Determine if this column is currently sorted.
  500. *
  501. * @return bool
  502. */
  503. protected function isSorted()
  504. {
  505. $this->sort = app('request')->get($this->grid->model()->getSortName());
  506. if (empty($this->sort)) {
  507. return false;
  508. }
  509. return isset($this->sort['column']) && $this->sort['column'] == $this->name;
  510. }
  511. /**
  512. * Find a displayer to display column.
  513. *
  514. * @param string $abstract
  515. * @param array $arguments
  516. *
  517. * @return Column
  518. */
  519. protected function resolveDisplayer($abstract, $arguments)
  520. {
  521. if (isset(static::$displayers[$abstract])) {
  522. return $this->callBuiltinDisplayer(static::$displayers[$abstract], $arguments);
  523. }
  524. return $this->callSupportDisplayer($abstract, $arguments);
  525. }
  526. /**
  527. * Call Illuminate/Support displayer.
  528. *
  529. * @param string $abstract
  530. * @param array $arguments
  531. *
  532. * @return Column
  533. */
  534. protected function callSupportDisplayer($abstract, $arguments)
  535. {
  536. return $this->display(function ($value) use ($abstract, $arguments) {
  537. if (is_array($value) || $value instanceof Arrayable) {
  538. return call_user_func_array([collect($value), $abstract], $arguments);
  539. }
  540. if (is_string($value)) {
  541. return call_user_func_array([Str::class, $abstract], array_merge([$value], $arguments));
  542. }
  543. return $value;
  544. });
  545. }
  546. /**
  547. * Call Builtin displayer.
  548. *
  549. * @param string $abstract
  550. * @param array $arguments
  551. *
  552. * @return Column
  553. */
  554. protected function callBuiltinDisplayer($abstract, $arguments)
  555. {
  556. if ($abstract instanceof Closure) {
  557. return $this->display(function ($value) use ($abstract, $arguments) {
  558. return $abstract->call($this, ...array_merge([$value], $arguments));
  559. });
  560. }
  561. if (is_subclass_of($abstract, AbstractDisplayer::class)) {
  562. $grid = $this->grid;
  563. $column = $this;
  564. return $this->display(function ($value) use ($abstract, $grid, $column, $arguments) {
  565. /** @var AbstractDisplayer $displayer */
  566. $displayer = new $abstract($value, $grid, $column, $this);
  567. return $displayer->display(...$arguments);
  568. });
  569. }
  570. return $this;
  571. }
  572. /**
  573. * Set column title attributes.
  574. *
  575. * @param array $attributes
  576. * @return $this
  577. */
  578. public function setHeaderAttributes(array $attributes = [])
  579. {
  580. $this->titleHtmlAttributes = array_merge($this->titleHtmlAttributes, $attributes);
  581. return $this;
  582. }
  583. /**
  584. * Set column title default attributes.
  585. *
  586. * @param array $attributes
  587. * @return $this
  588. */
  589. public function setDefaultHeaderAttribute(array $attributes)
  590. {
  591. foreach ($attributes as $key => $value) {
  592. if (isset($this->titleHtmlAttributes[$key])) {
  593. continue;
  594. }
  595. $this->titleHtmlAttributes[$key] = $value;
  596. }
  597. return $this;
  598. }
  599. /**
  600. * @return string
  601. */
  602. public function formatTitleAttributes()
  603. {
  604. $attrArr = [];
  605. foreach ($this->titleHtmlAttributes as $name => $val) {
  606. $attrArr[] = "$name=\"$val\"";
  607. }
  608. return implode(' ', $attrArr);
  609. }
  610. /**
  611. * Passes through all unknown calls to builtin displayer or supported displayer.
  612. *
  613. * Allow fluent calls on the Column object.
  614. *
  615. * @param string $method
  616. * @param array $arguments
  617. *
  618. * @return $this
  619. */
  620. public function __call($method, $arguments)
  621. {
  622. return $this->resolveDisplayer($method, $arguments);
  623. }
  624. }