Index: www/macros/macro_tasks.php
===================================================================
--- www/macros/macro_tasks.php	(revision 1124)
+++ www/macros/macro_tasks.php	(working copy)
@@ -152,17 +152,24 @@
                      identity_can("round-view-progress", $round);
 
     // get round tasks
-    $tasks = round_get_tasks($round_id,
+    $tasks = round_get_tasks(
+             $round_id,
              $options['first_entry'],
              $options['display_entries'],
-             $user_id, $scores,
-             $filter, $show_progress);
+             $user_id,
+             $scores,
+             $filter,
+             $show_progress);
+
     $options['total_entries'] = round_get_task_count(
              $round_id, $user_id, $filter);
     $options['row_style'] = 'task_row_style';
-    $options['css_class'] = 'tasks';
     $options['css_row_parity'] = true;
 
+    $options['css_class'] = 'tasks';
+    if (getattr($args, 'drag_and_drop', false))
+      $options['css_class'] .= ' dragndrop';
+
     $column_infos = array();
     if ($show_numbers) {
         $column_infos[] = array(
Index: www/static/js/foreach.js
===================================================================
--- www/static/js/foreach.js	(revision 0)
+++ www/static/js/foreach.js	(revision 0)
@@ -0,0 +1,53 @@
+// Dean's forEach: http://dean.edwards.name/base/forEach.js
+/*
+	forEach, version 1.0
+	Copyright 2006, Dean Edwards
+	License: http://www.opensource.org/licenses/mit-license.php
+*/
+
+// array-like enumeration
+if (!Array.forEach) { // mozilla already supports this
+	Array.forEach = function(array, block, context) {
+		for (var i = 0; i < array.length; i++) {
+			block.call(context, array[i], i, array);
+		}
+	};
+}
+
+// generic enumeration
+Function.prototype.forEach = function(object, block, context) {
+	for (var key in object) {
+		if (typeof this.prototype[key] == "undefined") {
+			block.call(context, object[key], key, object);
+		}
+	}
+};
+
+// character enumeration
+String.forEach = function(string, block, context) {
+	Array.forEach(string.split(""), function(chr, index) {
+		block.call(context, chr, index, string);
+	});
+};
+
+// globally resolve forEach enumeration
+var forEach = function(object, block, context) {
+	if (object) {
+		var resolve = Object; // default
+		if (object instanceof Function) {
+			// functions have a "length" property
+			resolve = Function;
+		} else if (object.forEach instanceof Function) {
+			// the object implements a custom forEach method so use that
+			object.forEach(block, context);
+			return;
+		} else if (typeof object == "string") {
+			// the object is a string
+			resolve = String;
+		} else if (typeof object.length == "number") {
+			// the object is array-like
+			resolve = Array;
+		}
+		resolve.forEach(object, block, context);
+	}
+};
Index: www/static/js/tablednd.js
===================================================================
--- www/static/js/tablednd.js	(revision 0)
+++ www/static/js/tablednd.js	(revision 0)
@@ -0,0 +1,228 @@
+// ===================================================================
+// Author: Denis Howlett <feedback@isocra.com>
+// WWW: http://www.isocra.com/
+//
+// NOTICE: You may use this code for any purpose, commercial or
+// private, without any further permission from the author. You may
+// remove this notice from your final code if you wish, however we
+// would appreciate it if at least the web site address is kept.
+//
+// You may *NOT* re-distribute this code in any way except through its
+// use. That means, you can include it in your product, or your web
+// site, or any other form where the code is actually being used. You
+// may not put the plain javascript up on your site for download or
+// include it in your javascript libraries for download.
+// If you wish to share this code with others, please just point them
+// to the URL instead.
+//
+// Please DO NOT link directly to this .js files from your site. Copy
+// the files to your server and use them there. Thank you.
+// ===================================================================
+
+/** Keep hold of the current table being dragged */
+var currenttable = null;
+
+/** Capture the onmousemove so that we can see if a row from the current
+ *  table if any is being dragged.
+ * @param ev the event (for Firefox and Safari, otherwise we use window.event for IE)
+ */
+document.onmousemove = function(ev){
+    if (currenttable && currenttable.dragObject) {
+        ev   = ev || window.event;
+        var mousePos = currenttable.mouseCoords(ev);
+        var y = mousePos.y - currenttable.mouseOffset.y;
+        if (y != currenttable.oldY) {
+            // work out if we're going up or down...
+            var movingDown = y > currenttable.oldY;
+            // update the old value
+            currenttable.oldY = y;
+            // update the style to show we're dragging
+            currenttable.dragObject.style.backgroundColor = "#eee";
+            // If we're over a row then move the dragged row to there so that the user sees the
+            // effect dynamically
+            var currentRow = currenttable.findDropTargetRow(y);
+            if (currentRow) {
+                if (movingDown && currenttable.dragObject != currentRow) {
+                    currenttable.dragObject.parentNode.insertBefore(currenttable.dragObject, currentRow.nextSibling)
+                } else if (! movingDown && currenttable.dragObject != currentRow) {
+                    currenttable.dragObject.parentNode.insertBefore(currenttable.dragObject, currentRow);
+                }
+            }
+        }
+
+        return false;
+    }
+}
+
+// Similarly for the mouseup
+document.onmouseup   = function(ev){
+    if (currenttable && currenttable.dragObject) {
+        var droppedRow = currenttable.dragObject;
+        // If we have a dragObject, then we need to release it,
+        // The row will already have been moved to the right place so we just reset stuff
+        droppedRow.style.backgroundColor = 'transparent';
+        currenttable.dragObject   = null;
+        // And then call the onDrop method in case anyone wants to do any post processing
+        currenttable.onDrop(currenttable.table, droppedRow);
+        currenttable = null; // let go of the table too
+    }
+}
+
+
+/** get the source element from an event in a way that works for IE and Firefox and Safari
+ * @param evt the source event for Firefox (but not IE--IE uses window.event) */
+function getEventSource(evt) {
+    if (window.event) {
+        evt = window.event; // For IE
+        return evt.srcElement;
+    } else {
+        return evt.target; // For Firefox
+    }
+}
+
+/**
+ * Create a TableDnD object for each table with drag and drop enabled.
+ */
+function initialize() {
+    forEach(document.getElementsByTagName('table'), function(table) {
+        if (table.className.search(/\bdragndrop\b/) != -1) {
+            var tableDnD = new TableDnD();
+            tableDnD.init(table);
+        }
+    });
+}
+
+/**
+ * Encapsulate table Drag and Drop in a class. We'll have this as a Singleton
+ * so we don't get scoping problems.
+ */
+function TableDnD() {
+    /** Keep hold of the current drag object if any */
+    this.dragObject = null;
+    /** The current mouse offset */
+    this.mouseOffset = null;
+    /** The current table */
+    this.table = null;
+    /** Remember the old value of Y so that we don't do too much processing */
+    this.oldY = 0;
+
+    /** Initialise the drag and drop by capturing mouse move events */
+    this.init = function(table) {
+        this.table = table;
+        var rows = table.tBodies[0].rows; //getElementsByTagName("tr")
+        for (var i=0; i<rows.length; i++) {
+			// John Tarr: added to ignore rows that I've added the NoDnD attribute to (Category and Header rows)
+			var nodrag = rows[i].getAttribute("NoDrag")
+			if (nodrag == null || nodrag == "undefined") { //There is no NoDnD attribute on rows I want to drag
+				this.makeDraggable(rows[i]);
+			}
+        }
+    }
+
+    /** This function is called when you drop a row, so redefine it in your code
+        to do whatever you want, for example use Ajax to update the server */
+    this.onDrop = function(table, droppedRow) {
+        // Do nothing for now
+    }
+
+	/** Get the position of an element by going up the DOM tree and adding up all the offsets */
+    this.getPosition = function(e){
+        var left = 0;
+        var top  = 0;
+		/** Safari fix -- thanks to Luis Chato for this! */
+		if (e.offsetHeight == 0) {
+			/** Safari 2 doesn't correctly grab the offsetTop of a table row
+			    this is detailed here:
+			    http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
+			    the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
+			    note that firefox will return a text node as a first child, so designing a more thorough
+			    solution may need to take that into account, for now this seems to work in firefox, safari, ie */
+			e = e.firstChild; // a table cell
+		}
+
+        while (e.offsetParent){
+            left += e.offsetLeft;
+            top  += e.offsetTop;
+            e     = e.offsetParent;
+        }
+
+        left += e.offsetLeft;
+        top  += e.offsetTop;
+
+        return {x:left, y:top};
+    }
+
+	/** Get the mouse coordinates from the event (allowing for browser differences) */
+    this.mouseCoords = function(ev){
+        if(ev.pageX || ev.pageY){
+            return {x:ev.pageX, y:ev.pageY};
+        }
+        return {
+            x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
+            y:ev.clientY + document.body.scrollTop  - document.body.clientTop
+        };
+    }
+
+	/** Given a target element and a mouse event, get the mouse offset from that element.
+		To do this we need the element's position and the mouse position */
+    this.getMouseOffset = function(target, ev){
+        ev = ev || window.event;
+
+        var docPos    = this.getPosition(target);
+        var mousePos  = this.mouseCoords(ev);
+        return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
+    }
+
+	/** Take an item and add an onmousedown method so that we can make it draggable */
+    this.makeDraggable = function(item) {
+        if(!item) return;
+        var self = this; // Keep the context of the TableDnd inside the function
+        item.onmousedown = function(ev) {
+            // Need to check to see if we are an input or not, if we are an input, then
+            // return true to allow normal processing
+            var target = getEventSource(ev);
+            if (target.tagName == 'INPUT' || target.tagName == 'SELECT') return true;
+            currenttable = self;
+            self.dragObject  = this;
+            self.mouseOffset = self.getMouseOffset(this, ev);
+            return false;
+        }
+        item.style.cursor = "move";
+    }
+
+    /** We're only worried about the y position really, because we can only move rows up and down */
+    this.findDropTargetRow = function(y) {
+        var rows = this.table.tBodies[0].rows;
+		for (var i=0; i<rows.length; i++) {
+			var row = rows[i];
+			// John Tarr added to ignore rows that I've added the NoDnD attribute to (Header rows)
+			var nodrop = row.getAttribute("NoDrop");
+			if (nodrop == null || nodrop == "undefined") {  //There is no NoDnD attribute on rows I want to drag
+				var rowY    = this.getPosition(row).y;
+				var rowHeight = parseInt(row.offsetHeight)/2;
+				if (row.offsetHeight == 0) {
+					rowY = this.getPosition(row.firstChild).y;
+					rowHeight = parseInt(row.firstChild.offsetHeight)/2;
+				}
+				// Because we always have to insert before, we need to offset the height a bit
+				if ((y > rowY - rowHeight) && (y < (rowY + rowHeight))) {
+					// that's the row we're over
+					return row;
+				}
+			}
+		}
+		return null;
+	}
+}
+
+/* for Safari */
+if (/WebKit/i.test(navigator.userAgent)) { // sniff
+    var _timer = setInterval(function() {
+        if (/loaded|complete/.test(document.readyState)) {
+            initialize(); // call the onload handler
+        }
+    }, 10);
+}
+
+/* for other browsers */
+window.onload = initialize;

Property changes on: www/static/js/tablednd.js
___________________________________________________________________
Added: svn:executable
   + *

Index: www/views/header.php
===================================================================
--- www/views/header.php	(revision 1124)
+++ www/views/header.php	(working copy)
@@ -68,6 +68,8 @@
     <script type="text/javascript" src="<?= html_escape(url_static('js/sh/shInit.js')) ?>"></script>
     <script type="text/javascript" src="<?= html_escape(url_static('js/tags.js')) ?>"></script>
     <script type="text/javascript" src="<?= html_escape(url_static('js/roundtimer.js')) ?>"></script>
+    <script type="text/javascript" src="<?= html_escape(url_static('js/foreach.js')) ?>"></script>
+    <script type="text/javascript" src="<?= html_escape(url_static('js/tablednd.js')) ?>"></script>
 
     <script type="text/javascript" src="http://summify.com/client/v1/249f00c6/client.js"></script>
 
