/public/javascript/jquery.simulate.drag-sortable.js
JavaScript | 235 lines | 177 code | 22 blank | 36 comment | 47 complexity | 5544bb3b7a87a5fa7ca3f5242d024542 MD5 | raw file
- (function($) {
- /*
- * Simulate drag of a JQuery UI sortable list
- * Repository: https://github.com/mattheworiordan/jquery.simulate.drag-sortable.js
- * Author: http://mattheworiordan.com
- *
- * options are:
- * - move: move item up (positive) or down (negative) by Integer amount
- * - dropOn: move item to a new linked list, move option now represents position in the new list (zero indexed)
- * - handle: selector for the draggable handle element (optional)
- * - listItem: selector to limit which sibling items can be used for reordering
- * - placeHolder: if a placeholder is used during dragging, we need to consider it's height
- * - tolerance: (optional) number of pixels to overlap by instead of the default 50% of the element height
- *
- */
- $.fn.simulateDragSortable = function(options) {
- // build main options before element iteration
- var opts = $.extend({}, $.fn.simulateDragSortable.defaults, options);
- applyDrag = function(options) {
- // allow for a drag handle if item is not draggable
- var that = this,
- options = options || opts, // default to plugin opts unless options explicitly provided
- handle = options.handle ? $(this).find(options.handle)[0] : $(this)[0],
- listItem = options.listItem,
- placeHolder = options.placeHolder,
- sibling = $(this),
- moveCounter = Math.floor(options.move),
- direction = moveCounter > 0 ? 'down' : 'up',
- moveVerticalAmount = 0,
- initialVerticalPosition = 0,
- extraDrag = !isNaN(parseInt(options.tolerance, 10)) ? function() { return Number(options.tolerance); } : function(obj) { return ($(obj).outerHeight() / 2) + 5; },
- dragPastBy = 0, // represents the additional amount one drags past an element and bounce back
- dropOn = options.dropOn ? $(options.dropOn) : false,
- center = findCenter(handle),
- x = Math.floor(center.x),
- y = Math.floor(center.y),
- mouseUpAfter = (opts.debug ? 2500 : 10);
- if (dropOn) {
- if (dropOn.length === 0) {
- if (console && console.log) { console.log('simulate.drag-sortable.js ERROR: Drop on target could not be found'); console.log(options.dropOn); }
- return;
- }
- sibling = dropOn.find('>*:last');
- moveCounter = -(dropOn.find('>*').length + 1) + (moveCounter + 1); // calculate length of list after this move, use moveCounter as a positive index position in list to reverse back up
- if (dropOn.offset().top - $(this).offset().top < 0) {
- // moving to a list above this list, so move to just above top of last item (tried moving to top but JQuery UI wouldn't bite)
- initialVerticalPosition = sibling.offset().top - $(this).offset().top - extraDrag(this);
- } else {
- // moving to a list below this list, so move to bottom and work up (JQuery UI does not trigger new list below unless you move past top item first)
- initialVerticalPosition = sibling.offset().top - $(this).offset().top - $(this).height();
- }
- } else if (moveCounter === 0) {
- if (console && console.log) { console.log('simulate.drag-sortable.js WARNING: Drag with move set to zero has no effect'); }
- return;
- } else {
- while (moveCounter !== 0) {
- if (direction === 'down') {
- if (sibling.next(listItem).length) {
- sibling = sibling.next(listItem);
- moveVerticalAmount += sibling.outerHeight();
- }
- moveCounter -= 1;
- } else {
- if (sibling.prev(listItem).length) {
- sibling = sibling.prev(listItem);
- moveVerticalAmount -= sibling.outerHeight();
- }
- moveCounter += 1;
- }
- }
- }
- dispatchEvent(handle, 'mousedown', createEvent('mousedown', handle, { clientX: x, clientY: y }));
- // simulate drag start
- dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x+1, clientY: y+1 }));
- if (dropOn) {
- // jump to top or bottom of new list but do it in increments so that JQuery UI registers the drag events
- slideUpTo(x, y, initialVerticalPosition);
- // reset y position to top or bottom of list and move from there
- y += initialVerticalPosition;
- // now call regular shift/down in a list
- options = jQuery.extend(options, { move: moveCounter });
- delete options.dropOn;
- // add some delays to allow JQuery UI to catch up
- setTimeout(function() {
- dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x, clientY: y }));
- }, 5);
- setTimeout(function() {
- dispatchEvent(handle, 'mouseup', createEvent('mouseup', handle, { clientX: x, clientY: y }));
- setTimeout(function() {
- if (options.move) {
- applyDrag.call(that, options);
- }
- }, 5);
- }, mouseUpAfter);
- // stop execution as applyDrag has been called again
- return;
- }
- // Sortable is using a fixed height placeholder meaning items jump up and down as you drag variable height items into fixed height placeholder
- placeHolder = placeHolder && $(this).parent().find(placeHolder);
- if (!placeHolder && (direction === 'down')) {
- // need to move at least as far as this item and or the last sibling
- if ($(this).outerHeight() > $(sibling).outerHeight()) {
- moveVerticalAmount += $(this).outerHeight() - $(sibling).outerHeight();
- }
- moveVerticalAmount += extraDrag(sibling);
- dragPastBy += extraDrag(sibling);
- } else if (direction === 'up') {
- // move a little extra to ensure item clips into next position
- moveVerticalAmount -= Math.max(extraDrag(this), 5);
- } else if (direction === 'down') {
- // moving down with a place holder
- if (placeHolder.height() < $(this).height()) {
- moveVerticalAmount += Math.max(placeHolder.height(), 5);
- } else {
- moveVerticalAmount += extraDrag(sibling);
- }
- }
- if (sibling[0] !== $(this)[0]) {
- // step through so that the UI controller can determine when to show the placeHolder
- slideUpTo(x, y, moveVerticalAmount, dragPastBy);
- } else {
- if (window.console) {
- console.log('simulate.drag-sortable.js WARNING: Could not move as at top or bottom already');
- }
- }
- setTimeout(function() {
- dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x, clientY: y + moveVerticalAmount }));
- }, 5);
- setTimeout(function() {
- dispatchEvent(handle, 'mouseup', createEvent('mouseup', handle, { clientX: x, clientY: y + moveVerticalAmount }));
- }, mouseUpAfter);
- };
- // iterate and move each matched element
- return this.each(applyDrag);
- };
- // fire mouse events, go half way, then the next half, so small mouse movements near target and big at the start
- function slideUpTo(x, y, targetOffset, goPastBy) {
- var moveBy, offset;
- if (!goPastBy) { goPastBy = 0; }
- if ((targetOffset < 0) && (goPastBy > 0)) { goPastBy = -goPastBy; } // ensure go past is in the direction as often passed in from object height so always positive
- // go forwards including goPastBy
- for (offset = 0; Math.abs(offset) + 1 < Math.abs(targetOffset + goPastBy); offset += ((targetOffset + goPastBy - offset)/2) ) {
- dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x, clientY: y + Math.ceil(offset) }));
- }
- offset = targetOffset + goPastBy;
- dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x, clientY: y + offset }));
- // now bounce back
- for (; Math.abs(offset) - 1 >= Math.abs(targetOffset); offset += ((targetOffset - offset)/2) ) {
- dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x, clientY: y + Math.ceil(offset) }));
- }
- dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x, clientY: y + targetOffset }));
- }
- function createEvent(type, target, options) {
- var evt;
- var e = $.extend({
- target: target,
- preventDefault: function() { },
- stopImmediatePropagation: function() { },
- stopPropagation: function() { },
- isPropagationStopped: function() { return true; },
- isImmediatePropagationStopped: function() { return true; },
- isDefaultPrevented: function() { return true; },
- bubbles: true,
- cancelable: (type != "mousemove"),
- view: window,
- detail: 0,
- screenX: 0,
- screenY: 0,
- clientX: 0,
- clientY: 0,
- ctrlKey: false,
- altKey: false,
- shiftKey: false,
- metaKey: false,
- button: 0,
- relatedTarget: undefined
- }, options || {});
- if ($.isFunction(document.createEvent)) {
- evt = document.createEvent("MouseEvents");
- evt.initMouseEvent(type, e.bubbles, e.cancelable, e.view, e.detail,
- e.screenX, e.screenY, e.clientX, e.clientY,
- e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
- e.button, e.relatedTarget || document.body.parentNode);
- } else if (document.createEventObject) {
- evt = document.createEventObject();
- $.extend(evt, e);
- evt.button = { 0:1, 1:4, 2:2 }[evt.button] || evt.button;
- }
- return evt;
- }
- function dispatchEvent(el, type, evt) {
- if (el.dispatchEvent) {
- el.dispatchEvent(evt);
- } else if (el.fireEvent) {
- el.fireEvent('on' + type, evt);
- }
- return evt;
- }
- function findCenter(el) {
- var elm = $(el),
- o = elm.offset();
- return {
- x: o.left + elm.outerWidth() / 2,
- y: o.top + elm.outerHeight() / 2
- };
- }
- //
- // plugin defaults
- //
- $.fn.simulateDragSortable.defaults = {
- move: 0
- };
- })(jQuery);