Built files from Bizgaze WebServer
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

jquery.nestable.js 39KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087
  1. /*!
  2. * Nestable jQuery Plugin - Copyright (c) 2014 Ramon Smit - https://github.com/RamonSmit/Nestable
  3. */
  4. (function($, window, document, undefined) {
  5. var hasTouch = 'ontouchstart' in document;
  6. /**
  7. * Detect CSS pointer-events property
  8. * events are normally disabled on the dragging element to avoid conflicts
  9. * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js
  10. */
  11. var hasPointerEvents = (function() {
  12. var el = document.createElement('div'),
  13. docEl = document.documentElement;
  14. if (!('pointerEvents' in el.style)) {
  15. return false;
  16. }
  17. el.style.pointerEvents = 'auto';
  18. el.style.pointerEvents = 'x';
  19. docEl.appendChild(el);
  20. var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto';
  21. docEl.removeChild(el);
  22. return !!supports;
  23. })();
  24. var defaults = {
  25. contentCallback: function(item) {return item.content || '' ? item.content : item.id;},
  26. listNodeName: 'ol',
  27. itemNodeName: 'li',
  28. handleNodeName: 'div',
  29. contentNodeName: 'span',
  30. rootClass: 'dd',
  31. listClass: 'dd-list',
  32. itemClass: 'dd-item',
  33. dragClass: 'dd-dragel',
  34. handleClass: 'dd-handle',
  35. contentClass: 'dd-content',
  36. collapsedClass: 'dd-collapsed',
  37. placeClass: 'dd-placeholder',
  38. noDragClass: 'dd-nodrag',
  39. noChildrenClass: 'dd-nochildren',
  40. emptyClass: 'dd-empty',
  41. expandBtnHTML: '<button class="dd-expand" data-action="expand" type="button">Expand</button>',
  42. collapseBtnHTML: '<button class="dd-collapse" data-action="collapse" type="button">Collapse</button>',
  43. group: 0,
  44. maxDepth: 5,
  45. threshold: 20,
  46. fixedDepth: false, //fixed item's depth
  47. fixed: false,
  48. includeContent: false,
  49. scroll: false,
  50. scrollSensitivity: 1,
  51. scrollSpeed: 5,
  52. scrollTriggers: {
  53. top: 40,
  54. left: 40,
  55. right: -40,
  56. bottom: -40
  57. },
  58. effect: {
  59. animation: 'none',
  60. time: 'slow'
  61. },
  62. callback: function(l, e, p) {},
  63. onDragStart: function(l, e, p) {},
  64. beforeDragStop: function(l, e, p) {},
  65. listRenderer: function(children, options) {
  66. var html = '<' + options.listNodeName + ' class="' + options.listClass + '">';
  67. html += children;
  68. html += '</' + options.listNodeName + '>';
  69. return html;
  70. },
  71. itemRenderer: function(item_attrs, content, children, options, item) {
  72. var item_attrs_string = $.map(item_attrs, function(value, key) {
  73. return ' ' + key + '="' + value + '"';
  74. }).join(' ');
  75. var html = '<' + options.itemNodeName + item_attrs_string + '>';
  76. html += '<' + options.handleNodeName + ' class="' + options.handleClass + '">';
  77. html += '<' + options.contentNodeName + ' class="' + options.contentClass + '">';
  78. html += content;
  79. html += '</' + options.contentNodeName + '>';
  80. html += '</' + options.handleNodeName + '>';
  81. html += children;
  82. html += '</' + options.itemNodeName + '>';
  83. return html;
  84. }
  85. };
  86. function Plugin(element, options) {
  87. this.w = $(document);
  88. this.el = $(element);
  89. options = options || defaults;
  90. if (options.rootClass !== undefined && options.rootClass !== 'dd') {
  91. options.listClass = options.listClass ? options.listClass : options.rootClass + '-list';
  92. options.itemClass = options.itemClass ? options.itemClass : options.rootClass + '-item';
  93. options.dragClass = options.dragClass ? options.dragClass : options.rootClass + '-dragel';
  94. options.handleClass = options.handleClass ? options.handleClass : options.rootClass + '-handle';
  95. options.collapsedClass = options.collapsedClass ? options.collapsedClass : options.rootClass + '-collapsed';
  96. options.placeClass = options.placeClass ? options.placeClass : options.rootClass + '-placeholder';
  97. options.noDragClass = options.noDragClass ? options.noDragClass : options.rootClass + '-nodrag';
  98. options.noChildrenClass = options.noChildrenClass ? options.noChildrenClass : options.rootClass + '-nochildren';
  99. options.emptyClass = options.emptyClass ? options.emptyClass : options.rootClass + '-empty';
  100. }
  101. this.options = $.extend({}, defaults, options);
  102. // build HTML from serialized JSON if passed
  103. if (this.options.json !== undefined) {
  104. this._build();
  105. }
  106. this.init();
  107. }
  108. Plugin.prototype = {
  109. init: function() {
  110. var list = this;
  111. list.reset();
  112. list.el.data('nestable-group', this.options.group);
  113. list.placeEl = $('<div class="' + list.options.placeClass + '"/>');
  114. var items = this.el.find(list.options.itemNodeName);
  115. $.each(items, function(k, el) {
  116. var item = $(el),
  117. parent = item.parent();
  118. list.setParent(item);
  119. if (parent.hasClass(list.options.collapsedClass)) {
  120. list.collapseItem(parent.parent());
  121. }
  122. });
  123. // Append the .dd-empty div if the list don't have any items on init
  124. if (!items.length) {
  125. this.appendEmptyElement(this.el);
  126. }
  127. list.el.on('click', 'button', function(e) {
  128. if (list.dragEl) {
  129. return;
  130. }
  131. var target = $(e.currentTarget),
  132. action = target.data('action'),
  133. item = target.parents(list.options.itemNodeName).eq(0);
  134. if (action === 'collapse') {
  135. list.collapseItem(item);
  136. }
  137. if (action === 'expand') {
  138. list.expandItem(item);
  139. }
  140. });
  141. var onStartEvent = function(e) {
  142. var handle = $(e.target);
  143. if (!handle.hasClass(list.options.handleClass)) {
  144. if (handle.closest('.' + list.options.noDragClass).length) {
  145. return;
  146. }
  147. handle = handle.closest('.' + list.options.handleClass);
  148. }
  149. if (!handle.length || list.dragEl) {
  150. return;
  151. }
  152. list.isTouch = /^touch/.test(e.type);
  153. if (list.isTouch && e.touches.length !== 1) {
  154. return;
  155. }
  156. e.preventDefault();
  157. list.dragStart(e.touches ? e.touches[0] : e);
  158. };
  159. var onMoveEvent = function(e) {
  160. if (list.dragEl) {
  161. e.preventDefault();
  162. list.dragMove(e.touches ? e.touches[0] : e);
  163. }
  164. };
  165. var onEndEvent = function(e) {
  166. if (list.dragEl) {
  167. e.preventDefault();
  168. list.dragStop(e.touches ? e.changedTouches[0] : e);
  169. }
  170. };
  171. if (hasTouch) {
  172. list.el[0].addEventListener('touchstart', onStartEvent, false);
  173. window.addEventListener('touchmove', onMoveEvent, false);
  174. window.addEventListener('touchend', onEndEvent, false);
  175. window.addEventListener('touchcancel', onEndEvent, false);
  176. }
  177. list.el.on('mousedown', onStartEvent);
  178. list.w.on('mousemove', onMoveEvent);
  179. list.w.on('mouseup', onEndEvent);
  180. var destroyNestable = function()
  181. {
  182. if (hasTouch) {
  183. list.el[0].removeEventListener('touchstart', onStartEvent, false);
  184. window.removeEventListener('touchmove', onMoveEvent, false);
  185. window.removeEventListener('touchend', onEndEvent, false);
  186. window.removeEventListener('touchcancel', onEndEvent, false);
  187. }
  188. list.el.off('mousedown', onStartEvent);
  189. list.w.off('mousemove', onMoveEvent);
  190. list.w.off('mouseup', onEndEvent);
  191. list.el.off('click');
  192. list.el.unbind('destroy-nestable');
  193. list.el.data("nestable", null);
  194. };
  195. list.el.bind('destroy-nestable', destroyNestable);
  196. },
  197. destroy: function ()
  198. {
  199. this.el.trigger('destroy-nestable');
  200. },
  201. add: function (item)
  202. {
  203. var listClassSelector = '.' + this.options.listClass;
  204. var tree = $(this.el).children(listClassSelector);
  205. if (item.parent_id !== undefined) {
  206. tree = tree.find('[data-id="' + item.parent_id + '"]');
  207. delete item.parent_id;
  208. if (tree.children(listClassSelector).length === 0) {
  209. tree = tree.append(this.options.listRenderer('', this.options));
  210. }
  211. tree = tree.find(listClassSelector + ':first');
  212. this.setParent(tree.parent());
  213. }
  214. tree.append(this._buildItem(item, this.options));
  215. },
  216. replace: function (item)
  217. {
  218. var html = this._buildItem(item, this.options);
  219. this._getItemById(item.id)
  220. .replaceWith(html);
  221. },
  222. //removes item and additional elements from list
  223. removeItem: function (item){
  224. var opts = this.options,
  225. el = this.el;
  226. // remove item
  227. item = item || this;
  228. item.remove();
  229. // remove empty children lists
  230. var emptyListsSelector = '.' + opts.listClass
  231. + ' .' + opts.listClass + ':not(:has(*))';
  232. $(el).find(emptyListsSelector).remove();
  233. // remove buttons if parents do not have children
  234. var buttonsSelector = '[data-action="expand"], [data-action="collapse"]';
  235. $(el).find(buttonsSelector).each(function() {
  236. var siblings = $(this).siblings('.' + opts.listClass);
  237. if (siblings.length === 0) {
  238. $(this).remove();
  239. }
  240. });
  241. },
  242. //removes item by itemId and run callback at the end
  243. remove: function (itemId, callback)
  244. {
  245. var opts = this.options;
  246. var list = this;
  247. var item = this._getItemById(itemId);
  248. //animation style
  249. var animation = opts.effect.animation || 'fade';
  250. //animation time
  251. var time = opts.effect.time || 'slow';
  252. //add fadeOut effect when removing
  253. if (animation === 'fade'){
  254. item.fadeOut(time, function(){
  255. list.removeItem(item);
  256. });
  257. }
  258. else {
  259. this.removeItem(item);
  260. }
  261. if (callback) callback();
  262. },
  263. //removes all items from the list and run callback at the end
  264. removeAll: function(callback){
  265. var list = this,
  266. opts = this.options,
  267. node = list.el.find(opts.listNodeName).first(),
  268. items = node.children(opts.itemNodeName);
  269. //animation style
  270. var animation = opts.effect.animation || 'fade';
  271. //animation time
  272. var time = opts.effect.time || 'slow';
  273. function remove(){
  274. //Removes each item and its children.
  275. items.each(function() {
  276. list.removeItem($(this));
  277. });
  278. //Now we can again show our node element
  279. node.show();
  280. if (callback) callback();
  281. }
  282. //add fadeOut effect when removing
  283. if (animation === 'fade'){
  284. node.fadeOut(time, remove);
  285. }
  286. else {
  287. remove();
  288. }
  289. },
  290. _getItemById: function(itemId) {
  291. return $(this.el).children('.' + this.options.listClass)
  292. .find('[data-id="' + itemId + '"]');
  293. },
  294. _build: function() {
  295. var json = this.options.json;
  296. if (typeof json === 'string') {
  297. json = JSON.parse(json);
  298. }
  299. $(this.el).html(this._buildList(json, this.options));
  300. },
  301. _buildList: function(items, options) {
  302. if (!items) {
  303. return '';
  304. }
  305. var children = '';
  306. var that = this;
  307. $.each(items, function(index, sub) {
  308. children += that._buildItem(sub, options);
  309. });
  310. return options.listRenderer(children, options);
  311. },
  312. _buildItem: function(item, options) {
  313. function escapeHtml(text) {
  314. var map = {
  315. '&': '&amp;',
  316. '<': '&lt;',
  317. '>': '&gt;',
  318. '"': '&quot;',
  319. "'": '&#039;'
  320. };
  321. return text + "".replace(/[&<>"']/g, function(m) { return map[m]; });
  322. }
  323. function filterClasses(classes) {
  324. var new_classes = {};
  325. for (var k in classes) {
  326. // Remove duplicates
  327. new_classes[classes[k]] = classes[k];
  328. }
  329. return new_classes;
  330. }
  331. function createClassesString(item, options) {
  332. var classes = item.classes || {};
  333. if (typeof classes === 'string') {
  334. classes = [classes];
  335. }
  336. var item_classes = filterClasses(classes);
  337. item_classes[options.itemClass] = options.itemClass;
  338. // create class string
  339. return $.map(item_classes, function(val) {
  340. return val;
  341. }).join(' ');
  342. }
  343. function createDataAttrs(attr) {
  344. attr = $.extend({}, attr);
  345. delete attr.children;
  346. delete attr.classes;
  347. delete attr.content;
  348. var data_attrs = {};
  349. $.each(attr, function(key, value) {
  350. if (typeof value === 'object') {
  351. value = JSON.stringify(value);
  352. }
  353. data_attrs["data-" + key] = escapeHtml(value);
  354. });
  355. return data_attrs;
  356. }
  357. var item_attrs = createDataAttrs(item);
  358. item_attrs["class"] = createClassesString(item, options);
  359. var content = options.contentCallback(item);
  360. var children = this._buildList(item.children, options);
  361. var html = $(options.itemRenderer(item_attrs, content, children, options, item));
  362. this.setParent(html);
  363. return html[0].outerHTML;
  364. },
  365. serialize: function() {
  366. var data, list = this, step = function(level) {
  367. var array = [],
  368. items = level.children(list.options.itemNodeName);
  369. items.each(function() {
  370. var li = $(this),
  371. item = $.extend({}, li.data()),
  372. sub = li.children(list.options.listNodeName);
  373. if (list.options.includeContent) {
  374. var content = li.find('.' + list.options.contentClass).html();
  375. if (content) {
  376. item.content = content;
  377. }
  378. }
  379. if (sub.length) {
  380. item.children = step(sub);
  381. }
  382. array.push(item);
  383. });
  384. return array;
  385. };
  386. data = step(list.el.find(list.options.listNodeName).first());
  387. return data;
  388. },
  389. asNestedSet: function() {
  390. var list = this, o = list.options, depth = -1, ret = [], lft = 1;
  391. var items = list.el.find(o.listNodeName).first().children(o.itemNodeName);
  392. items.each(function () {
  393. lft = traverse(this, depth + 1, lft);
  394. });
  395. ret = ret.sort(function(a,b){ return (a.lft - b.lft); });
  396. return ret;
  397. function traverse(item, depth, lft) {
  398. var rgt = lft + 1, id, pid;
  399. if ($(item).children(o.listNodeName).children(o.itemNodeName).length > 0 ) {
  400. depth++;
  401. $(item).children(o.listNodeName).children(o.itemNodeName).each(function () {
  402. rgt = traverse($(this), depth, rgt);
  403. });
  404. depth--;
  405. }
  406. id = $(item).attr('data-id');
  407. if (isInt(id)) {
  408. id = parseInt(id);
  409. }
  410. pid = $(item).parent(o.listNodeName).parent(o.itemNodeName).attr('data-id') || '';
  411. if (isInt(pid)) {
  412. pid = parseInt(pid);
  413. }
  414. if (id) {
  415. ret.push({"id": id, "parent_id": pid, "depth": depth, "lft": lft, "rgt": rgt});
  416. }
  417. lft = rgt + 1;
  418. return lft;
  419. }
  420. function isInt(value) {
  421. return $.isNumeric(value) && Math.floor(value) == value;
  422. }
  423. },
  424. returnOptions: function() {
  425. return this.options;
  426. },
  427. serialise: function() {
  428. return this.serialize();
  429. },
  430. toHierarchy: function(options) {
  431. var o = $.extend({}, this.options, options),
  432. ret = [];
  433. $(this.element).children(o.items).each(function() {
  434. var level = _recursiveItems(this);
  435. ret.push(level);
  436. });
  437. return ret;
  438. function _recursiveItems(item) {
  439. var id = ($(item).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
  440. if (id) {
  441. var currentItem = {
  442. "id": id[2]
  443. };
  444. if ($(item).children(o.listType).children(o.items).length > 0) {
  445. currentItem.children = [];
  446. $(item).children(o.listType).children(o.items).each(function() {
  447. var level = _recursiveItems(this);
  448. currentItem.children.push(level);
  449. });
  450. }
  451. return currentItem;
  452. }
  453. }
  454. },
  455. toArray: function() {
  456. var o = $.extend({}, this.options, this),
  457. sDepth = o.startDepthCount || 0,
  458. ret = [],
  459. left = 2,
  460. list = this,
  461. element = list.el.find(list.options.listNodeName).first();
  462. var items = element.children(list.options.itemNodeName);
  463. items.each(function() {
  464. left = _recursiveArray($(this), sDepth + 1, left);
  465. });
  466. ret = ret.sort(function(a, b) {
  467. return (a.left - b.left);
  468. });
  469. return ret;
  470. function _recursiveArray(item, depth, left) {
  471. var right = left + 1,
  472. id,
  473. pid;
  474. if (item.children(o.options.listNodeName).children(o.options.itemNodeName).length > 0) {
  475. depth++;
  476. item.children(o.options.listNodeName).children(o.options.itemNodeName).each(function() {
  477. right = _recursiveArray($(this), depth, right);
  478. });
  479. depth--;
  480. }
  481. id = item.data().id;
  482. if (depth === sDepth + 1) {
  483. pid = o.rootID;
  484. } else {
  485. var parentItem = (item.parent(o.options.listNodeName)
  486. .parent(o.options.itemNodeName)
  487. .data());
  488. pid = parentItem.id;
  489. }
  490. if (id) {
  491. ret.push({
  492. "id": id,
  493. "parent_id": pid,
  494. "depth": depth,
  495. "left": left,
  496. "right": right
  497. });
  498. }
  499. left = right + 1;
  500. return left;
  501. }
  502. },
  503. reset: function() {
  504. this.mouse = {
  505. offsetX: 0,
  506. offsetY: 0,
  507. startX: 0,
  508. startY: 0,
  509. lastX: 0,
  510. lastY: 0,
  511. nowX: 0,
  512. nowY: 0,
  513. distX: 0,
  514. distY: 0,
  515. dirAx: 0,
  516. dirX: 0,
  517. dirY: 0,
  518. lastDirX: 0,
  519. lastDirY: 0,
  520. distAxX: 0,
  521. distAxY: 0
  522. };
  523. this.isTouch = false;
  524. this.moving = false;
  525. this.dragEl = null;
  526. this.dragRootEl = null;
  527. this.dragDepth = 0;
  528. this.hasNewRoot = false;
  529. this.pointEl = null;
  530. },
  531. expandItem: function(li) {
  532. li.removeClass(this.options.collapsedClass);
  533. },
  534. collapseItem: function(li) {
  535. var lists = li.children(this.options.listNodeName);
  536. if (lists.length) {
  537. li.addClass(this.options.collapsedClass);
  538. }
  539. },
  540. expandAll: function() {
  541. var list = this;
  542. list.el.find(list.options.itemNodeName).each(function() {
  543. list.expandItem($(this));
  544. });
  545. },
  546. collapseAll: function() {
  547. var list = this;
  548. list.el.find(list.options.itemNodeName).each(function() {
  549. list.collapseItem($(this));
  550. });
  551. },
  552. setParent: function(li) {
  553. //Check if li is an element of itemNodeName type and has children
  554. if (li.is(this.options.itemNodeName) && li.children(this.options.listNodeName).length) {
  555. // make sure NOT showing two or more sets data-action buttons
  556. li.children('[data-action]').remove();
  557. li.prepend($(this.options.expandBtnHTML));
  558. li.prepend($(this.options.collapseBtnHTML));
  559. }
  560. },
  561. unsetParent: function(li) {
  562. li.removeClass(this.options.collapsedClass);
  563. li.children('[data-action]').remove();
  564. li.children(this.options.listNodeName).remove();
  565. },
  566. dragStart: function(e) {
  567. var mouse = this.mouse,
  568. target = $(e.target),
  569. dragItem = target.closest(this.options.itemNodeName),
  570. position = {
  571. top : e.pageY,
  572. left : e.pageX
  573. };
  574. var continueExecution = this.options.onDragStart.call(this, this.el, dragItem, position);
  575. if (typeof continueExecution !== 'undefined' && continueExecution === false) {
  576. return;
  577. }
  578. this.placeEl.css('height', dragItem.height());
  579. mouse.offsetX = e.pageX - dragItem.offset().left;
  580. mouse.offsetY = e.pageY - dragItem.offset().top;
  581. mouse.startX = mouse.lastX = e.pageX;
  582. mouse.startY = mouse.lastY = e.pageY;
  583. this.dragRootEl = this.el;
  584. this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass);
  585. this.dragEl.css('width', dragItem.outerWidth());
  586. this.setIndexOfItem(dragItem);
  587. // fix for zepto.js
  588. //dragItem.after(this.placeEl).detach().appendTo(this.dragEl);
  589. dragItem.after(this.placeEl);
  590. dragItem[0].parentNode.removeChild(dragItem[0]);
  591. dragItem.appendTo(this.dragEl);
  592. $(document.body).append(this.dragEl);
  593. this.dragEl.css({
  594. 'left': e.pageX - mouse.offsetX,
  595. 'top': e.pageY - mouse.offsetY
  596. });
  597. // total depth of dragging item
  598. var i, depth,
  599. items = this.dragEl.find(this.options.itemNodeName);
  600. for (i = 0; i < items.length; i++) {
  601. depth = $(items[i]).parents(this.options.listNodeName).length;
  602. if (depth > this.dragDepth) {
  603. this.dragDepth = depth;
  604. }
  605. }
  606. },
  607. //Create sublevel.
  608. // element : element which become parent
  609. // item : something to place into new sublevel
  610. createSubLevel: function(element, item) {
  611. var list = $('<' + this.options.listNodeName + '/>').addClass(this.options.listClass);
  612. if (item) list.append(item);
  613. element.append(list);
  614. this.setParent(element);
  615. return list;
  616. },
  617. setIndexOfItem: function(item, index) {
  618. index = index || [];
  619. index.unshift(item.index());
  620. if ($(item[0].parentNode)[0] !== this.dragRootEl[0]) {
  621. this.setIndexOfItem($(item[0].parentNode), index);
  622. }
  623. else {
  624. this.dragEl.data('indexOfItem', index);
  625. }
  626. },
  627. restoreItemAtIndex: function(dragElement, indexArray) {
  628. var currentEl = this.el,
  629. lastIndex = indexArray.length - 1;
  630. //Put drag element at current element position.
  631. function placeElement(currentEl, dragElement) {
  632. if (indexArray[lastIndex] === 0) {
  633. $(currentEl).prepend(dragElement.clone(true)); //using true saves added to element events.
  634. }
  635. else {
  636. $(currentEl.children[indexArray[lastIndex] - 1]).after(dragElement.clone(true)); //using true saves added to element events.
  637. }
  638. }
  639. //Diggin through indexArray to get home for dragElement.
  640. for (var i = 0; i < indexArray.length; i++) {
  641. if (lastIndex === parseInt(i)) {
  642. placeElement(currentEl, dragElement);
  643. return;
  644. }
  645. //element can have no indexes, so we have to use conditional here to avoid errors.
  646. //if element doesn't exist we defenetly need to add new list.
  647. var element = (currentEl[0]) ? currentEl[0] : currentEl;
  648. var nextEl = element.children[indexArray[i]];
  649. currentEl = (!nextEl) ? this.createSubLevel($(element)) : nextEl;
  650. }
  651. },
  652. dragStop: function(e) {
  653. // fix for zepto.js
  654. //this.placeEl.replaceWith(this.dragEl.children(this.options.itemNodeName + ':first').detach());
  655. var position = {
  656. top : e.pageY,
  657. left : e.pageX
  658. };
  659. //Get indexArray of item at drag start.
  660. var srcIndex = this.dragEl.data('indexOfItem');
  661. var el = this.dragEl.children(this.options.itemNodeName).first();
  662. el[0].parentNode.removeChild(el[0]);
  663. this.dragEl.remove(); //Remove dragEl, cause it can affect on indexing in html collection.
  664. //Before drag stop callback
  665. var continueExecution = this.options.beforeDragStop.call(this, this.el, el, this.placeEl.parent());
  666. if (typeof continueExecution !== 'undefined' && continueExecution === false) {
  667. var parent = this.placeEl.parent();
  668. this.placeEl.remove();
  669. if (!parent.children().length) {
  670. this.unsetParent(parent.parent());
  671. }
  672. this.restoreItemAtIndex(el, srcIndex);
  673. this.reset();
  674. return;
  675. }
  676. this.placeEl.replaceWith(el);
  677. if (this.hasNewRoot) {
  678. if (this.options.fixed === true) {
  679. this.restoreItemAtIndex(el, srcIndex);
  680. }
  681. else {
  682. this.el.trigger('lostItem');
  683. }
  684. this.dragRootEl.trigger('gainedItem');
  685. }
  686. else {
  687. this.dragRootEl.trigger('change');
  688. }
  689. this.options.callback.call(this, this.dragRootEl, el, position);
  690. this.reset();
  691. },
  692. dragMove: function(e) {
  693. var list, parent, prev, next, depth,
  694. opt = this.options,
  695. mouse = this.mouse;
  696. this.dragEl.css({
  697. 'left': e.pageX - mouse.offsetX,
  698. 'top': e.pageY - mouse.offsetY
  699. });
  700. // mouse position last events
  701. mouse.lastX = mouse.nowX;
  702. mouse.lastY = mouse.nowY;
  703. // mouse position this events
  704. mouse.nowX = e.pageX;
  705. mouse.nowY = e.pageY;
  706. // distance mouse moved between events
  707. mouse.distX = mouse.nowX - mouse.lastX;
  708. mouse.distY = mouse.nowY - mouse.lastY;
  709. // direction mouse was moving
  710. mouse.lastDirX = mouse.dirX;
  711. mouse.lastDirY = mouse.dirY;
  712. // direction mouse is now moving (on both axis)
  713. mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
  714. mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
  715. // axis mouse is now moving on
  716. var newAx = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;
  717. // do nothing on first move
  718. if (!mouse.moving) {
  719. mouse.dirAx = newAx;
  720. mouse.moving = true;
  721. return;
  722. }
  723. // do scrolling if enable
  724. if (opt.scroll) {
  725. if (typeof window.jQuery.fn.scrollParent !== 'undefined') {
  726. var scrolled = false;
  727. var scrollParent = this.el.scrollParent()[0];
  728. if (scrollParent !== document && scrollParent.tagName !== 'HTML') {
  729. if ((opt.scrollTriggers.bottom + scrollParent.offsetHeight) - e.pageY < opt.scrollSensitivity)
  730. scrollParent.scrollTop = scrolled = scrollParent.scrollTop + opt.scrollSpeed;
  731. else if (e.pageY - opt.scrollTriggers.top < opt.scrollSensitivity)
  732. scrollParent.scrollTop = scrolled = scrollParent.scrollTop - opt.scrollSpeed;
  733. if ((opt.scrollTriggers.right + scrollParent.offsetWidth) - e.pageX < opt.scrollSensitivity)
  734. scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + opt.scrollSpeed;
  735. else if (e.pageX - opt.scrollTriggers.left < opt.scrollSensitivity)
  736. scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - opt.scrollSpeed;
  737. } else {
  738. if (e.pageY - $(document).scrollTop() < opt.scrollSensitivity)
  739. scrolled = $(document).scrollTop($(document).scrollTop() - opt.scrollSpeed);
  740. else if ($(window).height() - (e.pageY - $(document).scrollTop()) < opt.scrollSensitivity)
  741. scrolled = $(document).scrollTop($(document).scrollTop() + opt.scrollSpeed);
  742. if (e.pageX - $(document).scrollLeft() < opt.scrollSensitivity)
  743. scrolled = $(document).scrollLeft($(document).scrollLeft() - opt.scrollSpeed);
  744. else if ($(window).width() - (e.pageX - $(document).scrollLeft()) < opt.scrollSensitivity)
  745. scrolled = $(document).scrollLeft($(document).scrollLeft() + opt.scrollSpeed);
  746. }
  747. } else {
  748. console.warn('To use scrolling you need to have scrollParent() function, check documentation for more information');
  749. }
  750. }
  751. if (this.scrollTimer) {
  752. clearTimeout(this.scrollTimer);
  753. }
  754. if (opt.scroll && scrolled) {
  755. this.scrollTimer = setTimeout(function() {
  756. $(window).trigger(e);
  757. }, 10);
  758. }
  759. // calc distance moved on this axis (and direction)
  760. if (mouse.dirAx !== newAx) {
  761. mouse.distAxX = 0;
  762. mouse.distAxY = 0;
  763. }
  764. else {
  765. mouse.distAxX += Math.abs(mouse.distX);
  766. if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
  767. mouse.distAxX = 0;
  768. }
  769. mouse.distAxY += Math.abs(mouse.distY);
  770. if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
  771. mouse.distAxY = 0;
  772. }
  773. }
  774. mouse.dirAx = newAx;
  775. /**
  776. * move horizontal
  777. */
  778. if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
  779. // reset move distance on x-axis for new phase
  780. mouse.distAxX = 0;
  781. prev = this.placeEl.prev(opt.itemNodeName);
  782. // increase horizontal level if previous sibling exists, is not collapsed, and can have children
  783. if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass) && !prev.hasClass(opt.noChildrenClass)) {
  784. // cannot increase level when item above is collapsed
  785. list = prev.find(opt.listNodeName).last();
  786. // check if depth limit has reached
  787. depth = this.placeEl.parents(opt.listNodeName).length;
  788. if (depth + this.dragDepth <= opt.maxDepth) {
  789. // create new sub-level if one doesn't exist
  790. if (!list.length) {
  791. this.createSubLevel(prev, this.placeEl);
  792. }
  793. else {
  794. // else append to next level up
  795. list = prev.children(opt.listNodeName).last();
  796. list.append(this.placeEl);
  797. }
  798. }
  799. }
  800. // decrease horizontal level
  801. if (mouse.distX < 0) {
  802. // we can't decrease a level if an item preceeds the current one
  803. next = this.placeEl.next(opt.itemNodeName);
  804. if (!next.length) {
  805. parent = this.placeEl.parent();
  806. this.placeEl.closest(opt.itemNodeName).after(this.placeEl);
  807. if (!parent.children().length) {
  808. this.unsetParent(parent.parent());
  809. }
  810. }
  811. }
  812. }
  813. var isEmpty = false;
  814. // find list item under cursor
  815. if (!hasPointerEvents) {
  816. this.dragEl[0].style.visibility = 'hidden';
  817. }
  818. this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop)));
  819. if (!hasPointerEvents) {
  820. this.dragEl[0].style.visibility = 'visible';
  821. }
  822. if (this.pointEl.hasClass(opt.handleClass)) {
  823. this.pointEl = this.pointEl.closest(opt.itemNodeName);
  824. }
  825. if (this.pointEl.hasClass(opt.emptyClass)) {
  826. isEmpty = true;
  827. }
  828. else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) {
  829. return;
  830. }
  831. // find parent list of item under cursor
  832. var pointElRoot = this.pointEl.closest('.' + opt.rootClass),
  833. isNewRoot = this.dragRootEl.data('nestable-id') !== pointElRoot.data('nestable-id');
  834. /**
  835. * move vertical
  836. */
  837. if (!mouse.dirAx || isNewRoot || isEmpty) {
  838. // check if groups match if dragging over new root
  839. if (isNewRoot && opt.group !== pointElRoot.data('nestable-group')) {
  840. return;
  841. }
  842. // fixed item's depth, use for some list has specific type, eg:'Volume, Section, Chapter ...'
  843. if (this.options.fixedDepth && this.dragDepth + 1 !== this.pointEl.parents(opt.listNodeName).length) {
  844. return;
  845. }
  846. // check depth limit
  847. depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;
  848. if (depth > opt.maxDepth) {
  849. return;
  850. }
  851. var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
  852. parent = this.placeEl.parent();
  853. // if empty create new list to replace empty placeholder
  854. if (isEmpty) {
  855. list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass);
  856. list.append(this.placeEl);
  857. this.pointEl.replaceWith(list);
  858. }
  859. else if (before) {
  860. this.pointEl.before(this.placeEl);
  861. }
  862. else {
  863. this.pointEl.after(this.placeEl);
  864. }
  865. if (!parent.children().length) {
  866. this.unsetParent(parent.parent());
  867. }
  868. if (!this.dragRootEl.find(opt.itemNodeName).length) {
  869. this.appendEmptyElement(this.dragRootEl);
  870. }
  871. // parent root list has changed
  872. this.dragRootEl = pointElRoot;
  873. if (isNewRoot) {
  874. this.hasNewRoot = this.el[0] !== this.dragRootEl[0];
  875. }
  876. }
  877. },
  878. // Append the .dd-empty div to the list so it can be populated and styled
  879. appendEmptyElement: function(element) {
  880. element.append('<div class="' + this.options.emptyClass + '"/>');
  881. }
  882. };
  883. $.fn.nestable = function(params) {
  884. var lists = this,
  885. retval = this,
  886. args = arguments;
  887. if (!('Nestable' in window)) {
  888. window.Nestable = {};
  889. Nestable.counter = 0;
  890. }
  891. lists.each(function() {
  892. var plugin = $(this).data("nestable");
  893. if (!plugin) {
  894. Nestable.counter++;
  895. $(this).data("nestable", new Plugin(this, params));
  896. $(this).data("nestable-id", Nestable.counter);
  897. }
  898. else {
  899. if (typeof params === 'string' && typeof plugin[params] === 'function') {
  900. if (args.length > 1){
  901. var pluginArgs = [];
  902. for (var i = 1; i < args.length; i++) {
  903. pluginArgs.push(args[i]);
  904. }
  905. retval = plugin[params].apply(plugin, pluginArgs);
  906. }
  907. else {
  908. retval = plugin[params]();
  909. }
  910. }
  911. }
  912. });
  913. return retval || lists;
  914. };
  915. })(window.jQuery || window.Zepto, window, document);