Column.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. <?php
  2. namespace Dcat\Admin\Grid;
  3. use Closure;
  4. use Dcat\Admin\Grid;
  5. use Dcat\Admin\Grid\Concerns;
  6. use Dcat\Admin\Grid\Displayers\AbstractDisplayer;
  7. use Dcat\Admin\Traits\BuilderEvents;
  8. use Dcat\Admin\Traits\Definitions;
  9. use Illuminate\Contracts\Support\Arrayable;
  10. use Illuminate\Database\Eloquent\Model;
  11. use Illuminate\Support\Arr;
  12. use Illuminate\Support\Collection;
  13. use Illuminate\Support\Fluent;
  14. use Illuminate\Support\Str;
  15. /**
  16. * @method $this switch(string $color = '')
  17. * @method $this switchGroup($columns = [], string $color = '')
  18. * @method $this editable($method = null, array $options = []);
  19. * @method $this image($server = '', int $width = 200, int $height = 200);
  20. * @method $this label($style = 'success', int $max = null);
  21. * @method $this button($style = 'success');
  22. * @method $this link($href = '', $target = '_blank');
  23. * @method $this badge($style = 'red');
  24. * @method $this progressBar($style = 'primary', $size = 'sm', $max = 100)
  25. * @method $this checkbox($options = [])
  26. * @method $this radio($options = [])
  27. * @method $this expand($callbackOrButton = null)
  28. * @method $this table($titles = [])
  29. * @method $this select($options = [])
  30. * @method $this modal($title = '', \Closure $callback = null)
  31. * @method $this tree($callbackOrNodes = null)
  32. * @method $this qrcode($formatter = null, $width = 150, $height = 150)
  33. * @method $this downloadable()
  34. * @method $this copyable()
  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 BuilderEvents,
  53. Definitions,
  54. Concerns\Displayers;
  55. const SELECT_COLUMN_NAME = '__row_selector__';
  56. /**
  57. * Displayers for grid column.
  58. *
  59. * @var array
  60. */
  61. protected static $displayers = [
  62. 'editable' => Displayers\Editable::class,
  63. 'switch' => Displayers\SwitchDisplay::class,
  64. 'switchGroup' => Displayers\SwitchGroup::class,
  65. 'select' => Displayers\Select::class,
  66. 'image' => Displayers\Image::class,
  67. 'label' => Displayers\Label::class,
  68. 'button' => Displayers\Button::class,
  69. 'link' => Displayers\Link::class,
  70. 'badge' => Displayers\Badge::class,
  71. 'progressBar' => Displayers\ProgressBar::class,
  72. 'radio' => Displayers\Radio::class,
  73. 'checkbox' => Displayers\Checkbox::class,
  74. 'table' => Displayers\Table::class,
  75. 'expand' => Displayers\Expand::class,
  76. 'modal' => Displayers\Modal::class,
  77. 'tree' => Displayers\Tree::class,
  78. 'qrcode' => Displayers\QRCode::class,
  79. 'downloadable' => Displayers\Downloadable::class,
  80. 'copyable' => Displayers\Copyable::class,
  81. ];
  82. /**
  83. * Original grid data.
  84. *
  85. * @var Collection
  86. */
  87. protected static $originalGridModels;
  88. /**
  89. * @var Grid
  90. */
  91. protected $grid;
  92. /**
  93. * Name of column.
  94. *
  95. * @var string
  96. */
  97. protected $name;
  98. /**
  99. * @var array
  100. */
  101. protected $htmlAttributes = [];
  102. /**
  103. * Label of column.
  104. *
  105. * @var string
  106. */
  107. protected $label;
  108. /**
  109. * Original value of column.
  110. *
  111. * @var mixed
  112. */
  113. protected $original;
  114. /**
  115. * @var mixed
  116. */
  117. protected $value;
  118. /**
  119. * Is column sortable.
  120. *
  121. * @var bool
  122. */
  123. protected $sortable = false;
  124. /**
  125. * Sort arguments.
  126. *
  127. * @var array
  128. */
  129. protected $sort;
  130. /**
  131. * @var string
  132. */
  133. protected $width;
  134. /**
  135. * Attributes of column.
  136. *
  137. * @var array
  138. */
  139. protected $attributes = [];
  140. /**
  141. * @var []Closure
  142. */
  143. protected $displayCallbacks = [];
  144. /**
  145. * @var array
  146. */
  147. protected $titleHtmlAttributes = [];
  148. /**
  149. * @var Model
  150. */
  151. protected static $model;
  152. /**
  153. * @param string $name
  154. * @param string $label
  155. */
  156. public function __construct($name, $label)
  157. {
  158. $this->name = $name;
  159. $this->label = $this->formatLabel($label);
  160. $this->callResolving();
  161. }
  162. /**
  163. * Extend column displayer.
  164. *
  165. * @param $name
  166. * @param $displayer
  167. */
  168. public static function extend($name, $displayer)
  169. {
  170. static::$displayers[$name] = $displayer;
  171. }
  172. /**
  173. * @return array
  174. */
  175. public static function getExtensions()
  176. {
  177. return static::$displayers;
  178. }
  179. /**
  180. * Set grid instance for column.
  181. *
  182. * @param Grid $grid
  183. */
  184. public function setGrid(Grid $grid)
  185. {
  186. $this->grid = $grid;
  187. }
  188. /**
  189. * Set original data for column.
  190. *
  191. * @param Collection $collection
  192. */
  193. public static function setOriginalGridModels(Collection $collection)
  194. {
  195. static::$originalGridModels = $collection;
  196. }
  197. /**
  198. * Set width for column.
  199. *
  200. * @param string $width
  201. * @return $this|string
  202. */
  203. public function width(?string $width)
  204. {
  205. $this->titleHtmlAttributes['width'] = $width;
  206. return $this;
  207. }
  208. /**
  209. * Set column attributes.
  210. *
  211. * @param array $attributes
  212. *
  213. * @return $this
  214. */
  215. public function setAttributes(array $attributes = [])
  216. {
  217. $this->htmlAttributes = array_merge($this->htmlAttributes, $attributes);
  218. return $this;
  219. }
  220. /**
  221. * Get column attributes.
  222. *
  223. * @param string $name
  224. *
  225. * @return mixed
  226. */
  227. public function getAttributes()
  228. {
  229. return $this->htmlAttributes;
  230. }
  231. /**
  232. *
  233. * @return $this
  234. */
  235. public function hide()
  236. {
  237. return $this->responsive(0);
  238. }
  239. /**
  240. * responsive
  241. *
  242. * data-priority=”1″ 保持可见,但可以在下拉列表筛选隐藏。
  243. * data-priority=”2″ 480px 分辨率以下可见
  244. * data-priority=”3″ 640px 以下可见
  245. * data-priority=”4″ 800px 以下可见
  246. * data-priority=”5″ 960px 以下可见
  247. * data-priority=”6″ 1120px 以下可见
  248. *
  249. * @param int $priority
  250. * @return $this
  251. */
  252. public function responsive(?int $priority = 1)
  253. {
  254. $this->grid->responsive();
  255. return $this->setHeaderAttributes(['data-priority' => $priority]);
  256. }
  257. /**
  258. * @return int|null
  259. */
  260. public function getDataPriority()
  261. {
  262. return isset($this->titleHtmlAttributes['data-priority']) ? $this->titleHtmlAttributes['data-priority'] : null;
  263. }
  264. /**
  265. * Set style of this column.
  266. *
  267. * @param string $style
  268. *
  269. * @return Column
  270. */
  271. public function style($style)
  272. {
  273. return $this->setAttributes(compact('style'));
  274. }
  275. /**
  276. * Get name of this column.
  277. *
  278. * @return mixed
  279. */
  280. public function getName()
  281. {
  282. return $this->name;
  283. }
  284. /**
  285. * @return mixed
  286. */
  287. public function getOriginal()
  288. {
  289. return $this->original;
  290. }
  291. /**
  292. * @return mixed
  293. */
  294. public function getValue()
  295. {
  296. return $this->value;
  297. }
  298. /**
  299. * Format label.
  300. *
  301. * @param $label
  302. *
  303. * @return mixed
  304. */
  305. protected function formatLabel($label)
  306. {
  307. if (!$label) $label = admin_trans_field($this->name);
  308. $label = $label ?: ucfirst($this->name);
  309. return str_replace(['.', '_'], ' ', $label);
  310. }
  311. /**
  312. * Get label of the column.
  313. *
  314. * @return mixed
  315. */
  316. public function getLabel()
  317. {
  318. return $this->label;
  319. }
  320. public function setLabel($label)
  321. {
  322. $this->label = $label;
  323. return $this;
  324. }
  325. /**
  326. * Set sort value.
  327. *
  328. * @param bool $sort
  329. *
  330. * @return Column
  331. */
  332. public function sort(bool $sort)
  333. {
  334. $this->sortable = $sort;
  335. return $this;
  336. }
  337. /**
  338. * Mark this column as sortable.
  339. *
  340. * @return $this
  341. */
  342. public function sortable()
  343. {
  344. return $this->sort(true);
  345. }
  346. /**
  347. * Add a display callback.
  348. *
  349. * @param $callback
  350. * @param array $params
  351. * @return $this
  352. */
  353. public function display($callback, ...$params)
  354. {
  355. $this->displayCallbacks[] = [&$callback, &$params];
  356. return $this;
  357. }
  358. /**
  359. * If has display callbacks.
  360. *
  361. * @return bool
  362. */
  363. protected function hasDisplayCallbacks()
  364. {
  365. return !empty($this->displayCallbacks);
  366. }
  367. /**
  368. * Call all of the "display" callbacks column.
  369. *
  370. * @param mixed $value
  371. * @param int $key
  372. *
  373. * @return mixed
  374. */
  375. protected function callDisplayCallbacks($value, $key)
  376. {
  377. foreach ($this->displayCallbacks as $callback) {
  378. list($callback, $params) = $callback;
  379. if (!$callback instanceof \Closure) {
  380. $value = $callback;
  381. continue;
  382. }
  383. $previous = $value;
  384. $callback = $this->bindOriginalRowModel($callback, $key);
  385. $value = $callback($value, $this, ...$params);
  386. if (($value instanceof static) &&
  387. ($last = array_pop($this->displayCallbacks))
  388. ) {
  389. list($last, $params) = $last;
  390. $last = $this->bindOriginalRowModel($last, $key);
  391. $value = call_user_func($last, $previous, $this, ...$params);
  392. }
  393. }
  394. return $value;
  395. }
  396. /**
  397. * Set original grid data to column.
  398. *
  399. * @param Closure $callback
  400. * @param int $key
  401. *
  402. * @return Closure
  403. */
  404. protected function bindOriginalRowModel(Closure $callback, $key)
  405. {
  406. $rowModel = static::$originalGridModels[$key];
  407. if (is_array($rowModel)) {
  408. $rowModel = new Fluent($rowModel);
  409. }
  410. return $callback->bindTo($rowModel);
  411. }
  412. /**
  413. * Fill all data to every column.
  414. *
  415. * @param array $data
  416. */
  417. public function fill(array &$data)
  418. {
  419. if (static::hasDefinition($this->name)) {
  420. $this->useDefinedColumn();
  421. }
  422. $i = 0;
  423. foreach ($data as $key => &$row) {
  424. $i++;
  425. if (!isset($row['#'])) {
  426. $row['#'] = $i;
  427. }
  428. $this->original = $value = Arr::get($row, $this->name);
  429. $this->value = $value = $this->htmlEntityEncode($value);
  430. Arr::set($row, $this->name, $value);
  431. if ($this->hasDisplayCallbacks()) {
  432. $value = $this->callDisplayCallbacks($this->original, $key);
  433. Arr::set($row, $this->name, $value);
  434. }
  435. }
  436. $this->value = $value ?? null;
  437. }
  438. /**
  439. * Use a defined column.
  440. *
  441. * @throws \Exception
  442. */
  443. protected function useDefinedColumn()
  444. {
  445. $class = static::$definitions[$this->name];
  446. if ($class instanceof Closure) {
  447. $this->display($class);
  448. return;
  449. }
  450. if (!class_exists($class) || !is_subclass_of($class, AbstractDisplayer::class)) {
  451. throw new \Exception("Invalid column definition [$class]");
  452. }
  453. $this->displayUsing($class);
  454. }
  455. /**
  456. * Convert characters to HTML entities recursively.
  457. *
  458. * @param array|string $item
  459. *
  460. * @return mixed
  461. */
  462. protected function htmlEntityEncode($item)
  463. {
  464. if (is_array($item)) {
  465. array_walk_recursive($item, function (&$value) {
  466. $value = htmlentities($value);
  467. });
  468. } else {
  469. $item = htmlentities($item);
  470. }
  471. return $item;
  472. }
  473. /**
  474. * Create the column sorter.
  475. *
  476. * @return string
  477. */
  478. public function sorter()
  479. {
  480. if (!$this->sortable) {
  481. return '';
  482. }
  483. $icon = '';
  484. $color = 'text-70';
  485. $type = 'desc';
  486. if ($this->isSorted()) {
  487. $type = $this->sort['type'] == 'desc' ? 'asc' : 'desc';
  488. if ($this->sort['type']) {
  489. $icon .= $this->sort['type'] == 'desc' ? '-by-attributes-alt' : '-by-attributes';
  490. $color = 'text-80';
  491. }
  492. }
  493. $url = request()->fullUrlWithQuery([
  494. $this->grid->model()->getSortName() => ['column' => $this->name, 'type' => $type]
  495. ]);
  496. return " <a class=' glyphicon glyphicon-sort{$icon} $color' href='$url'></a>";
  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. }