Column.php 18 KB

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