src/ztfy/myams/resources/js/ext/jquery-tablednd.js
changeset 108 41b902f8a713
equal deleted inserted replaced
107:8d815267d5cb 108:41b902f8a713
       
     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 = 'touchstart mousedown',
       
    85     moveEvent  = 'touchmove mousemove',
       
    86     endEvent   = '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 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 		e = e || window.event;
       
   287 
       
   288         if (e.changedTouches)
       
   289             return {
       
   290                 x: e.changedTouches[0].clientX,
       
   291                 y: e.changedTouches[0].clientY
       
   292             };
       
   293         
       
   294         if(e.pageX || e.pageY)
       
   295             return {
       
   296                 x: e.pageX,
       
   297                 y: e.pageY
       
   298             };
       
   299 
       
   300         return {
       
   301             x: e.clientX + document.body.scrollLeft - document.body.clientLeft,
       
   302             y: e.clientY + document.body.scrollTop  - document.body.clientTop
       
   303         };
       
   304     },
       
   305     /** Given a target element and a mouse eent, get the mouse offset from that element.
       
   306      To do this we need the element's position and the mouse position */
       
   307     getMouseOffset: function(target, e) {
       
   308         var mousePos,
       
   309             docPos;
       
   310 
       
   311         e = e || window.event;
       
   312 
       
   313         docPos    = this.getPosition(target);
       
   314         mousePos  = this.mouseCoords(e);
       
   315 
       
   316         return {
       
   317             x: mousePos.x - docPos.x,
       
   318             y: mousePos.y - docPos.y
       
   319         };
       
   320     },
       
   321     /** Get the position of an element by going up the DOM tree and adding up all the offsets */
       
   322     getPosition: function(element) {
       
   323         var left = 0,
       
   324             top  = 0;
       
   325 
       
   326         // Safari fix -- thanks to Luis Chato for this!
       
   327         // Safari 2 doesn't correctly grab the offsetTop of a table row
       
   328         // this is detailed here:
       
   329         // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
       
   330         // the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
       
   331         // note that firefox will return a text node as a first child, so designing a more thorough
       
   332         // solution may need to take that into account, for now this seems to work in firefox, safari, ie
       
   333         if (element.offsetHeight == 0)
       
   334             element = element.firstChild; // a table cell
       
   335 
       
   336         while (element.offsetParent) {
       
   337             left   += element.offsetLeft;
       
   338             top    += element.offsetTop;
       
   339             element = element.offsetParent;
       
   340         }
       
   341 
       
   342         left += element.offsetLeft;
       
   343         top  += element.offsetTop;
       
   344 
       
   345         return {
       
   346             x: left,
       
   347             y: top
       
   348         };
       
   349     },
       
   350     autoScroll: function (mousePos) {
       
   351       var config       = this.currentTable.tableDnDConfig,
       
   352           yOffset      = window.pageYOffset,
       
   353           windowHeight = window.innerHeight
       
   354             ? window.innerHeight
       
   355             : document.documentElement.clientHeight
       
   356             ? document.documentElement.clientHeight
       
   357             : document.body.clientHeight;
       
   358 
       
   359         // Windows version
       
   360         // yOffset=document.body.scrollTop;
       
   361         if (document.all)
       
   362             if (typeof document.compatMode != 'undefined'
       
   363                 && document.compatMode != 'BackCompat')
       
   364                 yOffset = document.documentElement.scrollTop;
       
   365             else if (typeof document.body != 'undefined')
       
   366                 yOffset = document.body.scrollTop;
       
   367 
       
   368         mousePos.y - yOffset < config.scrollAmount
       
   369             && window.scrollBy(0, - config.scrollAmount)
       
   370         || windowHeight - (mousePos.y - yOffset) < config.scrollAmount
       
   371             && window.scrollBy(0, config.scrollAmount);
       
   372 
       
   373     },
       
   374     moveVerticle: function (moving, currentRow) {
       
   375 
       
   376         if (0 != moving.vertical
       
   377             // If we're over a row then move the dragged row to there so that the user sees the
       
   378             // effect dynamically
       
   379             && currentRow
       
   380             && this.dragObject != currentRow
       
   381             && this.dragObject.parentNode == currentRow.parentNode)
       
   382             0 > moving.vertical
       
   383                 && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow.nextSibling)
       
   384             || 0 < moving.vertical
       
   385                 && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow);
       
   386 
       
   387     },
       
   388     moveHorizontal: function (moving, currentRow) {
       
   389         var config       = this.currentTable.tableDnDConfig,
       
   390             currentLevel;
       
   391 
       
   392         if (!config.hierarchyLevel
       
   393             || 0 == moving.horizontal
       
   394             // We only care if moving left or right on the current row
       
   395             || !currentRow
       
   396             || this.dragObject != currentRow)
       
   397                 return null;
       
   398 
       
   399             currentLevel = $(currentRow).data('level');
       
   400 
       
   401             0 < moving.horizontal
       
   402                 && currentLevel > 0
       
   403                 && $(currentRow).find('td:first').children(':first').remove()
       
   404                 && $(currentRow).data('level', --currentLevel);
       
   405 
       
   406             0 > moving.horizontal
       
   407                 && currentLevel < config.hierarchyLevel
       
   408                 && $(currentRow).prev().data('level') >= currentLevel
       
   409                 && $(currentRow).children(':first').prepend(config.indentArtifact)
       
   410                 && $(currentRow).data('level', ++currentLevel);
       
   411 
       
   412     },
       
   413     mousemove: function(e) {
       
   414         var dragObj      = $($.tableDnD.dragObject),
       
   415             config       = $.tableDnD.currentTable.tableDnDConfig,
       
   416             currentRow,
       
   417             mousePos,
       
   418             moving,
       
   419             x,
       
   420             y;
       
   421 
       
   422         e && e.preventDefault();
       
   423 
       
   424         if (!$.tableDnD.dragObject)
       
   425             return false;
       
   426 
       
   427         // prevent touch device screen scrolling
       
   428         e.type == 'touchmove'
       
   429             && event.preventDefault(); // TODO verify this is event and not really e
       
   430 
       
   431         // update the style to show we're dragging
       
   432         config.onDragClass
       
   433             && dragObj.addClass(config.onDragClass)
       
   434             || dragObj.css(config.onDragStyle);
       
   435 
       
   436         mousePos = $.tableDnD.mouseCoords(e);
       
   437         x = mousePos.x - $.tableDnD.mouseOffset.x;
       
   438         y = mousePos.y - $.tableDnD.mouseOffset.y;
       
   439 
       
   440         // auto scroll the window
       
   441         $.tableDnD.autoScroll(mousePos);
       
   442 
       
   443         currentRow = $.tableDnD.findDropTargetRow(dragObj, y);
       
   444         moving = $.tableDnD.findDragDirection(x, y);
       
   445 
       
   446         $.tableDnD.moveVerticle(moving, currentRow);
       
   447         $.tableDnD.moveHorizontal(moving, currentRow);
       
   448 
       
   449         return false;
       
   450     },
       
   451     findDragDirection: function (x,y) {
       
   452         var sensitivity = this.currentTable.tableDnDConfig.sensitivity,
       
   453             oldX        = this.oldX,
       
   454             oldY        = this.oldY,
       
   455             xMin        = oldX - sensitivity,
       
   456             xMax        = oldX + sensitivity,
       
   457             yMin        = oldY - sensitivity,
       
   458             yMax        = oldY + sensitivity,
       
   459             moving      = {
       
   460                 horizontal: x >= xMin && x <= xMax ? 0 : x > oldX ? -1 : 1,
       
   461                 vertical  : y >= yMin && y <= yMax ? 0 : y > oldY ? -1 : 1
       
   462             };
       
   463 
       
   464         // update the old value
       
   465         if (moving.horizontal != 0)
       
   466             this.oldX    = x;
       
   467         if (moving.vertical   != 0)
       
   468             this.oldY    = y;
       
   469 
       
   470         return moving;
       
   471     },
       
   472     /** We're only worried about the y position really, because we can only move rows up and down */
       
   473     findDropTargetRow: function(draggedRow, y) {
       
   474         var rowHeight = 0,
       
   475             rows      = this.currentTable.rows,
       
   476             config    = this.currentTable.tableDnDConfig,
       
   477             rowY      = 0,
       
   478             row       = null;
       
   479 
       
   480         for (var i = 0; i < rows.length; i++) {
       
   481             row       = rows[i];
       
   482             rowY      = this.getPosition(row).y;
       
   483             rowHeight = parseInt(row.offsetHeight) / 2;
       
   484             if (row.offsetHeight == 0) {
       
   485                 rowY      = this.getPosition(row.firstChild).y;
       
   486                 rowHeight = parseInt(row.firstChild.offsetHeight) / 2;
       
   487             }
       
   488             // Because we always have to insert before, we need to offset the height a bit
       
   489             if (y > (rowY - rowHeight) && y < (rowY + rowHeight))
       
   490                 // that's the row we're over
       
   491                 // If it's the same as the current row, ignore it
       
   492                 if (draggedRow.is(row)
       
   493                     || (config.onAllowDrop
       
   494                     && !config.onAllowDrop(draggedRow, row))
       
   495                     // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic)
       
   496                     || $(row).hasClass("nodrop"))
       
   497                         return null;
       
   498                 else
       
   499                     return row;
       
   500         }
       
   501         return null;
       
   502     },
       
   503     processMouseup: function() {
       
   504         if (!this.currentTable || !this.dragObject)
       
   505             return null;
       
   506 
       
   507         var config      = this.currentTable.tableDnDConfig,
       
   508             droppedRow  = this.dragObject,
       
   509             parentLevel = 0,
       
   510             myLevel     = 0;
       
   511 
       
   512         // Unbind the event handlers
       
   513         $(document)
       
   514             .unbind(moveEvent, this.mousemove)
       
   515             .unbind(endEvent,  this.mouseup);
       
   516 
       
   517         config.hierarchyLevel
       
   518             && config.autoCleanRelations
       
   519             && $(this.currentTable.rows).first().find('td:first').children().each(function () {
       
   520                 myLevel = $(this).parents('tr:first').data('level');
       
   521                 myLevel
       
   522                     && $(this).parents('tr:first').data('level', --myLevel)
       
   523                     && $(this).remove();
       
   524             })
       
   525             && config.hierarchyLevel > 1
       
   526             && $(this.currentTable.rows).each(function () {
       
   527                 myLevel = $(this).data('level');
       
   528                 if (myLevel > 1) {
       
   529                     parentLevel = $(this).prev().data('level');
       
   530                     while (myLevel > parentLevel + 1) {
       
   531                         $(this).find('td:first').children(':first').remove();
       
   532                         $(this).data('level', --myLevel);
       
   533                     }
       
   534                 }
       
   535             });
       
   536 
       
   537         // If we have a dragObject, then we need to release it,
       
   538         // The row will already have been moved to the right place so we just reset stuff
       
   539         config.onDragClass
       
   540             && $(droppedRow).removeClass(config.onDragClass)
       
   541             || $(droppedRow).css(config.onDropStyle);
       
   542 
       
   543         this.dragObject = null;
       
   544         // Call the onDrop method if there is one
       
   545         config.onDrop
       
   546             && this.originalOrder != this.currentOrder()
       
   547             && $(droppedRow).hide().fadeIn('fast')
       
   548             && config.onDrop(this.currentTable, droppedRow);
       
   549 
       
   550         this.currentTable = null; // let go of the table too
       
   551     },
       
   552     mouseup: function(e) {
       
   553         e && e.preventDefault();
       
   554         $.tableDnD.processMouseup();
       
   555         return false;
       
   556     },
       
   557     jsonize: function(pretify) {
       
   558         var table = this.currentTable;
       
   559         if (pretify)
       
   560             return JSON.stringify(
       
   561                 this.tableData(table),
       
   562                 null,
       
   563                 table.tableDnDConfig.jsonPretifySeparator
       
   564             );
       
   565         return JSON.stringify(this.tableData(table));
       
   566     },
       
   567     serialize: function() {
       
   568         return $.param(this.tableData(this.currentTable));
       
   569     },
       
   570     serializeTable: function(table) {
       
   571         var result = "";
       
   572         var paramName = table.tableDnDConfig.serializeParamName || table.id;
       
   573         var rows = table.rows;
       
   574         for (var i=0; i<rows.length; i++) {
       
   575             if (result.length > 0) result += "&";
       
   576             var rowId = rows[i].id;
       
   577             if (rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) {
       
   578                 rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0];
       
   579                 result += paramName + '[]=' + rowId;
       
   580             }
       
   581         }
       
   582         return result;
       
   583     },
       
   584     serializeTables: function() {
       
   585         var result = [];
       
   586         $('table').each(function() {
       
   587             this.id && result.push($.param(this.tableData(this)));
       
   588         });
       
   589         return result.join('&');
       
   590     },
       
   591     tableData: function (table) {
       
   592         var config = table.tableDnDConfig,
       
   593             previousIDs  = [],
       
   594             currentLevel = 0,
       
   595             indentLevel  = 0,
       
   596             rowID        = null,
       
   597             data         = {},
       
   598             getSerializeRegexp,
       
   599             paramName,
       
   600             currentID,
       
   601             rows;
       
   602 
       
   603         if (!table)
       
   604             table = this.currentTable;
       
   605         if (!table || !table.id || !table.rows || !table.rows.length)
       
   606             return {error: { code: 500, message: "Not a valid table, no serializable unique id provided."}};
       
   607 
       
   608         rows      = config.autoCleanRelations
       
   609                         && table.rows
       
   610                         || $.makeArray(table.rows);
       
   611         paramName = config.serializeParamName || table.id;
       
   612         currentID = paramName;
       
   613 
       
   614         getSerializeRegexp = function (rowId) {
       
   615             if (rowId && config && config.serializeRegexp)
       
   616                 return rowId.match(config.serializeRegexp)[0];
       
   617             return rowId;
       
   618         };
       
   619 
       
   620         data[currentID] = [];
       
   621         !config.autoCleanRelations
       
   622             && $(rows[0]).data('level')
       
   623             && rows.unshift({id: 'undefined'});
       
   624 
       
   625 
       
   626 
       
   627         for (var i=0; i < rows.length; i++) {
       
   628             if (config.hierarchyLevel) {
       
   629                 indentLevel = $(rows[i]).data('level') || 0;
       
   630                 if (indentLevel == 0) {
       
   631                     currentID   = paramName;
       
   632                     previousIDs = [];
       
   633                 }
       
   634                 else if (indentLevel > currentLevel) {
       
   635                     previousIDs.push([currentID, currentLevel]);
       
   636                     currentID = getSerializeRegexp(rows[i-1].id);
       
   637                 }
       
   638                 else if (indentLevel < currentLevel) {
       
   639                     for (var h = 0; h < previousIDs.length; h++) {
       
   640                         if (previousIDs[h][1] == indentLevel)
       
   641                             currentID         = previousIDs[h][0];
       
   642                         if (previousIDs[h][1] >= currentLevel)
       
   643                             previousIDs[h][1] = 0;
       
   644                     }
       
   645                 }
       
   646                 currentLevel = indentLevel;
       
   647 
       
   648                 if (!$.isArray(data[currentID]))
       
   649                     data[currentID] = [];
       
   650                 rowID = getSerializeRegexp(rows[i].id);
       
   651                 rowID && data[currentID].push(rowID);
       
   652             }
       
   653             else {
       
   654                 rowID = getSerializeRegexp(rows[i].id);
       
   655                 rowID && data[currentID].push(rowID);
       
   656             }
       
   657         }
       
   658         return data;
       
   659     }
       
   660 };
       
   661 
       
   662 jQuery.fn.extend(
       
   663     {
       
   664         tableDnD             : $.tableDnD.build,
       
   665         tableDnDUpdate       : $.tableDnD.updateTables,
       
   666         tableDnDSerialize    : $.proxy($.tableDnD.serialize, $.tableDnD),
       
   667         tableDnDSerializeAll : $.tableDnD.serializeTables,
       
   668         tableDnDData         : $.proxy($.tableDnD.tableData, $.tableDnD)
       
   669     }
       
   670 );
       
   671 
       
   672 }(jQuery, window, window.document);