jquery.nestable.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. /*!
  2. * Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/
  3. * Dual-licensed under the BSD or MIT licenses
  4. */
  5. ;(function($, window, document, undefined)
  6. {
  7. var hasTouch = 'ontouchstart' in document;
  8. /**
  9. * Detect CSS pointer-events property
  10. * events are normally disabled on the dragging element to avoid conflicts
  11. * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js
  12. */
  13. var hasPointerEvents = (function()
  14. {
  15. var el = document.createElement('div'),
  16. docEl = document.documentElement;
  17. if (!('pointerEvents' in el.style)) {
  18. return false;
  19. }
  20. el.style.pointerEvents = 'auto';
  21. el.style.pointerEvents = 'x';
  22. docEl.appendChild(el);
  23. var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto';
  24. docEl.removeChild(el);
  25. return !!supports;
  26. })();
  27. var defaults = {
  28. listNodeName : 'ol',
  29. itemNodeName : 'li',
  30. rootClass : 'dd',
  31. listClass : 'dd-list',
  32. itemClass : 'dd-item',
  33. dragClass : 'dd-dragel',
  34. handleClass : 'dd-handle',
  35. collapsedClass : 'dd-collapsed',
  36. placeClass : 'dd-placeholder',
  37. noDragClass : 'dd-nodrag',
  38. emptyClass : 'dd-empty',
  39. deleteClass : 'dd-del',
  40. itemKey :'',
  41. itemName :'',
  42. aliasName :'',
  43. expandBtnHTML : '<button data-action="expand" type="button">Expand</button>',
  44. collapseBtnHTML : '<button data-action="collapse" type="button">Collapse</button>',
  45. group : 0,
  46. maxDepth : 5,
  47. threshold : 20,
  48. rmSourceItem :true ,
  49. addItemCloseBtn :false,
  50. dragFlag :true,
  51. addItemQuickBtn:false,
  52. quickKey:'quickKey',
  53. quickDesc:'quickDesc',
  54. quickTitle:'快捷',
  55. quickEnum:[{'key':'是','value':'是'},{'key':'否','value':'否'}],
  56. data:null,
  57. dataJsonKey:'json'
  58. };
  59. function Plugin(element, options)
  60. {
  61. this.w = $(document);
  62. this.el = $(element);
  63. this.options = $.extend({}, defaults, options);
  64. this.init();
  65. }
  66. Plugin.prototype = {
  67. init: function()
  68. {
  69. var list = this;
  70. list.buildOl();
  71. list.reset();
  72. list.el.data('nestable-group', this.options.group);
  73. list.placeEl = $('<div class="' + list.options.placeClass + '"/>');
  74. $.each(this.el.find(list.options.itemNodeName), function(k, el) {
  75. list.setParent($(el));
  76. });
  77. list.el.on('click', 'button', function(e) {
  78. if (list.dragEl) {
  79. return;
  80. }
  81. var target = $(e.currentTarget),
  82. action = target.data('action'),
  83. item = target.parent(list.options.itemNodeName);
  84. if (action === 'collapse') {
  85. list.collapseItem(item);
  86. }
  87. if (action === 'expand') {
  88. list.expandItem(item);
  89. }
  90. });
  91. var onStartEvent = function(e)
  92. {
  93. var handle = $(e.target);
  94. if (!handle.hasClass(list.options.handleClass)) {
  95. if (handle.closest('.' + list.options.noDragClass).length) {
  96. return;
  97. }
  98. handle = handle.closest('.' + list.options.handleClass);
  99. }
  100. if (!handle.length || list.dragEl) {
  101. return;
  102. }
  103. list.isTouch = /^touch/.test(e.type);
  104. if (list.isTouch && e.touches.length !== 1) {
  105. return;
  106. }
  107. e.preventDefault();
  108. list.dragStart(e.touches ? e.touches[0] : e);
  109. };
  110. var onMoveEvent = function(e)
  111. {
  112. if (list.dragEl) {
  113. e.preventDefault();
  114. list.dragMove(e.touches ? e.touches[0] : e);
  115. }
  116. };
  117. var onEndEvent = function(e)
  118. {
  119. if (list.dragEl) {
  120. e.preventDefault();
  121. list.dragStop(e.touches ? e.touches[0] : e);
  122. }
  123. };
  124. if (hasTouch) {
  125. list.el[0].addEventListener('touchstart', onStartEvent, false);
  126. window.addEventListener('touchmove', onMoveEvent, false);
  127. window.addEventListener('touchend', onEndEvent, false);
  128. window.addEventListener('touchcancel', onEndEvent, false);
  129. }
  130. if(this.options.dragFlag){
  131. list.el.on('mousedown', onStartEvent);
  132. list.w.on('mousemove', onMoveEvent);
  133. list.w.on('mouseup', onEndEvent);
  134. }
  135. if(this.options.addItemCloseBtn){
  136. list.el.delegate("."+this.options.deleteClass,'click',function(e){
  137. var itemList = $(this).parent().parent();
  138. var length = itemList.children().length;
  139. $(this).parent('.'+list.options.itemClass).remove();
  140. if(length == 1){
  141. itemList.remove();
  142. list.el.append("<div class='"+list.options.emptyClass+"'></div>");
  143. }
  144. });
  145. }
  146. //2020年2月19日19:59:49 增加没有数据时插入空图片
  147. list.el.children().length == 0 && list.el.append("<div class='"+list.options.emptyClass+"'></div>");
  148. $.each(list.el.find("." + list.options.itemClass),function(i,item){
  149. var id = $(item).attr("data-id");
  150. if(id == null || id == undefined || id == ""){
  151. if(util.isNull(list.options.itemKey)){
  152. var num = list.randomNum();
  153. $(item).attr("data-id",num) ;
  154. return true;
  155. }
  156. $(item).attr("data-id",$(item).data(list.options.itemKey.toLowerCase())) ;
  157. }
  158. });
  159. },
  160. randomNum:function(){
  161. return new Date().getTime()+ parseInt(Math.random()*100000,10);
  162. },
  163. serialize: function()
  164. {
  165. var data,
  166. depth = 0,
  167. list = this;
  168. step = function(level, depth)
  169. {
  170. var array = [ ],
  171. items = level.children(list.options.itemNodeName);
  172. items.each(function()
  173. {
  174. var li = $(this),
  175. item = $.extend({}, li.data()),
  176. sub = li.children(list.options.listNodeName);
  177. if (sub.length) {
  178. item.children = step(sub, depth + 1);
  179. }
  180. array.push(item);
  181. });
  182. return array;
  183. };
  184. data = step(list.el.find(list.options.listNodeName).first(), depth);
  185. return data;
  186. },
  187. serialise: function()
  188. {
  189. return this.serialize();
  190. },
  191. reset: function()
  192. {
  193. this.mouse = {
  194. offsetX : 0,
  195. offsetY : 0,
  196. startX : 0,
  197. startY : 0,
  198. lastX : 0,
  199. lastY : 0,
  200. nowX : 0,
  201. nowY : 0,
  202. distX : 0,
  203. distY : 0,
  204. dirAx : 0,
  205. dirX : 0,
  206. dirY : 0,
  207. lastDirX : 0,
  208. lastDirY : 0,
  209. distAxX : 0,
  210. distAxY : 0
  211. };
  212. this.isTouch = false;
  213. this.moving = false;
  214. this.dragEl = null;
  215. this.dragRootEl = null;
  216. this.dragDepth = 0;
  217. this.hasNewRoot = false;
  218. this.pointEl = null;
  219. this.sourceIndex = null;
  220. this.sourceParent = null;
  221. },
  222. expandItem: function(li)
  223. {
  224. li.removeClass(this.options.collapsedClass);
  225. li.children('[data-action="expand"]').hide();
  226. li.children('[data-action="collapse"]').show();
  227. li.children(this.options.listNodeName).show();
  228. },
  229. collapseItem: function(li)
  230. {
  231. var lists = li.children(this.options.listNodeName);
  232. if (lists.length) {
  233. li.addClass(this.options.collapsedClass);
  234. li.children('[data-action="collapse"]').hide();
  235. li.children('[data-action="expand"]').show();
  236. li.children(this.options.listNodeName).hide();
  237. }
  238. },
  239. expandAll: function()
  240. {
  241. var list = this;
  242. list.el.find(list.options.itemNodeName).each(function() {
  243. list.expandItem($(this));
  244. });
  245. },
  246. collapseAll: function()
  247. {
  248. var list = this;
  249. list.el.find(list.options.itemNodeName).each(function() {
  250. list.collapseItem($(this));
  251. });
  252. },
  253. setParent: function(li)
  254. {
  255. if (li.children(this.options.listNodeName).length) {
  256. li.prepend($(this.options.expandBtnHTML));
  257. li.prepend($(this.options.collapseBtnHTML));
  258. }
  259. li.children('[data-action="expand"]').hide();
  260. },
  261. unsetParent: function(li)
  262. {
  263. li.removeClass(this.options.collapsedClass);
  264. li.children('[data-action]').remove();
  265. li.children(this.options.listNodeName).remove();
  266. },
  267. dragStart: function(e)
  268. {
  269. var mouse = this.mouse,
  270. target = $(e.target),
  271. dragItem = target.closest(this.options.itemNodeName);
  272. this.sourceIndex = dragItem.index();
  273. this.sourceParent = dragItem.parent();
  274. this.placeEl.css('height', dragItem.height());
  275. mouse.offsetX = e.offsetX !== undefined ? e.offsetX : e.pageX - target.offset().left;
  276. mouse.offsetY = e.offsetY !== undefined ? e.offsetY : e.pageY - target.offset().top;
  277. mouse.startX = mouse.lastX = e.pageX;
  278. mouse.startY = mouse.lastY = e.pageY;
  279. this.dragRootEl = this.el;
  280. this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass);
  281. this.dragEl.css('width', dragItem.width());
  282. dragItem.after(this.placeEl);
  283. dragItem[0].parentNode.removeChild(dragItem[0]);
  284. dragItem.appendTo(this.dragEl);
  285. $(document.body).append(this.dragEl);
  286. this.dragEl.css({
  287. 'left' : e.pageX - mouse.offsetX,
  288. 'top' : e.pageY - mouse.offsetY
  289. });
  290. // total depth of dragging item
  291. var i, depth,
  292. items = this.dragEl.find(this.options.itemNodeName);
  293. for (i = 0; i < items.length; i++) {
  294. depth = $(items[i]).parents(this.options.listNodeName).length;
  295. if (depth > this.dragDepth) {
  296. this.dragDepth = depth;
  297. }
  298. }
  299. },
  300. getCloseBtnFlag:function(){
  301. return this.options.addItemCloseBtn;
  302. },
  303. getQuickBtn:function(){
  304. return this.options.addItemQuickBtn;
  305. },
  306. getItems:function(){
  307. return this.el.find("."+this.options.itemClass);
  308. },
  309. getItemData: function(){
  310. var arr = new Array();
  311. var that =this;
  312. $.each(that.getItems(),function(i,item){
  313. var d = $(item).data("json");
  314. var quickFlag = that.options.quickEnum[1].key;
  315. var checkbox = $(item).find("input[type=checkbox][name="+that.options.quickKey+"]:checked");
  316. if(checkbox.length > 0 ){
  317. quickFlag = that.options.quickEnum[0].key;
  318. }
  319. d[that.options.quickKey] = checkbox.length > 0 ? that.options.quickEnum[0].key : that.options.quickEnum[1].key;
  320. d[that.options.quickDesc] = checkbox.length > 0 ? that.options.quickEnum[0].value : that.options.quickEnum[1].value;
  321. arr.push(d);
  322. });
  323. return arr;
  324. },
  325. getQuickHtml:function(flag){
  326. return '<div style="float:right;margin-top:-35px;"><input type="checkbox" name="'+this.options.quickKey+'" '+(flag ? 'checked="checked"': '')+' />'+this.options.quickTitle+'</div> ';
  327. },
  328. getCloseBtnHtml:function(flag){
  329. var f = flag ? flag :this.options.addItemQuickBtn;
  330. return '<i class="layui-icon layui-icon-delete '+this.options.deleteClass+'" style="font-size: 30px; color:red ;float:right;margin-top:-35px;'+(f?'margin-right:50px;':'')+'"></i> ';
  331. },
  332. dragStop: function(e)
  333. {
  334. var el = this.dragEl.children(this.options.itemNodeName).first();
  335. el[0].parentNode.removeChild(el[0]);
  336. var plceRoot = this.placeEl.parents("."+this.options.rootClass);
  337. var item = plceRoot.find("[data-id="+el.attr("data-id")+"]");
  338. if(item.length == 0){
  339. var clone = el.clone();
  340. if(plceRoot.nestable('getQuickBtn') && this.el.attr("id")!= plceRoot.attr("id")){
  341. clone.append(plceRoot.nestable('getQuickHtml'));
  342. }
  343. if(plceRoot.nestable('getCloseBtnFlag') && this.el.attr("id")!= plceRoot.attr("id")){
  344. clone.append(this.getCloseBtnHtml(plceRoot.nestable('getQuickBtn')));
  345. }
  346. this.placeEl.replaceWith(clone);
  347. }else{
  348. this.placeEl.remove();
  349. }
  350. this.dragEl.remove();
  351. this.el.trigger('change');
  352. if (this.hasNewRoot) {
  353. this.dragRootEl.trigger('change');
  354. }
  355. if( !this.options.rmSourceItem){
  356. var sourceItem = this.el.find("[data-id="+el.attr("data-id")+"]");
  357. sourceItem.length == 0 && this.addItem(el);
  358. }
  359. this.reset();
  360. },
  361. addItem :function (el){
  362. if(this.sourceParent.children().length == 0){
  363. this.sourceParent.append(el.clone());
  364. return ;
  365. }
  366. if(this.sourceIndex == this.sourceParent.children().length){
  367. this.sourceParent.append(el.clone());
  368. return ;
  369. }
  370. this.sourceParent.children().eq(this.sourceIndex).before(el.clone());
  371. },
  372. buildOl :function (){
  373. var that = this;
  374. if(!util.isNull(that.options.data) && that.options.data.length > 0){
  375. var ol = $('<ol class="dd-list"></ol>');
  376. $.each(that.options.data,function(i,item){
  377. var li = $('<li class="dd-item" ></div>');
  378. li.attr("data-"+that.options.dataJsonKey,JSON.stringify(item));
  379. li.attr("data-"+that.options.itemKey.toLowerCase(),item[that.options.itemKey]);
  380. var div = $('<div class="dd-handle">'+(util.isNull(item[that.options.aliasName]) ? item[that.options.itemName] : item[that.options.aliasName])+'</div>');
  381. li.append(div);
  382. if(that.options.addItemQuickBtn){
  383. li.append(that.getQuickHtml(item[that.options.quickKey] == that.options.quickEnum[0].key ? true :false));
  384. }
  385. if(that.options.addItemCloseBtn){
  386. li.append(that.getCloseBtnHtml(true));
  387. }
  388. ol.append(li);
  389. });
  390. that.el.append(ol);
  391. }
  392. },
  393. dragMove: function(e)
  394. {
  395. var list, parent, prev, next, depth,
  396. opt = this.options,
  397. mouse = this.mouse;
  398. this.dragEl.css({
  399. 'left' : e.pageX - mouse.offsetX,
  400. 'top' : e.pageY - mouse.offsetY
  401. });
  402. // mouse position last events
  403. mouse.lastX = mouse.nowX;
  404. mouse.lastY = mouse.nowY;
  405. // mouse position this events
  406. mouse.nowX = e.pageX;
  407. mouse.nowY = e.pageY;
  408. // distance mouse moved between events
  409. mouse.distX = mouse.nowX - mouse.lastX;
  410. mouse.distY = mouse.nowY - mouse.lastY;
  411. // direction mouse was moving
  412. mouse.lastDirX = mouse.dirX;
  413. mouse.lastDirY = mouse.dirY;
  414. // direction mouse is now moving (on both axis)
  415. mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
  416. mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
  417. // axis mouse is now moving on
  418. var newAx = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;
  419. // do nothing on first move
  420. if (!mouse.moving) {
  421. mouse.dirAx = newAx;
  422. mouse.moving = true;
  423. return;
  424. }
  425. // calc distance moved on this axis (and direction)
  426. if (mouse.dirAx !== newAx) {
  427. mouse.distAxX = 0;
  428. mouse.distAxY = 0;
  429. } else {
  430. mouse.distAxX += Math.abs(mouse.distX);
  431. if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
  432. mouse.distAxX = 0;
  433. }
  434. mouse.distAxY += Math.abs(mouse.distY);
  435. if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
  436. mouse.distAxY = 0;
  437. }
  438. }
  439. mouse.dirAx = newAx;
  440. /**
  441. * move horizontal
  442. */
  443. if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
  444. // reset move distance on x-axis for new phase
  445. mouse.distAxX = 0;
  446. prev = this.placeEl.prev(opt.itemNodeName);
  447. // increase horizontal level if previous sibling exists and is not collapsed
  448. if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass)) {
  449. // cannot increase level when item above is collapsed
  450. list = prev.find(opt.listNodeName).last();
  451. // check if depth limit has reached
  452. depth = this.placeEl.parents(opt.listNodeName).length;
  453. if (depth + this.dragDepth <= opt.maxDepth) {
  454. // create new sub-level if one doesn't exist
  455. if (!list.length) {
  456. list = $('<' + opt.listNodeName + '/>').addClass(opt.listClass);
  457. list.append(this.placeEl);
  458. prev.append(list);
  459. this.setParent(prev);
  460. } else {
  461. // else append to next level up
  462. list = prev.children(opt.listNodeName).last();
  463. list.append(this.placeEl);
  464. }
  465. }
  466. }
  467. // decrease horizontal level
  468. if (mouse.distX < 0) {
  469. // we can't decrease a level if an item preceeds the current one
  470. next = this.placeEl.next(opt.itemNodeName);
  471. if (!next.length) {
  472. parent = this.placeEl.parent();
  473. this.placeEl.closest(opt.itemNodeName).after(this.placeEl);
  474. if (!parent.children().length) {
  475. this.unsetParent(parent.parent());
  476. }
  477. }
  478. }
  479. }
  480. var isEmpty = false;
  481. // find list item under cursor
  482. if (!hasPointerEvents) {
  483. this.dragEl[0].style.visibility = 'hidden';
  484. }
  485. this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop)));
  486. if (!hasPointerEvents) {
  487. this.dragEl[0].style.visibility = 'visible';
  488. }
  489. if (this.pointEl.hasClass(opt.handleClass)) {
  490. this.pointEl = this.pointEl.parent(opt.itemNodeName);
  491. }
  492. if (this.pointEl.hasClass(opt.emptyClass)) {
  493. isEmpty = true;
  494. }
  495. else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) {
  496. return;
  497. }
  498. // find parent list of item under cursor
  499. var pointElRoot = this.pointEl.closest('.' + opt.rootClass),
  500. isNewRoot = this.dragRootEl.data('nestable-id') !== pointElRoot.data('nestable-id');
  501. /**
  502. * move vertical
  503. */
  504. if (!mouse.dirAx || isNewRoot || isEmpty) {
  505. // check if groups match if dragging over new root
  506. if (isNewRoot && opt.group !== pointElRoot.data('nestable-group')) {
  507. return;
  508. }
  509. // check depth limit
  510. depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;
  511. if (depth > opt.maxDepth) {
  512. return;
  513. }
  514. var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
  515. parent = this.placeEl.parent();
  516. // if empty create new list to replace empty placeholder
  517. if (isEmpty) {
  518. list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass);
  519. list.append(this.placeEl);
  520. this.pointEl.replaceWith(list);
  521. }
  522. else if (before) {
  523. this.pointEl.before(this.placeEl);
  524. }
  525. else {
  526. this.pointEl.after(this.placeEl);
  527. }
  528. if (!parent.children().length) {
  529. this.unsetParent(parent.parent());
  530. }
  531. if (!this.dragRootEl.find(opt.itemNodeName).length) {
  532. this.dragRootEl.append('<div class="' + opt.emptyClass + '"/>');
  533. }
  534. // parent root list has changed
  535. if (isNewRoot) {
  536. this.dragRootEl = pointElRoot;
  537. this.hasNewRoot = this.el[0] !== this.dragRootEl[0];
  538. }
  539. }
  540. }
  541. };
  542. $.fn.nestable = function(params)
  543. {
  544. var lists = this,
  545. retval = this;
  546. lists.each(function()
  547. {
  548. var plugin = $(this).data("nestable");
  549. if (!plugin) {
  550. $(this).data("nestable", new Plugin(this, params));
  551. $(this).data("nestable-id", new Date().getTime());
  552. } else {
  553. if (typeof params === 'string' && typeof plugin[params] === 'function') {
  554. retval = plugin[params]();
  555. }
  556. }
  557. });
  558. return retval || lists;
  559. };
  560. })(window.jQuery || window.Zepto, window, document);