jstree.dnd.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. /**
  2. * ### Drag'n'drop plugin
  3. *
  4. * Enables dragging and dropping of nodes in the tree, resulting in a move or copy operations.
  5. */
  6. /*globals jQuery, define, exports, require, document */
  7. (function (factory) {
  8. "use strict";
  9. if (typeof define === 'function' && define.amd) {
  10. define('jstree.dnd', ['jquery','./jstree.js'], factory);
  11. }
  12. else if(typeof exports === 'object') {
  13. factory(require('jquery'), require('./jstree.js'));
  14. }
  15. else {
  16. factory(jQuery, jQuery.jstree);
  17. }
  18. }(function ($, jstree, undefined) {
  19. "use strict";
  20. if($.jstree.plugins.dnd) { return; }
  21. /**
  22. * stores all defaults for the drag'n'drop plugin
  23. * @name $.jstree.defaults.dnd
  24. * @plugin dnd
  25. */
  26. $.jstree.defaults.dnd = {
  27. /**
  28. * a boolean indicating if a copy should be possible while dragging (by pressint the meta key or Ctrl). Defaults to `true`.
  29. * @name $.jstree.defaults.dnd.copy
  30. * @plugin dnd
  31. */
  32. copy : true,
  33. /**
  34. * a number indicating how long a node should remain hovered while dragging to be opened. Defaults to `500`.
  35. * @name $.jstree.defaults.dnd.open_timeout
  36. * @plugin dnd
  37. */
  38. open_timeout : 500,
  39. /**
  40. * a function invoked each time a node is about to be dragged, invoked in the tree's scope and receives the nodes about to be dragged as an argument (array) and the event that started the drag - return `false` to prevent dragging
  41. * @name $.jstree.defaults.dnd.is_draggable
  42. * @plugin dnd
  43. */
  44. is_draggable : true,
  45. /**
  46. * a boolean indicating if checks should constantly be made while the user is dragging the node (as opposed to checking only on drop), default is `true`
  47. * @name $.jstree.defaults.dnd.check_while_dragging
  48. * @plugin dnd
  49. */
  50. check_while_dragging : true,
  51. /**
  52. * a boolean indicating if nodes from this tree should only be copied with dnd (as opposed to moved), default is `false`
  53. * @name $.jstree.defaults.dnd.always_copy
  54. * @plugin dnd
  55. */
  56. always_copy : false,
  57. /**
  58. * when dropping a node "inside", this setting indicates the position the node should go to - it can be an integer or a string: "first" (same as 0) or "last", default is `0`
  59. * @name $.jstree.defaults.dnd.inside_pos
  60. * @plugin dnd
  61. */
  62. inside_pos : 0,
  63. /**
  64. * when starting the drag on a node that is selected this setting controls if all selected nodes are dragged or only the single node, default is `true`, which means all selected nodes are dragged when the drag is started on a selected node
  65. * @name $.jstree.defaults.dnd.drag_selection
  66. * @plugin dnd
  67. */
  68. drag_selection : true,
  69. /**
  70. * controls whether dnd works on touch devices. If left as boolean true dnd will work the same as in desktop browsers, which in some cases may impair scrolling. If set to boolean false dnd will not work on touch devices. There is a special third option - string "selected" which means only selected nodes can be dragged on touch devices.
  71. * @name $.jstree.defaults.dnd.touch
  72. * @plugin dnd
  73. */
  74. touch : true,
  75. /**
  76. * controls whether items can be dropped anywhere on the node, not just on the anchor, by default only the node anchor is a valid drop target. Works best with the wholerow plugin. If enabled on mobile depending on the interface it might be hard for the user to cancel the drop, since the whole tree container will be a valid drop target.
  77. * @name $.jstree.defaults.dnd.large_drop_target
  78. * @plugin dnd
  79. */
  80. large_drop_target : false,
  81. /**
  82. * controls whether a drag can be initiated from any part of the node and not just the text/icon part, works best with the wholerow plugin. Keep in mind it can cause problems with tree scrolling on mobile depending on the interface - in that case set the touch option to "selected".
  83. * @name $.jstree.defaults.dnd.large_drag_target
  84. * @plugin dnd
  85. */
  86. large_drag_target : false,
  87. /**
  88. * controls whether use HTML5 dnd api instead of classical. That will allow better integration of dnd events with other HTML5 controls.
  89. * @reference http://caniuse.com/#feat=dragndrop
  90. * @name $.jstree.defaults.dnd.use_html5
  91. * @plugin dnd
  92. */
  93. use_html5: false,
  94. /**
  95. * controls whether items can be dropped anywhere on the tree.
  96. * @name $.jstree.defaults.dnd.blank_space_drop
  97. * @plugin dnd
  98. */
  99. blank_space_drop: false
  100. };
  101. var drg, elm;
  102. // TODO: now check works by checking for each node individually, how about max_children, unique, etc?
  103. $.jstree.plugins.dnd = function (options, parent) {
  104. this.init = function (el, options) {
  105. parent.init.call(this, el, options);
  106. this.settings.dnd.use_html5 = this.settings.dnd.use_html5 && ('draggable' in document.createElement('span'));
  107. };
  108. this.bind = function () {
  109. parent.bind.call(this);
  110. this.element
  111. .on(this.settings.dnd.use_html5 ? 'dragstart.jstree' : 'mousedown.jstree touchstart.jstree', this.settings.dnd.large_drag_target ? '.jstree-node' : '.jstree-anchor', function (e) {
  112. if(this.settings.dnd.large_drag_target && $(e.target).closest('.jstree-node')[0] !== e.currentTarget) {
  113. return true;
  114. }
  115. if(e.type === "touchstart" && (!this.settings.dnd.touch || (this.settings.dnd.touch === 'selected' && !$(e.currentTarget).closest('.jstree-node').children('.jstree-anchor').hasClass('jstree-clicked')))) {
  116. return true;
  117. }
  118. var obj = this.get_node(e.target),
  119. mlt = this.is_selected(obj) && this.settings.dnd.drag_selection ? this.get_top_selected().length : 1,
  120. txt = (mlt > 1 ? mlt + ' ' + this.get_string('nodes') : this.get_text(e.currentTarget));
  121. if(this.settings.core.force_text) {
  122. txt = $.vakata.html.escape(txt);
  123. }
  124. if(obj && (obj.id || obj.id === 0) && obj.id !== $.jstree.root && (e.which === 1 || e.type === "touchstart" || e.type === "dragstart") &&
  125. (this.settings.dnd.is_draggable === true || ($.vakata.is_function(this.settings.dnd.is_draggable) && this.settings.dnd.is_draggable.call(this, (mlt > 1 ? this.get_top_selected(true) : [obj]), e)))
  126. ) {
  127. drg = { 'jstree' : true, 'origin' : this, 'obj' : this.get_node(obj,true), 'nodes' : mlt > 1 ? this.get_top_selected() : [obj.id] };
  128. elm = e.currentTarget;
  129. if (this.settings.dnd.use_html5) {
  130. $.vakata.dnd._trigger('start', e, { 'helper': $(), 'element': elm, 'data': drg });
  131. } else {
  132. this.element.trigger('mousedown.jstree');
  133. return $.vakata.dnd.start(e, drg, '<div id="jstree-dnd" class="jstree-' + this.get_theme() + ' jstree-' + this.get_theme() + '-' + this.get_theme_variant() + ' ' + ( this.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ) + '"><i class="jstree-icon jstree-er"></i>' + txt + '<ins class="jstree-copy">+</ins></div>');
  134. }
  135. }
  136. }.bind(this));
  137. if (this.settings.dnd.use_html5) {
  138. this.element
  139. .on('dragover.jstree', function (e) {
  140. e.preventDefault();
  141. $.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
  142. return false;
  143. })
  144. //.on('dragenter.jstree', this.settings.dnd.large_drop_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) {
  145. // e.preventDefault();
  146. // $.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
  147. // return false;
  148. // }, this))
  149. .on('drop.jstree', function (e) {
  150. e.preventDefault();
  151. $.vakata.dnd._trigger('stop', e, { 'helper': $(), 'element': elm, 'data': drg });
  152. return false;
  153. }.bind(this));
  154. }
  155. };
  156. this.redraw_node = function(obj, deep, callback, force_render) {
  157. obj = parent.redraw_node.apply(this, arguments);
  158. if (obj && this.settings.dnd.use_html5) {
  159. if (this.settings.dnd.large_drag_target) {
  160. obj.setAttribute('draggable', true);
  161. } else {
  162. var i, j, tmp = null;
  163. for(i = 0, j = obj.childNodes.length; i < j; i++) {
  164. if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
  165. tmp = obj.childNodes[i];
  166. break;
  167. }
  168. }
  169. if(tmp) {
  170. tmp.setAttribute('draggable', true);
  171. }
  172. }
  173. }
  174. return obj;
  175. };
  176. };
  177. $(function() {
  178. // bind only once for all instances
  179. var lastmv = false,
  180. laster = false,
  181. lastev = false,
  182. opento = false,
  183. marker = $('<div id="jstree-marker">&#160;</div>').hide(); //.appendTo('body');
  184. $(document)
  185. .on('dragover.vakata.jstree', function (e) {
  186. if (elm) {
  187. $.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
  188. }
  189. })
  190. .on('drop.vakata.jstree', function (e) {
  191. if (elm) {
  192. $.vakata.dnd._trigger('stop', e, { 'helper': $(), 'element': elm, 'data': drg });
  193. elm = null;
  194. drg = null;
  195. }
  196. })
  197. .on('dnd_start.vakata.jstree', function (e, data) {
  198. lastmv = false;
  199. lastev = false;
  200. if(!data || !data.data || !data.data.jstree) { return; }
  201. marker.appendTo(document.body); //.show();
  202. })
  203. .on('dnd_move.vakata.jstree', function (e, data) {
  204. var isDifferentNode = data.event.target !== lastev.target;
  205. if(opento) {
  206. if (!data.event || data.event.type !== 'dragover' || isDifferentNode) {
  207. clearTimeout(opento);
  208. }
  209. }
  210. if(!data || !data.data || !data.data.jstree) { return; }
  211. // if we are hovering the marker image do nothing (can happen on "inside" drags)
  212. if(data.event.target.id && data.event.target.id === 'jstree-marker') {
  213. return;
  214. }
  215. lastev = data.event;
  216. var ins = $.jstree.reference(data.event.target),
  217. ref = false,
  218. off = false,
  219. rel = false,
  220. tmp, l, t, h, p, i, o, ok, t1, t2, op, ps, pr, ip, tm, is_copy, pn, c;
  221. // if we are over an instance
  222. if(ins && ins._data && ins._data.dnd) {
  223. marker.attr('class', 'jstree-' + ins.get_theme() + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ));
  224. is_copy = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey)));
  225. data.helper
  226. .children().attr('class', 'jstree-' + ins.get_theme() + ' jstree-' + ins.get_theme() + '-' + ins.get_theme_variant() + ' ' + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ))
  227. .find('.jstree-copy').first()[ is_copy ? 'show' : 'hide' ]();
  228. // if are hovering the container itself add a new root node
  229. //console.log(data.event);
  230. if( (data.event.target === ins.element[0] || data.event.target === ins.get_container_ul()[0]) && (ins.get_container_ul().children().length === 0 || ins.settings.dnd.blank_space_drop)) {
  231. ok = true;
  232. for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
  233. ok = ok && ins.check( (data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey)) ) ? "copy_node" : "move_node"), (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), $.jstree.root, 'last', { 'dnd' : true, 'ref' : ins.get_node($.jstree.root), 'pos' : 'i', 'origin' : data.data.origin, 'is_multi' : (data.data.origin && data.data.origin !== ins), 'is_foreign' : (!data.data.origin) });
  234. if(!ok) { break; }
  235. }
  236. if(ok) {
  237. lastmv = { 'ins' : ins, 'par' : $.jstree.root, 'pos' : 'last' };
  238. marker.hide();
  239. data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok');
  240. if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
  241. data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move';
  242. }
  243. return;
  244. }
  245. }
  246. else {
  247. // if we are hovering a tree node
  248. ref = ins.settings.dnd.large_drop_target ? $(data.event.target).closest('.jstree-node').children('.jstree-anchor') : $(data.event.target).closest('.jstree-anchor');
  249. if(ref && ref.length && ref.parent().is('.jstree-closed, .jstree-open, .jstree-leaf')) {
  250. off = ref.offset();
  251. rel = (data.event.pageY !== undefined ? data.event.pageY : data.event.originalEvent.pageY) - off.top;
  252. h = ref.outerHeight();
  253. if(rel < h / 3) {
  254. o = ['b', 'i', 'a'];
  255. }
  256. else if(rel > h - h / 3) {
  257. o = ['a', 'i', 'b'];
  258. }
  259. else {
  260. o = rel > h / 2 ? ['i', 'a', 'b'] : ['i', 'b', 'a'];
  261. }
  262. $.each(o, function (j, v) {
  263. switch(v) {
  264. case 'b':
  265. l = off.left - 6;
  266. t = off.top;
  267. p = ins.get_parent(ref);
  268. i = ref.parent().index();
  269. c = 'jstree-below';
  270. break;
  271. case 'i':
  272. ip = ins.settings.dnd.inside_pos;
  273. tm = ins.get_node(ref.parent());
  274. l = off.left - 2;
  275. t = off.top + h / 2 + 1;
  276. p = tm.id;
  277. i = ip === 'first' ? 0 : (ip === 'last' ? tm.children.length : Math.min(ip, tm.children.length));
  278. c = 'jstree-inside';
  279. break;
  280. case 'a':
  281. l = off.left - 6;
  282. t = off.top + h;
  283. p = ins.get_parent(ref);
  284. i = ref.parent().index() + 1;
  285. c = 'jstree-above';
  286. break;
  287. }
  288. ok = true;
  289. for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
  290. op = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? "copy_node" : "move_node";
  291. ps = i;
  292. if(op === "move_node" && v === 'a' && (data.data.origin && data.data.origin === ins) && p === ins.get_parent(data.data.nodes[t1])) {
  293. pr = ins.get_node(p);
  294. if(ps > $.inArray(data.data.nodes[t1], pr.children)) {
  295. ps -= 1;
  296. }
  297. }
  298. ok = ok && ( (ins && ins.settings && ins.settings.dnd && ins.settings.dnd.check_while_dragging === false) || ins.check(op, (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), p, ps, { 'dnd' : true, 'ref' : ins.get_node(ref.parent()), 'pos' : v, 'origin' : data.data.origin, 'is_multi' : (data.data.origin && data.data.origin !== ins), 'is_foreign' : (!data.data.origin) }) );
  299. if(!ok) {
  300. if(ins && ins.last_error) { laster = ins.last_error(); }
  301. break;
  302. }
  303. }
  304. if(v === 'i' && ref.parent().is('.jstree-closed') && ins.settings.dnd.open_timeout) {
  305. if (!data.event || data.event.type !== 'dragover' || isDifferentNode) {
  306. if (opento) { clearTimeout(opento); }
  307. opento = setTimeout((function (x, z) { return function () { x.open_node(z); }; }(ins, ref)), ins.settings.dnd.open_timeout);
  308. }
  309. }
  310. if(ok) {
  311. pn = ins.get_node(p, true);
  312. if (!pn.hasClass('.jstree-dnd-parent')) {
  313. $('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
  314. pn.addClass('jstree-dnd-parent');
  315. }
  316. lastmv = { 'ins' : ins, 'par' : p, 'pos' : v === 'i' && ip === 'last' && i === 0 && !ins.is_loaded(tm) ? 'last' : i };
  317. marker.css({ 'left' : l + 'px', 'top' : t + 'px' }).show();
  318. marker.removeClass('jstree-above jstree-inside jstree-below').addClass(c);
  319. data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok');
  320. if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
  321. data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move';
  322. }
  323. laster = {};
  324. o = true;
  325. return false;
  326. }
  327. });
  328. if(o === true) { return; }
  329. }
  330. }
  331. }
  332. $('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
  333. lastmv = false;
  334. data.helper.find('.jstree-icon').removeClass('jstree-ok').addClass('jstree-er');
  335. if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
  336. //data.event.originalEvent.dataTransfer.dropEffect = 'none';
  337. }
  338. marker.hide();
  339. })
  340. .on('dnd_scroll.vakata.jstree', function (e, data) {
  341. if(!data || !data.data || !data.data.jstree) { return; }
  342. marker.hide();
  343. lastmv = false;
  344. lastev = false;
  345. data.helper.find('.jstree-icon').first().removeClass('jstree-ok').addClass('jstree-er');
  346. })
  347. .on('dnd_stop.vakata.jstree', function (e, data) {
  348. $('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
  349. if(opento) { clearTimeout(opento); }
  350. if(!data || !data.data || !data.data.jstree) { return; }
  351. marker.hide().detach();
  352. var i, j, nodes = [];
  353. if(lastmv) {
  354. for(i = 0, j = data.data.nodes.length; i < j; i++) {
  355. nodes[i] = data.data.origin ? data.data.origin.get_node(data.data.nodes[i]) : data.data.nodes[i];
  356. }
  357. lastmv.ins[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? 'copy_node' : 'move_node' ](nodes, lastmv.par, lastmv.pos, false, false, false, data.data.origin);
  358. }
  359. else {
  360. i = $(data.event.target).closest('.jstree');
  361. if(i.length && laster && laster.error && laster.error === 'check') {
  362. i = i.jstree(true);
  363. if(i) {
  364. i.settings.core.error.call(this, laster);
  365. }
  366. }
  367. }
  368. lastev = false;
  369. lastmv = false;
  370. })
  371. .on('keyup.jstree keydown.jstree', function (e, data) {
  372. data = $.vakata.dnd._get();
  373. if(data && data.data && data.data.jstree) {
  374. if (e.type === "keyup" && e.which === 27) {
  375. if (opento) { clearTimeout(opento); }
  376. lastmv = false;
  377. laster = false;
  378. lastev = false;
  379. opento = false;
  380. marker.hide().detach();
  381. $.vakata.dnd._clean();
  382. } else {
  383. data.helper.find('.jstree-copy').first()[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (e.metaKey || e.ctrlKey))) ? 'show' : 'hide' ]();
  384. if(lastev) {
  385. lastev.metaKey = e.metaKey;
  386. lastev.ctrlKey = e.ctrlKey;
  387. $.vakata.dnd._trigger('move', lastev);
  388. }
  389. }
  390. }
  391. });
  392. });
  393. // helpers
  394. (function ($) {
  395. $.vakata.html = {
  396. div : $('<div></div>'),
  397. escape : function (str) {
  398. return $.vakata.html.div.text(str).html();
  399. },
  400. strip : function (str) {
  401. return $.vakata.html.div.empty().append($.parseHTML(str)).text();
  402. }
  403. };
  404. // private variable
  405. var vakata_dnd = {
  406. element : false,
  407. target : false,
  408. is_down : false,
  409. is_drag : false,
  410. helper : false,
  411. helper_w: 0,
  412. data : false,
  413. init_x : 0,
  414. init_y : 0,
  415. scroll_l: 0,
  416. scroll_t: 0,
  417. scroll_e: false,
  418. scroll_i: false,
  419. is_touch: false
  420. };
  421. $.vakata.dnd = {
  422. settings : {
  423. scroll_speed : 10,
  424. scroll_proximity : 20,
  425. helper_left : 5,
  426. helper_top : 10,
  427. threshold : 5,
  428. threshold_touch : 10
  429. },
  430. _trigger : function (event_name, e, data) {
  431. if (data === undefined) {
  432. data = $.vakata.dnd._get();
  433. }
  434. data.event = e;
  435. $(document).triggerHandler("dnd_" + event_name + ".vakata", data);
  436. },
  437. _get : function () {
  438. return {
  439. "data" : vakata_dnd.data,
  440. "element" : vakata_dnd.element,
  441. "helper" : vakata_dnd.helper
  442. };
  443. },
  444. _clean : function () {
  445. if(vakata_dnd.helper) { vakata_dnd.helper.remove(); }
  446. if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
  447. vakata_dnd = {
  448. element : false,
  449. target : false,
  450. is_down : false,
  451. is_drag : false,
  452. helper : false,
  453. helper_w: 0,
  454. data : false,
  455. init_x : 0,
  456. init_y : 0,
  457. scroll_l: 0,
  458. scroll_t: 0,
  459. scroll_e: false,
  460. scroll_i: false,
  461. is_touch: false
  462. };
  463. elm = null;
  464. $(document).off("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag);
  465. $(document).off("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop);
  466. },
  467. _scroll : function (init_only) {
  468. if(!vakata_dnd.scroll_e || (!vakata_dnd.scroll_l && !vakata_dnd.scroll_t)) {
  469. if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
  470. return false;
  471. }
  472. if(!vakata_dnd.scroll_i) {
  473. vakata_dnd.scroll_i = setInterval($.vakata.dnd._scroll, 100);
  474. return false;
  475. }
  476. if(init_only === true) { return false; }
  477. var i = vakata_dnd.scroll_e.scrollTop(),
  478. j = vakata_dnd.scroll_e.scrollLeft();
  479. vakata_dnd.scroll_e.scrollTop(i + vakata_dnd.scroll_t * $.vakata.dnd.settings.scroll_speed);
  480. vakata_dnd.scroll_e.scrollLeft(j + vakata_dnd.scroll_l * $.vakata.dnd.settings.scroll_speed);
  481. if(i !== vakata_dnd.scroll_e.scrollTop() || j !== vakata_dnd.scroll_e.scrollLeft()) {
  482. /**
  483. * triggered on the document when a drag causes an element to scroll
  484. * @event
  485. * @plugin dnd
  486. * @name dnd_scroll.vakata
  487. * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
  488. * @param {DOM} element the DOM element being dragged
  489. * @param {jQuery} helper the helper shown next to the mouse
  490. * @param {jQuery} event the element that is scrolling
  491. */
  492. $.vakata.dnd._trigger("scroll", vakata_dnd.scroll_e);
  493. }
  494. },
  495. start : function (e, data, html) {
  496. if(e.type === "touchstart" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
  497. e.pageX = e.originalEvent.changedTouches[0].pageX;
  498. e.pageY = e.originalEvent.changedTouches[0].pageY;
  499. e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
  500. }
  501. if(vakata_dnd.is_drag) { $.vakata.dnd.stop({}); }
  502. try {
  503. e.currentTarget.unselectable = "on";
  504. e.currentTarget.onselectstart = function() { return false; };
  505. if(e.currentTarget.style) {
  506. e.currentTarget.style.touchAction = "none";
  507. e.currentTarget.style.msTouchAction = "none";
  508. e.currentTarget.style.MozUserSelect = "none";
  509. }
  510. } catch(ignore) { }
  511. vakata_dnd.init_x = e.pageX;
  512. vakata_dnd.init_y = e.pageY;
  513. vakata_dnd.data = data;
  514. vakata_dnd.is_down = true;
  515. vakata_dnd.element = e.currentTarget;
  516. vakata_dnd.target = e.target;
  517. vakata_dnd.is_touch = e.type === "touchstart";
  518. if(html !== false) {
  519. vakata_dnd.helper = $("<div id='vakata-dnd'></div>").html(html).css({
  520. "display" : "block",
  521. "margin" : "0",
  522. "padding" : "0",
  523. "position" : "absolute",
  524. "top" : "-2000px",
  525. "lineHeight" : "16px",
  526. "zIndex" : "10000"
  527. });
  528. }
  529. $(document).on("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag);
  530. $(document).on("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop);
  531. return false;
  532. },
  533. drag : function (e) {
  534. if(e.type === "touchmove" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
  535. e.pageX = e.originalEvent.changedTouches[0].pageX;
  536. e.pageY = e.originalEvent.changedTouches[0].pageY;
  537. e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
  538. }
  539. if(!vakata_dnd.is_down) { return; }
  540. if(!vakata_dnd.is_drag) {
  541. if(
  542. Math.abs(e.pageX - vakata_dnd.init_x) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold) ||
  543. Math.abs(e.pageY - vakata_dnd.init_y) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold)
  544. ) {
  545. if(vakata_dnd.helper) {
  546. vakata_dnd.helper.appendTo(document.body);
  547. vakata_dnd.helper_w = vakata_dnd.helper.outerWidth();
  548. }
  549. vakata_dnd.is_drag = true;
  550. $(vakata_dnd.target).one('click.vakata', false);
  551. /**
  552. * triggered on the document when a drag starts
  553. * @event
  554. * @plugin dnd
  555. * @name dnd_start.vakata
  556. * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
  557. * @param {DOM} element the DOM element being dragged
  558. * @param {jQuery} helper the helper shown next to the mouse
  559. * @param {Object} event the event that caused the start (probably mousemove)
  560. */
  561. $.vakata.dnd._trigger("start", e);
  562. }
  563. else { return; }
  564. }
  565. var d = false, w = false,
  566. dh = false, wh = false,
  567. dw = false, ww = false,
  568. dt = false, dl = false,
  569. ht = false, hl = false;
  570. vakata_dnd.scroll_t = 0;
  571. vakata_dnd.scroll_l = 0;
  572. vakata_dnd.scroll_e = false;
  573. $($(e.target).parentsUntil("body").addBack().get().reverse())
  574. .filter(function () {
  575. return this.ownerDocument && (/^auto|scroll$/).test($(this).css("overflow")) &&
  576. (this.scrollHeight > this.offsetHeight || this.scrollWidth > this.offsetWidth);
  577. })
  578. .each(function () {
  579. var t = $(this), o = t.offset();
  580. if(this.scrollHeight > this.offsetHeight) {
  581. if(o.top + t.height() - e.pageY < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = 1; }
  582. if(e.pageY - o.top < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = -1; }
  583. }
  584. if(this.scrollWidth > this.offsetWidth) {
  585. if(o.left + t.width() - e.pageX < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = 1; }
  586. if(e.pageX - o.left < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = -1; }
  587. }
  588. if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
  589. vakata_dnd.scroll_e = $(this);
  590. return false;
  591. }
  592. });
  593. if(!vakata_dnd.scroll_e) {
  594. d = $(document); w = $(window);
  595. dh = d.height(); wh = w.height();
  596. dw = d.width(); ww = w.width();
  597. dt = d.scrollTop(); dl = d.scrollLeft();
  598. if(dh > wh && e.pageY - dt < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = -1; }
  599. if(dh > wh && wh - (e.pageY - dt) < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = 1; }
  600. if(dw > ww && e.pageX - dl < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = -1; }
  601. if(dw > ww && ww - (e.pageX - dl) < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = 1; }
  602. if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
  603. vakata_dnd.scroll_e = d;
  604. }
  605. }
  606. if(vakata_dnd.scroll_e) { $.vakata.dnd._scroll(true); }
  607. if(vakata_dnd.helper) {
  608. ht = parseInt(e.pageY + $.vakata.dnd.settings.helper_top, 10);
  609. hl = parseInt(e.pageX + $.vakata.dnd.settings.helper_left, 10);
  610. if(dh && ht + 25 > dh) { ht = dh - 50; }
  611. if(dw && hl + vakata_dnd.helper_w > dw) { hl = dw - (vakata_dnd.helper_w + 2); }
  612. vakata_dnd.helper.css({
  613. left : hl + "px",
  614. top : ht + "px"
  615. });
  616. }
  617. /**
  618. * triggered on the document when a drag is in progress
  619. * @event
  620. * @plugin dnd
  621. * @name dnd_move.vakata
  622. * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
  623. * @param {DOM} element the DOM element being dragged
  624. * @param {jQuery} helper the helper shown next to the mouse
  625. * @param {Object} event the event that caused this to trigger (most likely mousemove)
  626. */
  627. $.vakata.dnd._trigger("move", e);
  628. return false;
  629. },
  630. stop : function (e) {
  631. if(e.type === "touchend" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
  632. e.pageX = e.originalEvent.changedTouches[0].pageX;
  633. e.pageY = e.originalEvent.changedTouches[0].pageY;
  634. e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
  635. }
  636. if(vakata_dnd.is_drag) {
  637. /**
  638. * triggered on the document when a drag stops (the dragged element is dropped)
  639. * @event
  640. * @plugin dnd
  641. * @name dnd_stop.vakata
  642. * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
  643. * @param {DOM} element the DOM element being dragged
  644. * @param {jQuery} helper the helper shown next to the mouse
  645. * @param {Object} event the event that caused the stop
  646. */
  647. if (e.target !== vakata_dnd.target) {
  648. $(vakata_dnd.target).off('click.vakata');
  649. }
  650. $.vakata.dnd._trigger("stop", e);
  651. }
  652. else {
  653. if(e.type === "touchend" && e.target === vakata_dnd.target) {
  654. var to = setTimeout(function () { $(e.target).trigger('click'); }, 100);
  655. $(e.target).one('click', function() { if(to) { clearTimeout(to); } });
  656. }
  657. }
  658. $.vakata.dnd._clean();
  659. return false;
  660. }
  661. };
  662. }($));
  663. // include the dnd plugin by default
  664. // $.jstree.defaults.plugins.push("dnd");
  665. }));