My personal site (brandoncornejo.name) (binaryatrocity.name)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

591 lines
21 KiB

10 years ago
  1. /*! UIkit 2.3.1 | http://www.getuikit.com | (c) 2014 YOOtheme | MIT License */
  2. /*
  3. * Based on Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/
  4. */
  5. (function($, UI, window, document, undefined) {
  6. var hasTouch = 'ontouchstart' in window,
  7. html = $("html"),
  8. touchedlists = [];
  9. /**
  10. * Detect CSS pointer-events property
  11. * events are normally disabled on the dragging element to avoid conflicts
  12. * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js
  13. */
  14. var hasPointerEvents = (function()
  15. {
  16. var el = document.createElement('div'),
  17. docEl = document.documentElement;
  18. if (!('pointerEvents' in el.style)) {
  19. return false;
  20. }
  21. el.style.pointerEvents = 'auto';
  22. el.style.pointerEvents = 'x';
  23. docEl.appendChild(el);
  24. var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto';
  25. docEl.removeChild(el);
  26. return !!supports;
  27. })();
  28. var eStart = hasTouch ? 'touchstart' : 'mousedown',
  29. eMove = hasTouch ? 'touchmove' : 'mousemove',
  30. eEnd = hasTouch ? 'touchend' : 'mouseup',
  31. eCancel = hasTouch ? 'touchcancel' : 'mouseup';
  32. function Plugin(element, options)
  33. {
  34. var $element = $(element);
  35. if($element.data("uksortable")) return;
  36. this.w = $(window);
  37. this.el = $element;
  38. this.options = $.extend({}, $.fn.uksortable.defaults, options);
  39. this.tplempty = '<div class="' + this.options.emptyClass + '"/>';
  40. this.el.find(">"+this.options.itemNodeName).addClass(this.options.listitemClass).end().find("ul:not(.ignore-list)").addClass(this.options.listClass).find(">li").addClass(this.options.listitemClass);
  41. if(!this.el.children(this.options.itemNodeName).length) {
  42. this.el.append(this.tplempty);
  43. }
  44. this.el.data("uksortable", this);
  45. this.el.data("uksortable-id", "ID"+(new Date().getTime())+"RAND"+(Math.ceil(Math.random() *100000)));
  46. this.init();
  47. }
  48. Plugin.prototype = {
  49. init: function()
  50. {
  51. var list = this;
  52. list.reset();
  53. list.el.data('uksortable-group', this.options.group);
  54. list.placeEl = $('<div class="' + list.options.placeClass + '"/>');
  55. $.each(this.el.find(list.options.itemNodeName), function(k, el)
  56. {
  57. list.setParent($(el));
  58. });
  59. list.el.on('click', '[data-sortable-action]', function(e)
  60. {
  61. if (list.dragEl || (!hasTouch && e.button !== 0)) {
  62. return;
  63. }
  64. e.preventDefault();
  65. var target = $(e.currentTarget),
  66. action = target.data('sortableAction'),
  67. item = target.closest(list.options.itemNodeName);
  68. if (action === 'collapse') {
  69. list.collapseItem(item);
  70. }
  71. if (action === 'expand') {
  72. list.expandItem(item);
  73. }
  74. if (action === 'toggle') {
  75. list.toggleItem(item);
  76. }
  77. });
  78. var onStartEvent = function(e)
  79. {
  80. var handle = $(e.target);
  81. if (!handle.hasClass(list.options.handleClass)) {
  82. if (handle.closest('.' + list.options.noDragClass).length) {
  83. return;
  84. }
  85. handle = handle.closest('.' + list.options.handleClass);
  86. }
  87. if (!handle.length || list.dragEl || (!hasTouch && e.button !== 0) || (hasTouch && e.touches.length !== 1)) {
  88. return;
  89. }
  90. e.preventDefault();
  91. list.dragStart(hasTouch ? e.touches[0] : e);
  92. };
  93. var onMoveEvent = function(e)
  94. {
  95. if (list.dragEl) {
  96. e.preventDefault();
  97. list.dragMove(hasTouch ? e.touches[0] : e);
  98. }
  99. };
  100. var onEndEvent = function(e)
  101. {
  102. if (list.dragEl) {
  103. e.preventDefault();
  104. list.dragStop(hasTouch ? e.touches[0] : e);
  105. }
  106. };
  107. if (hasTouch) {
  108. list.el[0].addEventListener(eStart, onStartEvent, false);
  109. window.addEventListener(eMove, onMoveEvent, false);
  110. window.addEventListener(eEnd, onEndEvent, false);
  111. window.addEventListener(eCancel, onEndEvent, false);
  112. } else {
  113. list.el.on(eStart, onStartEvent);
  114. list.w.on(eMove, onMoveEvent);
  115. list.w.on(eEnd, onEndEvent);
  116. }
  117. },
  118. serialize: function()
  119. {
  120. var data,
  121. depth = 0,
  122. list = this;
  123. step = function(level, depth)
  124. {
  125. var array = [ ],
  126. items = level.children(list.options.itemNodeName);
  127. items.each(function()
  128. {
  129. var li = $(this),
  130. item = $.extend({}, li.data()),
  131. sub = li.children(list.options.listNodeName);
  132. if (sub.length) {
  133. item.children = step(sub, depth + 1);
  134. }
  135. array.push(item);
  136. });
  137. return array;
  138. };
  139. data = step(list.el, depth);
  140. return data;
  141. },
  142. list: function(options)
  143. {
  144. var data = [],
  145. list = this,
  146. depth = 0,
  147. options = $.extend({}, list.options, options),
  148. step = function(level, depth, parent)
  149. {
  150. var items = level.children(options.itemNodeName);
  151. items.each(function(index)
  152. {
  153. var li = $(this),
  154. item = $.extend({parent_id: (parent ? parent : null), depth: depth, order: index}, li.data()),
  155. sub = li.children(options.listNodeName);
  156. data.push(item);
  157. if (sub.length) {
  158. step(sub, depth + 1, li.data(options.idProperty || 'id'));
  159. }
  160. });
  161. };
  162. step(list.el, depth);
  163. return data;
  164. },
  165. reset: function()
  166. {
  167. this.mouse = {
  168. offsetX : 0,
  169. offsetY : 0,
  170. startX : 0,
  171. startY : 0,
  172. lastX : 0,
  173. lastY : 0,
  174. nowX : 0,
  175. nowY : 0,
  176. distX : 0,
  177. distY : 0,
  178. dirAx : 0,
  179. dirX : 0,
  180. dirY : 0,
  181. lastDirX : 0,
  182. lastDirY : 0,
  183. distAxX : 0,
  184. distAxY : 0
  185. };
  186. this.moving = false;
  187. this.dragEl = null;
  188. this.dragRootEl = null;
  189. this.dragDepth = 0;
  190. this.hasNewRoot = false;
  191. this.pointEl = null;
  192. for(var i=0; i<touchedlists.length; i++) {
  193. if(!touchedlists[i].children().length) {
  194. touchedlists[i].append(this.tplempty);
  195. }
  196. }
  197. touchedlists = [];
  198. },
  199. toggleItem: function(li) {
  200. this[li.hasClass(this.options.collapsedClass) ? "expandItem":"collapseItem"](li);
  201. },
  202. expandItem: function(li)
  203. {
  204. li.removeClass(this.options.collapsedClass);
  205. },
  206. collapseItem: function(li)
  207. {
  208. var lists = li.children(this.options.listNodeName);
  209. if (lists.length) {
  210. li.addClass(this.options.collapsedClass);
  211. }
  212. },
  213. expandAll: function()
  214. {
  215. var list = this;
  216. list.el.find(list.options.itemNodeName).each(function()
  217. {
  218. list.expandItem($(this));
  219. });
  220. },
  221. collapseAll: function()
  222. {
  223. var list = this;
  224. list.el.find(list.options.itemNodeName).each(function()
  225. {
  226. list.collapseItem($(this));
  227. });
  228. },
  229. setParent: function(li)
  230. {
  231. if (li.children(this.options.listNodeName).length) {
  232. li.addClass("uk-parent");
  233. }
  234. },
  235. unsetParent: function(li)
  236. {
  237. li.removeClass('uk-parent '+this.options.collapsedClass);
  238. li.children(this.options.listNodeName).remove();
  239. },
  240. dragStart: function(e)
  241. {
  242. var mouse = this.mouse,
  243. target = $(e.target),
  244. dragItem = target.closest(this.options.itemNodeName),
  245. offset = dragItem.offset();
  246. this.placeEl.css('height', dragItem.height());
  247. mouse.offsetX = e.pageX - offset.left;
  248. mouse.offsetY = e.pageY - offset.top;
  249. mouse.startX = mouse.lastX = offset.left;
  250. mouse.startY = mouse.lastY = offset.top;
  251. this.dragRootEl = this.el;
  252. this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass);
  253. this.dragEl.css('width', dragItem.width());
  254. this.tmpDragOnSiblings = [dragItem[0].previousSibling, dragItem[0].nextSibling];
  255. // fix for zepto.js
  256. //dragItem.after(this.placeEl).detach().appendTo(this.dragEl);
  257. dragItem.after(this.placeEl);
  258. dragItem[0].parentNode.removeChild(dragItem[0]);
  259. dragItem.appendTo(this.dragEl);
  260. $(document.body).append(this.dragEl);
  261. this.dragEl.css({
  262. left : offset.left,
  263. top : offset.top
  264. });
  265. // total depth of dragging item
  266. var i, depth,
  267. items = this.dragEl.find(this.options.itemNodeName);
  268. for (i = 0; i < items.length; i++) {
  269. depth = $(items[i]).parents(this.options.listNodeName).length;
  270. if (depth > this.dragDepth) {
  271. this.dragDepth = depth;
  272. }
  273. }
  274. html.addClass(this.options.movingClass);
  275. },
  276. dragStop: function(e)
  277. {
  278. // fix for zepto.js
  279. //this.placeEl.replaceWith(this.dragEl.children(this.options.itemNodeName + ':first').detach());
  280. var el = this.dragEl.children(this.options.itemNodeName).first();
  281. el[0].parentNode.removeChild(el[0]);
  282. this.placeEl.replaceWith(el);
  283. this.dragEl.remove();
  284. if (this.tmpDragOnSiblings[0]!=el[0].previousSibling || this.tmpDragOnSiblings[0]!=el[0].previousSibling) {
  285. this.el.trigger('sortable-change',[el, this.hasNewRoot ? "added":"moved"]);
  286. if (this.hasNewRoot) {
  287. this.dragRootEl.trigger('sortable-change', [el, "removed"]);
  288. }
  289. }
  290. this.reset();
  291. html.removeClass(this.options.movingClass);
  292. },
  293. dragMove: function(e)
  294. {
  295. var list, parent, prev, next, depth,
  296. opt = this.options,
  297. mouse = this.mouse;
  298. this.dragEl.css({
  299. left : e.pageX - mouse.offsetX,
  300. top : e.pageY - mouse.offsetY
  301. });
  302. // mouse position last events
  303. mouse.lastX = mouse.nowX;
  304. mouse.lastY = mouse.nowY;
  305. // mouse position this events
  306. mouse.nowX = e.pageX;
  307. mouse.nowY = e.pageY;
  308. // distance mouse moved between events
  309. mouse.distX = mouse.nowX - mouse.lastX;
  310. mouse.distY = mouse.nowY - mouse.lastY;
  311. // direction mouse was moving
  312. mouse.lastDirX = mouse.dirX;
  313. mouse.lastDirY = mouse.dirY;
  314. // direction mouse is now moving (on both axis)
  315. mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
  316. mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
  317. // axis mouse is now moving on
  318. var newAx = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;
  319. // do nothing on first move
  320. if (!mouse.moving) {
  321. mouse.dirAx = newAx;
  322. mouse.moving = true;
  323. return;
  324. }
  325. // calc distance moved on this axis (and direction)
  326. if (mouse.dirAx !== newAx) {
  327. mouse.distAxX = 0;
  328. mouse.distAxY = 0;
  329. } else {
  330. mouse.distAxX += Math.abs(mouse.distX);
  331. if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
  332. mouse.distAxX = 0;
  333. }
  334. mouse.distAxY += Math.abs(mouse.distY);
  335. if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
  336. mouse.distAxY = 0;
  337. }
  338. }
  339. mouse.dirAx = newAx;
  340. /**
  341. * move horizontal
  342. */
  343. if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
  344. // reset move distance on x-axis for new phase
  345. mouse.distAxX = 0;
  346. prev = this.placeEl.prev(opt.itemNodeName);
  347. // increase horizontal level if previous sibling exists and is not collapsed
  348. if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass)) {
  349. // cannot increase level when item above is collapsed
  350. list = prev.find(opt.listNodeName).last();
  351. // check if depth limit has reached
  352. depth = this.placeEl.parents(opt.listNodeName).length;
  353. if (depth + this.dragDepth <= opt.maxDepth) {
  354. // create new sub-level if one doesn't exist
  355. if (!list.length) {
  356. list = $('<' + opt.listNodeName + '/>').addClass(opt.listClass);
  357. list.append(this.placeEl);
  358. prev.append(list);
  359. this.setParent(prev);
  360. } else {
  361. // else append to next level up
  362. list = prev.children(opt.listNodeName).last();
  363. list.append(this.placeEl);
  364. }
  365. }
  366. }
  367. // decrease horizontal level
  368. if (mouse.distX < 0) {
  369. // we can't decrease a level if an item preceeds the current one
  370. next = this.placeEl.next(opt.itemNodeName);
  371. if (!next.length) {
  372. parent = this.placeEl.parent();
  373. this.placeEl.closest(opt.itemNodeName).after(this.placeEl);
  374. if (!parent.children().length) {
  375. this.unsetParent(parent.parent());
  376. }
  377. }
  378. }
  379. }
  380. var isEmpty = false;
  381. // find list item under cursor
  382. if (!hasPointerEvents) {
  383. this.dragEl[0].style.visibility = 'hidden';
  384. }
  385. this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop)));
  386. if (!hasPointerEvents) {
  387. this.dragEl[0].style.visibility = 'visible';
  388. }
  389. if (this.pointEl.hasClass(opt.handleClass)) {
  390. this.pointEl = this.pointEl.closest(opt.itemNodeName);
  391. } else {
  392. var sortableitem = this.pointEl.closest('.'+opt.itemClass);
  393. if(sortableitem.length) {
  394. this.pointEl = sortableitem.closest(opt.itemNodeName);
  395. }
  396. }
  397. if (this.pointEl.hasClass(opt.emptyClass)) {
  398. isEmpty = true;
  399. }
  400. else if (this.pointEl.data('uksortable') && !this.pointEl.children().length) {
  401. isEmpty = true;
  402. this.pointEl = $(this.tplempty).appendTo(this.pointEl);
  403. }
  404. else if (!this.pointEl.length || !this.pointEl.hasClass(opt.listitemClass)) {
  405. return;
  406. }
  407. // find parent list of item under cursor
  408. var pointElRoot = this.el,
  409. tmpRoot = this.pointEl.closest('.'+this.options.listBaseClass),
  410. isNewRoot = pointElRoot[0] !== this.pointEl.closest('.'+this.options.listBaseClass)[0],
  411. $newRoot = tmpRoot;
  412. /**
  413. * move vertical
  414. */
  415. if (!mouse.dirAx || isNewRoot || isEmpty) {
  416. // check if groups match if dragging over new root
  417. if (isNewRoot && opt.group !== $newRoot.data('uksortable-group')) {
  418. return;
  419. } else {
  420. touchedlists.push(pointElRoot);
  421. }
  422. // check depth limit
  423. depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;
  424. if (depth > opt.maxDepth) {
  425. return;
  426. }
  427. var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
  428. parent = this.placeEl.parent();
  429. // if empty create new list to replace empty placeholder
  430. if (isEmpty) {
  431. this.pointEl.replaceWith(this.placeEl);
  432. }
  433. else if (before) {
  434. this.pointEl.before(this.placeEl);
  435. }
  436. else {
  437. this.pointEl.after(this.placeEl);
  438. }
  439. if (!parent.children().length) {
  440. if(!parent.data("uksortable")) this.unsetParent(parent.parent());
  441. }
  442. if (!this.dragRootEl.find(opt.itemNodeName).length && !this.dragRootEl.children().length) {
  443. this.dragRootEl.append(this.tplempty);
  444. }
  445. // parent root list has changed
  446. if (isNewRoot) {
  447. this.dragRootEl = tmpRoot;
  448. this.hasNewRoot = this.el[0] !== this.dragRootEl[0];
  449. }
  450. }
  451. }
  452. };
  453. $.fn.uksortable = function(params)
  454. {
  455. var lists = this,
  456. retval = this;
  457. lists.each(function()
  458. {
  459. var element = $(this),
  460. plugin = element.data("uksortable");
  461. if (!plugin) {
  462. plugin = new Plugin(element, params);
  463. } else {
  464. if (typeof params === 'string' && typeof plugin[params] === 'function') {
  465. retval = plugin[params]();
  466. }
  467. }
  468. });
  469. return retval || lists;
  470. };
  471. $.fn.uksortable.defaults = {
  472. prefix : 'uk',
  473. listNodeName : 'ul',
  474. itemNodeName : 'li',
  475. listBaseClass : '{prefix}-sortable',
  476. listClass : '{prefix}-sortable-list',
  477. listitemClass : '{prefix}-sortable-list-item',
  478. itemClass : '{prefix}-sortable-item',
  479. dragClass : '{prefix}-sortable-list-dragged',
  480. movingClass : '{prefix}-sortable-moving',
  481. handleClass : '{prefix}-sortable-handle',
  482. collapsedClass : '{prefix}-collapsed',
  483. placeClass : '{prefix}-sortable-placeholder',
  484. noDragClass : '{prefix}-sortable-nodrag',
  485. emptyClass : '{prefix}-sortable-empty',
  486. group : 0,
  487. maxDepth : 10,
  488. threshold : 20
  489. };
  490. $(document).on("uk-domready", function(e) {
  491. $("[data-uk-sortable]").each(function(){
  492. var ele = $(this),
  493. options = $.extend({}, $.fn.uksortable.defaults, UI.Utils.options(ele.attr("data-uk-sortable")));
  494. Object.keys(options).forEach(function(key){
  495. if(String(options[key]).indexOf('{prefix}')!=-1) {
  496. options[key] = options[key].replace('{prefix}', options.prefix);
  497. }
  498. });
  499. if(!ele.data("uksortable")) {
  500. ele.uksortable(options);
  501. }
  502. });
  503. });
  504. })(window.jQuery, jQuery.UIkit, window, document);