Call hooks on split.
[order_line_extra.git] / TableDnD / js / jquery.tablednd.js
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 a jQuery mach string for one or more cells in each row that is draggable. If you
40  *     specify this, 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  * Version 0.6: 2011-12-02 Added support for touch devices
79  * Version 0.7  2012-04-09 Now works with jQuery 1.7 and supports touch, tidied up tabs and spaces
80  */
81 !function ($, window, document, undefined) {
82 // Determine if this is a touch device
83 var hasTouch   = 'ontouchstart' in document.documentElement,
84     startEvent = hasTouch ? 'touchstart' : 'mousedown',
85     moveEvent  = hasTouch ? 'touchmove'  : 'mousemove',
86     endEvent   = hasTouch ? 'touchend'   : 'mouseup';
87
88 // If we're on a touch device, then wire up the events
89 // see http://stackoverflow.com/a/8456194/1316086
90 hasTouch
91     && $.each("touchstart touchmove touchend".split(" "), function(i, name) {
92         $.event.fixHooks[name] = $.event.mouseHooks;
93     });
94
95
96 $(document).ready(function () {
97     function parseStyle(css) {
98         var objMap = {},
99             parts = css.match(/([^;:]+)/g) || [];
100         while (parts.length)
101             objMap[parts.shift()] = parts.shift().trim();
102
103         return objMap;
104     }
105     $('table').each(function () {
106         if ($(this).data('table') == 'dnd') {
107
108             $(this).tableDnD({
109                 onDragStyle: $(this).data('ondragstyle') && parseStyle($(this).data('ondragstyle')) || null,
110                 onDropStyle: $(this).data('ondropstyle') && parseStyle($(this).data('ondropstyle')) || null,
111                 onDragClass: $(this).data('ondragclass') == undefined && "tDnD_whileDrag" || $(this).data('ondragclass'),
112                 onDrop: $(this).data('ondrop') && new Function('table', 'row', $(this).data('ondrop')), // 'return eval("'+$(this).data('ondrop')+'");') || null,
113                 onDragStart: $(this).data('ondragstart') && new Function('table', 'row' ,$(this).data('ondragstart')), // 'return eval("'+$(this).data('ondragstart')+'");') || null,
114                 scrollAmount: $(this).data('scrollamount') || 5,
115                 sensitivity: $(this).data('sensitivity') || 10,
116                 hierarchyLevel: $(this).data('hierarchylevel') || 0,
117                 indentArtifact: $(this).data('indentartifact') || '<div class="indent">&nbsp;</div>',
118                 autoWidthAdjust: $(this).data('autowidthadjust') || true,
119                 autoCleanRelations: $(this).data('autocleanrelations') || true,
120                 jsonPretifySeparator: $(this).data('jsonpretifyseparator') || '\t',
121                 serializeRegexp: $(this).data('serializeregexp') && new RegExp($(this).data('serializeregexp')) || /[^\-]*$/,
122                 serializeParamName: $(this).data('serializeparamname') || false,
123                 dragHandle: $(this).data('draghandle') || null
124             });
125         }
126
127
128     });
129 });
130
131 window.jQuery.tableDnD = {
132     /** Keep hold of the current table being dragged */
133     currentTable: null,
134     /** Keep hold of the current drag object if any */
135     dragObject: null,
136     /** The current mouse offset */
137     mouseOffset: null,
138     /** Remember the old value of X and Y so that we don't do too much processing */
139     oldX: 0,
140     oldY: 0,
141
142     /** Actually build the structure */
143     build: function(options) {
144         // Set up the defaults if any
145
146         this.each(function() {
147             // This is bound to each matching table, set up the defaults and override with user options
148             this.tableDnDConfig = $.extend({
149                 onDragStyle: null,
150                 onDropStyle: null,
151                 // Add in the default class for whileDragging
152                 onDragClass: "tDnD_whileDrag",
153                 onDrop: null,
154                 onDragStart: null,
155                 scrollAmount: 5,
156                 /** Sensitivity setting will throttle the trigger rate for movement detection */
157                 sensitivity: 10,
158                 /** Hierarchy level to support parent child. 0 switches this functionality off */
159                 hierarchyLevel: 0,
160                 /** The html artifact to prepend the first cell with as indentation */
161                 indentArtifact: '<div class="indent">&nbsp;</div>',
162                 /** Automatically adjust width of first cell */
163                 autoWidthAdjust: true,
164                 /** Automatic clean-up to ensure relationship integrity */
165                 autoCleanRelations: true,
166                 /** Specify a number (4) as number of spaces or any indent string for JSON.stringify */
167                 jsonPretifySeparator: '\t',
168                 /** The regular expression to use to trim row IDs */
169                 serializeRegexp: /[^\-]*$/,
170                 /** If you want to specify another parameter name instead of the table ID */
171                 serializeParamName: false,
172                 /** If you give the name of a class here, then only Cells with this class will be draggable */
173                 dragHandle: null
174             }, options || {});
175
176             // Now make the rows draggable
177             $.tableDnD.makeDraggable(this);
178             // Prepare hierarchy support
179             this.tableDnDConfig.hierarchyLevel
180                 && $.tableDnD.makeIndented(this);
181         });
182
183         // Don't break the chain
184         return this;
185     },
186     makeIndented: function (table) {
187         var config = table.tableDnDConfig,
188             rows = table.rows,
189             firstCell = $(rows).first().find('td:first')[0],
190             indentLevel = 0,
191             cellWidth = 0,
192             longestCell,
193             tableStyle;
194
195         if ($(table).hasClass('indtd'))
196             return null;
197
198         tableStyle = $(table).addClass('indtd').attr('style');
199         $(table).css({whiteSpace: "nowrap"});
200
201         for (var w = 0; w < rows.length; w++) {
202             if (cellWidth < $(rows[w]).find('td:first').text().length) {
203                 cellWidth = $(rows[w]).find('td:first').text().length;
204                 longestCell = w;
205             }
206         }
207         $(firstCell).css({width: 'auto'});
208         for (w = 0; w < config.hierarchyLevel; w++)
209             $(rows[longestCell]).find('td:first').prepend(config.indentArtifact);
210         firstCell && $(firstCell).css({width: firstCell.offsetWidth});
211         tableStyle && $(table).css(tableStyle);
212
213         for (w = 0; w < config.hierarchyLevel; w++)
214             $(rows[longestCell]).find('td:first').children(':first').remove();
215
216         config.hierarchyLevel
217             && $(rows).each(function () {
218                 indentLevel = $(this).data('level') || 0;
219                 indentLevel <= config.hierarchyLevel
220                     && $(this).data('level', indentLevel)
221                     || $(this).data('level', 0);
222                 for (var i = 0; i < $(this).data('level'); i++)
223                     $(this).find('td:first').prepend(config.indentArtifact);
224             });
225
226         return this;
227     },
228     /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */
229     makeDraggable: function(table) {
230         var config = table.tableDnDConfig;
231
232         config.dragHandle
233             // We only need to add the event to the specified cells
234             && $(config.dragHandle, table).each(function() {
235                 // The cell is bound to "this"
236                 $(this).bind(startEvent, function(e) {
237                     $.tableDnD.initialiseDrag($(this).parents('tr')[0], table, this, e, config);
238                     return false;
239                 });
240             })
241             // For backwards compatibility, we add the event to the whole row
242             // get all the rows as a wrapped set
243             || $(table.rows).each(function() {
244                 // Iterate through each row, the row is bound to "this"
245                 if (! $(this).hasClass("nodrag")) {
246                     $(this).bind(startEvent, function(e) {
247                         if (e.target.tagName == "TD") {
248                             $.tableDnD.initialiseDrag(this, table, this, e, config);
249                             return false;
250                         }
251                     }).css("cursor", "move"); // Store the tableDnD object
252                 }
253             });
254     },
255     currentOrder: function() {
256         var rows = this.currentTable.rows;
257         return $.map(rows, function (val) {
258             return ($(val).data('level') + val.id).replace(/\s/g, '');
259         }).join('');
260     },
261     initialiseDrag: function(dragObject, table, target, e, config) {
262         this.dragObject    = dragObject;
263         this.currentTable  = table;
264         this.mouseOffset   = this.getMouseOffset(target, e);
265         this.originalOrder = this.currentOrder();
266
267         // Now we need to capture the mouse up and mouse move event
268         // We can use bind so that we don't interfere with other event handlers
269         $(document)
270             .bind(moveEvent, this.mousemove)
271             .bind(endEvent, this.mouseup);
272
273         // Call the onDragStart method if there is one
274         config.onDragStart
275             && config.onDragStart(table, target);
276     },
277     updateTables: function() {
278         this.each(function() {
279             // this is now bound to each matching table
280             if (this.tableDnDConfig)
281                 $.tableDnD.makeDraggable(this);
282         });
283     },
284     /** Get the mouse coordinates from the event (allowing for browser differences) */
285     mouseCoords: function(e) {
286         if(e.pageX || e.pageY)
287             return {
288                 x: e.pageX,
289                 y: e.pageY
290             };
291
292         return {
293             x: e.clientX + document.body.scrollLeft - document.body.clientLeft,
294             y: e.clientY + document.body.scrollTop  - document.body.clientTop
295         };
296     },
297     /** Given a target element and a mouse eent, get the mouse offset from that element.
298      To do this we need the element's position and the mouse position */
299     getMouseOffset: function(target, e) {
300         var mousePos,
301             docPos;
302
303         e = e || window.event;
304
305         docPos    = this.getPosition(target);
306         mousePos  = this.mouseCoords(e);
307
308         return {
309             x: mousePos.x - docPos.x,
310             y: mousePos.y - docPos.y
311         };
312     },
313     /** Get the position of an element by going up the DOM tree and adding up all the offsets */
314     getPosition: function(element) {
315         var left = 0,
316             top  = 0;
317
318         // Safari fix -- thanks to Luis Chato for this!
319         // Safari 2 doesn't correctly grab the offsetTop of a table row
320         // this is detailed here:
321         // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
322         // the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
323         // note that firefox will return a text node as a first child, so designing a more thorough
324         // solution may need to take that into account, for now this seems to work in firefox, safari, ie
325         if (element.offsetHeight == 0)
326             element = element.firstChild; // a table cell
327
328         while (element.offsetParent) {
329             left   += element.offsetLeft;
330             top    += element.offsetTop;
331             element = element.offsetParent;
332         }
333
334         left += element.offsetLeft;
335         top  += element.offsetTop;
336
337         return {
338             x: left,
339             y: top
340         };
341     },
342     autoScroll: function (mousePos) {
343       var config       = this.currentTable.tableDnDConfig,
344           yOffset      = window.pageYOffset,
345           windowHeight = window.innerHeight
346             ? window.innerHeight
347             : document.documentElement.clientHeight
348             ? document.documentElement.clientHeight
349             : document.body.clientHeight;
350
351         // Windows version
352         // yOffset=document.body.scrollTop;
353         if (document.all)
354             if (typeof document.compatMode != 'undefined'
355                 && document.compatMode != 'BackCompat')
356                 yOffset = document.documentElement.scrollTop;
357             else if (typeof document.body != 'undefined')
358                 yOffset = document.body.scrollTop;
359
360         mousePos.y - yOffset < config.scrollAmount
361             && window.scrollBy(0, - config.scrollAmount)
362         || windowHeight - (mousePos.y - yOffset) < config.scrollAmount
363             && window.scrollBy(0, config.scrollAmount);
364
365     },
366     moveVerticle: function (moving, currentRow) {
367
368         if (0 != moving.vertical
369             // If we're over a row then move the dragged row to there so that the user sees the
370             // effect dynamically
371             && currentRow
372             && this.dragObject != currentRow
373             && this.dragObject.parentNode == currentRow.parentNode)
374             0 > moving.vertical
375                 && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow.nextSibling)
376             || 0 < moving.vertical
377                 && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow);
378
379     },
380     moveHorizontal: function (moving, currentRow) {
381         var config       = this.currentTable.tableDnDConfig,
382             currentLevel;
383
384         if (!config.hierarchyLevel
385             || 0 == moving.horizontal
386             // We only care if moving left or right on the current row
387             || !currentRow
388             || this.dragObject != currentRow)
389                 return null;
390
391             currentLevel = $(currentRow).data('level');
392
393             0 < moving.horizontal
394                 && currentLevel > 0
395                 && $(currentRow).find('td:first').children(':first').remove()
396                 && $(currentRow).data('level', --currentLevel);
397
398             0 > moving.horizontal
399                 && currentLevel < config.hierarchyLevel
400                 && $(currentRow).prev().data('level') >= currentLevel
401                 && $(currentRow).children(':first').prepend(config.indentArtifact)
402                 && $(currentRow).data('level', ++currentLevel);
403
404     },
405     mousemove: function(e) {
406         var dragObj      = $($.tableDnD.dragObject),
407             config       = $.tableDnD.currentTable.tableDnDConfig,
408             currentRow,
409             mousePos,
410             moving,
411             x,
412             y;
413
414         e && e.preventDefault();
415
416         if (!$.tableDnD.dragObject)
417             return false;
418
419         // prevent touch device screen scrolling
420         e.type == 'touchmove'
421             && event.preventDefault(); // TODO verify this is event and not really e
422
423         // update the style to show we're dragging
424         config.onDragClass
425             && dragObj.addClass(config.onDragClass)
426             || dragObj.css(config.onDragStyle);
427
428         mousePos = $.tableDnD.mouseCoords(e);
429         x = mousePos.x - $.tableDnD.mouseOffset.x;
430         y = mousePos.y - $.tableDnD.mouseOffset.y;
431
432         // auto scroll the window
433         $.tableDnD.autoScroll(mousePos);
434
435         currentRow = $.tableDnD.findDropTargetRow(dragObj, y);
436         moving = $.tableDnD.findDragDirection(x, y);
437
438         $.tableDnD.moveVerticle(moving, currentRow);
439         $.tableDnD.moveHorizontal(moving, currentRow);
440
441         return false;
442     },
443     findDragDirection: function (x,y) {
444         var sensitivity = this.currentTable.tableDnDConfig.sensitivity,
445             oldX        = this.oldX,
446             oldY        = this.oldY,
447             xMin        = oldX - sensitivity,
448             xMax        = oldX + sensitivity,
449             yMin        = oldY - sensitivity,
450             yMax        = oldY + sensitivity,
451             moving      = {
452                 horizontal: x >= xMin && x <= xMax ? 0 : x > oldX ? -1 : 1,
453                 vertical  : y >= yMin && y <= yMax ? 0 : y > oldY ? -1 : 1
454             };
455
456         // update the old value
457         if (moving.horizontal != 0)
458             this.oldX    = x;
459         if (moving.vertical   != 0)
460             this.oldY    = y;
461
462         return moving;
463     },
464     /** We're only worried about the y position really, because we can only move rows up and down */
465     findDropTargetRow: function(draggedRow, y) {
466         var rowHeight = 0,
467             rows      = this.currentTable.rows,
468             config    = this.currentTable.tableDnDConfig,
469             rowY      = 0,
470             row       = null;
471
472         for (var i = 0; i < rows.length; i++) {
473             row       = rows[i];
474             rowY      = this.getPosition(row).y;
475             rowHeight = parseInt(row.offsetHeight) / 2;
476             if (row.offsetHeight == 0) {
477                 rowY      = this.getPosition(row.firstChild).y;
478                 rowHeight = parseInt(row.firstChild.offsetHeight) / 2;
479             }
480             // Because we always have to insert before, we need to offset the height a bit
481             if (y > (rowY - rowHeight) && y < (rowY + rowHeight))
482                 // that's the row we're over
483                 // If it's the same as the current row, ignore it
484                 if (row == draggedRow
485                     || (config.onAllowDrop
486                     && !config.onAllowDrop(draggedRow, row))
487                     // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic)
488                     || $(row).hasClass("nodrop"))
489                         return null;
490                 else
491                     return row;
492         }
493         return null;
494     },
495     processMouseup: function() {
496         var config      = this.currentTable.tableDnDConfig,
497             droppedRow  = this.dragObject,
498             parentLevel = 0,
499             myLevel     = 0;
500
501         if (!this.currentTable || !droppedRow)
502             return null;
503
504         // Unbind the event handlers
505         $(document)
506             .unbind(moveEvent, this.mousemove)
507             .unbind(endEvent,  this.mouseup);
508
509         config.hierarchyLevel
510             && config.autoCleanRelations
511             && $(this.currentTable.rows).first().find('td:first').children().each(function () {
512                 myLevel = $(this).parents('tr:first').data('level');
513                 myLevel
514                     && $(this).parents('tr:first').data('level', --myLevel)
515                     && $(this).remove();
516             })
517             && config.hierarchyLevel > 1
518             && $(this.currentTable.rows).each(function () {
519                 myLevel = $(this).data('level');
520                 if (myLevel > 1) {
521                     parentLevel = $(this).prev().data('level');
522                     while (myLevel > parentLevel + 1) {
523                         $(this).find('td:first').children(':first').remove();
524                         $(this).data('level', --myLevel);
525                     }
526                 }
527             });
528
529         // If we have a dragObject, then we need to release it,
530         // The row will already have been moved to the right place so we just reset stuff
531         config.onDragClass
532             && $(droppedRow).removeClass(config.onDragClass)
533             || $(droppedRow).css(config.onDropStyle);
534
535         this.dragObject = null;
536         // Call the onDrop method if there is one
537         config.onDrop
538             && this.originalOrder != this.currentOrder()
539             && $(droppedRow).hide().fadeIn('fast')
540             && config.onDrop(this.currentTable, droppedRow);
541
542         this.currentTable = null; // let go of the table too
543     },
544     mouseup: function(e) {
545         e && e.preventDefault();
546         $.tableDnD.processMouseup();
547         return false;
548     },
549     jsonize: function(pretify) {
550         var table = this.currentTable;
551         if (pretify)
552             return JSON.stringify(
553                 this.tableData(table),
554                 null,
555                 table.tableDnDConfig.jsonPretifySeparator
556             );
557         return JSON.stringify(this.tableData(table));
558     },
559     serialize: function() {
560         return $.param(this.tableData(this.currentTable));
561     },
562     serializeTable: function(table) {
563         var result = "";
564         var paramName = table.tableDnDConfig.serializeParamName || table.id;
565         var rows = table.rows;
566         for (var i=0; i<rows.length; i++) {
567             if (result.length > 0) result += "&";
568             var rowId = rows[i].id;
569             if (rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) {
570                 rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0];
571                 result += paramName + '[]=' + rowId;
572             }
573         }
574         return result;
575     },
576     serializeTables: function() {
577         var result = [];
578         $('table').each(function() {
579             this.id && result.push($.param(this.tableData(this)));
580         });
581         return result.join('&');
582     },
583     tableData: function (table) {
584         var config = table.tableDnDConfig,
585             previousIDs  = [],
586             currentLevel = 0,
587             indentLevel  = 0,
588             rowID        = null,
589             data         = {},
590             getSerializeRegexp,
591             paramName,
592             currentID,
593             rows;
594
595         if (!table)
596             table = this.currentTable;
597         if (!table || !table.id || !table.rows || !table.rows.length)
598             return {error: { code: 500, message: "Not a valid table, no serializable unique id provided."}};
599
600         rows      = config.autoCleanRelations
601                         && table.rows
602                         || $.makeArray(table.rows);
603         paramName = config.serializeParamName || table.id;
604         currentID = paramName;
605
606         getSerializeRegexp = function (rowId) {
607             if (rowId && config && config.serializeRegexp)
608                 return rowId.match(config.serializeRegexp)[0];
609             return rowId;
610         };
611
612         data[currentID] = [];
613         !config.autoCleanRelations
614             && $(rows[0]).data('level')
615             && rows.unshift({id: 'undefined'});
616
617
618
619         for (var i=0; i < rows.length; i++) {
620             if (config.hierarchyLevel) {
621                 indentLevel = $(rows[i]).data('level') || 0;
622                 if (indentLevel == 0) {
623                     currentID   = paramName;
624                     previousIDs = [];
625                 }
626                 else if (indentLevel > currentLevel) {
627                     previousIDs.push([currentID, currentLevel]);
628                     currentID = getSerializeRegexp(rows[i-1].id);
629                 }
630                 else if (indentLevel < currentLevel) {
631                     for (var h = 0; h < previousIDs.length; h++) {
632                         if (previousIDs[h][1] == indentLevel)
633                             currentID         = previousIDs[h][0];
634                         if (previousIDs[h][1] >= currentLevel)
635                             previousIDs[h][1] = 0;
636                     }
637                 }
638                 currentLevel = indentLevel;
639
640                 if (!$.isArray(data[currentID]))
641                     data[currentID] = [];
642                 rowID = getSerializeRegexp(rows[i].id);
643                 rowID && data[currentID].push(rowID);
644             }
645             else {
646                 rowID = getSerializeRegexp(rows[i].id);
647                 rowID && data[currentID].push(rowID);
648             }
649         }
650         return data;
651     }
652 };
653
654 window.jQuery.fn.extend(
655     {
656         tableDnD             : $.tableDnD.build,
657         tableDnDUpdate       : $.tableDnD.updateTables,
658         tableDnDSerialize    : $.proxy($.tableDnD.serialize, $.tableDnD),
659         tableDnDSerializeAll : $.tableDnD.serializeTables,
660         tableDnDData         : $.proxy($.tableDnD.tableData, $.tableDnD)
661     }
662 );
663
664 }(window.jQuery, window, window.document);