| Line 0
          
      
      
        Link Here | 
            
              |  |  | 1 | /** | 
            
              | 2 |  * TableDnD plug-in for JQuery, allows you to drag and drop table rows | 
            
              | 3 |  * You can set up various options to control how the system will work | 
            
              | 4 |  * Copyright (c) Denis Howlett <denish@isocra.com> | 
            
              | 5 |  * Licensed like jQuery, see http://docs.jquery.com/License. | 
            
              | 6 |  * | 
            
              | 7 |  * Configuration options: | 
            
              | 8 |  *  | 
            
              | 9 |  * onDragStyle | 
            
              | 10 |  *     This is the style that is assigned to the row during drag. There are limitations to the styles that can be | 
            
              | 11 |  *     associated with a row (such as you can't assign a border--well you can, but it won't be | 
            
              | 12 |  *     displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as | 
            
              | 13 |  *     a map (as used in the jQuery css(...) function). | 
            
              | 14 |  * onDropStyle | 
            
              | 15 |  *     This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations | 
            
              | 16 |  *     to what you can do. Also this replaces the original style, so again consider using onDragClass which | 
            
              | 17 |  *     is simply added and then removed on drop. | 
            
              | 18 |  * onDragClass | 
            
              | 19 |  *     This class is added for the duration of the drag and then removed when the row is dropped. It is more | 
            
              | 20 |  *     flexible than using onDragStyle since it can be inherited by the row cells and other content. The default | 
            
              | 21 |  *     is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your | 
            
              | 22 |  *     stylesheet. | 
            
              | 23 |  * onDrop | 
            
              | 24 |  *     Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table | 
            
              | 25 |  *     and the row that was dropped. You can work out the new order of the rows by using | 
            
              | 26 |  *     table.rows. | 
            
              | 27 |  * onDragStart | 
            
              | 28 |  *     Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the | 
            
              | 29 |  *     table and the row which the user has started to drag. | 
            
              | 30 |  * onAllowDrop | 
            
              | 31 |  *     Pass a function that will be called as a row is over another row. If the function returns true, allow  | 
            
              | 32 |  *     dropping on that row, otherwise not. The function takes 2 parameters: the dragged row and the row under | 
            
              | 33 |  *     the cursor. It returns a boolean: true allows the drop, false doesn't allow it. | 
            
              | 34 |  * scrollAmount | 
            
              | 35 |  *     This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the | 
            
              | 36 |  *     window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2, | 
            
              | 37 |  *     FF3 beta | 
            
              | 38 |  * dragHandle | 
            
              | 39 |  *     This is the name of a class that you assign to one or more cells in each row that is draggable. If you | 
            
              | 40 |  *     specify this class, then you are responsible for setting cursor: move in the CSS and only these cells | 
            
              | 41 |  *     will have the drag behaviour. If you do not specify a dragHandle, then you get the old behaviour where | 
            
              | 42 |  *     the whole row is draggable. | 
            
              | 43 |  *  | 
            
              | 44 |  * Other ways to control behaviour: | 
            
              | 45 |  * | 
            
              | 46 |  * Add class="nodrop" to any rows for which you don't want to allow dropping, and class="nodrag" to any rows | 
            
              | 47 |  * that you don't want to be draggable. | 
            
              | 48 |  * | 
            
              | 49 |  * Inside the onDrop method you can also call $.tableDnD.serialize() this returns a string of the form | 
            
              | 50 |  * <tableID>[]=<rowID1>&<tableID>[]=<rowID2> so that you can send this back to the server. The table must have | 
            
              | 51 |  * an ID as must all the rows. | 
            
              | 52 |  * | 
            
              | 53 |  * Other methods: | 
            
              | 54 |  * | 
            
              | 55 |  * $("...").tableDnDUpdate()  | 
            
              | 56 |  * Will update all the matching tables, that is it will reapply the mousedown method to the rows (or handle cells). | 
            
              | 57 |  * This is useful if you have updated the table rows using Ajax and you want to make the table draggable again. | 
            
              | 58 |  * The table maintains the original configuration (so you don't have to specify it again). | 
            
              | 59 |  * | 
            
              | 60 |  * $("...").tableDnDSerialize() | 
            
              | 61 |  * Will serialize and return the serialized string as above, but for each of the matching tables--so it can be | 
            
              | 62 |  * called from anywhere and isn't dependent on the currentTable being set up correctly before calling | 
            
              | 63 |  * | 
            
              | 64 |  * Known problems: | 
            
              | 65 |  * - Auto-scoll has some problems with IE7  (it scrolls even when it shouldn't), work-around: set scrollAmount to 0 | 
            
              | 66 |  *  | 
            
              | 67 |  * Version 0.2: 2008-02-20 First public version | 
            
              | 68 |  * Version 0.3: 2008-02-07 Added onDragStart option | 
            
              | 69 |  *                         Made the scroll amount configurable (default is 5 as before) | 
            
              | 70 |  * Version 0.4: 2008-03-15 Changed the noDrag/noDrop attributes to nodrag/nodrop classes | 
            
              | 71 |  *                         Added onAllowDrop to control dropping | 
            
              | 72 |  *                         Fixed a bug which meant that you couldn't set the scroll amount in both directions | 
            
              | 73 |  *                         Added serialize method | 
            
              | 74 |  * Version 0.5: 2008-05-16 Changed so that if you specify a dragHandle class it doesn't make the whole row | 
            
              | 75 |  *                         draggable | 
            
              | 76 |  *                         Improved the serialize method to use a default (and settable) regular expression. | 
            
              | 77 |  *                         Added tableDnDupate() and tableDnDSerialize() to be called when you are outside the table | 
            
              | 78 |  */ | 
            
              | 79 | jQuery.tableDnD = { | 
            
              | 80 |     /** Keep hold of the current table being dragged */ | 
            
              | 81 |     currentTable : null, | 
            
              | 82 |     /** Keep hold of the current drag object if any */ | 
            
              | 83 |     dragObject: null, | 
            
              | 84 |     /** The current mouse offset */ | 
            
              | 85 |     mouseOffset: null, | 
            
              | 86 |     /** Remember the old value of Y so that we don't do too much processing */ | 
            
              | 87 |     oldY: 0, | 
            
              | 88 |  | 
            
              | 89 |     /** Actually build the structure */ | 
            
              | 90 |     build: function(options) { | 
            
              | 91 |         // Set up the defaults if any | 
            
              | 92 |  | 
            
              | 93 |         this.each(function() { | 
            
              | 94 |             // This is bound to each matching table, set up the defaults and override with user options | 
            
              | 95 |             this.tableDnDConfig = jQuery.extend({ | 
            
              | 96 |                 onDragStyle: null, | 
            
              | 97 |                 onDropStyle: null, | 
            
              | 98 | 				// Add in the default class for whileDragging | 
            
              | 99 | 				onDragClass: "tDnD_whileDrag", | 
            
              | 100 |                 onDrop: null, | 
            
              | 101 |                 onDragStart: null, | 
            
              | 102 |                 scrollAmount: 5, | 
            
              | 103 | 				serializeRegexp: /[^\-]*$/, // The regular expression to use to trim row IDs | 
            
              | 104 | 				serializeParamName: null, // If you want to specify another parameter name instead of the table ID | 
            
              | 105 |                 dragHandle: null // If you give the name of a class here, then only Cells with this class will be draggable | 
            
              | 106 |             }, options || {}); | 
            
              | 107 |             // Now make the rows draggable | 
            
              | 108 |             jQuery.tableDnD.makeDraggable(this); | 
            
              | 109 |         }); | 
            
              | 110 |  | 
            
              | 111 |         // Now we need to capture the mouse up and mouse move event | 
            
              | 112 |         // We can use bind so that we don't interfere with other event handlers | 
            
              | 113 |         jQuery(document) | 
            
              | 114 |             .bind('mousemove', jQuery.tableDnD.mousemove) | 
            
              | 115 |             .bind('mouseup', jQuery.tableDnD.mouseup); | 
            
              | 116 |  | 
            
              | 117 |         // Don't break the chain | 
            
              | 118 |         return this; | 
            
              | 119 |     }, | 
            
              | 120 |  | 
            
              | 121 |     /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */ | 
            
              | 122 |     makeDraggable: function(table) { | 
            
              | 123 |         var config = table.tableDnDConfig; | 
            
              | 124 | 		if (table.tableDnDConfig.dragHandle) { | 
            
              | 125 | 			// We only need to add the event to the specified cells | 
            
              | 126 | 			var cells = jQuery("td."+table.tableDnDConfig.dragHandle, table); | 
            
              | 127 | 			cells.each(function() { | 
            
              | 128 | 				// The cell is bound to "this" | 
            
              | 129 |                 jQuery(this).mousedown(function(ev) { | 
            
              | 130 |                     jQuery.tableDnD.dragObject = this.parentNode; | 
            
              | 131 |                     jQuery.tableDnD.currentTable = table; | 
            
              | 132 |                     jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev); | 
            
              | 133 |                     if (config.onDragStart) { | 
            
              | 134 |                         // Call the onDrop method if there is one | 
            
              | 135 |                         config.onDragStart(table, this); | 
            
              | 136 |                     } | 
            
              | 137 |                     return false; | 
            
              | 138 |                 }); | 
            
              | 139 | 			}) | 
            
              | 140 | 		} else { | 
            
              | 141 | 			// For backwards compatibility, we add the event to the whole row | 
            
              | 142 | 	        var rows = jQuery("tr", table); // get all the rows as a wrapped set | 
            
              | 143 | 	        rows.each(function() { | 
            
              | 144 | 				// Iterate through each row, the row is bound to "this" | 
            
              | 145 | 				var row = jQuery(this); | 
            
              | 146 | 				if (! row.hasClass("nodrag")) { | 
            
              | 147 | 	                row.mousedown(function(ev) { | 
            
              | 148 | 	                    if (ev.target.tagName == "TD") { | 
            
              | 149 | 	                        jQuery.tableDnD.dragObject = this; | 
            
              | 150 | 	                        jQuery.tableDnD.currentTable = table; | 
            
              | 151 | 	                        jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev); | 
            
              | 152 | 	                        if (config.onDragStart) { | 
            
              | 153 | 	                            // Call the onDrop method if there is one | 
            
              | 154 | 	                            config.onDragStart(table, this); | 
            
              | 155 | 	                        } | 
            
              | 156 | 	                        return false; | 
            
              | 157 | 	                    } | 
            
              | 158 | 	                }).css("cursor", "move"); // Store the tableDnD object | 
            
              | 159 | 				} | 
            
              | 160 | 			}); | 
            
              | 161 | 		} | 
            
              | 162 | 	}, | 
            
              | 163 |  | 
            
              | 164 | 	updateTables: function() { | 
            
              | 165 | 		this.each(function() { | 
            
              | 166 | 			// this is now bound to each matching table | 
            
              | 167 | 			if (this.tableDnDConfig) { | 
            
              | 168 | 				jQuery.tableDnD.makeDraggable(this); | 
            
              | 169 | 			} | 
            
              | 170 | 		}) | 
            
              | 171 | 	}, | 
            
              | 172 |  | 
            
              | 173 |     /** Get the mouse coordinates from the event (allowing for browser differences) */ | 
            
              | 174 |     mouseCoords: function(ev){ | 
            
              | 175 |         if(ev.pageX || ev.pageY){ | 
            
              | 176 |             return {x:ev.pageX, y:ev.pageY}; | 
            
              | 177 |         } | 
            
              | 178 |         return { | 
            
              | 179 |             x:ev.clientX + document.body.scrollLeft - document.body.clientLeft, | 
            
              | 180 |             y:ev.clientY + document.body.scrollTop  - document.body.clientTop | 
            
              | 181 |         }; | 
            
              | 182 |     }, | 
            
              | 183 |  | 
            
              | 184 |     /** Given a target element and a mouse event, get the mouse offset from that element. | 
            
              | 185 |         To do this we need the element's position and the mouse position */ | 
            
              | 186 |     getMouseOffset: function(target, ev) { | 
            
              | 187 |         ev = ev || window.event; | 
            
              | 188 |  | 
            
              | 189 |         var docPos    = this.getPosition(target); | 
            
              | 190 |         var mousePos  = this.mouseCoords(ev); | 
            
              | 191 |         return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y}; | 
            
              | 192 |     }, | 
            
              | 193 |  | 
            
              | 194 |     /** Get the position of an element by going up the DOM tree and adding up all the offsets */ | 
            
              | 195 |     getPosition: function(e){ | 
            
              | 196 |         var left = 0; | 
            
              | 197 |         var top  = 0; | 
            
              | 198 |         /** Safari fix -- thanks to Luis Chato for this! */ | 
            
              | 199 |         if (e.offsetHeight == 0) { | 
            
              | 200 |             /** Safari 2 doesn't correctly grab the offsetTop of a table row | 
            
              | 201 |             this is detailed here: | 
            
              | 202 |             http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/ | 
            
              | 203 |             the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild. | 
            
              | 204 |             note that firefox will return a text node as a first child, so designing a more thorough | 
            
              | 205 |             solution may need to take that into account, for now this seems to work in firefox, safari, ie */ | 
            
              | 206 |             e = e.firstChild; // a table cell | 
            
              | 207 |         } | 
            
              | 208 |  | 
            
              | 209 |         while (e.offsetParent){ | 
            
              | 210 |             left += e.offsetLeft; | 
            
              | 211 |             top  += e.offsetTop; | 
            
              | 212 |             e     = e.offsetParent; | 
            
              | 213 |         } | 
            
              | 214 |  | 
            
              | 215 |         left += e.offsetLeft; | 
            
              | 216 |         top  += e.offsetTop; | 
            
              | 217 |  | 
            
              | 218 |         return {x:left, y:top}; | 
            
              | 219 |     }, | 
            
              | 220 |  | 
            
              | 221 |     mousemove: function(ev) { | 
            
              | 222 |         if (jQuery.tableDnD.dragObject == null) { | 
            
              | 223 |             return; | 
            
              | 224 |         } | 
            
              | 225 |  | 
            
              | 226 |         var dragObj = jQuery(jQuery.tableDnD.dragObject); | 
            
              | 227 |         var config = jQuery.tableDnD.currentTable.tableDnDConfig; | 
            
              | 228 |         var mousePos = jQuery.tableDnD.mouseCoords(ev); | 
            
              | 229 |         var y = mousePos.y - jQuery.tableDnD.mouseOffset.y; | 
            
              | 230 |         //auto scroll the window | 
            
              | 231 | 	    var yOffset = window.pageYOffset; | 
            
              | 232 | 	 	if (document.all) { | 
            
              | 233 | 	        // Windows version | 
            
              | 234 | 	        //yOffset=document.body.scrollTop; | 
            
              | 235 | 	        if (typeof document.compatMode != 'undefined' && | 
            
              | 236 | 	             document.compatMode != 'BackCompat') { | 
            
              | 237 | 	           yOffset = document.documentElement.scrollTop; | 
            
              | 238 | 	        } | 
            
              | 239 | 	        else if (typeof document.body != 'undefined') { | 
            
              | 240 | 	           yOffset=document.body.scrollTop; | 
            
              | 241 | 	        } | 
            
              | 242 |  | 
            
              | 243 | 	    } | 
            
              | 244 | 		     | 
            
              | 245 | 		if (mousePos.y-yOffset < config.scrollAmount) { | 
            
              | 246 | 	    	window.scrollBy(0, -config.scrollAmount); | 
            
              | 247 | 	    } else { | 
            
              | 248 |             var windowHeight = window.innerHeight ? window.innerHeight | 
            
              | 249 |                     : document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight; | 
            
              | 250 |             if (windowHeight-(mousePos.y-yOffset) < config.scrollAmount) { | 
            
              | 251 |                 window.scrollBy(0, config.scrollAmount); | 
            
              | 252 |             } | 
            
              | 253 |         } | 
            
              | 254 |  | 
            
              | 255 |  | 
            
              | 256 |         if (y != jQuery.tableDnD.oldY) { | 
            
              | 257 |             // work out if we're going up or down... | 
            
              | 258 |             var movingDown = y > jQuery.tableDnD.oldY; | 
            
              | 259 |             // update the old value | 
            
              | 260 |             jQuery.tableDnD.oldY = y; | 
            
              | 261 |             // update the style to show we're dragging | 
            
              | 262 | 			if (config.onDragClass) { | 
            
              | 263 | 				dragObj.addClass(config.onDragClass); | 
            
              | 264 | 			} else { | 
            
              | 265 | 	            dragObj.css(config.onDragStyle); | 
            
              | 266 | 			} | 
            
              | 267 |             // If we're over a row then move the dragged row to there so that the user sees the | 
            
              | 268 |             // effect dynamically | 
            
              | 269 |             var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y); | 
            
              | 270 |             if (currentRow) { | 
            
              | 271 |                 // TODO worry about what happens when there are multiple TBODIES | 
            
              | 272 |                 if (movingDown && jQuery.tableDnD.dragObject != currentRow) { | 
            
              | 273 |                     jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling); | 
            
              | 274 |                 } else if (! movingDown && jQuery.tableDnD.dragObject != currentRow) { | 
            
              | 275 |                     jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow); | 
            
              | 276 |                 } | 
            
              | 277 |             } | 
            
              | 278 |         } | 
            
              | 279 |  | 
            
              | 280 |         return false; | 
            
              | 281 |     }, | 
            
              | 282 |  | 
            
              | 283 |     /** We're only worried about the y position really, because we can only move rows up and down */ | 
            
              | 284 |     findDropTargetRow: function(draggedRow, y) { | 
            
              | 285 |         var rows = jQuery.tableDnD.currentTable.rows; | 
            
              | 286 |         for (var i=0; i<rows.length; i++) { | 
            
              | 287 |             var row = rows[i]; | 
            
              | 288 |             var rowY    = this.getPosition(row).y; | 
            
              | 289 |             var rowHeight = parseInt(row.offsetHeight)/2; | 
            
              | 290 |             if (row.offsetHeight == 0) { | 
            
              | 291 |                 rowY = this.getPosition(row.firstChild).y; | 
            
              | 292 |                 rowHeight = parseInt(row.firstChild.offsetHeight)/2; | 
            
              | 293 |             } | 
            
              | 294 |             // Because we always have to insert before, we need to offset the height a bit | 
            
              | 295 |             if ((y > rowY - rowHeight) && (y < (rowY + rowHeight))) { | 
            
              | 296 |                 // that's the row we're over | 
            
              | 297 | 				// If it's the same as the current row, ignore it | 
            
              | 298 | 				if (row == draggedRow) {return null;} | 
            
              | 299 |                 var config = jQuery.tableDnD.currentTable.tableDnDConfig; | 
            
              | 300 |                 if (config.onAllowDrop) { | 
            
              | 301 |                     if (config.onAllowDrop(draggedRow, row)) { | 
            
              | 302 |                         return row; | 
            
              | 303 |                     } else { | 
            
              | 304 |                         return null; | 
            
              | 305 |                     } | 
            
              | 306 |                 } else { | 
            
              | 307 | 					// If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic) | 
            
              | 308 |                     var nodrop = jQuery(row).hasClass("nodrop"); | 
            
              | 309 |                     if (! nodrop) { | 
            
              | 310 |                         return row; | 
            
              | 311 |                     } else { | 
            
              | 312 |                         return null; | 
            
              | 313 |                     } | 
            
              | 314 |                 } | 
            
              | 315 |                 return row; | 
            
              | 316 |             } | 
            
              | 317 |         } | 
            
              | 318 |         return null; | 
            
              | 319 |     }, | 
            
              | 320 |  | 
            
              | 321 |     mouseup: function(e) { | 
            
              | 322 |         if (jQuery.tableDnD.currentTable && jQuery.tableDnD.dragObject) { | 
            
              | 323 |             var droppedRow = jQuery.tableDnD.dragObject; | 
            
              | 324 |             var config = jQuery.tableDnD.currentTable.tableDnDConfig; | 
            
              | 325 |             // If we have a dragObject, then we need to release it, | 
            
              | 326 |             // The row will already have been moved to the right place so we just reset stuff | 
            
              | 327 | 			if (config.onDragClass) { | 
            
              | 328 | 	            jQuery(droppedRow).removeClass(config.onDragClass); | 
            
              | 329 | 			} else { | 
            
              | 330 | 	            jQuery(droppedRow).css(config.onDropStyle); | 
            
              | 331 | 			} | 
            
              | 332 |             jQuery.tableDnD.dragObject   = null; | 
            
              | 333 |             if (config.onDrop) { | 
            
              | 334 |                 // Call the onDrop method if there is one | 
            
              | 335 |                 config.onDrop(jQuery.tableDnD.currentTable, droppedRow); | 
            
              | 336 |             } | 
            
              | 337 |             jQuery.tableDnD.currentTable = null; // let go of the table too | 
            
              | 338 |         } | 
            
              | 339 |     }, | 
            
              | 340 |  | 
            
              | 341 |     serialize: function() { | 
            
              | 342 |         if (jQuery.tableDnD.currentTable) { | 
            
              | 343 |             return jQuery.tableDnD.serializeTable(jQuery.tableDnD.currentTable); | 
            
              | 344 |         } else { | 
            
              | 345 |             return "Error: No Table id set, you need to set an id on your table and every row"; | 
            
              | 346 |         } | 
            
              | 347 |     }, | 
            
              | 348 |  | 
            
              | 349 | 	serializeTable: function(table) { | 
            
              | 350 |         var result = ""; | 
            
              | 351 |         var tableId = table.id; | 
            
              | 352 |         var rows = table.rows; | 
            
              | 353 |         for (var i=0; i<rows.length; i++) { | 
            
              | 354 |             if (result.length > 0) result += "&"; | 
            
              | 355 |             var rowId = rows[i].id; | 
            
              | 356 |             if (rowId && rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) { | 
            
              | 357 |                 rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0]; | 
            
              | 358 |             } | 
            
              | 359 |  | 
            
              | 360 |             result += tableId + '[]=' + rowId; | 
            
              | 361 |         } | 
            
              | 362 |         return result; | 
            
              | 363 | 	}, | 
            
              | 364 |  | 
            
              | 365 | 	serializeTables: function() { | 
            
              | 366 |         var result = ""; | 
            
              | 367 |         this.each(function() { | 
            
              | 368 | 			// this is now bound to each matching table | 
            
              | 369 | 			result += jQuery.tableDnD.serializeTable(this); | 
            
              | 370 | 		}); | 
            
              | 371 |         return result; | 
            
              | 372 |     } | 
            
              | 373 |  | 
            
              | 374 | } | 
            
              | 375 |  | 
            
              | 376 | jQuery.fn.extend( | 
            
              | 377 | 	{ | 
            
              | 378 | 		tableDnD : jQuery.tableDnD.build, | 
            
              | 379 | 		tableDnDUpdate : jQuery.tableDnD.updateTables, | 
            
              | 380 | 		tableDnDSerialize: jQuery.tableDnD.serializeTables | 
            
              | 381 | 	} | 
            
              | 382 | ); |