OLD | NEW |
(Empty) | |
| 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 © 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 limi
tations 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 app
ly 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 consi
der 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 th
e row is dropped. It is more |
| 20 * flexible than using onDragStyle since it can be inherited by the row cell
s 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 fu
nction 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 all
ow 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 (t
ested in IE6, IE7, Safari, FF2, |
| 37 * FF3 beta) |
| 38 * |
| 39 * Other ways to control behaviour: |
| 40 * |
| 41 * Add class="nodrop" to any rows for which you don't want to allow dropping, an
d class="nodrag" to any rows |
| 42 * that you don't want to be draggable. |
| 43 * |
| 44 * Inside the onDrop method you can also call $.tableDnD.serialize() this return
s a string of the form |
| 45 * <tableID>[]=<rowID1>&<tableID>[]=<rowID2> so that you can send this back to t
he server. The table must have |
| 46 * an ID as must all the rows. |
| 47 * |
| 48 * Known problems: |
| 49 * - Auto-scoll has some problems with IE7 (it scrolls even when it shouldn't),
work-around: set scrollAmount to 0 |
| 50 * |
| 51 * Version 0.2: 2008-02-20 First public version |
| 52 * Version 0.3: 2008-02-07 Added onDragStart option |
| 53 * Made the scroll amount configurable (default is 5 as
before) |
| 54 * Version 0.4: 2008-03-15 Changed the noDrag/noDrop attributes to nodrag/nodrop
classes |
| 55 * Added onAllowDrop to control dropping |
| 56 * Fixed a bug which meant that you couldn't set the scr
oll amount in both directions |
| 57 * Added serialise method |
| 58 */ |
| 59 jQuery.tableDnD = { |
| 60 /** Keep hold of the current table being dragged */ |
| 61 currentTable : null, |
| 62 /** Keep hold of the current drag object if any */ |
| 63 dragObject: null, |
| 64 /** The current mouse offset */ |
| 65 mouseOffset: null, |
| 66 /** Remember the old value of Y so that we don't do too much processing */ |
| 67 oldY: 0, |
| 68 |
| 69 /** Actually build the structure */ |
| 70 build: function(options) { |
| 71 // Make sure options exists |
| 72 options = options || {}; |
| 73 // Set up the defaults if any |
| 74 |
| 75 this.each(function() { |
| 76 // Remember the options |
| 77 this.tableDnDConfig = { |
| 78 onDragStyle: options.onDragStyle, |
| 79 onDropStyle: options.onDropStyle, |
| 80 // Add in the default class for whileDragging |
| 81 onDragClass: options.onDragClass ? options.onDra
gClass : "tDnD_whileDrag", |
| 82 onDrop: options.onDrop, |
| 83 onDragStart: options.onDragStart, |
| 84 scrollAmount: options.scrollAmount ? options.scrollAmount : 5 |
| 85 }; |
| 86 // Now make the rows draggable |
| 87 jQuery.tableDnD.makeDraggable(this); |
| 88 }); |
| 89 |
| 90 // Now we need to capture the mouse up and mouse move event |
| 91 // We can use bind so that we don't interfere with other event handlers |
| 92 jQuery(document) |
| 93 .bind('mousemove', jQuery.tableDnD.mousemove) |
| 94 .bind('mouseup', jQuery.tableDnD.mouseup); |
| 95 |
| 96 // Don't break the chain |
| 97 return this; |
| 98 }, |
| 99 |
| 100 /** This function makes all the rows on the table draggable apart from those
marked as "NoDrag" */ |
| 101 makeDraggable: function(table) { |
| 102 // Now initialise the rows |
| 103 var rows = table.rows; //getElementsByTagName("tr") |
| 104 var config = table.tableDnDConfig; |
| 105 for (var i=0; i<rows.length; i++) { |
| 106 // To make non-draggable rows, add the nodrag class (eg for Category
and Header rows) |
| 107 // inspired by John Tarr and Famic |
| 108 var nodrag = $(rows[i]).hasClass("nodrag"); |
| 109 if (! nodrag) { //There is no NoDnD attribute on rows I want to drag |
| 110 jQuery(rows[i]).mousedown(function(ev) { |
| 111 if (ev.target.tagName == "TD") { |
| 112 jQuery.tableDnD.dragObject = this; |
| 113 jQuery.tableDnD.currentTable = table; |
| 114 jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOf
fset(this, ev); |
| 115 if (config.onDragStart) { |
| 116 // Call the onDrop method if there is one |
| 117 config.onDragStart(table, this); |
| 118 } |
| 119 return false; |
| 120 } |
| 121 }).css("cursor", "move"); // Store the tableDnD object |
| 122 } |
| 123 } |
| 124 }, |
| 125 |
| 126 /** Get the mouse coordinates from the event (allowing for browser differenc
es) */ |
| 127 mouseCoords: function(ev){ |
| 128 if(ev.pageX || ev.pageY){ |
| 129 return {x:ev.pageX, y:ev.pageY}; |
| 130 } |
| 131 return { |
| 132 x:ev.clientX + document.body.scrollLeft - document.body.clientLeft, |
| 133 y:ev.clientY + document.body.scrollTop - document.body.clientTop |
| 134 }; |
| 135 }, |
| 136 |
| 137 /** Given a target element and a mouse event, get the mouse offset from that
element. |
| 138 To do this we need the element's position and the mouse position */ |
| 139 getMouseOffset: function(target, ev) { |
| 140 ev = ev || window.event; |
| 141 |
| 142 var docPos = this.getPosition(target); |
| 143 var mousePos = this.mouseCoords(ev); |
| 144 return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y}; |
| 145 }, |
| 146 |
| 147 /** Get the position of an element by going up the DOM tree and adding up al
l the offsets */ |
| 148 getPosition: function(e){ |
| 149 var left = 0; |
| 150 var top = 0; |
| 151 /** Safari fix -- thanks to Luis Chato for this! */ |
| 152 if (e.offsetHeight == 0) { |
| 153 /** Safari 2 doesn't correctly grab the offsetTop of a table row |
| 154 this is detailed here: |
| 155 http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-b
ug-in-safari/ |
| 156 the solution is likewise noted there, grab the offset of a table cel
l in the row - the firstChild. |
| 157 note that firefox will return a text node as a first child, so desig
ning a more thorough |
| 158 solution may need to take that into account, for now this seems to w
ork in firefox, safari, ie */ |
| 159 e = e.firstChild; // a table cell |
| 160 } |
| 161 |
| 162 while (e.offsetParent){ |
| 163 left += e.offsetLeft; |
| 164 top += e.offsetTop; |
| 165 e = e.offsetParent; |
| 166 } |
| 167 |
| 168 left += e.offsetLeft; |
| 169 top += e.offsetTop; |
| 170 |
| 171 return {x:left, y:top}; |
| 172 }, |
| 173 |
| 174 mousemove: function(ev) { |
| 175 if (jQuery.tableDnD.dragObject == null) { |
| 176 return; |
| 177 } |
| 178 |
| 179 var dragObj = jQuery(jQuery.tableDnD.dragObject); |
| 180 var config = jQuery.tableDnD.currentTable.tableDnDConfig; |
| 181 var mousePos = jQuery.tableDnD.mouseCoords(ev); |
| 182 var y = mousePos.y - jQuery.tableDnD.mouseOffset.y; |
| 183 //auto scroll the window |
| 184 var yOffset = window.pageYOffset; |
| 185 if (document.all) { |
| 186 // Windows version |
| 187 //yOffset=document.body.scrollTop; |
| 188 if (typeof document.compatMode != 'undefined' && |
| 189 document.compatMode != 'BackCompat') { |
| 190 yOffset = document.documentElement.scrollTop; |
| 191 } |
| 192 else if (typeof document.body != 'undefined') { |
| 193 yOffset=document.body.scrollTop; |
| 194 } |
| 195 |
| 196 } |
| 197 |
| 198 if (mousePos.y-yOffset < config.scrollAmount) { |
| 199 window.scrollBy(0, -config.scrollAmount); |
| 200 } else { |
| 201 var windowHeight = window.innerHeight ? window.innerHeight |
| 202 : document.documentElement.clientHeight ? document.documentE
lement.clientHeight : document.body.clientHeight; |
| 203 if (windowHeight-(mousePos.y-yOffset) < config.scrollAmount) { |
| 204 window.scrollBy(0, config.scrollAmount); |
| 205 } |
| 206 } |
| 207 |
| 208 |
| 209 if (y != jQuery.tableDnD.oldY) { |
| 210 // work out if we're going up or down... |
| 211 var movingDown = y > jQuery.tableDnD.oldY; |
| 212 // update the old value |
| 213 jQuery.tableDnD.oldY = y; |
| 214 // update the style to show we're dragging |
| 215 if (config.onDragClass) { |
| 216 dragObj.addClass(config.onDragClass); |
| 217 } else { |
| 218 dragObj.css(config.onDragStyle); |
| 219 } |
| 220 // If we're over a row then move the dragged row to there so that th
e user sees the |
| 221 // effect dynamically |
| 222 var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y); |
| 223 if (currentRow) { |
| 224 // TODO worry about what happens when there are multiple TBODIES |
| 225 if (movingDown && jQuery.tableDnD.dragObject != currentRow) { |
| 226 jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.ta
bleDnD.dragObject, currentRow.nextSibling); |
| 227 } else if (! movingDown && jQuery.tableDnD.dragObject != current
Row) { |
| 228 jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.ta
bleDnD.dragObject, currentRow); |
| 229 } |
| 230 } |
| 231 } |
| 232 |
| 233 return false; |
| 234 }, |
| 235 |
| 236 /** We're only worried about the y position really, because we can only move
rows up and down */ |
| 237 findDropTargetRow: function(draggedRow, y) { |
| 238 var rows = jQuery.tableDnD.currentTable.rows; |
| 239 for (var i=0; i<rows.length; i++) { |
| 240 var row = rows[i]; |
| 241 var rowY = this.getPosition(row).y; |
| 242 var rowHeight = parseInt(row.offsetHeight)/2; |
| 243 if (row.offsetHeight == 0) { |
| 244 rowY = this.getPosition(row.firstChild).y; |
| 245 rowHeight = parseInt(row.firstChild.offsetHeight)/2; |
| 246 } |
| 247 // Because we always have to insert before, we need to offset the he
ight a bit |
| 248 if ((y > rowY - rowHeight) && (y < (rowY + rowHeight))) { |
| 249 // that's the row we're over |
| 250 // If it's the same as the current row, ignore i
t |
| 251 if (row == draggedRow) {return null;} |
| 252 var config = jQuery.tableDnD.currentTable.tableDnDConfig; |
| 253 if (config.onAllowDrop) { |
| 254 if (config.onAllowDrop(draggedRow, row)) { |
| 255 return row; |
| 256 } else { |
| 257 return null; |
| 258 } |
| 259 } else { |
| 260 // If a row has nodrop class, then don't
allow dropping (inspired by John Tarr and Famic) |
| 261 var nodrop = $(row).hasClass("nodrop"); |
| 262 if (! nodrop) { |
| 263 return row; |
| 264 } else { |
| 265 return null; |
| 266 } |
| 267 } |
| 268 return row; |
| 269 } |
| 270 } |
| 271 return null; |
| 272 }, |
| 273 |
| 274 mouseup: function(e) { |
| 275 if (jQuery.tableDnD.currentTable && jQuery.tableDnD.dragObject) { |
| 276 var droppedRow = jQuery.tableDnD.dragObject; |
| 277 var config = jQuery.tableDnD.currentTable.tableDnDConfig; |
| 278 // If we have a dragObject, then we need to release it, |
| 279 // The row will already have been moved to the right place so we jus
t reset stuff |
| 280 if (config.onDragClass) { |
| 281 jQuery(droppedRow).removeClass(config.onDragClass); |
| 282 } else { |
| 283 jQuery(droppedRow).css(config.onDropStyle); |
| 284 } |
| 285 jQuery.tableDnD.dragObject = null; |
| 286 if (config.onDrop) { |
| 287 // Call the onDrop method if there is one |
| 288 config.onDrop(jQuery.tableDnD.currentTable, droppedRow); |
| 289 } |
| 290 jQuery.tableDnD.currentTable = null; // let go of the table too |
| 291 } |
| 292 }, |
| 293 |
| 294 serialize: function() { |
| 295 if (jQuery.tableDnD.currentTable) { |
| 296 var result = ""; |
| 297 var tableId = jQuery.tableDnD.currentTable.id; |
| 298 var rows = jQuery.tableDnD.currentTable.rows; |
| 299 for (var i=0; i<rows.length; i++) { |
| 300 if (result.length > 0) result += "&"; |
| 301 result += tableId + '[]=' + rows[i].id; |
| 302 } |
| 303 return result; |
| 304 } else { |
| 305 return "Error: No Table id set, you need to set an id on your table
and every row"; |
| 306 } |
| 307 } |
| 308 } |
| 309 |
| 310 jQuery.fn.extend( |
| 311 { |
| 312 tableDnD : jQuery.tableDnD.build |
| 313 } |
| 314 ); |
OLD | NEW |