/* Drag and drop tools by Tim Taylor. Code is under public license. http://tool-man.org/examples/sorting.html */ var Coordinates = { ORIGIN : new Coordinate(0, 0), northwestPosition : function(element) { var x = parseInt(element.style.left); var y = parseInt(element.style.top); return new Coordinate(isNaN(x) ? 0 : x, isNaN(y) ? 0 : y); }, southeastPosition : function(element) { return Coordinates.northwestPosition(element).plus( new Coordinate(element.offsetWidth, element.offsetHeight)); }, northwestOffset : function(element, isRecursive) { var offset = new Coordinate(element.offsetLeft, element.offsetTop); if (!isRecursive) return offset; var parent = element.offsetParent; while (parent) { offset = offset.plus( new Coordinate(parent.offsetLeft, parent.offsetTop)); parent = parent.offsetParent; } return offset; }, southeastOffset : function(element, isRecursive) { return Coordinates.northwestOffset(element, isRecursive).plus( new Coordinate(element.offsetWidth, element.offsetHeight)); }, fixEvent : function(event) { event.windowCoordinate = new Coordinate(event.clientX, event.clientY); } }; function Coordinate(x, y) { this.x = x; this.y = y; } Coordinate.prototype.toString = function() { return "(" + this.x + "," + this.y + ")"; } Coordinate.prototype.plus = function(that) { return new Coordinate(this.x + that.x, this.y + that.y); } Coordinate.prototype.minus = function(that) { return new Coordinate(this.x - that.x, this.y - that.y); } Coordinate.prototype.distance = function(that) { var deltaX = this.x - that.x; var deltaY = this.y - that.y; return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); } Coordinate.prototype.max = function(that) { var x = Math.max(this.x, that.x); var y = Math.max(this.y, that.y); return new Coordinate(x, y); } Coordinate.prototype.constrain = function(min, max) { if (min.x > max.x || min.y > max.y) return this; var x = this.x; var y = this.y; if (min.x != null) x = Math.max(x, min.x); if (max.x != null) x = Math.min(x, max.x); if (min.y != null) y = Math.max(y, min.y); if (max.y != null) y = Math.min(y, max.y); return new Coordinate(x, y); } Coordinate.prototype.reposition = function(element) { element.style["top"] = this.y + "px"; element.style["left"] = this.x + "px"; } Coordinate.prototype.equals = function(that) { if (this == that) return true; if (!that || that == null) return false; return this.x == that.x && this.y == that.y; } /* * drag.js - click & drag DOM elements * * originally based on Youngpup's dom-drag.js, www.youngpup.net */ var Drag = { BIG_Z_INDEX : 10000, group : null, isDragging : false, makeDraggable : function(group) { group.handle = group; group.handle.group = group; group.minX = null; group.minY = null; group.maxX = null; group.maxY = null; group.threshold = 0; group.thresholdY = 0; group.thresholdX = 0; group.onDragStart = new Function(); group.onDragEnd = new Function(); group.onDrag = new Function(); // TODO: use element.prototype.myFunc group.setDragHandle = Drag.setDragHandle; group.setDragThreshold = Drag.setDragThreshold; group.setDragThresholdX = Drag.setDragThresholdX; group.setDragThresholdY = Drag.setDragThresholdY; group.constrain = Drag.constrain; group.constrainVertical = Drag.constrainVertical; group.constrainHorizontal = Drag.constrainHorizontal; group.onmousedown = Drag.onMouseDown; }, constrainVertical : function() { var nwOffset = Coordinates.northwestOffset(this, true); this.minX = nwOffset.x; this.maxX = nwOffset.x; }, constrainHorizontal : function() { var nwOffset = Coordinates.northwestOffset(this, true); this.minY = nwOffset.y; this.maxY = nwOffset.y; }, constrain : function(nwPosition, sePosition) { this.minX = nwPosition.x; this.minY = nwPosition.y; this.maxX = sePosition.x; this.maxY = sePosition.y; }, setDragHandle : function(handle) { if (handle && handle != null) this.handle = handle; else this.handle = this; this.handle.group = this; this.onmousedown = null; this.handle.onmousedown = Drag.onMouseDown; }, setDragThreshold : function(threshold) { if (isNaN(parseInt(threshold))) return; this.threshold = threshold; }, setDragThresholdX : function(threshold) { if (isNaN(parseInt(threshold))) return; this.thresholdX = threshold; }, setDragThresholdY : function(threshold) { if (isNaN(parseInt(threshold))) return; this.thresholdY = threshold; }, onMouseDown : function(event) { event = Drag.fixEvent(event); Drag.group = this.group; var group = this.group; var mouse = event.windowCoordinate; var nwOffset = Coordinates.northwestOffset(group, true); var nwPosition = Coordinates.northwestPosition(group); var sePosition = Coordinates.southeastPosition(group); var seOffset = Coordinates.southeastOffset(group, true); group.originalOpacity = group.style.opacity; group.originalZIndex = group.style.zIndex; group.initialWindowCoordinate = mouse; // TODO: need a better name, but don't yet understand how it // participates in the magic while dragging group.dragCoordinate = mouse; Drag.showStatus(mouse, nwPosition, sePosition, nwOffset, seOffset); group.onDragStart(nwPosition, sePosition, nwOffset, seOffset); // TODO: need better constraint API if (group.minX != null) group.minMouseX = mouse.x - nwPosition.x + group.minX - nwOffset.x; if (group.maxX != null) group.maxMouseX = group.minMouseX + group.maxX - group.minX; if (group.minY != null) group.minMouseY = mouse.y - nwPosition.y + group.minY - nwOffset.y; if (group.maxY != null) group.maxMouseY = group.minMouseY + group.maxY - group.minY; group.mouseMin = new Coordinate(group.minMouseX, group.minMouseY); group.mouseMax = new Coordinate(group.maxMouseX, group.maxMouseY); document.onmousemove = Drag.onMouseMove; document.onmouseup = Drag.onMouseUp; return false; }, showStatus : function(mouse, nwPosition, sePosition, nwOffset, seOffset) { window.status = "mouse: " + mouse.toString() + " " + "NW pos: " + nwPosition.toString() + " " + "SE pos: " + sePosition.toString() + " " + "NW offset: " + nwOffset.toString() + " " + "SE offset: " + seOffset.toString(); }, onMouseMove : function(event) { event = Drag.fixEvent(event); var group = Drag.group; var mouse = event.windowCoordinate; var nwOffset = Coordinates.northwestOffset(group, true); var nwPosition = Coordinates.northwestPosition(group); var sePosition = Coordinates.southeastPosition(group); var seOffset = Coordinates.southeastOffset(group, true); Drag.showStatus(mouse, nwPosition, sePosition, nwOffset, seOffset); if (!Drag.isDragging) { if (group.threshold > 0) { var distance = group.initialWindowCoordinate.distance( mouse); if (distance < group.threshold) return true; } else if (group.thresholdY > 0) { var deltaY = Math.abs(group.initialWindowCoordinate.y - mouse.y); if (deltaY < group.thresholdY) return true; } else if (group.thresholdX > 0) { var deltaX = Math.abs(group.initialWindowCoordinate.x - mouse.x); if (deltaX < group.thresholdX) return true; } Drag.isDragging = true; group.style["zIndex"] = Drag.BIG_Z_INDEX; group.style["opacity"] = 0.75; } // TODO: need better constraint API var adjusted = mouse.constrain(group.mouseMin, group.mouseMax); nwPosition = nwPosition.plus(adjusted.minus(group.dragCoordinate)); nwPosition.reposition(group); group.dragCoordinate = adjusted; // once dragging has started, the position of the group // relative to the mouse should stay fixed. They can get out // of sync if the DOM is manipulated while dragging, so we // correct the error here // // TODO: what we really want to do is find the offset from // our corner to the mouse coordinate and adjust to keep it // the same var offsetBefore = Coordinates.northwestOffset(group); group.onDrag(nwPosition, sePosition, nwOffset, seOffset); var offsetAfter = Coordinates.northwestOffset(group); if (!offsetBefore.equals(offsetAfter)) { var errorDelta = offsetBefore.minus(offsetAfter); nwPosition = Coordinates.northwestPosition(group).plus(errorDelta); nwPosition.reposition(group); } return false; }, onMouseUp : function(event) { event = Drag.fixEvent(event); var group = Drag.group; var mouse = event.windowCoordinate; var nwOffset = Coordinates.northwestOffset(group, true); var nwPosition = Coordinates.northwestPosition(group); var sePosition = Coordinates.southeastPosition(group); var seOffset = Coordinates.southeastOffset(group, true); document.onmousemove = null; document.onmouseup = null; group.onDragEnd(nwPosition, sePosition, nwOffset, seOffset); if (Drag.isDragging) { // restoring zIndex before opacity avoids visual flicker in Firefox group.style["zIndex"] = group.originalZIndex; group.style["opacity"] = group.originalOpacity; } Drag.group = null; Drag.isDragging = false; return false; }, fixEvent : function(event) { if (typeof event == 'undefined') event = window.event; Coordinates.fixEvent(event); return event; } };