328 lines
9.4 KiB
JavaScript
328 lines
9.4 KiB
JavaScript
/* 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;
|
|
}
|
|
};
|