diff options
Diffstat (limited to 'vendor/assets/javascripts')
-rw-r--r-- | vendor/assets/javascripts/coordinates.js | 94 | ||||
-rw-r--r-- | vendor/assets/javascripts/drag.js | 229 | ||||
-rw-r--r-- | vendor/assets/javascripts/dragsort.js | 234 |
3 files changed, 557 insertions, 0 deletions
diff --git a/vendor/assets/javascripts/coordinates.js b/vendor/assets/javascripts/coordinates.js new file mode 100644 index 0000000..e2f5bf2 --- /dev/null +++ b/vendor/assets/javascripts/coordinates.js @@ -0,0 +1,94 @@ +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; +} diff --git a/vendor/assets/javascripts/drag.js b/vendor/assets/javascripts/drag.js new file mode 100644 index 0000000..9ba6746 --- /dev/null +++ b/vendor/assets/javascripts/drag.js @@ -0,0 +1,229 @@ +/* + * 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; + } +}; diff --git a/vendor/assets/javascripts/dragsort.js b/vendor/assets/javascripts/dragsort.js new file mode 100644 index 0000000..6356663 --- /dev/null +++ b/vendor/assets/javascripts/dragsort.js @@ -0,0 +1,234 @@ +// TODO: refactor away duplicationg in DragSort and DragSortX + +var BetterDragSort = { + + makeListSortable : function(list) { + var items = list.getElementsByTagName("li"); + + for (var i = 0; i < items.length; i++) { + BetterDragSort.makeItemSortable(items[i]); + } + }, + + makeItemSortable : function(item) { + Drag.makeDraggable(item); + item.setDragThresholdY(5); + + item.onDragStart = BetterDragSort.onDragStart; + item.onDrag = BetterDragSort.onDrag; + item.onDragEnd = BetterDragSort.onDragEnd; + }, + + onDragStart : function(nwPosition, sePosition, nwOffset, seOffset) { + var items = this.parentNode.getElementsByTagName("li"); + var minOffset = Coordinates.northwestOffset(items[0], true); + var maxOffset = minOffset; + for (var i = 0; i < items.length; i++) { + maxOffset = maxOffset.max(Coordinates.northwestOffset(items[i], true)); + } + this.constrain(minOffset, maxOffset); + }, + + onDrag : function(nwPosition, sePosition, nwOffset, seOffset) { + var swapper = null; + + var next = DragUtils.nextItem(this); + while (next != null) { + var nextNWOffset = Coordinates.northwestOffset(next, true); + var nextSEOffset = Coordinates.southeastOffset(next, true); + + if (nwOffset.y >= (nextNWOffset.y - 2) && + nwOffset.y <= (nextSEOffset.y + 2) && + nwOffset.x >= (nextNWOffset.x - 2) && + nwOffset.x <= (nextSEOffset.x + 2)) { + var swapper = next; + break; + } + var next = DragUtils.nextItem(next); + } + if (swapper != null) { + BetterDragSort.moveAfter(this, swapper); + return; + } + + var previous = DragUtils.previousItem(this); + while (previous != null) { + var previousNWOffset = Coordinates.northwestOffset(previous, true); + var previousSEOffset = Coordinates.southeastOffset(previous, true); + + var fudgeFactor = 2; + if (nwOffset.y >= (previousNWOffset.y - fudgeFactor) && + nwOffset.y <= (previousSEOffset.y + fudgeFactor) && + nwOffset.x >= (previousNWOffset.x - fudgeFactor) && + nwOffset.x <= (previousSEOffset.x + fudgeFactor)) { + var swapper = previous; + break; + } + var previous = DragUtils.previousItem(previous); + } + if (swapper != null) { + BetterDragSort.moveBefore(this, swapper); + return; + } + }, + + moveAfter : function(item1, item2) { + var parent = item1.parentNode; + parent.removeChild(item1); + parent.insertBefore(item1, item2.nextSibling); + + item1.style["top"] = "0px"; + item1.style["left"] = "0px"; + }, + + moveBefore : function(item1, item2) { + var parent = item1.parentNode; + parent.removeChild(item1); + parent.insertBefore(item1, item2); + + item1.style["top"] = "0px"; + item1.style["left"] = "0px"; + }, + + onDragEnd : function(nwPosition, sePosition, nwOffset, seOffset) { + this.style["top"] = "0px"; + this.style["left"] = "0px"; + } +}; + + +var DragSort = { + + makeListSortable : function(list) { + var items = list.getElementsByTagName("li"); + + for (var i = 0; i < items.length; i++) { + DragSort.makeItemSortable(items[i]); + } + }, + + makeItemSortable : function(item) { + Drag.makeDraggable(item); + item.setDragThresholdY(5); + + item.onDragStart = DragSort.onDragStart; + item.onDrag = DragSort.onDrag; + item.onDragEnd = DragSort.onDragEnd; + }, + + onDragStart : function(nwPosition, sePosition, nwOffset, seOffset) { + var items = this.parentNode.getElementsByTagName("li"); + var minOffset = Coordinates.northwestOffset(items[0], true); + var maxOffset = Coordinates.northwestOffset(items[items.length - 1], true); + this.constrain(minOffset, maxOffset); + }, + + onDrag : function(nwPosition, sePosition, nwOffset, seOffset) { + var parent = this.parentNode; + + var item = this; + var next = DragUtils.nextItem(item); + while (next != null && this.offsetTop >= next.offsetTop - 2) { + var item = next; + var next = DragUtils.nextItem(item); + } + if (this != item) { + DragUtils.swap(this, next); + return; + } + + var item = this; + var previous = DragUtils.previousItem(item); + while (previous != null && this.offsetTop <= previous.offsetTop + 2) { + var item = previous; + var previous = DragUtils.previousItem(item); + } + if (this != item) { + DragUtils.swap(this, item); + return; + } + }, + + onDragEnd : function(nwPosition, sePosition, nwOffset, seOffset) { + this.style["top"] = "0px"; + this.style["left"] = "0px"; + } +}; + +var DragSortX = { + + makeListSortable : function(list) { + var items = list.getElementsByTagName("li"); + + var minOffset = Coordinates.northwestOffset(items[0], true); + var maxOffset = Coordinates.northwestOffset(items[items.length - 1], true); + + for (var i = 0; i < items.length; i++) { + Drag.makeDraggable(items[i]); + items[i].constrain(minOffset, maxOffset); + items[i].setDragThresholdX(5); + + items[i].onDrag = DragSortX.onDrag; + + items[i].onDragEnd = function(nwPosition, sePosition, nwOffset, seOffset) { + this.style["top"] = "0px"; + this.style["left"] = "0px"; + }; + } + }, + + onDrag : function(nwPosition, sePosition, nwOffset, seOffset) { + var parent = this.parentNode; + + var item = this; + var next = DragUtils.nextItem(item); + while (next != null && this.offsetLeft >= next.offsetLeft - 2) { + var item = next; + var next = DragUtils.nextItem(item); + } + if (this != item) { + DragUtils.swap(this, next); + return; + } + + var item = this; + var previous = DragUtils.previousItem(item); + while (previous != null && this.offsetLeft <= previous.offsetLeft + 2) { + var item = previous; + var previous = DragUtils.previousItem(item); + } + if (this != item) { + DragUtils.swap(this, item); + return; + } + } +}; + +var DragUtils = { + swap : function(item1, item2) { + var parent = item1.parentNode; + parent.removeChild(item1); + parent.insertBefore(item1, item2); + + item1.style["top"] = "0px"; + item1.style["left"] = "0px"; + }, + + nextItem : function(item) { + var sibling = item.nextSibling; + while (sibling != null) { + if (sibling.nodeName == item.nodeName) return sibling; + sibling = sibling.nextSibling; + } + return null; + }, + + previousItem : function(item) { + var sibling = item.previousSibling; + while (sibling != null) { + if (sibling.nodeName == item.nodeName) return sibling; + sibling = sibling.previousSibling; + } + return null; + } +}; |