Tree.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. <?php
  2. namespace Dcat\Admin;
  3. use Closure;
  4. use Dcat\Admin\Traits\BuilderEvents;
  5. use Dcat\Admin\Tree\Tools;
  6. use Illuminate\Contracts\Support\Renderable;
  7. use Illuminate\Database\Eloquent\Model;
  8. use Illuminate\Support\Traits\Macroable;
  9. class Tree implements Renderable
  10. {
  11. use BuilderEvents,
  12. Macroable;
  13. /**
  14. * @var array
  15. */
  16. protected $items = [];
  17. /**
  18. * @var string
  19. */
  20. protected $elementId = 'tree-';
  21. /**
  22. * @var Model
  23. */
  24. protected $model;
  25. /**
  26. * @var \Closure
  27. */
  28. protected $queryCallback;
  29. /**
  30. * View of tree to render.
  31. *
  32. * @var string
  33. */
  34. protected $view = [
  35. 'tree' => 'admin::tree',
  36. 'branch' => 'admin::tree.branch',
  37. ];
  38. /**
  39. * @var \Closure
  40. */
  41. protected $callback;
  42. /**
  43. * @var null
  44. */
  45. protected $branchCallback = null;
  46. /**
  47. * @var string
  48. */
  49. public $path;
  50. /**
  51. * @var bool
  52. */
  53. public $useCreate = true;
  54. /**
  55. * @var bool
  56. */
  57. public $useQuickCreate = true;
  58. /**
  59. * @var array
  60. */
  61. public $dialogFormDimensions = ['700px', '620px'];
  62. /**
  63. * @var bool
  64. */
  65. public $useSave = true;
  66. /**
  67. * @var bool
  68. */
  69. public $useRefresh = true;
  70. /**
  71. * @var bool
  72. */
  73. public $useEdit = true;
  74. /**
  75. * @var bool
  76. */
  77. public $useQuickEdit = true;
  78. /**
  79. * @var bool
  80. */
  81. public $useDelete = true;
  82. /**
  83. * @var array
  84. */
  85. protected $nestableOptions = [];
  86. /**
  87. * Header tools.
  88. *
  89. * @var Tools
  90. */
  91. public $tools;
  92. /**
  93. * @var Closure
  94. */
  95. protected $wrapper;
  96. /**
  97. * Menu constructor.
  98. *
  99. * @param Model|null $model
  100. */
  101. public function __construct(Model $model = null, \Closure $callback = null)
  102. {
  103. $this->model = $model;
  104. $this->path = $this->path ?: request()->getPathInfo();
  105. $this->elementId .= uniqid();
  106. $this->setupTools();
  107. $this->collectAssets();
  108. if ($callback instanceof \Closure) {
  109. call_user_func($callback, $this);
  110. }
  111. $this->initBranchCallback();
  112. $this->callResolving();
  113. }
  114. /**
  115. * Setup tree tools.
  116. */
  117. public function setupTools()
  118. {
  119. $this->tools = new Tools($this);
  120. }
  121. protected function collectAssets()
  122. {
  123. Admin::collectComponentAssets('jquery.nestable');
  124. }
  125. /**
  126. * Initialize branch callback.
  127. *
  128. * @return void
  129. */
  130. protected function initBranchCallback()
  131. {
  132. if (is_null($this->branchCallback)) {
  133. $this->branchCallback = function ($branch) {
  134. $key = $branch[$this->model->getKeyName()];
  135. $title = $branch[$this->model->getTitleColumn()];
  136. return "$key - $title";
  137. };
  138. }
  139. }
  140. /**
  141. * Set branch callback.
  142. *
  143. * @param \Closure $branchCallback
  144. *
  145. * @return $this
  146. */
  147. public function branch(\Closure $branchCallback)
  148. {
  149. $this->branchCallback = $branchCallback;
  150. return $this;
  151. }
  152. /**
  153. * Set query callback this tree.
  154. *
  155. * @return Model
  156. */
  157. public function query(\Closure $callback)
  158. {
  159. $this->queryCallback = $callback;
  160. return $this;
  161. }
  162. /**
  163. * Set nestable options.
  164. *
  165. * @param array $options
  166. *
  167. * @return $this
  168. */
  169. public function nestable($options = [])
  170. {
  171. $this->nestableOptions = array_merge($this->nestableOptions, $options);
  172. return $this;
  173. }
  174. /**
  175. * Disable create.
  176. *
  177. * @return void
  178. */
  179. public function disableCreateButton()
  180. {
  181. $this->useCreate = false;
  182. }
  183. public function disableQuickCreateButton()
  184. {
  185. $this->useQuickCreate = false;
  186. }
  187. /**
  188. * @param string $width
  189. * @param string $height
  190. * @return $this
  191. */
  192. public function setdialogFormDimensions(string $width, string $height)
  193. {
  194. $this->dialogFormDimensions = [$width, $height];
  195. return $this;
  196. }
  197. /**
  198. * Disable save.
  199. *
  200. * @return void
  201. */
  202. public function disableSaveButton()
  203. {
  204. $this->useSave = false;
  205. }
  206. /**
  207. * Disable refresh.
  208. *
  209. * @return void
  210. */
  211. public function disableRefreshButton()
  212. {
  213. $this->useRefresh = false;
  214. }
  215. public function disableQuickEditButton()
  216. {
  217. $this->useQuickEdit = false;
  218. }
  219. public function disableEditButton()
  220. {
  221. $this->useEdit = false;
  222. }
  223. public function disableDeleteButton()
  224. {
  225. $this->useDelete = false;
  226. }
  227. /**
  228. * @param Closure $closure
  229. * @return $this;
  230. */
  231. public function wrap(\Closure $closure)
  232. {
  233. $this->wrapper = $closure;
  234. return $this;
  235. }
  236. /**
  237. * @return bool
  238. */
  239. public function hasWrapper()
  240. {
  241. return $this->wrapper ? true : false;
  242. }
  243. /**
  244. * Save tree order from a input.
  245. *
  246. * @param string $serialize
  247. *
  248. * @return bool
  249. */
  250. public function saveOrder($serialize)
  251. {
  252. $tree = json_decode($serialize, true);
  253. if (json_last_error() != JSON_ERROR_NONE) {
  254. throw new \InvalidArgumentException(json_last_error_msg());
  255. }
  256. $this->model->saveOrder($tree);
  257. return true;
  258. }
  259. /**
  260. * Build tree grid scripts.
  261. *
  262. * @return string
  263. */
  264. protected function script()
  265. {
  266. $deleteConfirm = trans('admin.delete_confirm');
  267. $saveSucceeded = trans('admin.save_succeeded');
  268. $deleteSucceeded = trans('admin.delete_succeeded');
  269. $confirm = trans('admin.confirm');
  270. $cancel = trans('admin.cancel');
  271. $nestableOptions = json_encode($this->nestableOptions);
  272. return <<<JS
  273. $('#{$this->elementId}').nestable($nestableOptions);
  274. $('.tree_branch_delete').click(function() {
  275. var id = $(this).data('id');
  276. LA.confirm("$deleteConfirm", function () {
  277. LA.NP.start();
  278. $.ajax({
  279. method: 'post',
  280. url: '{$this->path}/' + id,
  281. data: {
  282. _method:'delete',
  283. _token:LA.token,
  284. },
  285. success: function (data) {
  286. LA.NP.done();
  287. if (typeof data === 'object') {
  288. if (data.status) {
  289. LA.reload();
  290. LA.success("$deleteSucceeded");
  291. } else {
  292. LA.error(data.message || 'Delete failed.');
  293. }
  294. }
  295. }
  296. });
  297. }, "$confirm", "$cancel");
  298. });
  299. $('.{$this->elementId}-save').click(function () {
  300. var serialize = $('#{$this->elementId}').nestable('serialize');
  301. LA.NP.start();
  302. $.post('{$this->path}', {
  303. _token: LA.token,
  304. _order: JSON.stringify(serialize)
  305. },
  306. function(data){
  307. LA.NP.done();
  308. LA.reload();
  309. LA.success('{$saveSucceeded}');
  310. });
  311. });
  312. $('.{$this->elementId}-tree-tools').on('click', function(e){
  313. var action = $(this).data('action');
  314. if (action === 'expand') {
  315. $('.dd').nestable('expandAll');
  316. }
  317. if (action === 'collapse') {
  318. $('.dd').nestable('collapseAll');
  319. }
  320. });
  321. JS;
  322. }
  323. /**
  324. * Set view of tree.
  325. *
  326. * @param string $view
  327. */
  328. public function setView($view)
  329. {
  330. $this->view = $view;
  331. }
  332. /**
  333. * Return all items of the tree.
  334. *
  335. * @param array $items
  336. */
  337. public function getItems()
  338. {
  339. return $this->model->withQuery($this->queryCallback)->toTree();
  340. }
  341. /**
  342. * Variables in tree template.
  343. *
  344. * @return array
  345. */
  346. public function variables()
  347. {
  348. return [
  349. 'id' => $this->elementId,
  350. 'tools' => $this->tools->render(),
  351. 'items' => $this->getItems(),
  352. 'useCreate' => $this->useCreate,
  353. 'useQuickCreate' => $this->useQuickCreate,
  354. 'useSave' => $this->useSave,
  355. 'useRefresh' => $this->useRefresh,
  356. 'useEdit' => $this->useEdit,
  357. 'useQuickEdit' => $this->useQuickEdit,
  358. 'useDelete' => $this->useDelete,
  359. 'createButton' => $this->renderCreateButton(),
  360. ];
  361. }
  362. /**
  363. * Setup grid tools.
  364. *
  365. * @param Closure $callback
  366. *
  367. * @return void
  368. */
  369. public function tools(Closure $callback)
  370. {
  371. call_user_func($callback, $this->tools);
  372. }
  373. /**
  374. * @return string
  375. */
  376. protected function renderCreateButton()
  377. {
  378. if (!$this->useQuickCreate && !$this->useCreate) {
  379. return '';
  380. }
  381. $url = $this->path . '/create';
  382. $new = trans('admin.new');
  383. $quickBtn = $btn = '';
  384. if ($this->useCreate) {
  385. $btn = "<a href='{$url}' class='btn btn-sm btn-success'><i class='ti-plus'></i><span class='hidden-xs'>&nbsp;&nbsp;{$new}</span></a>";
  386. }
  387. if ($this->useQuickCreate) {
  388. $text = $this->useCreate ? '' : "<span class='hidden-xs'> &nbsp; $new</span>";
  389. $quickBtn = "<a data-url='$url' class='btn btn-sm btn-success tree-quick-create'><i class=' fa fa-clone'></i>$text</a>";
  390. }
  391. return "<div class='btn-group pull-right' style='margin-right:3px'>{$btn}{$quickBtn}</div>";
  392. }
  393. /**
  394. * Render a tree.
  395. *
  396. * @return \Illuminate\Http\JsonResponse|string
  397. */
  398. public function render()
  399. {
  400. try {
  401. $this->callResolving();
  402. if ($this->useQuickEdit) {
  403. list($width, $height) = $this->dialogFormDimensions;
  404. Form::popup(trans('admin.edit'))
  405. ->click('.tree-quick-edit')
  406. ->success('LA.reload()')
  407. ->dimensions($width, $height)
  408. ->render();
  409. }
  410. if ($this->useQuickCreate) {
  411. list($width, $height) = $this->dialogFormDimensions;
  412. Form::popup(trans('admin.new'))
  413. ->click('.tree-quick-create')
  414. ->success('LA.reload()')
  415. ->dimensions($width, $height)
  416. ->render();
  417. }
  418. Admin::script($this->script());
  419. view()->share([
  420. 'path' => url($this->path),
  421. 'keyName' => $this->model->getKeyName(),
  422. 'branchView' => $this->view['branch'],
  423. 'branchCallback' => $this->branchCallback,
  424. ]);
  425. return $this->doWrap();
  426. } catch (\Throwable $e) {
  427. return Admin::makeExceptionHandler()->renderException($e);
  428. }
  429. }
  430. /**
  431. * @return string
  432. */
  433. protected function doWrap()
  434. {
  435. $view = view($this->view['tree'], $this->variables());
  436. if (!$wrapper = $this->wrapper) {
  437. return "<div class='box box-default'>{$view->render()}</div>";
  438. }
  439. return $wrapper($view);
  440. }
  441. /**
  442. * Get the string contents of the grid view.
  443. *
  444. * @return string
  445. */
  446. public function __toString()
  447. {
  448. return $this->render();
  449. }
  450. /**
  451. * Create a tree instance.
  452. *
  453. * @param Model|null $model
  454. * @param Closure|null $callback
  455. * @return Tree
  456. */
  457. public static function make(Model $model = null, \Closure $callback = null)
  458. {
  459. return new static($model, $callback);
  460. }
  461. }