Column.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  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. * @method $this ascii()
  36. * @method $this camel()
  37. * @method $this finish($cap)
  38. * @method $this lower()
  39. * @method $this words($words = 100, $end = '...')
  40. * @method $this upper()
  41. * @method $this title()
  42. * @method $this slug($separator = '-')
  43. * @method $this snake($delimiter = '_')
  44. * @method $this studly()
  45. * @method $this substr($start, $length = null)
  46. * @method $this ucfirst()
  47. *
  48. * @mixin Collection
  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. * @var Fluent
  112. */
  113. protected $originalModel;
  114. /**
  115. * Original value of column.
  116. *
  117. * @var mixed
  118. */
  119. protected $original;
  120. /**
  121. * @var mixed
  122. */
  123. protected $value;
  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. * @var Grid\Column\Condition
  154. */
  155. protected $conditions = [];
  156. /**
  157. * @param string $name
  158. * @param string $label
  159. */
  160. public function __construct($name, $label)
  161. {
  162. $this->name = $name;
  163. $this->label = $this->formatLabel($label);
  164. $this->callResolving();
  165. }
  166. /**
  167. * Extend column displayer.
  168. *
  169. * @param $name
  170. * @param $displayer
  171. */
  172. public static function extend($name, $displayer)
  173. {
  174. static::$displayers[$name] = $displayer;
  175. }
  176. /**
  177. * @return array
  178. */
  179. public static function getExtensions()
  180. {
  181. return static::$displayers;
  182. }
  183. /**
  184. * Set grid instance for column.
  185. *
  186. * @param Grid $grid
  187. */
  188. public function setGrid(Grid $grid)
  189. {
  190. $this->grid = $grid;
  191. }
  192. /**
  193. * @return Grid
  194. */
  195. public function grid()
  196. {
  197. return $this->grid;
  198. }
  199. /**
  200. * Set original data for column.
  201. *
  202. * @param Collection $collection
  203. */
  204. public static function setOriginalGridModels(Collection $collection)
  205. {
  206. static::$originalGridModels = $collection;
  207. }
  208. /**
  209. * Set width for column.
  210. *
  211. * @param string $width
  212. *
  213. * @return $this|string
  214. */
  215. public function width(?string $width)
  216. {
  217. $this->titleHtmlAttributes['width'] = $width;
  218. return $this;
  219. }
  220. /**
  221. * @example
  222. * $grid->config
  223. * ->if(function () {
  224. * return $this->config ? true : false;
  225. * })
  226. * ->display($view)
  227. * ->expand(...)
  228. * ->else()
  229. * ->showEmpty()
  230. *
  231. * $grid->config
  232. * ->if(function () {
  233. * return $this->config ? true : false;
  234. * })
  235. * ->then(function (Column $column) {
  236. * $column ->display($view)->expand(...);
  237. * })
  238. * ->else(function (Column $column) {
  239. * $column->showEmpty();
  240. * })
  241. *
  242. * @param \Closure $condition
  243. *
  244. * @return Column\Condition
  245. */
  246. public function if(\Closure $condition)
  247. {
  248. return $this->conditions[] = new Grid\Column\Condition($condition, $this);
  249. }
  250. /**
  251. * Set column attributes.
  252. *
  253. * @param array $attributes
  254. *
  255. * @return $this
  256. */
  257. public function setAttributes(array $attributes = [])
  258. {
  259. $this->htmlAttributes = array_merge($this->htmlAttributes, $attributes);
  260. return $this;
  261. }
  262. /**
  263. * Get column attributes.
  264. *
  265. * @param string $name
  266. *
  267. * @return mixed
  268. */
  269. public function getAttributes()
  270. {
  271. return $this->htmlAttributes;
  272. }
  273. /**
  274. * @return $this
  275. */
  276. public function hide()
  277. {
  278. return $this->responsive(0);
  279. }
  280. /**
  281. * data-priority=”1″ 保持可见,但可以在下拉列表筛选隐藏。
  282. * data-priority=”2″ 480px 分辨率以下可见
  283. * data-priority=”3″ 640px 以下可见
  284. * data-priority=”4″ 800px 以下可见
  285. * data-priority=”5″ 960px 以下可见
  286. * data-priority=”6″ 1120px 以下可见
  287. *
  288. * @param int $priority
  289. *
  290. * @return $this
  291. */
  292. public function responsive(?int $priority = 1)
  293. {
  294. $this->grid->responsive();
  295. return $this->setHeaderAttributes(['data-priority' => $priority]);
  296. }
  297. /**
  298. * @return int|null
  299. */
  300. public function getDataPriority()
  301. {
  302. return isset($this->titleHtmlAttributes['data-priority']) ? $this->titleHtmlAttributes['data-priority'] : null;
  303. }
  304. /**
  305. * Set style of this column.
  306. *
  307. * @param string $style
  308. *
  309. * @return Column
  310. */
  311. public function style($style)
  312. {
  313. return $this->setAttributes(compact('style'));
  314. }
  315. /**
  316. * Get name of this column.
  317. *
  318. * @return mixed
  319. */
  320. public function getName()
  321. {
  322. return $this->name;
  323. }
  324. /**
  325. * @param array|Model $model
  326. */
  327. public function setOriginalModel($model)
  328. {
  329. if (is_array($model)) {
  330. $model = new Fluent($model);
  331. }
  332. $this->originalModel = $model;
  333. }
  334. /**
  335. * @return Fluent|Model
  336. */
  337. public function getOriginalModel()
  338. {
  339. return $this->originalModel;
  340. }
  341. /**
  342. * @return mixed
  343. */
  344. public function getOriginal()
  345. {
  346. return $this->original;
  347. }
  348. /**
  349. * @return mixed
  350. */
  351. public function getValue()
  352. {
  353. return $this->value;
  354. }
  355. /**
  356. * Format label.
  357. *
  358. * @param string $label
  359. *
  360. * @return mixed
  361. */
  362. protected function formatLabel($label)
  363. {
  364. if (! $label) {
  365. $label = admin_trans_field($this->name);
  366. }
  367. $label = $label ?: ucfirst($this->name);
  368. return str_replace(['.', '_'], ' ', $label);
  369. }
  370. /**
  371. * Get label of the column.
  372. *
  373. * @return mixed
  374. */
  375. public function getLabel()
  376. {
  377. return $this->label;
  378. }
  379. /**
  380. * @param string $label
  381. *
  382. * @return $this
  383. */
  384. public function setLabel($label)
  385. {
  386. $this->label = $label;
  387. return $this;
  388. }
  389. /**
  390. * Mark this column as sortable.
  391. *
  392. * @param string $cast
  393. *
  394. * @return $this
  395. */
  396. public function sortable($cast = null)
  397. {
  398. return $this->addSorter($cast);
  399. }
  400. /**
  401. * Set help message for column.
  402. *
  403. * @param string|\Closure $help
  404. * @param null|string $style 'green', 'blue', 'red', 'purple'
  405. * @param null|string $placement 'bottom', 'left', 'right', 'top'
  406. *
  407. * @return $this
  408. */
  409. public function help($help = '', ?string $style = null, ?string $placement = 'bottom')
  410. {
  411. return $this->addHelp($help, $style, $placement);
  412. }
  413. /**
  414. * Set column filter.
  415. *
  416. * @example
  417. * $grid->username()->filter(
  418. * Grid\Column\Filter\StartWith::make(__('admin.username'))
  419. * );
  420. *
  421. * $grid->created_at()->filter(
  422. * Grid\Column\Filter\Equal::make(__('admin.created_at'))->date()
  423. * );
  424. *
  425. * @param Grid\Column\Filter $builder
  426. *
  427. * @return $this
  428. */
  429. public function filter(Grid\Column\Filter $filter)
  430. {
  431. return $this->addFilter($filter);
  432. }
  433. /**
  434. * Add a display callback.
  435. *
  436. * @param \Closure $callback
  437. * @param array $params
  438. *
  439. * @return $this
  440. */
  441. public function display($callback, ...$params)
  442. {
  443. $this->displayCallbacks[] = [&$callback, &$params];
  444. return $this;
  445. }
  446. /**
  447. * If has display callbacks.
  448. *
  449. * @return bool
  450. */
  451. public function hasDisplayCallbacks()
  452. {
  453. return ! empty($this->displayCallbacks);
  454. }
  455. /**
  456. * @param array $callbacks
  457. *
  458. * @return void
  459. */
  460. public function setDisplayCallbacks(array $callbacks)
  461. {
  462. $this->displayCallbacks = $callbacks;
  463. }
  464. /**
  465. * @return \Closure[]
  466. */
  467. public function getDisplayCallbacks()
  468. {
  469. return $this->displayCallbacks;
  470. }
  471. /**
  472. * Call all of the "display" callbacks column.
  473. *
  474. * @param mixed $value
  475. *
  476. * @return mixed
  477. */
  478. protected function callDisplayCallbacks($value)
  479. {
  480. foreach ($this->displayCallbacks as $callback) {
  481. [$callback, $params] = $callback;
  482. if (! $callback instanceof \Closure) {
  483. $value = $callback;
  484. continue;
  485. }
  486. $previous = $value;
  487. $callback = $this->bindOriginalRowModel($callback);
  488. $value = $callback($value, $this, ...$params);
  489. if (
  490. $value instanceof static
  491. && ($last = array_pop($this->displayCallbacks))
  492. ) {
  493. [$last, $params] = $last;
  494. $last = $this->bindOriginalRowModel($last);
  495. $value = call_user_func($last, $previous, $this, ...$params);
  496. }
  497. }
  498. return $value;
  499. }
  500. /**
  501. * Set original grid data to column.
  502. *
  503. * @param Closure $callback
  504. *
  505. * @return Closure
  506. */
  507. protected function bindOriginalRowModel(Closure $callback)
  508. {
  509. return $callback->bindTo($this->getOriginalModel());
  510. }
  511. /**
  512. * Fill all data to every column.
  513. *
  514. * @param array $data
  515. */
  516. public function fill(array &$data)
  517. {
  518. if (static::hasDefinition($this->name)) {
  519. $this->useDefinedColumn();
  520. }
  521. $i = 0;
  522. foreach ($data as $key => &$row) {
  523. $i++;
  524. if (! isset($row['#'])) {
  525. $row['#'] = $i;
  526. }
  527. $this->original = $value = Arr::get($row, $this->name);
  528. $this->value = $value = $this->htmlEntityEncode($value);
  529. $this->setOriginalModel(static::$originalGridModels[$key]);
  530. $this->processConditions();
  531. Arr::set($row, $this->name, $value);
  532. if ($this->hasDisplayCallbacks()) {
  533. $value = $this->callDisplayCallbacks($this->original);
  534. Arr::set($row, $this->name, $value);
  535. }
  536. }
  537. $this->value = $value ?? null;
  538. }
  539. /**
  540. * @return void
  541. */
  542. protected function processConditions()
  543. {
  544. foreach ($this->conditions as $condition) {
  545. $condition->reset();
  546. }
  547. foreach ($this->conditions as $condition) {
  548. $condition->process();
  549. }
  550. }
  551. /**
  552. * Use a defined column.
  553. *
  554. * @throws \Exception
  555. */
  556. protected function useDefinedColumn()
  557. {
  558. $class = static::$definitions[$this->name];
  559. if ($class instanceof Closure) {
  560. $this->display($class);
  561. return;
  562. }
  563. if (! class_exists($class) || ! is_subclass_of($class, AbstractDisplayer::class)) {
  564. throw new \Exception("Invalid column definition [$class]");
  565. }
  566. $this->displayUsing($class);
  567. }
  568. /**
  569. * Convert characters to HTML entities recursively.
  570. *
  571. * @param array|string $item
  572. *
  573. * @return mixed
  574. */
  575. protected function htmlEntityEncode($item)
  576. {
  577. if (is_array($item)) {
  578. array_walk_recursive($item, function (&$value) {
  579. $value = htmlentities($value);
  580. });
  581. } else {
  582. $item = htmlentities($item);
  583. }
  584. return $item;
  585. }
  586. /**
  587. * Determine if this column is currently sorted.
  588. *
  589. * @return bool
  590. */
  591. protected function isSorted()
  592. {
  593. $this->sort = app('request')->get($this->grid->model()->getSortName());
  594. if (empty($this->sort)) {
  595. return false;
  596. }
  597. return isset($this->sort['column']) && $this->sort['column'] == $this->name;
  598. }
  599. /**
  600. * Find a displayer to display column.
  601. *
  602. * @param string $abstract
  603. * @param array $arguments
  604. *
  605. * @return Column
  606. */
  607. protected function resolveDisplayer($abstract, $arguments)
  608. {
  609. if (isset(static::$displayers[$abstract])) {
  610. return $this->callBuiltinDisplayer(static::$displayers[$abstract], $arguments);
  611. }
  612. return $this->callSupportDisplayer($abstract, $arguments);
  613. }
  614. /**
  615. * Call Illuminate/Support displayer.
  616. *
  617. * @param string $abstract
  618. * @param array $arguments
  619. *
  620. * @return Column
  621. */
  622. protected function callSupportDisplayer($abstract, $arguments)
  623. {
  624. return $this->display(function ($value) use ($abstract, $arguments) {
  625. if (is_array($value) || $value instanceof Arrayable) {
  626. return call_user_func_array([collect($value), $abstract], $arguments);
  627. }
  628. if (is_string($value)) {
  629. return call_user_func_array([Str::class, $abstract], array_merge([$value], $arguments));
  630. }
  631. return $value;
  632. });
  633. }
  634. /**
  635. * Call Builtin displayer.
  636. *
  637. * @param string $abstract
  638. * @param array $arguments
  639. *
  640. * @return Column
  641. */
  642. protected function callBuiltinDisplayer($abstract, $arguments)
  643. {
  644. if ($abstract instanceof Closure) {
  645. return $this->display(function ($value) use ($abstract, $arguments) {
  646. return $abstract->call($this, ...array_merge([$value], $arguments));
  647. });
  648. }
  649. if (is_subclass_of($abstract, AbstractDisplayer::class)) {
  650. $grid = $this->grid;
  651. $column = $this;
  652. return $this->display(function ($value) use ($abstract, $grid, $column, $arguments) {
  653. /** @var AbstractDisplayer $displayer */
  654. $displayer = new $abstract($value, $grid, $column, $this);
  655. return $displayer->display(...$arguments);
  656. });
  657. }
  658. return $this;
  659. }
  660. /**
  661. * Set column title attributes.
  662. *
  663. * @param array $attributes
  664. *
  665. * @return $this
  666. */
  667. public function setHeaderAttributes(array $attributes = [])
  668. {
  669. $this->titleHtmlAttributes = array_merge($this->titleHtmlAttributes, $attributes);
  670. return $this;
  671. }
  672. /**
  673. * Set column title default attributes.
  674. *
  675. * @param array $attributes
  676. *
  677. * @return $this
  678. */
  679. public function setDefaultHeaderAttribute(array $attributes)
  680. {
  681. foreach ($attributes as $key => $value) {
  682. if (isset($this->titleHtmlAttributes[$key])) {
  683. continue;
  684. }
  685. $this->titleHtmlAttributes[$key] = $value;
  686. }
  687. return $this;
  688. }
  689. /**
  690. * @return string
  691. */
  692. public function formatTitleAttributes()
  693. {
  694. $attrArr = [];
  695. foreach ($this->titleHtmlAttributes as $name => $val) {
  696. $attrArr[] = "$name=\"$val\"";
  697. }
  698. return implode(' ', $attrArr);
  699. }
  700. /**
  701. * Passes through all unknown calls to builtin displayer or supported displayer.
  702. *
  703. * Allow fluent calls on the Column object.
  704. *
  705. * @param string $method
  706. * @param array $arguments
  707. *
  708. * @return $this
  709. */
  710. public function __call($method, $arguments)
  711. {
  712. return $this->resolveDisplayer($method, $arguments);
  713. }
  714. }