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 |