diff options
Diffstat (limited to 'static/js/kbjr-DragDrop-906d7ff/drag-drop.js')
| -rw-r--r-- | static/js/kbjr-DragDrop-906d7ff/drag-drop.js | 572 |
1 files changed, 572 insertions, 0 deletions
diff --git a/static/js/kbjr-DragDrop-906d7ff/drag-drop.js b/static/js/kbjr-DragDrop-906d7ff/drag-drop.js new file mode 100644 index 0000000..db720ec --- /dev/null +++ b/static/js/kbjr-DragDrop-906d7ff/drag-drop.js @@ -0,0 +1,572 @@ +/** + * DragDrop.js + * + * A JavaScript micro-framework for adding drag-and-drop functionality + * to elements for advanced UI development. + * + * @author James Brumond + * @version 0.3.0 + * @copyright Copyright 2011 James Brumond + * @license Dual licensed under MIT and GPL + */ + +/*jshint browser: true, bitwise: false, camelcase: false, eqnull: true, latedef: false, + plusplus: false, jquery: true, shadow: true, smarttabs: true, loopfunc: true */ + +(function() { + + var + + // Is this a touch device? + touchEvents = ('ontouchstart' in window), + + // A class to add when an element is being dragged + dragClass = 'drag', + + /** + * The DragDrop namespace + * + * Example: + * + * DragDrop.bind ( element[, options ]); + * DragDrop.unbind ( reference ); + * + * @access public + */ + DragDrop = (function() { + var self = { }, + + // Determine the events to bind to + events = (touchEvents ? + { + start: 'touchstart', + move: 'touchmove', + end: 'touchend' + } : { + start: 'mousedown', + move: 'mousemove', + end: 'mouseup' + } + ), + + // Elements already bound + bindings = [ ], + + // Check if a given binding (element/anchor pair) already exists + bindingExists = function(element, anchor) { + for (var i = 0, c = bindings.length; i < c; i++) { + if (bindings[i] && bindings[i].element === element && bindings[i].anchor === anchor) { + return true; + } + } + return false; + }, + + // Do something with a given binding's given event stack + withBindingEvent = function(reference, event, func) { + if (bindings[reference._id] && bindings[reference._id].events[event]) { + func(bindings[reference._id].events[event]); + } + }, + + // Parse the arguments of DragDrop.bind + parseOptions = function(element, options) { + options = options || { }; + options.element = element; + options.anchor = options.anchor || element; + options.boundingBox = options.boundingBox || null; + options.releaseAnchors = options.releaseAnchors || [ ]; + options.releaseAnchors.unshift(document); + return options; + }, + + // The next binding ID to use + nextBinding = 1, + + // ------------------------------------------------------------------ + // A constructor for a resource type used in referencing bindings + + BindingReference = function() { + this._id = nextBinding++; + }; + + BindingReference.prototype.unbind = function() { + return DragDrop.unbind(this); + }; + + BindingReference.prototype.bindEvent = function(event, func) { + return DragDrop.bindEvent(this, event, func); + }; + + BindingReference.prototype.unbindEvent = function(event, func) { + return DragDrop.unbindEvent(this, event, func); + }; + + BindingReference.prototype.invokeEvent = function(event, source) { + return DragDrop.invokeEvent(this, event, source); + }; + + BindingReference.prototype.setBoundingBox = function(box) { + bindings[this._id].boundingBox = box; + }; + + // ---------------------------------------------------------------------------- + // Public Functions + + // Make an element draggable + self.bind = function(element, options) { + options = parseOptions(element, options); + if (! isObject(options.element)) { + throw new Error('Must give an element to drag'); + } + if (getStyle(options.element, 'position') === 'static') { + throw new Error('Cannot drag-drop an element with position:static'); + } + // Check to make sure the elements aren't already bound + if (! bindingExists(options.element, options.anchor)) { + // Initialize the binding object + var reference = new BindingReference(); + var binding = { + element: options.element, + anchor: options.anchor, + releaseAnchors: options.releaseAnchors, + dragging: false, + event: null, + shouldUnbind: false, + boundingBox: options.boundingBox, + events: { + beforedrag: Callstack(options.beforedrag), + dragstart: Callstack(options.dragstart), + dragend: Callstack(options.dragend), + drag: Callstack(options.drag), + unbind: Callstack(options.unbind) + } + }; + // Bind the first event + binding.event = Events.bind(binding.anchor, events.start, function(e) { + // Make sure it's a left click or touch event + if ((window.event && e.button === 1) || e.button === 0 || touchEvents) { + stopEvent(e); + // Call any "beforedrag" events before calculations begin + binding.events.beforedrag.call( + binding.element, new DragEvent('beforedrag', e, binding) + ); + // Make sure everyone knows the element is being dragged + binding.dragging = true; + addClass(binding.element, dragClass); + // Start calculating movement + var startX = getPos(binding.element, 'left'); + var startY = getPos(binding.element, 'top'); + // These are used in some bounding box calculations + var startOffsetLeft = binding.element.offsetLeft; + var startOffsetTop = binding.element.offsetTop; + var startTotalOffset = getOffset(binding.element); + // A place to hold on to event functions we are going to unbind later + var tempEvents = [ ]; + // The target for the move and end events is dependent on the input type + var target = (touchEvents ? binding.anchor : document); + // Bind the movement event + tempEvents.push(Events.bind(target, events.move, function(e2) { + // Find all needed offsets + var offsetX = e2.clientX - e.clientX; + var offsetY = e2.clientY - e.clientY; + var offsetWidth = binding.element.offsetWidth; + var offsetHeight = binding.element.offsetHeight; + // Find the new positions + var posX = startX + offsetX; + var posY = startY + offsetY; + // Enforce any bounding box + if (binding.boundingBox) { + var box = binding.boundingBox; + var minX, maxX, minY, maxY; + // Bound inside offset parent + if (box === 'offsetParent') { + var parent = binding.element.offsetParent; + if (getStyle(binding.element, 'position') === 'relative') { + minX = -startOffsetLeft; + minY = -startOffsetTop; + } else { + minX = minY = 0; + } + maxX = parent.clientWidth + minX; + maxY = parent.clientHeight + minY; + } + // Bound to the dimensions of the window + else if (box === 'windowSize') { + var dimensions = getWindowSize(); + if (getStyle(binding.element, 'position') === 'relative') { + minX = -startTotalOffset.x; + minY = -startTotalOffset.y; + } else { + minX = minY = 0; + } + maxX = dimensions.x + minX; + maxY = dimensions.y + minY; + } + // Manual bounding box + else { + minX = box.x.min; + maxX = box.x.max; + minY = box.y.min; + maxY = box.y.max; + } + posX = Math.max(minX, Math.min(maxX - offsetWidth, posX)); + posY = Math.max(minY, Math.min(maxY - offsetHeight, posY)); + } + // Move the element + binding.element.style.left = posX + 'px'; + binding.element.style.top = posY + 'px'; + // Call any "drag" events + binding.events.drag.call( + binding.element, new DragEvent('drag', e2, binding) + ); + return stopEvent(e2); + })); + // Bind the release events + for (var i = 0, c = binding.releaseAnchors.length; i < c; i++) { + var elem = binding.releaseAnchors[i]; + tempEvents.push( + Events.bind(elem, events.end, onRelease(elem)) + ); + } + // Avoid text selection problems + document.body.focus(); + tempEvents.push(Events.bind(document, 'selectstart', false)); + tempEvents.push(Events.bind(binding.anchor, 'dragstart', false)); + // Call any "dragstart" events + binding.events.dragstart.call( + binding.element, new DragEvent('dragstart', e, binding) + ); + return false; + } + function onRelease(elem) { + return function(e2) { + // Unbind move and end events + for (var i = 0, c = tempEvents.length; i < c; i++) { + Events.unbind(tempEvents[i]); + } + // Clean up... + binding.dragging = false; + removeClass(binding.element, dragClass); + if (binding.shouldUnbind) { + DragDrop.unbind(binding.element, binding.anchor); + } + // Call any "dragend" events + binding.events.dragend.call( + binding.element, new DragEvent('dragend', e2, binding, { + releaseAnchor: elem + }) + ); + return stopEvent(e2); + }; + } + }); + // Add the binding to the list + bindings[reference._id] = binding; + return reference; + } + }; + + // Remove an element's draggableness + self.unbind = function(reference) { + if (reference instanceof BindingReference) { + var id = reference._id; + if (bindings[id]) { + var binding = bindings[id]; + + if (binding.dragging) { + binding.shouldUnbind = true; + } else { + Events.unbind(binding.event); + bindings[id] = null; + } + // Call any "unbind" events + binding.events.unbind.call( + binding.element, new DragEvent('unbind', { target: true }, binding) + ); + } + } + }; + + // Bind a drag event + self.bindEvent = function(reference, event, func) { + withBindingEvent(reference, event, function(stack) { + stack.push(func); + }); + }; + + // Unbind a drag event + self.unbindEvent = function(reference, event, func) { + withBindingEvent(reference, event, function(stack) { + stack.remove(func); + }); + }; + + // Manually invoke a drag event + self.invokeEvent = function(reference, event, source) { + withBindingEvent(reference, event, function(stack) { + stack.call( + bindings[reference._id].element, + new DragEvent(event, source, reference) + ); + }); + }; + + return self; + }()), + +// ---------------------------------------------------------------------------- +// Helper Functions + + // Array Remove - By John Resig (MIT Licensed) + arrayRemove = function(array, from, to) { + var rest = array.slice((to || from) + 1 || array.length); + array.length = from < 0 ? array.length + from : from; + return array.push.apply(array, rest); + }, + + // Get the position of an element + getPos = function(elem, from) { + var pos = parseFloat(getStyle(elem, from)); + return (isNaN(pos) ? 0 : pos); + }, + + // Get a style property from an element + getStyle = function(elem, prop) { + if (elem.currentStyle) { + return elem.currentStyle[prop]; + } else if (window.getComputedStyle) { + return document.defaultView.getComputedStyle(elem, null).getPropertyValue(prop); + } else if (elem.style) { + return elem.style[prop]; + } + }, + + // Get the dimensions of the window + getWindowSize = function() { + return { + x: window.innerWidth || document.documentElement.clientWidth || body().clientWidth, + y: window.innerHeight || document.documentElement.clientHeight || body().clientHeight + }; + }, + + // Get the total offset position of an element in the document + getOffset = function(elem) { + var x = 0; + var y = 0; + if (elem.offsetParent) { + do { + x += elem.offsetLeft; + y += elem.offsetTop; + } while (elem = elem.offsetParent); + } + return {x: x, y: y}; + }, + + // Stop an event + stopEvent = function(evt) { + if (evt.preventDefault) { + evt.preventDefault(); + } + if (evt.stopPropagation) { + evt.stopPropagation(); + } + evt.returnValue = false; + return false; + }, + + // Regular expressions for matching classnames + cnRegexes = { }, + + // Remove a class from an element + removeClass = function(elem, cn) { + if (! cnRegexes[cn]) { + cnRegexes[cn] = new RegExp('(^|\\s)+' + cn + '(\\s|$)+'); + } + elem.className = elem.className.replace(cnRegexes[cn], ' '); + }, + + // Add a class to an element + addClass = function(elem, cn) { + removeClass(elem, cn); + elem.className += ' ' + cn; + }, + + // Check for a non-null object + isObject = function(value) { + return !! (value && typeof value === 'object'); + }, + + // Gets the target property of an event + getEventTarget = function(evt) { + var target; + if (evt.target) { + target = evt.target; + } else if (evt.srcElement) { + target = evt.srcElement; + } + if (target.nodeType === 3) { + target = target.parentNode; + } + return target; + }, + + /** + * A stackable function + * + * @access private + * @param function an initial function + * @return function + */ + Callstack = function(func) { + var stack = [ ]; + var result = function() { + var ret; + for (var i = 0, c = stack.length; i < c; i++) { + ret = stack[i].apply(this, arguments); + } + return ret; + }; + result.push = function() { + stack.push.apply(stack, arguments); + }; + result.remove = function() { + var args = Array.prototype.slice.call(arguments); + var result = [ ]; + OUTER: for (var i = 0, c1 = stack.length; i < c1; i++) { + for (var j = 0, c2 = args.length; j < c2; j++) { + if (stack[i] === args[j]) { + continue OUTER; + } + } + result.push(stack[i]); + } + stack = result; + }; + if (typeof func === 'function') { + stack.push(func); + } + return result; + }, + + /** + * Custom event constructor + * + * @access private + * @param string type + * @param object original event object + */ + DragEvent = function DragEvent(type, original, binding, extras) { + this.type = type; + this.originalEvent = original; + this.altKey = original.altKey || false; + this.ctrlKey = original.ctrlKey || false; + this.shiftKey = original.shiftKey || false; + this.timestamp = original.timestamp || (+new Date()); + this.pos = getPosition(original); + this.binding = binding; + this.target = getEventTarget(original); + + if (extras) { + for (var i in extras) { + if (extras.hasOwnProperty(i)) { + this[i] = extras[i]; + } + } + } + }, + + /** + * A namespace with functions for event binding + * + * Example: + * + * Bind + * var evt = Events.bind(obj, 'event', function() { ... }); + * + * Unbind + * Events.unbind(evt); + * -OR- + * evt.unbind(); + * + * @access private + */ + Events = (function() { + + var + + // Bind an event + bindEvent = (function() { + if (document.addEventListener) { + return function(obj, event, func) { + obj.addEventListener(event, func, false); + }; + } else if (document.attachEvent) { + return function(obj, event, func) { + obj.attachEvent('on' + event, func); + }; + } else { + return function() { }; + } + }()), + + // Unbind an event + unbindEvent = (function() { + if (document.removeEventListener) { + return function(obj, event, func) { + obj.removeEventListener(event, func, false); + }; + } else if (document.detachEvent) { + return function(obj, event, func) { + obj.detachEvent('on' + event, func); + }; + } else { + return function() { }; + } + }()); + + // Build the return value + return { + bind: function(obj, event, func) { + var oldFunc = (func === false) ? function(e) { + return stopEvent(e); + } : func; + func = function(e) { + return oldFunc.call(obj, e || window.event); + }; + bindEvent(obj, event, func); + var ret = function() { + unbindEvent(obj, event, func); + }; + ret.unbind = function() {ret();}; + return ret; + }, + unbind: function(unbinder) { + unbinder(); + } + }; + + }()); + + function getPosition(evt) { + var posX = 0; + var posY = 0; + if (evt.targetTouches) { + posX = evt.targetTouches[0].pageX; + posY = evt.targetTouches[0].pageY; + } else if (evt.pageX || evt.pageY) { + posX = evt.pageX; + posY = evt.pageY; + } else if (evt.clientX || evt.clientY) { + posX = evt.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + posY = evt.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + return {x: posX, y: posY}; + } + +// ---------------------------------------------------------------------------- +// Expose + + window.DragDrop = DragDrop; + +}());
\ No newline at end of file |
