Dropdown.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <?php
  2. namespace Dcat\Admin\Widgets;
  3. use Dcat\Admin\Admin;
  4. use Illuminate\Contracts\Support\Arrayable;
  5. use Illuminate\Contracts\Support\Renderable;
  6. use Illuminate\Support\Arr;
  7. use Illuminate\Support\Str;
  8. class Dropdown extends Widget
  9. {
  10. const DIVIDER = '_divider';
  11. /**
  12. * @var string
  13. */
  14. protected static $dividerHtml = '<li class="dropdown-divider"></li>';
  15. /**
  16. * @var string
  17. */
  18. protected $template = '<span class="dropdown" style="display:inline-block">%s<ul class="dropdown-menu">%s</ul></span>';
  19. /**
  20. * @var array
  21. */
  22. protected $button = [
  23. 'text' => null,
  24. 'class' => 'btn btn-sm btn-white waves-effect',
  25. 'style' => null,
  26. ];
  27. /**
  28. * @var string
  29. */
  30. protected $buttonId;
  31. /**
  32. * @var \Closure
  33. */
  34. protected $builder;
  35. /**
  36. * @var bool
  37. */
  38. protected $divider;
  39. /**
  40. * @var bool
  41. */
  42. protected $click = false;
  43. /**
  44. * @var array
  45. */
  46. protected $firstOptions = [];
  47. public function __construct(array $options = [])
  48. {
  49. $this->options($options);
  50. }
  51. /**
  52. * Set the options of dropdown menus.
  53. *
  54. * @param array $options
  55. * @param string|null $title
  56. *
  57. * @return $this
  58. */
  59. public function options($options = [], string $title = null)
  60. {
  61. if (! $options) {
  62. return $this;
  63. }
  64. if ($options instanceof Arrayable) {
  65. $options = $options->toArray();
  66. }
  67. $options = (array) $options;
  68. if (! $this->options) {
  69. $this->firstOptions = &$options;
  70. }
  71. $this->options[] = [$title, &$options];
  72. return $this;
  73. }
  74. /**
  75. * Set the button text.
  76. *
  77. * @param string|null $text
  78. *
  79. * @return $this
  80. */
  81. public function button(?string $text)
  82. {
  83. $this->button['text'] = $text;
  84. return $this;
  85. }
  86. /**
  87. * Without text of button.
  88. *
  89. * @return $this
  90. */
  91. public function withoutTextButton()
  92. {
  93. return $this->button('');
  94. }
  95. /**
  96. * Set the button class.
  97. *
  98. * @param string $class
  99. *
  100. * @return $this
  101. */
  102. public function buttonClass(string $class)
  103. {
  104. $this->button['class'] = $class;
  105. return $this;
  106. }
  107. /**
  108. * Set the button style.
  109. *
  110. * @param string $class
  111. *
  112. * @return $this
  113. */
  114. public function buttonStyle(string $style)
  115. {
  116. $this->button['style'] = $style;
  117. return $this;
  118. }
  119. /**
  120. * Show divider.
  121. *
  122. * @param string $class
  123. *
  124. * @return $this
  125. */
  126. public function divider()
  127. {
  128. $this->divider = true;
  129. return $this;
  130. }
  131. /**
  132. * Applies the callback to the elements of the options.
  133. *
  134. * @param string $class
  135. *
  136. * @return $this
  137. */
  138. public function map(\Closure $builder)
  139. {
  140. $this->builder = $builder;
  141. return $this;
  142. }
  143. /**
  144. * Add click event listener.
  145. *
  146. * @param string|null $defaultLabel
  147. *
  148. * @return $this
  149. */
  150. public function click(?string $defaultLabel = null)
  151. {
  152. $this->click = true;
  153. $this->buttonId = 'dropd_'.Str::random(8);
  154. if ($defaultLabel !== null) {
  155. $this->button($defaultLabel);
  156. }
  157. return $this;
  158. }
  159. /**
  160. * Set the template of dropdown menu.
  161. *
  162. * @param string|\Closure|Renderable $template
  163. *
  164. * @return $this
  165. */
  166. public function template($template)
  167. {
  168. $this->template = $this->toString($template);
  169. return $this;
  170. }
  171. /**
  172. * @return string
  173. */
  174. protected function renderButton()
  175. {
  176. if (is_null($this->button['text']) && ! $this->click) {
  177. return;
  178. }
  179. $text = $this->button['text'];
  180. $class = $this->button['class'];
  181. $style = $this->button['style'];
  182. if ($this->click && ! $text) {
  183. if (Arr::isAssoc($this->firstOptions)) {
  184. $text = array_keys($this->firstOptions)[0];
  185. } else {
  186. $text = $this->firstOptions[0] ?? '';
  187. }
  188. if (is_array($text)) {
  189. $text = $text['label'] ?? current($text);
  190. }
  191. }
  192. return str_replace(
  193. ['{id}', '{class}', '{style}', '{text}'],
  194. [
  195. $this->buttonId,
  196. $class,
  197. $style ? "style='$style'" : '',
  198. $text ? " $text &nbsp;" : '',
  199. ],
  200. <<<'HTML'
  201. <a id="{id}" class="{class} dropdown-toggle " data-toggle="dropdown" href="javascript:void(0)" {style}>
  202. <stub>{text}</stub>
  203. <span class="caret"></span>
  204. </a>
  205. HTML
  206. );
  207. }
  208. /**
  209. * @return string
  210. */
  211. public function getButtonId()
  212. {
  213. return $this->buttonId;
  214. }
  215. /**
  216. * @return string
  217. */
  218. protected function renderOptions()
  219. {
  220. $opt = '';
  221. foreach ($this->options as &$items) {
  222. [$title, $options] = $items;
  223. if ($title) {
  224. $opt .= "<li class='dropdown-header'>$title</li>";
  225. }
  226. foreach ($options as $key => $val) {
  227. $opt .= $this->renderOption($key, $val);
  228. }
  229. }
  230. return $opt;
  231. }
  232. /**
  233. * @param mixed $k
  234. * @param mixed $v
  235. *
  236. * @return mixed|string
  237. */
  238. protected function renderOption($k, $v)
  239. {
  240. if ($v === static::DIVIDER) {
  241. return static::$dividerHtml;
  242. }
  243. if ($builder = $this->builder) {
  244. $v = $builder->call($this, $v, $k);
  245. }
  246. $v = mb_strpos($v, '</a>') ? $v : "<a href='javascript:void(0)'>$v</a>";
  247. $v = "<li class='dropdown-item'>$v</li>";
  248. if ($this->divider) {
  249. $v .= static::$dividerHtml;
  250. $this->divider = null;
  251. }
  252. return $v;
  253. }
  254. /**
  255. * @return string
  256. */
  257. public function render()
  258. {
  259. if (is_null($this->button['text']) && ! $this->options) {
  260. return '';
  261. }
  262. $button = $this->renderButton();
  263. if (! $this->options) {
  264. return $button;
  265. }
  266. $opt = $this->renderOptions();
  267. if (! $button) {
  268. return sprintf('<ul class="dropdown-menu">%s</ul>', $opt);
  269. }
  270. $label = $this->button['text'];
  271. if ($this->click) {
  272. Admin::script(
  273. <<<JS
  274. (function () {
  275. var btn = $('#{$this->buttonId}'), _a = btn.parent().find('ul li a'), text = '$label';
  276. _a.on('click', function () {
  277. btn.find('stub').html($(this).html() + ' &nbsp;');
  278. });
  279. if (text) {
  280. btn.find('stub').html(text + ' &nbsp;');
  281. } else {
  282. (!_a.length) || btn.find('stub').html($(_a[0]).html() + ' &nbsp;');
  283. }
  284. })();
  285. JS
  286. );
  287. }
  288. return sprintf(
  289. $this->template,
  290. $button,
  291. $opt
  292. );
  293. }
  294. }