1 /* 2 * File: ColReorder.js 3 * Version: 1.0.4 4 * CVS: $Id$ 5 * Description: Controls for column visiblity in DataTables 6 * Author: Allan Jardine (www.sprymedia.co.uk) 7 * Created: Wed Sep 15 18:23:29 BST 2010 8 * Modified: $Date$ by $Author$ 9 * Language: Javascript 10 * License: GPL v2 or BSD 3 point style 11 * Project: DataTables 12 * Contact: www.sprymedia.co.uk/contact 13 * 14 * Copyright 2010-2011 Allan Jardine, all rights reserved. 15 * 16 * This source file is free software, under either the GPL v2 license or a 17 * BSD style license, available at: 18 * http://datatables.net/license_gpl2 19 * http://datatables.net/license_bsd 20 * 21 */ 22 23 24 (function($, window, document) { 25 26 27 /** 28 * Switch the key value pairing of an index array to be value key (i.e. the old value is now the 29 * key). For example consider [ 2, 0, 1 ] this would be returned as [ 1, 2, 0 ]. 30 * @method fnInvertKeyValues 31 * @param array aIn Array to switch around 32 * @returns array 33 */ 34 function fnInvertKeyValues( aIn ) 35 { 36 var aRet=[]; 37 for ( var i=0, iLen=aIn.length ; i<iLen ; i++ ) 38 { 39 aRet[ aIn[i] ] = i; 40 } 41 return aRet; 42 } 43 44 45 /** 46 * Modify an array by switching the position of two elements 47 * @method fnArraySwitch 48 * @param array aArray Array to consider, will be modified by reference (i.e. no return) 49 * @param int iFrom From point 50 * @param int iTo Insert point 51 * @returns void 52 */ 53 function fnArraySwitch( aArray, iFrom, iTo ) 54 { 55 var mStore = aArray.splice( iFrom, 1 )[0]; 56 aArray.splice( iTo, 0, mStore ); 57 } 58 59 60 /** 61 * Switch the positions of nodes in a parent node (note this is specifically designed for 62 * table rows). Note this function considers all element nodes under the parent! 63 * @method fnDomSwitch 64 * @param string sTag Tag to consider 65 * @param int iFrom Element to move 66 * @param int Point to element the element to (before this point), can be null for append 67 * @returns void 68 */ 69 function fnDomSwitch( nParent, iFrom, iTo ) 70 { 71 var anTags = []; 72 for ( var i=0, iLen=nParent.childNodes.length ; i<iLen ; i++ ) 73 { 74 if ( nParent.childNodes[i].nodeType == 1 ) 75 { 76 anTags.push( nParent.childNodes[i] ); 77 } 78 } 79 var nStore = anTags[ iFrom ]; 80 81 if ( iTo !== null ) 82 { 83 nParent.insertBefore( nStore, anTags[iTo] ); 84 } 85 else 86 { 87 nParent.appendChild( nStore ); 88 } 89 } 90 91 92 93 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 94 * DataTables plug-in API functions 95 * 96 * This are required by ColReorder in order to perform the tasks required, and also keep this 97 * code portable, to be used for other column reordering projects with DataTables, if needed. 98 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 99 100 101 /** 102 * Plug-in for DataTables which will reorder the internal column structure by taking the column 103 * from one position (iFrom) and insert it into a given point (iTo). 104 * @method $.fn.dataTableExt.oApi.fnColReorder 105 * @param object oSettings DataTables settings object - automatically added by DataTables! 106 * @param int iFrom Take the column to be repositioned from this point 107 * @param int iTo and insert it into this point 108 * @returns void 109 */ 110 $.fn.dataTableExt.oApi.fnColReorder = function ( oSettings, iFrom, iTo ) 111 { 112 var i, iLen, j, jLen, iCols=oSettings.aoColumns.length, nTrs, oCol; 113 114 /* Sanity check in the input */ 115 if ( iFrom == iTo ) 116 { 117 /* Pointless reorder */ 118 return; 119 } 120 121 if ( iFrom < 0 || iFrom >= iCols ) 122 { 123 this.oApi._fnLog( oSettings, 1, "ColReorder 'from' index is out of bounds: "+iFrom ); 124 return; 125 } 126 127 if ( iTo < 0 || iTo >= iCols ) 128 { 129 this.oApi._fnLog( oSettings, 1, "ColReorder 'to' index is out of bounds: "+iTo ); 130 return; 131 } 132 133 /* 134 * Calculate the new column array index, so we have a mapping between the old and new 135 */ 136 var aiMapping = []; 137 for ( i=0, iLen=iCols ; i<iLen ; i++ ) 138 { 139 aiMapping[i] = i; 140 } 141 fnArraySwitch( aiMapping, iFrom, iTo ); 142 var aiInvertMapping = fnInvertKeyValues( aiMapping ); 143 144 145 /* 146 * Convert all internal indexing to the new column order indexes 147 */ 148 /* Sorting */ 149 for ( i=0, iLen=oSettings.aaSorting.length ; i<iLen ; i++ ) 150 { 151 oSettings.aaSorting[i][0] = aiInvertMapping[ oSettings.aaSorting[i][0] ]; 152 } 153 154 /* Fixed sorting */ 155 if ( oSettings.aaSortingFixed !== null ) 156 { 157 for ( i=0, iLen=oSettings.aaSortingFixed.length ; i<iLen ; i++ ) 158 { 159 oSettings.aaSortingFixed[i][0] = aiInvertMapping[ oSettings.aaSortingFixed[i][0] ]; 160 } 161 } 162 163 /* Data column sorting (the column which the sort for a given column should take place on) */ 164 for ( i=0, iLen=iCols ; i<iLen ; i++ ) 165 { 166 oSettings.aoColumns[i].iDataSort = aiInvertMapping[ oSettings.aoColumns[i].iDataSort ]; 167 } 168 169 /* Update the Get and Set functions for each column */ 170 for ( i=0, iLen=iCols ; i<iLen ; i++ ) 171 { 172 oCol = oSettings.aoColumns[i]; 173 if ( typeof oCol.mDataProp == 'number' ) { 174 oCol.mDataProp = aiInvertMapping[ oCol.mDataProp ]; 175 oCol.fnGetData = oSettings.oApi._fnGetObjectDataFn( oCol.mDataProp ); 176 oCol.fnSetData = oSettings.oApi._fnSetObjectDataFn( oCol.mDataProp ); 177 } 178 } 179 180 181 /* 182 * Move the DOM elements 183 */ 184 if ( oSettings.aoColumns[iFrom].bVisible ) 185 { 186 /* Calculate the current visible index and the point to insert the node before. The insert 187 * before needs to take into account that there might not be an element to insert before, 188 * in which case it will be null, and an appendChild should be used 189 */ 190 var iVisibleIndex = this.oApi._fnColumnIndexToVisible( oSettings, iFrom ); 191 var iInsertBeforeIndex = null; 192 193 i = iTo < iFrom ? iTo : iTo + 1; 194 while ( iInsertBeforeIndex === null && i < iCols ) 195 { 196 iInsertBeforeIndex = this.oApi._fnColumnIndexToVisible( oSettings, i ); 197 i++; 198 } 199 200 /* Header */ 201 nTrs = oSettings.nTHead.getElementsByTagName('tr'); 202 for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) 203 { 204 fnDomSwitch( nTrs[i], iVisibleIndex, iInsertBeforeIndex ); 205 } 206 207 /* Footer */ 208 if ( oSettings.nTFoot !== null ) 209 { 210 nTrs = oSettings.nTFoot.getElementsByTagName('tr'); 211 for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) 212 { 213 fnDomSwitch( nTrs[i], iVisibleIndex, iInsertBeforeIndex ); 214 } 215 } 216 217 /* Body */ 218 for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ ) 219 { 220 if ( oSettings.aoData[i].nTr !== null ) 221 { 222 fnDomSwitch( oSettings.aoData[i].nTr, iVisibleIndex, iInsertBeforeIndex ); 223 } 224 } 225 } 226 227 228 /* 229 * Move the internal array elements 230 */ 231 /* Columns */ 232 fnArraySwitch( oSettings.aoColumns, iFrom, iTo ); 233 234 /* Search columns */ 235 fnArraySwitch( oSettings.aoPreSearchCols, iFrom, iTo ); 236 237 /* Array array - internal data anodes cache */ 238 for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ ) 239 { 240 if ( $.isArray( oSettings.aoData[i]._aData ) ) { 241 fnArraySwitch( oSettings.aoData[i]._aData, iFrom, iTo ); 242 } 243 fnArraySwitch( oSettings.aoData[i]._anHidden, iFrom, iTo ); 244 } 245 246 /* Reposition the header elements in the header layout array */ 247 for ( i=0, iLen=oSettings.aoHeader.length ; i<iLen ; i++ ) 248 { 249 fnArraySwitch( oSettings.aoHeader[i], iFrom, iTo ); 250 } 251 252 if ( oSettings.aoFooter !== null ) 253 { 254 for ( i=0, iLen=oSettings.aoFooter.length ; i<iLen ; i++ ) 255 { 256 fnArraySwitch( oSettings.aoFooter[i], iFrom, iTo ); 257 } 258 } 259 260 261 /* 262 * Update DataTables' event handlers 263 */ 264 265 /* Sort listener */ 266 for ( i=0, iLen=iCols ; i<iLen ; i++ ) 267 { 268 $(oSettings.aoColumns[i].nTh).unbind('click'); 269 this.oApi._fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i ); 270 } 271 272 273 /* 274 * Any extra operations for the other plug-ins 275 */ 276 if ( typeof ColVis != 'undefined' ) 277 { 278 ColVis.fnRebuild( oSettings.oInstance ); 279 } 280 281 if ( typeof oSettings.oInstance._oPluginFixedHeader != 'undefined' ) 282 { 283 oSettings.oInstance._oPluginFixedHeader.fnUpdate(); 284 } 285 }; 286 287 288 289 290 /** 291 * ColReorder provides column visiblity control for DataTables 292 * @class ColReorder 293 * @constructor 294 * @param {object} DataTables object 295 * @param {object} ColReorder options 296 */ 297 ColReorder = function( oTable, oOpts ) 298 { 299 /* Santiy check that we are a new instance */ 300 if ( !this.CLASS || this.CLASS != "ColReorder" ) 301 { 302 alert( "Warning: ColReorder must be initialised with the keyword 'new'" ); 303 } 304 305 if ( typeof oOpts == 'undefined' ) 306 { 307 oOpts = {}; 308 } 309 310 311 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 312 * Public class variables 313 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 314 315 /** 316 * @namespace Settings object which contains customisable information for ColReorder instance 317 */ 318 this.s = { 319 /** 320 * DataTables settings object 321 * @property dt 322 * @type Object 323 * @default null 324 */ 325 dt: null, 326 327 /** 328 * Initialisation object used for this instance 329 * @property init 330 * @type object 331 * @default {} 332 */ 333 init: oOpts, 334 335 /** 336 * Number of columns to fix (not allow to be reordered) 337 * @property fixed 338 * @type int 339 * @default 0 340 */ 341 fixed: 0, 342 343 /** 344 * Callback function for once the reorder has been done 345 * @property dropcallback 346 * @type function 347 * @default null 348 */ 349 dropCallback: null, 350 351 /** 352 * @namespace Information used for the mouse drag 353 */ 354 mouse: { 355 startX: -1, 356 startY: -1, 357 offsetX: -1, 358 offsetY: -1, 359 target: -1, 360 targetIndex: -1, 361 fromIndex: -1 362 }, 363 364 /** 365 * Information which is used for positioning the insert cusor and knowing where to do the 366 * insert. Array of objects with the properties: 367 * x: x-axis position 368 * to: insert point 369 * @property aoTargets 370 * @type array 371 * @default [] 372 */ 373 aoTargets: [] 374 }; 375 376 377 /** 378 * @namespace Common and useful DOM elements for the class instance 379 */ 380 this.dom = { 381 /** 382 * Dragging element (the one the mouse is moving) 383 * @property drag 384 * @type element 385 * @default null 386 */ 387 drag: null, 388 389 /** 390 * The insert cursor 391 * @property pointer 392 * @type element 393 * @default null 394 */ 395 pointer: null 396 }; 397 398 399 /* Constructor logic */ 400 this.s.dt = oTable.fnSettings(); 401 this._fnConstruct(); 402 403 /* Store the instance for later use */ 404 ColReorder.aoInstances.push( this ); 405 return this; 406 }; 407 408 409 410 ColReorder.prototype = { 411 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 412 * Public methods 413 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 414 415 fnReset: function () 416 { 417 var a = []; 418 for ( var i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ ) 419 { 420 a.push( this.s.dt.aoColumns[i]._ColReorder_iOrigCol ); 421 } 422 423 this._fnOrderColumns( a ); 424 }, 425 426 427 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 428 * Private methods (they are of course public in JS, but recommended as private) 429 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 430 431 /** 432 * Constructor logic 433 * @method _fnConstruct 434 * @returns void 435 * @private 436 */ 437 _fnConstruct: function () 438 { 439 var that = this; 440 var i, iLen; 441 442 /* Columns discounted from reordering - counting left to right */ 443 if ( typeof this.s.init.iFixedColumns != 'undefined' ) 444 { 445 this.s.fixed = this.s.init.iFixedColumns; 446 } 447 448 /* Drop callback initialisation option */ 449 if ( typeof this.s.init.fnReorderCallback != 'undefined' ) 450 { 451 this.s.dropCallback = this.s.init.fnReorderCallback; 452 } 453 454 /* Add event handlers for the drag and drop, and also mark the original column order */ 455 for ( i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ ) 456 { 457 if ( i > this.s.fixed-1 ) 458 { 459 this._fnMouseListener( i, this.s.dt.aoColumns[i].nTh ); 460 } 461 462 /* Mark the original column order for later reference */ 463 this.s.dt.aoColumns[i]._ColReorder_iOrigCol = i; 464 } 465 466 /* State saving */ 467 this.s.dt.aoStateSave.push( { 468 fn: function (oS, sVal) { 469 return that._fnStateSave.call( that, sVal ); 470 }, 471 sName: "ColReorder_State" 472 } ); 473 474 /* An initial column order has been specified */ 475 var aiOrder = null; 476 if ( typeof this.s.init.aiOrder != 'undefined' ) 477 { 478 aiOrder = this.s.init.aiOrder.slice(); 479 } 480 481 /* State loading, overrides the column order given */ 482 if ( this.s.dt.oLoadedState && typeof this.s.dt.oLoadedState.ColReorder != 'undefined' && 483 this.s.dt.oLoadedState.ColReorder.length == this.s.dt.aoColumns.length ) 484 { 485 aiOrder = this.s.dt.oLoadedState.ColReorder; 486 } 487 488 /* If we have an order to apply - do so */ 489 if ( aiOrder ) 490 { 491 /* We might be called during or after the DataTables initialisation. If before, then we need 492 * to wait until the draw is done, if after, then do what we need to do right away 493 */ 494 if ( !that.s.dt._bInitComplete ) 495 { 496 var bDone = false; 497 this.s.dt.aoDrawCallback.push( { 498 fn: function () { 499 if ( !that.s.dt._bInitComplete && !bDone ) 500 { 501 bDone = true; 502 var resort = fnInvertKeyValues( aiOrder ); 503 that._fnOrderColumns.call( that, resort ); 504 } 505 }, 506 sName: "ColReorder_Pre" 507 } ); 508 } 509 else 510 { 511 var resort = fnInvertKeyValues( aiOrder ); 512 that._fnOrderColumns.call( that, resort ); 513 } 514 } 515 }, 516 517 518 /** 519 * Set the column order from an array 520 * @method _fnOrderColumns 521 * @param array a An array of integers which dictate the column order that should be applied 522 * @returns void 523 * @private 524 */ 525 _fnOrderColumns: function ( a ) 526 { 527 if ( a.length != this.s.dt.aoColumns.length ) 528 { 529 this.s.dt.oInstance.oApi._fnLog( oDTSettings, 1, "ColReorder - array reorder does not "+ 530 "match known number of columns. Skipping." ); 531 return; 532 } 533 534 for ( var i=0, iLen=a.length ; i<iLen ; i++ ) 535 { 536 var currIndex = $.inArray( i, a ); 537 if ( i != currIndex ) 538 { 539 /* Reorder our switching array */ 540 fnArraySwitch( a, currIndex, i ); 541 542 /* Do the column reorder in the table */ 543 this.s.dt.oInstance.fnColReorder( currIndex, i ); 544 } 545 } 546 547 /* When scrolling we need to recalculate the column sizes to allow for the shift */ 548 if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" ) 549 { 550 this.s.dt.oInstance.fnAdjustColumnSizing(); 551 } 552 553 /* Save the state */ 554 this.s.dt.oInstance.oApi._fnSaveState( this.s.dt ); 555 }, 556 557 558 /** 559 * This function effectively replaces the state saving function in DataTables (this is needed 560 * because otherwise DataTables would state save the columns in their reordered state, not the 561 * original which is needed on first draw). This is sensitive to any changes in the DataTables 562 * state saving method! 563 * @method _fnStateSave 564 * @param string sCurrentVal 565 * @returns string JSON encoded cookie string for DataTables 566 * @private 567 */ 568 _fnStateSave: function ( sCurrentVal ) 569 { 570 var i, iLen, sTmp; 571 var sValue = sCurrentVal.split('"aaSorting"')[0]; 572 var a = []; 573 var oSettings = this.s.dt; 574 575 /* Sorting */ 576 sValue += '"aaSorting":[ '; 577 for ( i=0 ; i<oSettings.aaSorting.length ; i++ ) 578 { 579 sValue += '['+oSettings.aoColumns[ oSettings.aaSorting[i][0] ]._ColReorder_iOrigCol+ 580 ',"'+oSettings.aaSorting[i][1]+'"],'; 581 } 582 sValue = sValue.substring(0, sValue.length-1); 583 sValue += "],"; 584 585 /* Column filter */ 586 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) 587 { 588 a[ oSettings.aoColumns[i]._ColReorder_iOrigCol ] = { 589 sSearch: encodeURIComponent(oSettings.aoPreSearchCols[i].sSearch), 590 bRegex: !oSettings.aoPreSearchCols[i].bRegex 591 }; 592 } 593 594 sValue += '"aaSearchCols":[ '; 595 for ( i=0 ; i<a.length ; i++ ) 596 { 597 sValue += '["'+a[i].sSearch+'",'+a[i].bRegex+'],'; 598 } 599 sValue = sValue.substring(0, sValue.length-1); 600 sValue += "],"; 601 602 /* Visibility */ 603 a = []; 604 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) 605 { 606 a[ oSettings.aoColumns[i]._ColReorder_iOrigCol ] = oSettings.aoColumns[i].bVisible; 607 } 608 609 sValue += '"abVisCols":[ '; 610 for ( i=0 ; i<a.length ; i++ ) 611 { 612 sValue += a[i]+","; 613 } 614 sValue = sValue.substring(0, sValue.length-1); 615 sValue += "],"; 616 617 /* Column reordering */ 618 a = []; 619 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { 620 a.push( oSettings.aoColumns[i]._ColReorder_iOrigCol ); 621 } 622 sValue += '"ColReorder":['+a.join(',')+']'; 623 624 return sValue; 625 }, 626 627 628 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 629 * Mouse drop and drag 630 */ 631 632 /** 633 * Add a mouse down listener to a particluar TH element 634 * @method _fnMouseListener 635 * @param int i Column index 636 * @param element nTh TH element clicked on 637 * @returns void 638 * @private 639 */ 640 _fnMouseListener: function ( i, nTh ) 641 { 642 var that = this; 643 $(nTh).bind( 'mousedown.ColReorder', function (e) { 644 that._fnMouseDown.call( that, e, nTh ); 645 return false; 646 } ); 647 }, 648 649 650 /** 651 * Mouse down on a TH element in the table header 652 * @method _fnMouseDown 653 * @param event e Mouse event 654 * @param element nTh TH element to be dragged 655 * @returns void 656 * @private 657 */ 658 _fnMouseDown: function ( e, nTh ) 659 { 660 var 661 that = this, 662 aoColumns = this.s.dt.aoColumns; 663 664 /* Store information about the mouse position */ 665 var nThTarget = e.target.nodeName == "TH" ? e.target : $(e.target).parents('TH')[0]; 666 var offset = $(nThTarget).offset(); 667 this.s.mouse.startX = e.pageX; 668 this.s.mouse.startY = e.pageY; 669 this.s.mouse.offsetX = e.pageX - offset.left; 670 this.s.mouse.offsetY = e.pageY - offset.top; 671 this.s.mouse.target = nTh; 672 this.s.mouse.targetIndex = $('th', nTh.parentNode).index( nTh ); 673 this.s.mouse.fromIndex = this.s.dt.oInstance.oApi._fnVisibleToColumnIndex( this.s.dt, 674 this.s.mouse.targetIndex ); 675 676 /* Calculate a cached array with the points of the column inserts, and the 'to' points */ 677 this.s.aoTargets.splice( 0, this.s.aoTargets.length ); 678 679 this.s.aoTargets.push( { 680 x: $(this.s.dt.nTable).offset().left, 681 to: 0 682 } ); 683 684 var iToPoint = 0; 685 for ( var i=0, iLen=aoColumns.length ; i<iLen ; i++ ) 686 { 687 /* For the column / header in question, we want it's position to remain the same if the 688 * position is just to it's immediate left or right, so we only incremement the counter for 689 * other columns 690 */ 691 if ( i != this.s.mouse.fromIndex ) 692 { 693 iToPoint++; 694 } 695 696 if ( aoColumns[i].bVisible ) 697 { 698 this.s.aoTargets.push( { 699 x: $(aoColumns[i].nTh).offset().left + $(aoColumns[i].nTh).outerWidth(), 700 to: iToPoint 701 } ); 702 } 703 } 704 705 /* Disallow columns for being reordered by drag and drop, counting left to right */ 706 if ( this.s.fixed !== 0 ) 707 { 708 this.s.aoTargets.splice( 0, this.s.fixed ); 709 } 710 711 /* Add event handlers to the document */ 712 $(document).bind( 'mousemove.ColReorder', function (e) { 713 that._fnMouseMove.call( that, e ); 714 } ); 715 716 $(document).bind( 'mouseup.ColReorder', function (e) { 717 that._fnMouseUp.call( that, e ); 718 } ); 719 }, 720 721 722 /** 723 * Deal with a mouse move event while dragging a node 724 * @method _fnMouseMove 725 * @param event e Mouse event 726 * @returns void 727 * @private 728 */ 729 _fnMouseMove: function ( e ) 730 { 731 var that = this; 732 733 if ( this.dom.drag === null ) 734 { 735 /* Only create the drag element if the mouse has moved a specific distance from the start 736 * point - this allows the user to make small mouse movements when sorting and not have a 737 * possibly confusing drag element showing up 738 */ 739 if ( Math.pow( 740 Math.pow(e.pageX - this.s.mouse.startX, 2) + 741 Math.pow(e.pageY - this.s.mouse.startY, 2), 0.5 ) < 5 ) 742 { 743 return; 744 } 745 this._fnCreateDragNode(); 746 } 747 748 /* Position the element - we respect where in the element the click occured */ 749 this.dom.drag.style.left = (e.pageX - this.s.mouse.offsetX) + "px"; 750 this.dom.drag.style.top = (e.pageY - this.s.mouse.offsetY) + "px"; 751 752 /* Based on the current mouse position, calculate where the insert should go */ 753 var bSet = false; 754 for ( var i=1, iLen=this.s.aoTargets.length ; i<iLen ; i++ ) 755 { 756 if ( e.pageX < this.s.aoTargets[i-1].x + ((this.s.aoTargets[i].x-this.s.aoTargets[i-1].x)/2) ) 757 { 758 this.dom.pointer.style.left = this.s.aoTargets[i-1].x +"px"; 759 this.s.mouse.toIndex = this.s.aoTargets[i-1].to; 760 bSet = true; 761 break; 762 } 763 } 764 765 /* The insert element wasn't positioned in the array (less than operator), so we put it at 766 * the end 767 */ 768 if ( !bSet ) 769 { 770 this.dom.pointer.style.left = this.s.aoTargets[this.s.aoTargets.length-1].x +"px"; 771 this.s.mouse.toIndex = this.s.aoTargets[this.s.aoTargets.length-1].to; 772 } 773 }, 774 775 776 /** 777 * Finish off the mouse drag and insert the column where needed 778 * @method _fnMouseUp 779 * @param event e Mouse event 780 * @returns void 781 * @private 782 */ 783 _fnMouseUp: function ( e ) 784 { 785 var that = this; 786 787 $(document).unbind( 'mousemove.ColReorder' ); 788 $(document).unbind( 'mouseup.ColReorder' ); 789 790 if ( this.dom.drag !== null ) 791 { 792 /* Remove the guide elements */ 793 document.body.removeChild( this.dom.drag ); 794 document.body.removeChild( this.dom.pointer ); 795 this.dom.drag = null; 796 this.dom.pointer = null; 797 798 /* Actually do the reorder */ 799 this.s.dt.oInstance.fnColReorder( this.s.mouse.fromIndex, this.s.mouse.toIndex ); 800 801 /* When scrolling we need to recalculate the column sizes to allow for the shift */ 802 if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" ) 803 { 804 this.s.dt.oInstance.fnAdjustColumnSizing(); 805 } 806 807 if ( this.s.dropCallback !== null ) 808 { 809 this.s.dropCallback.call( this ); 810 } 811 812 /* Save the state */ 813 this.s.dt.oInstance.oApi._fnSaveState( this.s.dt ); 814 } 815 }, 816 817 818 /** 819 * Copy the TH element that is being drags so the user has the idea that they are actually 820 * moving it around the page. 821 * @method _fnCreateDragNode 822 * @returns void 823 * @private 824 */ 825 _fnCreateDragNode: function () 826 { 827 var that = this; 828 829 this.dom.drag = $(this.s.dt.nTHead.parentNode).clone(true)[0]; 830 this.dom.drag.className += " DTCR_clonedTable"; 831 while ( this.dom.drag.getElementsByTagName('caption').length > 0 ) 832 { 833 this.dom.drag.removeChild( this.dom.drag.getElementsByTagName('caption')[0] ); 834 } 835 while ( this.dom.drag.getElementsByTagName('tbody').length > 0 ) 836 { 837 this.dom.drag.removeChild( this.dom.drag.getElementsByTagName('tbody')[0] ); 838 } 839 while ( this.dom.drag.getElementsByTagName('tfoot').length > 0 ) 840 { 841 this.dom.drag.removeChild( this.dom.drag.getElementsByTagName('tfoot')[0] ); 842 } 843 844 $('thead tr:eq(0)', this.dom.drag).each( function () { 845 $('th:not(:eq('+that.s.mouse.targetIndex+'))', this).remove(); 846 } ); 847 $('tr', this.dom.drag).height( $('tr:eq(0)', that.s.dt.nTHead).height() ); 848 849 $('thead tr:gt(0)', this.dom.drag).remove(); 850 851 $('thead th:eq(0)', this.dom.drag).each( function (i) { 852 this.style.width = $('th:eq('+that.s.mouse.targetIndex+')', that.s.dt.nTHead).width()+"px"; 853 } ); 854 855 this.dom.drag.style.position = "absolute"; 856 this.dom.drag.style.top = "0px"; 857 this.dom.drag.style.left = "0px"; 858 this.dom.drag.style.width = $('th:eq('+that.s.mouse.targetIndex+')', that.s.dt.nTHead).outerWidth()+"px"; 859 860 861 this.dom.pointer = document.createElement( 'div' ); 862 this.dom.pointer.className = "DTCR_pointer"; 863 this.dom.pointer.style.position = "absolute"; 864 865 if ( this.s.dt.oScroll.sX === "" && this.s.dt.oScroll.sY === "" ) 866 { 867 this.dom.pointer.style.top = $(this.s.dt.nTable).offset().top+"px"; 868 this.dom.pointer.style.height = $(this.s.dt.nTable).height()+"px"; 869 } 870 else 871 { 872 this.dom.pointer.style.top = $('div.dataTables_scroll', this.s.dt.nTableWrapper).offset().top+"px"; 873 this.dom.pointer.style.height = $('div.dataTables_scroll', this.s.dt.nTableWrapper).height()+"px"; 874 } 875 876 document.body.appendChild( this.dom.pointer ); 877 document.body.appendChild( this.dom.drag ); 878 } 879 }; 880 881 882 883 884 885 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 886 * Static parameters 887 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 888 889 /** 890 * Array of all ColReorder instances for later reference 891 * @property ColReorder.aoInstances 892 * @type array 893 * @default [] 894 * @static 895 */ 896 ColReorder.aoInstances = []; 897 898 899 900 901 902 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 903 * Static functions 904 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 905 906 /** 907 * Reset the column ordering for a DataTables instance 908 * @method ColReorder.fnReset 909 * @param object oTable DataTables instance to consider 910 * @returns void 911 * @static 912 */ 913 ColReorder.fnReset = function ( oTable ) 914 { 915 for ( var i=0, iLen=ColReorder.aoInstances.length ; i<iLen ; i++ ) 916 { 917 if ( ColReorder.aoInstances[i].s.dt.oInstance == oTable ) 918 { 919 ColReorder.aoInstances[i].fnReset(); 920 } 921 } 922 }; 923 924 925 926 927 928 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 929 * Constants 930 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 931 932 /** 933 * Name of this class 934 * @constant CLASS 935 * @type String 936 * @default ColReorder 937 */ 938 ColReorder.prototype.CLASS = "ColReorder"; 939 940 941 /** 942 * ColReorder version 943 * @constant VERSION 944 * @type String 945 * @default As code 946 */ 947 ColReorder.VERSION = "1.0.4"; 948 ColReorder.prototype.VERSION = ColReorder.VERSION; 949 950 951 952 953 954 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 955 * Initialisation 956 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 957 958 /* 959 * Register a new feature with DataTables 960 */ 961 if ( typeof $.fn.dataTable == "function" && 962 typeof $.fn.dataTableExt.fnVersionCheck == "function" && 963 $.fn.dataTableExt.fnVersionCheck('1.8.0') ) 964 { 965 $.fn.dataTableExt.aoFeatures.push( { 966 fnInit: function( oDTSettings ) { 967 var oTable = oDTSettings.oInstance; 968 if ( typeof oTable._oPluginColReorder == 'undefined' ) { 969 var opts = typeof oDTSettings.oInit.oColReorder != 'undefined' ? 970 oDTSettings.oInit.oColReorder : {}; 971 oTable._oPluginColReorder = new ColReorder( oDTSettings.oInstance, opts ); 972 } else { 973 oTable.oApi._fnLog( oDTSettings, 1, "ColReorder attempted to initialise twice. Ignoring second" ); 974 } 975 976 return null; /* No node to insert */ 977 }, 978 cFeature: "R", 979 sFeature: "ColReorder" 980 } ); 981 } 982 else 983 { 984 alert( "Warning: ColReorder requires DataTables 1.8.0 or greater - www.datatables.net/download"); 985 } 986 987 })(jQuery, window, document); 988