/*
dragtable v1.0
June 26, 2008
Dan Vanderkam, http://danvk.org/dragtable/
http://code.google.com/p/dragtable/
Instructions:
- Download this file
- Add to your HTML.
- Add class="draggable" to any table you might like to reorder.
- Drag the headers around to reorder them.
This is code was based on:
- Stuart Langridge's SortTable (kryogenix.org/code/browser/sorttable)
- Mike Hall's draggable class (http://www.brainjar.com/dhtml/drag/)
- A discussion of permuting table columns on comp.lang.javascript
Licensed under the MIT license.
*/
// Here's the notice from Mike Hall's draggable script:
//*****************************************************************************
// Do not remove this notice.
//
// Copyright 2001 by Mike Hall.
// See http://www.brainjar.com for terms of use.
//*****************************************************************************
dragtable = {
// How far should the mouse move before it's considered a drag, not a click?
dragRadius2: 100,
setMinDragDistance: function (x) {
dragtable.dragRadius2 = x * x;
},
// How long should cookies persist? (in days)
cookieDays: 365,
setCookieDays: function (x) {
dragtable.cookieDays = x;
},
// Determine browser and version.
// TODO: eliminate browser sniffing except where it's really necessary.
Browser: function () {
var ua, s, i;
this.isIE = false;
this.isNS = false;
this.version = null;
ua = navigator.userAgent;
s = "MSIE";
if ((i = ua.indexOf(s)) >= 0) {
this.isIE = true;
this.version = parseFloat(ua.substr(i + s.length));
return;
}
s = "Netscape6/";
if ((i = ua.indexOf(s)) >= 0) {
this.isNS = true;
this.version = parseFloat(ua.substr(i + s.length));
return;
}
// Treat any other "Gecko" browser as NS 6.1.
s = "Gecko";
if ((i = ua.indexOf(s)) >= 0) {
this.isNS = true;
this.version = 6.1;
return;
}
},
browser: null,
// Detect all draggable tables and attach handlers to their headers.
init: function () {
// Don't initialize twice
if (arguments.callee.done) return;
arguments.callee.done = true;
if (_dgtimer) clearInterval(_dgtimer);
if (!document.createElement || !document.getElementsByTagName) return;
dragtable.dragObj.zIndex = 0;
dragtable.browser = new dragtable.Browser();
forEach(document.getElementsByTagName('table'), function (table) {
if (table.className.search(/\bdraggable\b/) != -1) {
dragtable.makeDraggable(table);
}
});
},
// The thead business is taken straight from sorttable.
makeDraggable: function (table) {
if (table.getElementsByTagName('thead').length == 0) {
the = document.createElement('thead');
the.appendChild(table.rows[0]);
table.insertBefore(the, table.firstChild);
}
// Safari doesn't support table.tHead, sigh
if (table.tHead == null) {
table.tHead = table.getElementsByTagName('thead')[0];
}
var headers = table.tHead.rows[0].cells;
for (var i = 1; i < headers.length - 1; i++) {
headers[i].onmousedown = dragtable.dragStart;
}
// Replay reorderings from cookies if there are any.
if (dragtable.cookiesEnabled() && table.id &&
table.className.search(/\bforget-ordering\b/) == -1) {
dragtable.replayDrags(table);
}
},
// Global object to hold drag information.
dragObj: new Object(),
// Climb up the DOM until there's a tag that matches.
findUp: function (elt, tag) {
do {
if (elt.nodeName && elt.nodeName.search(tag) != -1)
return elt;
} while (elt = elt.parentNode);
return null;
},
// clone an element, copying its style and class.
fullCopy: function (elt, deep) {
var new_elt = elt.cloneNode(deep);
new_elt.className = elt.className;
forEach(elt.style,
function (value, key, object) {
if (value == null) return;
if (typeof (value) == "string" && value.length == 0) return;
new_elt.style[key] = elt.style[key];
});
return new_elt;
},
eventPosition: function (event) {
var x, y;
if (dragtable.browser.isIE) {
x = window.event.clientX + document.documentElement.scrollLeft
+ document.body.scrollLeft;
y = window.event.clientY + document.documentElement.scrollTop
+ document.body.scrollTop;
return { x: x, y: y };
}
return { x: event.pageX, y: event.pageY };
},
// Determine the position of this element on the page. Many thanks to Magnus
// Kristiansen for help making this work with "position: fixed" elements.
absolutePosition: function (elt, stopAtRelative) {
var ex = 0, ey = 0;
do {
var curStyle = dragtable.browser.isIE ? elt.currentStyle
: window.getComputedStyle(elt, '');
var supportFixed = !(dragtable.browser.isIE &&
dragtable.browser.version < 7);
if (stopAtRelative && curStyle.position == 'relative') {
break;
} else if (supportFixed && curStyle.position == 'fixed') {
// Get the fixed el's offset
ex += parseInt(curStyle.left, 10);
ey += parseInt(curStyle.top, 10);
// Compensate for scrolling
ex += document.body.scrollLeft;
ey += document.body.scrollTop;
// End the loop
break;
} else {
ex += elt.offsetLeft;
ey += elt.offsetTop;
}
} while (elt = elt.offsetParent);
return { x: ex, y: ey };
},
// MouseDown handler -- sets up the appropriate mousemove/mouseup handlers
// and fills in the global dragtable.dragObj object.
dragStart: function (event, id) {
var el;
var x, y;
var dragObj = dragtable.dragObj;
var browser = dragtable.browser;
if (browser.isIE)
dragObj.origNode = window.event.srcElement;
else
dragObj.origNode = event.target;
var pos = dragtable.eventPosition(event);
// Drag the entire table cell, not just the element that was clicked.
dragObj.origNode = dragtable.findUp(dragObj.origNode, /T[DH]/);
// Since a column header can't be dragged directly, duplicate its contents
// in a div and drag that instead.
// TODO: I can assume a tHead...
var table = dragtable.findUp(dragObj.origNode, "TABLE");
dragObj.table = table;
dragObj.startCol = dragtable.findColumn(table, pos.x);
if (dragObj.startCol == -1) return;
var new_elt = dragtable.fullCopy(table, false);
new_elt.style.margin = '0';
// Copy the entire column
var copySectionColumn = function (sec, col) {
var new_sec = dragtable.fullCopy(sec, false);
forEach(sec.rows, function (row) {
var cell = row.cells[col];
var new_tr = dragtable.fullCopy(row, false);
if (row.offsetHeight) new_tr.style.height = row.offsetHeight + "px";
var new_td = dragtable.fullCopy(cell, true);
if (cell.offsetWidth) new_td.style.width = cell.offsetWidth + "px";
new_tr.appendChild(new_td);
new_sec.appendChild(new_tr);
});
return new_sec;
};
// First the heading
if (table.tHead) {
new_elt.appendChild(copySectionColumn(table.tHead, dragObj.startCol));
}
forEach(table.tBodies, function (tb) {
new_elt.appendChild(copySectionColumn(tb, dragObj.startCol));
});
if (table.tFoot) {
new_elt.appendChild(copySectionColumn(table.tFoot, dragObj.startCol));
}
var obj_pos = dragtable.absolutePosition(dragObj.origNode, true);
new_elt.style.position = "absolute";
new_elt.style.left = obj_pos.x + "px";
new_elt.style.top = obj_pos.y + "px";
new_elt.style.width = dragObj.origNode.offsetWidth + "px";
new_elt.style.height = dragObj.origNode.offsetHeight + "px";
new_elt.style.opacity = 0.7;
// Hold off adding the element until this is clearly a drag.
dragObj.addedNode = false;
dragObj.tableContainer = dragObj.table.parentNode || document.body;
dragObj.elNode = new_elt;
// Save starting positions of cursor and element.
dragObj.cursorStartX = pos.x;
dragObj.cursorStartY = pos.y;
dragObj.elStartLeft = parseInt(dragObj.elNode.style.left, 10);
dragObj.elStartTop = parseInt(dragObj.elNode.style.top, 10);
if (isNaN(dragObj.elStartLeft)) dragObj.elStartLeft = 0;
if (isNaN(dragObj.elStartTop)) dragObj.elStartTop = 0;
// Update element's z-index.
dragObj.elNode.style.zIndex = ++dragObj.zIndex;
// Capture mousemove and mouseup events on the page.
if (browser.isIE) {
document.attachEvent("onmousemove", dragtable.dragMove);
document.attachEvent("onmouseup", dragtable.dragEnd);
window.event.cancelBubble = true;
window.event.returnValue = false;
} else {
document.addEventListener("mousemove", dragtable.dragMove, true);
document.addEventListener("mouseup", dragtable.dragEnd, true);
event.preventDefault();
}
},
// Move the floating column header with the mouse
// TODO: Reorder columns as the mouse moves for a more interactive feel.
dragMove: function (event) {
var x, y;
var dragObj = dragtable.dragObj;
// Get cursor position with respect to the page.
var pos = dragtable.eventPosition(event);
var dx = dragObj.cursorStartX - pos.x;
var dy = dragObj.cursorStartY - pos.y;
if (!dragObj.addedNode && dx * dx + dy * dy > dragtable.dragRadius2) {
dragObj.tableContainer.insertBefore(dragObj.elNode, dragObj.table);
dragObj.addedNode = true;
}
// Move drag element by the same amount the cursor has moved.
var style = dragObj.elNode.style;
style.left = (dragObj.elStartLeft + pos.x - dragObj.cursorStartX) + "px";
style.top = (dragObj.elStartTop + pos.y - dragObj.cursorStartY) + "px";
if (dragtable.browser.isIE) {
window.event.cancelBubble = true;
window.event.returnValue = false;
} else {
event.preventDefault();
}
},
// Stop capturing mousemove and mouseup events.
// Determine which (if any) column we're over and shuffle the table.
dragEnd: function (event) {
if (dragtable.browser.isIE) {
document.detachEvent("onmousemove", dragtable.dragMove);
document.detachEvent("onmouseup", dragtable.dragEnd);
} else {
document.removeEventListener("mousemove", dragtable.dragMove, true);
document.removeEventListener("mouseup", dragtable.dragEnd, true);
}
// If the floating header wasn't added, the mouse didn't move far enough.
var dragObj = dragtable.dragObj;
if (!dragObj.addedNode) {
return;
}
dragObj.tableContainer.removeChild(dragObj.elNode);
// Determine whether the drag ended over the table, and over which column.
var pos = dragtable.eventPosition(event);
var table_pos = dragtable.absolutePosition(dragObj.table);
if (pos.y < table_pos.y ||
pos.y > table_pos.y + dragObj.table.offsetHeight) {
return;
}
var targetCol = dragtable.findColumn(dragObj.table, pos.x);
if (targetCol != -1 && targetCol != dragObj.startCol) {
dragtable.moveColumn(dragObj.table, dragObj.startCol, targetCol);
if (dragObj.table.id && dragtable.cookiesEnabled() &&
dragObj.table.className.search(/\bforget-ordering\b/) == -1) {
dragtable.rememberDrag(dragObj.table.id, dragObj.startCol, targetCol);
}
}
},
// Which column does the x value fall inside of? x should include scrollLeft.
findColumn: function (table, x) {
var header = table.tHead.rows[0].cells;
for (var i = 0; i < header.length; i++) {
//var left = header[i].offsetLeft;
var pos = dragtable.absolutePosition(header[i]);
//if (left <= x && x <= left + header[i].offsetWidth) {
if (pos.x <= x && x <= pos.x + header[i].offsetWidth) {
return i;
}
}
return -1;
},
// Move a column of table from start index to finish index.
// Based on the "Swapping table columns" discussion on comp.lang.javascript.
// Assumes there are columns at sIdx and fIdx
moveColumn: function (table, sIdx, fIdx) {
var row, cA;
var i = table.rows.length;
while (i--) {
row = table.rows[i]
var x = row.removeChild(row.cells[sIdx]);
if (fIdx < row.cells.length) {
row.insertBefore(x, row.cells[fIdx]);
} else {
row.appendChild(x);
}
}
// For whatever reason, sorttable tracks column indices this way.
// Without a manual update, clicking one column will sort on another.
var headrow = table.tHead.rows[0].cells;
for (var i = 0; i < headrow.length; i++) {
headrow[i].sorttable_columnindex = i;
}
},
// Are cookies enabled? We should not attempt to set cookies on a local file.
cookiesEnabled: function () {
return (window.location.protocol != 'file:') && navigator.cookieEnabled;
},
// Store a column swap in a cookie for posterity.
rememberDrag: function (id, a, b) {
var cookieName = "dragtable-" + id;
var prev = dragtable.readCookie(cookieName);
var new_val = "";
if (prev) new_val = prev + ",";
new_val += a + "/" + b;
dragtable.createCookie(cookieName, new_val, dragtable.cookieDays);
},
// Replay all column swaps for a table.
replayDrags: function (table) {
if (!dragtable.cookiesEnabled()) return;
var dragstr = dragtable.readCookie("dragtable-" + table.id);
if (!dragstr) return;
var drags = dragstr.split(',');
for (var i = 0; i < drags.length; i++) {
var pair = drags[i].split("/");
if (pair.length != 2) continue;
var a = parseInt(pair[0]);
var b = parseInt(pair[1]);
if (isNaN(a) || isNaN(b)) continue;
dragtable.moveColumn(table, a, b);
}
},
// Cookie functions based on http://www.quirksmode.org/js/cookies.html
// Cookies won't work for local files.
cookiesEnabled: function () {
return (window.location.protocol != 'file:') && navigator.cookieEnabled;
},
createCookie: function (name, value, days) {
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "; expires=" + date.toGMTString();
}
else var expires = "";
var path = document.location.pathname;
document.cookie = name + "=" + value + expires + "; path=" + path
},
readCookie: function (name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
},
eraseCookie: function (name) {
dragtable.createCookie(name, "", -1);
}
}
/* ******************************************************************
Supporting functions: bundled here to avoid depending on a library
****************************************************************** */
// Dean Edwards/Matthias Miller/John Resig
// has a hook for dragtable.init already been added? (see below)
var dgListenOnLoad = false;
/* for Mozilla/Opera9 */
if (document.addEventListener) {
dgListenOnLoad = true;
document.addEventListener("DOMContentLoaded", dragtable.init, false);
}
/* for Internet Explorer */
/*@cc_on @*/
/*@if (@_win32)
dgListenOnLoad = true;
document.write("