PluginProbe
RSVP and Event Management / 1.3.0
RSVP and Event Management v1.3.0
trunk 0.5.0 0.6.0 0.7.0 0.8.0 0.9.0 0.9.5 1.0.0 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.5.0 1.6.0 1.6.1 1.6.2 1.6.5 1.7.0 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 All 136 releases
rsvp / jquery.tablednd_0_5.js

jquery.tablednd_0_5.js in RSVP and Event Management 1.3.0, at jquery.tablednd_0_5.js

382 lines 16.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 );