1 /* 2 * File: TableTools.js 3 * Version: 2.0.0 4 * Description: Tools and buttons for DataTables 5 * Author: Allan Jardine (www.sprymedia.co.uk) 6 * Language: Javascript 7 * License: LGPL / 3 point BSD 8 * Project: DataTables 9 * 10 * Copyright 2009-2010 Allan Jardine, all rights reserved. 11 */ 12 13 /* Global scope for TableTools */ 14 var TableTools; 15 16 (function($, window, document) { 17 18 /** 19 * TableTools provides flexible buttons and other tools for a DataTables enhanced table 20 * @class TableTools 21 * @constructor 22 * @param {Object} oDT DataTables instance 23 * @param {Object} oOpts TableTools options 24 * @param {String} oOpts.sSwfPath ZeroClipboard SWF path 25 * @param {String} oOpts.sRowSelect Row selection options - 'none', 'single' or 'multi' 26 * @param {Function} oOpts.fnPreRowSelect Callback function just prior to row selection 27 * @param {Function} oOpts.fnRowSelected Callback function just after row selection 28 * @param {Function} oOpts.fnRowDeselected Callback function when row is deselected 29 * @param {Array} oOpts.aButtons List of buttons to be used 30 */ 31 TableTools = function( oDT, oOpts ) 32 { 33 /* Santiy check that we are a new instance */ 34 if ( !this.CLASS || this.CLASS != "TableTools" ) 35 { 36 alert( "Warning: TableTools must be initialised with the keyword 'new'" ); 37 } 38 39 40 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 41 * Public class variables 42 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 43 44 /** 45 * @namespace Settings object which contains customisable information for TableTools instance 46 */ 47 this.s = { 48 /** 49 * Store 'this' so the instance can be retreieved from the settings object 50 * @property that 51 * @type object 52 * @default this 53 */ 54 that: this, 55 56 /** 57 * DataTables settings objects 58 * @property dt 59 * @type object 60 * @default null 61 */ 62 dt: null, 63 64 /** 65 * @namespace Print specific information 66 */ 67 print: { 68 /** 69 * DataTables draw 'start' point before the printing display was shown 70 * @property saveStart 71 * @type int 72 * @default -1 73 */ 74 saveStart: -1, 75 76 /** 77 * DataTables draw 'length' point before the printing display was shown 78 * @property saveLength 79 * @type int 80 * @default -1 81 */ 82 saveLength: -1, 83 84 /** 85 * Page scrolling point before the printing display was shown so it can be restored 86 * @property saveScroll 87 * @type int 88 * @default -1 89 */ 90 saveScroll: -1, 91 92 /** 93 * Wrapped function to end the print display (to maintain scope) 94 * @property funcEnd 95 * @type Function 96 * @default function () {} 97 */ 98 funcEnd: function () {} 99 }, 100 101 /** 102 * A unique ID is assigned to each button in each instance 103 * @property buttonCounter 104 * @type int 105 * @default 0 106 */ 107 buttonCounter: 0, 108 109 /** 110 * @namespace Select rows specific information 111 */ 112 select: { 113 /** 114 * Select type - can be 'none', 'single' or 'multi' 115 * @property type 116 * @type string 117 * @default "" 118 */ 119 type: "", 120 121 /** 122 * Array of nodes which are currently selected 123 * @property selected 124 * @type array 125 * @default [] 126 */ 127 selected: [], 128 129 /** 130 * Function to run before the selection can take place. Will cancel the select if the 131 * function returns false 132 * @property preRowSelect 133 * @type Function 134 * @default null 135 */ 136 preRowSelect: null, 137 138 /** 139 * Function to run when a row is selected 140 * @property postSelected 141 * @type Function 142 * @default null 143 */ 144 postSelected: null, 145 146 /** 147 * Function to run when a row is deselected 148 * @property postDeselected 149 * @type Function 150 * @default null 151 */ 152 postDeselected: null, 153 154 /** 155 * Indicate if all rows are selected (needed for server-side processing) 156 * @property all 157 * @type boolean 158 * @default false 159 */ 160 all: false 161 }, 162 163 /** 164 * Store of the user input customisation object 165 * @property custom 166 * @type object 167 * @default {} 168 */ 169 custom: {}, 170 171 /** 172 * SWF movie path 173 * @property swfPath 174 * @type string 175 * @default "" 176 */ 177 swfPath: "", 178 179 /** 180 * Default button set 181 * @property buttonSet 182 * @type array 183 * @default [] 184 */ 185 buttonSet: [], 186 187 /** 188 * When there is more than one TableTools instance for a DataTable, there must be a 189 * master which controls events (row selection etc) 190 * @property master 191 * @type boolean 192 * @default false 193 */ 194 master: false 195 }; 196 197 198 /** 199 * @namespace Common and useful DOM elements for the class instance 200 */ 201 this.dom = { 202 /** 203 * DIV element that is create and all TableTools buttons (and their children) put into 204 * @property container 205 * @type node 206 * @default null 207 */ 208 container: null, 209 210 /** 211 * The table node to which TableTools will be applied 212 * @property table 213 * @type node 214 * @default null 215 */ 216 table: null, 217 218 /** 219 * @namespace Nodes used for the print display 220 */ 221 print: { 222 /** 223 * Nodes which have been removed from the display by setting them to display none 224 * @property hidden 225 * @type array 226 * @default [] 227 */ 228 hidden: [], 229 230 /** 231 * The information display saying tellng the user about the print display 232 * @property message 233 * @type node 234 * @default null 235 */ 236 message: null 237 }, 238 239 /** 240 * @namespace Nodes used for a collection display. This contains the currently used collection 241 */ 242 collection: { 243 /** 244 * The div wrapper containing the buttons in the collection (i.e. the menu) 245 * @property collection 246 * @type node 247 * @default null 248 */ 249 collection: null, 250 251 /** 252 * Background display to provide focus and capture events 253 * @property background 254 * @type node 255 * @default null 256 */ 257 background: null 258 } 259 }; 260 261 262 263 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 264 * Public class methods 265 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 266 267 /** 268 * Retreieve the settings object from an instance 269 * @method fnSettings 270 * @returns {object} TableTools settings object 271 */ 272 this.fnSettings = function () { 273 return this.s; 274 }; 275 276 277 /* Constructor logic */ 278 if ( typeof oOpts == 'undefined' ) 279 { 280 oOpts = {}; 281 } 282 283 this.s.dt = oDT.fnSettings(); 284 this._fnConstruct( oOpts ); 285 286 return this; 287 }; 288 289 290 291 TableTools.prototype = { 292 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 293 * Public methods 294 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 295 296 /** 297 * Retreieve the settings object from an instance 298 * @method fnGetSelected 299 * @returns {array} List of TR nodes which are currently selected 300 */ 301 fnGetSelected: function () 302 { 303 var masterS = this._fnGetMasterSettings(); 304 return masterS.select.selected; 305 }, 306 307 /** 308 * Check to see if a current row is selected or not 309 * @method fnGetSelected 310 * @param {Node} n TR node to check if it is currently selected or not 311 * @returns {Boolean} true if select, false otherwise 312 */ 313 fnIsSelected: function ( n ) 314 { 315 var selected = this.fnGetSelected(); 316 for ( var i=0, iLen=selected.length ; i<iLen ; i++ ) 317 { 318 if ( n == selected[i] ) 319 { 320 return true; 321 } 322 } 323 return false; 324 }, 325 326 /** 327 * Select all rows in the table 328 * @method fnSelectAll 329 * @returns void 330 */ 331 fnSelectAll: function () 332 { 333 var masterS = this._fnGetMasterSettings(); 334 masterS.that._fnRowSelectAll(); 335 }, 336 337 338 /** 339 * Deselect all rows in the table 340 * @method fnSelectNone 341 * @returns void 342 */ 343 fnSelectNone: function () 344 { 345 var masterS = this._fnGetMasterSettings(); 346 masterS.that._fnRowDeselectAll(); 347 }, 348 349 350 /** 351 * Get the title of the document - useful for file names. The title is retrieved from either 352 * the configuration object's 'title' parameter, or the HTML document title 353 * @method fnGetTitle 354 * @param {Object} oConfig Button configuration object 355 * @returns {String} Button title 356 */ 357 fnGetTitle: function( oConfig ) 358 { 359 var sTitle = ""; 360 if ( typeof oConfig.sTitle != 'undefined' && oConfig.sTitle !== "" ) { 361 sTitle = oConfig.sTitle; 362 } else { 363 var anTitle = document.getElementsByTagName('title'); 364 if ( anTitle.length > 0 ) 365 { 366 sTitle = anTitle[0].innerHTML; 367 } 368 } 369 370 /* Strip characters which the OS will object to - checking for UTF8 support in the scripting 371 * engine 372 */ 373 if ( "\u00A1".toString().length < 4 ) { 374 return sTitle.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, ""); 375 } else { 376 return sTitle.replace(/[^a-zA-Z0-9_\.,\-_ !\(\)]/g, ""); 377 } 378 }, 379 380 381 /** 382 * Calculate a unity array with the column width by proportion for a set of columns to be 383 * included for a button. This is particularly useful for PDF creation, where we can use the 384 * column widths calculated by the browser to size the columns in the PDF. 385 * @method fnCalcColRations 386 * @param {Object} oConfig Button configuration object 387 * @returns {Array} Unity array of column ratios 388 */ 389 fnCalcColRatios: function ( oConfig ) 390 { 391 var 392 aoCols = this.s.dt.aoColumns, 393 aColumnsInc = this._fnColumnTargets( oConfig.mColumns ), 394 aColWidths = [], 395 iWidth = 0, iTotal = 0, i, iLen; 396 397 for ( i=0, iLen=aColumnsInc.length ; i<iLen ; i++ ) 398 { 399 if ( aColumnsInc[i] ) 400 { 401 iWidth = aoCols[i].nTh.offsetWidth; 402 iTotal += iWidth; 403 aColWidths.push( iWidth ); 404 } 405 } 406 407 for ( i=0, iLen=aColWidths.length ; i<iLen ; i++ ) 408 { 409 aColWidths[i] = aColWidths[i] / iTotal; 410 } 411 412 return aColWidths.join('\t'); 413 }, 414 415 416 /** 417 * Get the information contained in a table as a string 418 * @method fnGetTableData 419 * @param {Object} oConfig Button configuration object 420 * @returns {String} Table data as a string 421 */ 422 fnGetTableData: function ( oConfig ) 423 { 424 /* In future this could be used to get data from a plain HTML source as well as DataTables */ 425 if ( this.s.dt ) 426 { 427 return this._fnGetDataTablesData( oConfig ); 428 } 429 }, 430 431 432 /** 433 * Pass text to a flash button instance, which will be used on the button's click handler 434 * @method fnSetText 435 * @param {Object} clip Flash button object 436 * @param {String} text Text to set 437 * @returns void 438 */ 439 fnSetText: function ( clip, text ) 440 { 441 this._fnFlashSetText( clip, text ); 442 }, 443 444 445 446 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 447 * Private methods (they are of course public in JS, but recommended as private) 448 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 449 450 /** 451 * Constructor logic 452 * @method _fnConstruct 453 * @param {Object} oOpts Same as TableTools constructor 454 * @returns void 455 * @private 456 */ 457 _fnConstruct: function ( oOpts ) 458 { 459 this._fnCustomiseSettings( oOpts ); 460 461 /* Container element */ 462 this.dom.container = document.createElement('div'); 463 this.dom.container.style.position = "relative"; 464 this.dom.container.className = !this.s.dt.bJUI ? "DTTT_container" : 465 "DTTT_container ui-buttonset ui-buttonset-multi"; 466 467 /* Row selection config */ 468 if ( this.s.select.type != 'none' ) 469 { 470 this._fnRowSelectConfig(); 471 } 472 473 /* Buttons */ 474 this._fnButtonDefinations( this.s.buttonSet, this.dom.container ); 475 }, 476 477 478 /** 479 * Take the user defined settings and the default settings and combine them. 480 * @method _fnCustomiseSettings 481 * @param {Object} oOpts Same as TableTools constructor 482 * @returns void 483 * @private 484 */ 485 _fnCustomiseSettings: function ( oOpts ) 486 { 487 /* Is this the master control instance or not? */ 488 if ( typeof this.s.dt._TableToolsInit == 'undefined' ) 489 { 490 this.s.master = true; 491 this.s.dt._TableToolsInit = true; 492 } 493 494 /* We can use the table node from comparisons to group controls */ 495 this.dom.table = this.s.dt.nTable; 496 497 /* Clone the defaults and then the user options */ 498 this.s.custom = $.extend( {}, TableTools.DEFAULTS, oOpts ); 499 500 /* Flash file location */ 501 this.s.swfPath = this.s.custom.sSwfPath; 502 if ( typeof ZeroClipboard != 'undefined' ) 503 { 504 ZeroClipboard.moviePath = this.s.swfPath; 505 } 506 507 /* Table row selecting */ 508 this.s.select.type = this.s.custom.sRowSelect; 509 this.s.select.preRowSelect = this.s.custom.fnPreRowSelect; 510 this.s.select.postSelected = this.s.custom.fnRowSelected; 511 this.s.select.postDeselected = this.s.custom.fnRowDeselected; 512 513 /* Button set */ 514 this.s.buttonSet = this.s.custom.aButtons; 515 }, 516 517 518 /** 519 * Take the user input arrays and expand them to be fully defined, and then add them to a given 520 * DOM element 521 * @method _fnButtonDefinations 522 * @param {array} buttonSet Set of user defined buttons 523 * @param {node} wrapper Node to add the created buttons to 524 * @returns void 525 * @private 526 */ 527 _fnButtonDefinations: function ( buttonSet, wrapper ) 528 { 529 var buttonDef; 530 531 for ( var i=0, iLen=buttonSet.length ; i<iLen ; i++ ) 532 { 533 if ( typeof buttonSet[i] == "string" ) 534 { 535 if ( typeof TableTools.BUTTONS[ buttonSet[i] ] == 'undefined' ) 536 { 537 alert( "TableTools: Warning - unknown button type: "+buttonSet[i] ); 538 continue; 539 } 540 buttonDef = $.extend( {}, TableTools.BUTTONS[ buttonSet[i] ], true ); 541 } 542 else 543 { 544 if ( typeof TableTools.BUTTONS[ buttonSet[i].sExtends ] == 'undefined' ) 545 { 546 alert( "TableTools: Warning - unknown button type: "+buttonSet[i].sExtends ); 547 continue; 548 } 549 var o = $.extend( {}, TableTools.BUTTONS[ buttonSet[i].sExtends ], true ); 550 buttonDef = $.extend( o, buttonSet[i], true ); 551 } 552 553 if ( this.s.dt.bJUI ) 554 { 555 buttonDef.sButtonClass += " ui-button ui-state-default"; 556 buttonDef.sButtonClassHover += " ui-button ui-state-default ui-state-hover"; 557 } 558 559 wrapper.appendChild( this._fnCreateButton( buttonDef ) ); 560 } 561 }, 562 563 564 /** 565 * Create and configure a TableTools button 566 * @method _fnCreateButton 567 * @param {Object} oConfig Button configuration object 568 * @returns {Node} Button element 569 * @private 570 */ 571 _fnCreateButton: function ( oConfig ) 572 { 573 var nButton = this._fnButtonBase( oConfig ); 574 575 if ( oConfig.sAction == "print" ) 576 { 577 this._fnPrintConfig( nButton, oConfig ); 578 } 579 else if ( oConfig.sAction.match(/flash/) ) 580 { 581 this._fnFlashConfig( nButton, oConfig ); 582 } 583 else if ( oConfig.sAction == "text" ) 584 { 585 this._fnTextConfig( nButton, oConfig ); 586 } 587 else if ( oConfig.sAction == "collection" ) 588 { 589 this._fnTextConfig( nButton, oConfig ); 590 this._fnCollectionConfig( nButton, oConfig ); 591 } 592 593 return nButton; 594 }, 595 596 597 /** 598 * Create the DOM needed for the button and apply some base properties. All buttons start here 599 * @method _fnButtonBase 600 * @param {o} oConfig Button configuration object 601 * @returns {Node} DIV element for the button 602 * @private 603 */ 604 _fnButtonBase: function ( o ) 605 { 606 var 607 nButton = document.createElement('button'), 608 nSpan = document.createElement('span'), 609 masterS = this._fnGetMasterSettings(); 610 611 nButton.className = "DTTT_button "+o.sButtonClass; 612 nButton.setAttribute('id', "ToolTables_"+this.s.dt.sInstance+"_"+masterS.buttonCounter ); 613 nButton.appendChild( nSpan ); 614 nSpan.innerHTML = o.sButtonText; 615 616 masterS.buttonCounter++; 617 618 return nButton; 619 }, 620 621 622 /** 623 * Get the settings object for the master instance. When more than one TableTools instance is 624 * assigned to a DataTable, only one of them can be the 'master' (for the select rows). As such, 625 * we will typically want to interact with that master for global properties. 626 * @method _fnGetMasterSettings 627 * @returns {Object} TableTools settings object 628 * @private 629 */ 630 _fnGetMasterSettings: function () 631 { 632 if ( this.s.master ) 633 { 634 return this.s; 635 } 636 else 637 { 638 /* Look for the master which has the same DT as this one */ 639 var instances = TableTools._aInstances; 640 for ( var i=0, iLen=instances.length ; i<iLen ; i++ ) 641 { 642 if ( this.dom.table == instances[i].s.dt.nTable ) 643 { 644 return instances[i].s; 645 } 646 } 647 } 648 }, 649 650 651 652 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 653 * Button collection functions 654 */ 655 656 /** 657 * Create a collection button, when activated will present a drop downlist of other buttons 658 * @param {Node} nButton Button to use for the collection activation 659 * @param {Object} oConfig Button configuration object 660 * @returns void 661 * @private 662 */ 663 _fnCollectionConfig: function ( nButton, oConfig ) 664 { 665 var nHidden = document.createElement('div'); 666 nHidden.style.display = "none"; 667 nHidden.className = !this.s.dt.bJUI ? "DTTT_collection" : 668 "DTTT_collection ui-buttonset ui-buttonset-multi"; 669 oConfig._collection = nHidden; 670 671 this._fnButtonDefinations( oConfig.aButtons, nHidden ); 672 }, 673 674 675 /** 676 * Show a button collection 677 * @param {Node} nButton Button to use for the collection 678 * @param {Object} oConfig Button configuration object 679 * @returns void 680 * @private 681 */ 682 _fnCollectionShow: function ( nButton, oConfig ) 683 { 684 var 685 that = this, 686 oPos = $(nButton).offset(), 687 nHidden = oConfig._collection, 688 iDivX = oPos.left, 689 iDivY = oPos.top + $(nButton).outerHeight(), 690 iWinHeight = $(window).height(), iDocHeight = $(document).height(), 691 iWinWidth = $(window).width(), iDocWidth = $(document).width(); 692 693 nHidden.style.position = "absolute"; 694 nHidden.style.left = iDivX+"px"; 695 nHidden.style.top = iDivY+"px"; 696 nHidden.style.display = "block"; 697 $(nHidden).css('opacity',0); 698 699 var nBackground = document.createElement('div'); 700 nBackground.style.position = "absolute"; 701 nBackground.style.left = "0px"; 702 nBackground.style.top = "0px"; 703 nBackground.style.height = ((iWinHeight>iDocHeight)? iWinHeight : iDocHeight) +"px"; 704 nBackground.style.width = ((iWinWidth>iDocWidth)? iWinWidth : iDocWidth) +"px"; 705 nBackground.className = "DTTT_collection_background"; 706 $(nBackground).css('opacity',0); 707 708 document.body.appendChild( nBackground ); 709 document.body.appendChild( nHidden ); 710 711 /* Visual corrections to try and keep the collection visible */ 712 var iDivWidth = $(nHidden).outerWidth(); 713 var iDivHeight = $(nHidden).outerHeight(); 714 715 if ( iDivX + iDivWidth > iDocWidth ) 716 { 717 nHidden.style.left = (iDocWidth-iDivWidth)+"px"; 718 } 719 720 if ( iDivY + iDivHeight > iDocHeight ) 721 { 722 nHidden.style.top = (iDivY-iDivHeight-$(nButton).outerHeight())+"px"; 723 } 724 725 this.dom.collection.collection = nHidden; 726 this.dom.collection.background = nBackground; 727 728 /* This results in a very small delay for the end user but it allows the animation to be 729 * much smoother. If you don't want the animation, then the setTimeout can be removed 730 */ 731 setTimeout( function () { 732 $(nHidden).animate({opacity: 1}, 500); 733 $(nBackground).animate({opacity: 0.25}, 500); 734 }, 10 ); 735 736 /* Event handler to remove the collection display */ 737 $(nBackground).click( function () { 738 that._fnCollectionHide.call( that, null, null ); 739 } ); 740 }, 741 742 743 /** 744 * Hide a button collection 745 * @param {Node} nButton Button to use for the collection 746 * @param {Object} oConfig Button configuration object 747 * @returns void 748 * @private 749 */ 750 _fnCollectionHide: function ( nButton, oConfig ) 751 { 752 if ( oConfig !== null && oConfig.sExtends == 'collection' ) 753 { 754 return; 755 } 756 757 if ( this.dom.collection.collection !== null ) 758 { 759 $(this.dom.collection.collection).animate({opacity: 0}, 500, function (e) { 760 this.style.display = "none"; 761 } ); 762 763 $(this.dom.collection.background).animate({opacity: 0}, 500, function (e) { 764 this.parentNode.removeChild( this ); 765 } ); 766 767 this.dom.collection.collection = null; 768 this.dom.collection.background = null; 769 } 770 }, 771 772 773 774 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 775 * Row selection functions 776 */ 777 778 /** 779 * Add event handlers to a table to allow for row selection 780 * @method _fnRowSelectConfig 781 * @returns void 782 * @private 783 */ 784 _fnRowSelectConfig: function () 785 { 786 if ( this.s.master ) 787 { 788 var 789 that = this, 790 i, iLen, 791 aoOpenRows = this.s.dt.aoOpenRows; 792 793 $(that.s.dt.nTable).addClass( 'DTTT_selectable' ); 794 795 $('tr', that.s.dt.nTBody).live( 'click', function(e) { 796 /* Sub-table must be ignored (odd that the selector won't do this with >) */ 797 if ( this.parentNode != that.s.dt.nTBody ) 798 { 799 return; 800 } 801 802 /* Not interested in selecting 'opened' rows */ 803 for ( i=0, iLen=aoOpenRows.length ; i<iLen ; i++ ) 804 { 805 if ( this == aoOpenRows[i].nTr ) 806 { 807 return; 808 } 809 } 810 811 /* User defined selection function */ 812 if ( that.s.select.preRowSelect !== null && !that.s.select.preRowSelect.call(that, e) ) 813 { 814 return; 815 } 816 817 /* And go */ 818 if ( that.s.select.type == "single" ) 819 { 820 that._fnRowSelectSingle.call( that, this ); 821 } 822 else 823 { 824 that._fnRowSelectMulti.call( that, this ); 825 } 826 } ); 827 828 /* Add a draw callback handler for when 'select' all is active and we are using server-side 829 * processing, so TableTools will automatically select the new rows for us 830 */ 831 that.s.dt.aoDrawCallback.push( { 832 fn: function () { 833 if ( that.s.select.all && that.s.dt.oFeatures.bServerSide ) 834 { 835 that.fnSelectAll(); 836 } 837 }, 838 sName: "TableTools_select" 839 } ); 840 } 841 }, 842 843 844 /** 845 * Select or deselect a row based on its current state when only one row is allowed to be 846 * selected at a time (i.e. if there is a row already selected, deselect it). If the selected 847 * row is the one being passed in, just deselect and take no further action. 848 * @method _fnRowSelectSingle 849 * @param {Node} nNode TR element which is being 'activated' in some way 850 * @returns void 851 * @private 852 */ 853 _fnRowSelectSingle: function ( nNode ) 854 { 855 if ( this.s.master ) 856 { 857 /* Do nothing on the DataTables 'empty' result set row */ 858 if ( $('td', nNode).hasClass(this.s.dt.oClasses.sRowEmpty) ) 859 { 860 return; 861 } 862 863 if ( $(nNode).hasClass('DTTT_selected') ) 864 { 865 this._fnRowDeselect( nNode ); 866 } 867 else 868 { 869 if ( this.s.select.selected.length !== 0 ) 870 { 871 this._fnRowDeselectAll(); 872 } 873 874 this.s.select.selected.push( nNode ); 875 $(nNode).addClass( 'DTTT_selected' ); 876 877 if ( this.s.select.postSelected !== null ) 878 { 879 this.s.select.postSelected.call( this, nNode ); 880 } 881 } 882 883 TableTools._fnEventDispatch( this, 'select', nNode ); 884 } 885 }, 886 887 888 /** 889 * Select or deselect a row based on its current state when multiple rows are allowed to be 890 * selected. 891 * @method _fnRowSelectMulti 892 * @param {Node} nNode TR element which is being 'activated' in some way 893 * @returns void 894 * @private 895 */ 896 _fnRowSelectMulti: function ( nNode ) 897 { 898 if ( this.s.master ) 899 { 900 /* Do nothing on the DataTables 'empty' result set row */ 901 if ( $('td', nNode).hasClass(this.s.dt.oClasses.sRowEmpty) ) 902 { 903 return; 904 } 905 906 if ( $(nNode).hasClass('DTTT_selected') ) 907 { 908 this._fnRowDeselect( nNode ); 909 } 910 else 911 { 912 this.s.select.selected.push( nNode ); 913 $(nNode).addClass( 'DTTT_selected' ); 914 915 if ( this.s.select.postSelected !== null ) 916 { 917 this.s.select.postSelected.call( this, nNode ); 918 } 919 } 920 921 TableTools._fnEventDispatch( this, 'select', nNode ); 922 } 923 }, 924 925 926 /** 927 * Select all TR elements in the table. Note that this function will still operate in 'single' 928 * select mode, which might not be what you desire (in which case, don't call this function!) 929 * @method _fnRowSelectAll 930 * @returns void 931 * @private 932 */ 933 _fnRowSelectAll: function ( ) 934 { 935 if ( this.s.master ) 936 { 937 var n; 938 for ( var i=0, iLen=this.s.dt.aiDisplayMaster.length ; i<iLen ; i++ ) 939 { 940 n = this.s.dt.aoData[ this.s.dt.aiDisplayMaster[i] ].nTr; 941 942 if ( !$(n).hasClass('DTTT_selected') ) 943 { 944 this.s.select.selected.push( n ); 945 $(n).addClass( 'DTTT_selected' ); 946 } 947 } 948 949 this.s.select.all = true; 950 TableTools._fnEventDispatch( this, 'select', null ); 951 } 952 }, 953 954 955 /** 956 * Deselect all TR elements in the table. If nothing is currently selected, then no action is 957 * taken. 958 * @method _fnRowDeselectAll 959 * @returns void 960 * @private 961 */ 962 _fnRowDeselectAll: function ( ) 963 { 964 if ( this.s.master ) 965 { 966 for ( var i=this.s.select.selected.length-1 ; i>=0 ; i-- ) 967 { 968 this._fnRowDeselect( i ); 969 } 970 971 this.s.select.all = false; 972 TableTools._fnEventDispatch( this, 'select', null ); 973 } 974 }, 975 976 977 /** 978 * Deselect a single row, based on its index in the selected array, or a TR node (when the 979 * index is then computed) 980 * @method _fnRowDeselect 981 * @param {int|Node} i Node or index of node in selected array, which is to be deselected 982 * @returns void 983 * @private 984 */ 985 _fnRowDeselect: function ( i ) 986 { 987 if ( typeof i.nodeName != 'undefined' ) 988 { 989 i = $.inArray( i, this.s.select.selected ); 990 } 991 992 var nNode = this.s.select.selected[i]; 993 $(nNode).removeClass('DTTT_selected'); 994 this.s.select.selected.splice( i, 1 ); 995 996 if ( this.s.select.postDeselected !== null ) 997 { 998 this.s.select.postDeselected.call( this, nNode ); 999 } 1000 1001 this.s.select.all = false; 1002 }, 1003 1004 1005 1006 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1007 * Text button functions 1008 */ 1009 1010 /** 1011 * Configure a text based button for interaction events 1012 * @method _fnTextConfig 1013 * @param {Node} nButton Button element which is being considered 1014 * @param {Object} oConfig Button configuration object 1015 * @returns void 1016 * @private 1017 */ 1018 _fnTextConfig: function ( nButton, oConfig ) 1019 { 1020 var that = this; 1021 1022 if ( oConfig.fnInit !== null ) 1023 { 1024 oConfig.fnInit.call( this, nButton, oConfig ); 1025 } 1026 1027 if ( oConfig.sToolTip != "" ) 1028 { 1029 nButton.title = oConfig.sToolTip; 1030 } 1031 1032 $(nButton).hover( function () { 1033 $(nButton).removeClass( oConfig.sButtonClass ). 1034 addClass(oConfig.sButtonClassHover ); 1035 if ( oConfig.fnMouseover !== null ) 1036 { 1037 oConfig.fnMouseover.call( this, nButton, oConfig, null ); 1038 } 1039 }, function () { 1040 $(nButton).removeClass( oConfig.sButtonClassHover ). 1041 addClass( oConfig.sButtonClass ); 1042 if ( oConfig.fnMouseout !== null ) 1043 { 1044 oConfig.fnMouseout.call( this, nButton, oConfig, null ); 1045 } 1046 } ); 1047 1048 if ( oConfig.fnSelect !== null ) 1049 { 1050 TableTools._fnEventListen( this, 'select', function (n) { 1051 oConfig.fnSelect.call( that, nButton, oConfig, n ); 1052 } ); 1053 } 1054 1055 $(nButton).click( function (e) { 1056 e.preventDefault(); 1057 1058 if ( oConfig.fnClick !== null ) 1059 { 1060 oConfig.fnClick.call( that, nButton, oConfig, null ); 1061 } 1062 1063 /* Provide a complete function to match the behaviour of the flash elements */ 1064 if ( oConfig.fnComplete !== null ) 1065 { 1066 oConfig.fnComplete.call( that, nButton, oConfig, null, null ); 1067 } 1068 1069 that._fnCollectionHide( nButton, oConfig ); 1070 } ); 1071 }, 1072 1073 1074 1075 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1076 * Flash button functions 1077 */ 1078 1079 /** 1080 * Configure a flash based button for interaction events 1081 * @method _fnFlashConfig 1082 * @param {Node} nButton Button element which is being considered 1083 * @param {o} oConfig Button configuration object 1084 * @returns void 1085 * @private 1086 */ 1087 _fnFlashConfig: function ( nButton, oConfig ) 1088 { 1089 var that = this; 1090 var flash = new ZeroClipboard.Client(); 1091 1092 if ( oConfig.fnInit !== null ) 1093 { 1094 oConfig.fnInit.call( this, nButton, oConfig ); 1095 } 1096 1097 flash.setHandCursor( true ); 1098 1099 if ( oConfig.sAction == "flash_save" ) 1100 { 1101 flash.setAction( 'save' ); 1102 flash.setCharSet( (oConfig.sCharSet=="utf16le") ? 'UTF16LE' : 'UTF8' ); 1103 flash.setBomInc( oConfig.bBomInc ); 1104 flash.setFileName( oConfig.sFileName.replace('*', this.fnGetTitle(oConfig)) ); 1105 } 1106 else if ( oConfig.sAction == "flash_pdf" ) 1107 { 1108 flash.setAction( 'pdf' ); 1109 flash.setFileName( oConfig.sFileName.replace('*', this.fnGetTitle(oConfig)) ); 1110 } 1111 else 1112 { 1113 flash.setAction( 'copy' ); 1114 } 1115 1116 flash.addEventListener('mouseOver', function(client) { 1117 $(nButton).removeClass( oConfig.sButtonClass ). 1118 addClass(oConfig.sButtonClassHover ); 1119 1120 if ( oConfig.fnMouseover !== null ) 1121 { 1122 oConfig.fnMouseover.call( that, nButton, oConfig, flash ); 1123 } 1124 } ); 1125 1126 flash.addEventListener('mouseOut', function(client) { 1127 $(nButton).removeClass( oConfig.sButtonClassHover ). 1128 addClass(oConfig.sButtonClass ); 1129 1130 if ( oConfig.fnMouseout !== null ) 1131 { 1132 oConfig.fnMouseout.call( that, nButton, oConfig, flash ); 1133 } 1134 } ); 1135 1136 flash.addEventListener('mouseDown', function(client) { 1137 if ( oConfig.fnClick !== null ) 1138 { 1139 oConfig.fnClick.call( that, nButton, oConfig, flash ); 1140 } 1141 } ); 1142 1143 flash.addEventListener('complete', function (client, text) { 1144 if ( oConfig.fnComplete !== null ) 1145 { 1146 oConfig.fnComplete.call( that, nButton, oConfig, flash, text ); 1147 } 1148 that._fnCollectionHide( nButton, oConfig ); 1149 } ); 1150 1151 this._fnFlashGlue( flash, nButton, oConfig.sToolTip ); 1152 }, 1153 1154 1155 /** 1156 * Wait until the id is in the DOM before we "glue" the swf. Note that this function will call 1157 * itself (using setTimeout) until it completes successfully 1158 * @method _fnFlashGlue 1159 * @param {Object} clip Zero clipboard object 1160 * @param {Node} node node to glue swf to 1161 * @param {String} text title of the flash movie 1162 * @returns void 1163 * @private 1164 */ 1165 _fnFlashGlue: function ( flash, node, text ) 1166 { 1167 var that = this; 1168 var id = node.getAttribute('id'); 1169 1170 if ( document.getElementById(id) ) 1171 { 1172 flash.glue( node, text ); 1173 } 1174 else 1175 { 1176 setTimeout( function () { 1177 that._fnFlashGlue( flash, node, text ); 1178 }, 100 ); 1179 } 1180 }, 1181 1182 1183 /** 1184 * Set the text for the flash clip to deal with 1185 * 1186 * This function is required for large information sets. There is a limit on the 1187 * amount of data that can be transfered between Javascript and Flash in a single call, so 1188 * we use this method to build up the text in Flash by sending over chunks. It is estimated 1189 * that the data limit is around 64k, although it is undocuments, and appears to be different 1190 * between different flash versions. We chunk at 8KiB. 1191 * @method _fnFlashSetText 1192 * @param {Object} clip the ZeroClipboard object 1193 * @param {String} sData the data to be set 1194 * @returns void 1195 * @private 1196 */ 1197 _fnFlashSetText: function ( clip, sData ) 1198 { 1199 var asData = this._fnChunkData( sData, 8192 ); 1200 1201 clip.clearText(); 1202 for ( var i=0, iLen=asData.length ; i<iLen ; i++ ) 1203 { 1204 clip.appendText( asData[i] ); 1205 } 1206 }, 1207 1208 1209 1210 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1211 * Data retrieval functions 1212 */ 1213 1214 /** 1215 * Convert the mixed columns variable into a boolean array the same size as the columns, which 1216 * indicates which columns we want to include 1217 * @method _fnColumnTargets 1218 * @param {String|Array} mColumns The columns to be included in data retreieval. If a string 1219 * then it can take the value of "visible" or "hidden" (to include all visible or 1220 * hidden columns respectively). Or an array of column indexes 1221 * @returns {Array} A boolean array the length of the columns of the table, which each value 1222 * indicating if the column is to be included or not 1223 * @private 1224 */ 1225 _fnColumnTargets: function ( mColumns ) 1226 { 1227 var aColumns = []; 1228 var dt = this.s.dt; 1229 1230 if ( typeof mColumns == "object" ) 1231 { 1232 for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ ) 1233 { 1234 aColumns.push( false ); 1235 } 1236 1237 for ( i=0, iLen=mColumns.length ; i<iLen ; i++ ) 1238 { 1239 aColumns[ mColumns[i] ] = true; 1240 } 1241 } 1242 else if ( mColumns == "visible" ) 1243 { 1244 for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ ) 1245 { 1246 aColumns.push( dt.aoColumns[i].bVisible ? true : false ); 1247 } 1248 } 1249 else if ( mColumns == "hidden" ) 1250 { 1251 for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ ) 1252 { 1253 aColumns.push( dt.aoColumns[i].bVisible ? false : true ); 1254 } 1255 } 1256 else /* all */ 1257 { 1258 for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ ) 1259 { 1260 aColumns.push( true ); 1261 } 1262 } 1263 1264 return aColumns; 1265 }, 1266 1267 1268 /** 1269 * New line character(s) depend on the platforms 1270 * @method method 1271 * @param {Object} oConfig Button configuration object - only interested in oConfig.sNewLine 1272 * @returns {String} Newline character 1273 */ 1274 _fnNewline: function ( oConfig ) 1275 { 1276 if ( oConfig.sNewLine == "auto" ) 1277 { 1278 return navigator.userAgent.match(/Windows/) ? "\r\n" : "\n"; 1279 } 1280 else 1281 { 1282 return oConfig.sNewLine; 1283 } 1284 }, 1285 1286 1287 /** 1288 * Get data from DataTables' internals and format it for output 1289 * @method _fnGetDataTablesData 1290 * @param {Object} oConfig Button configuration object 1291 * @returns {String} Concatinated string of data 1292 * @private 1293 */ 1294 _fnGetDataTablesData: function ( oConfig ) 1295 { 1296 var i, iLen, j, jLen; 1297 var sData = '', sLoopData = ''; 1298 var dt = this.s.dt; 1299 var regex = new RegExp(oConfig.sFieldBoundary, "g"); /* Do it here for speed */ 1300 var aColumnsInc = this._fnColumnTargets( oConfig.mColumns ); 1301 var sNewline = this._fnNewline( oConfig ); 1302 1303 /* 1304 * Header 1305 */ 1306 if ( oConfig.bHeader ) 1307 { 1308 for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ ) 1309 { 1310 if ( aColumnsInc[i] ) 1311 { 1312 sLoopData = dt.aoColumns[i].sTitle.replace(/\n/g," ").replace( /<.*?>/g, "" ); 1313 sLoopData = this._fnHtmlDecode( sLoopData ); 1314 1315 sData += this._fnBoundData( sLoopData, oConfig.sFieldBoundary, regex ) + 1316 oConfig.sFieldSeperator; 1317 } 1318 } 1319 sData = sData.slice( 0, oConfig.sFieldSeperator.length*-1 ); 1320 sData += sNewline; 1321 } 1322 1323 /* 1324 * Body 1325 */ 1326 for ( j=0, jLen=dt.aiDisplay.length ; j<jLen ; j++ ) 1327 { 1328 /* Columns */ 1329 for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ ) 1330 { 1331 if ( aColumnsInc[i] ) 1332 { 1333 /* Convert to strings (with small optimisation) */ 1334 var mTypeData = dt.aoData[ dt.aiDisplay[j] ]._aData[ i ]; 1335 if ( typeof mTypeData == "string" ) 1336 { 1337 /* Strip newlines, replace img tags with alt attr. and finally strip html... */ 1338 sLoopData = mTypeData.replace(/\n/g," "); 1339 sLoopData = 1340 sLoopData.replace(/<img.*?\s+alt\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+)).*?>/gi, 1341 '$1$2$3'); 1342 sLoopData = sLoopData.replace( /<.*?>/g, "" ); 1343 } 1344 else 1345 { 1346 sLoopData = mTypeData+""; 1347 } 1348 1349 /* Trim and clean the data */ 1350 sLoopData = sLoopData.replace(/^\s+/, '').replace(/\s+$/, ''); 1351 sLoopData = this._fnHtmlDecode( sLoopData ); 1352 1353 /* Bound it and add it to the total data */ 1354 sData += this._fnBoundData( sLoopData, oConfig.sFieldBoundary, regex ) + 1355 oConfig.sFieldSeperator; 1356 } 1357 } 1358 sData = sData.slice( 0, oConfig.sFieldSeperator.length*-1 ); 1359 sData += sNewline; 1360 } 1361 1362 /* Remove the last new line */ 1363 sData.slice( 0, -1 ); 1364 1365 /* 1366 * Footer 1367 */ 1368 if ( oConfig.bFooter ) 1369 { 1370 for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ ) 1371 { 1372 if ( aColumnsInc[i] && dt.aoColumns[i].nTf !== null ) 1373 { 1374 sLoopData = dt.aoColumns[i].nTf.innerHTML.replace(/\n/g," ").replace( /<.*?>/g, "" ); 1375 sLoopData = this._fnHtmlDecode( sLoopData ); 1376 1377 sData += this._fnBoundData( sLoopData, oConfig.sFieldBoundary, regex ) + 1378 oConfig.sFieldSeperator; 1379 } 1380 } 1381 sData = sData.slice( 0, oConfig.sFieldSeperator.length*-1 ); 1382 } 1383 1384 /* No pointers here - this is a string copy :-) */ 1385 _sLastData = sData; 1386 return sData; 1387 }, 1388 1389 1390 /** 1391 * Wrap data up with a boundary string 1392 * @method _fnBoundData 1393 * @param {String} sData data to bound 1394 * @param {String} sBoundary bounding char(s) 1395 * @param {RegExp} regex search for the bounding chars - constructed outside for efficincy 1396 * in the loop 1397 * @returns {String} bound data 1398 * @private 1399 */ 1400 _fnBoundData: function ( sData, sBoundary, regex ) 1401 { 1402 if ( sBoundary === "" ) 1403 { 1404 return sData; 1405 } 1406 else 1407 { 1408 return sBoundary + sData.replace(regex, "\\"+sBoundary) + sBoundary; 1409 } 1410 }, 1411 1412 1413 /** 1414 * Break a string up into an array of smaller strings 1415 * @method _fnChunkData 1416 * @param {String} sData data to be broken up 1417 * @param {Int} iSize chunk size 1418 * @returns {Array} String array of broken up text 1419 * @private 1420 */ 1421 _fnChunkData: function ( sData, iSize ) 1422 { 1423 var asReturn = []; 1424 var iStrlen = sData.length; 1425 1426 for ( var i=0 ; i<iStrlen ; i+=iSize ) 1427 { 1428 if ( i+iSize < iStrlen ) 1429 { 1430 asReturn.push( sData.substring( i, i+iSize ) ); 1431 } 1432 else 1433 { 1434 asReturn.push( sData.substring( i, iStrlen ) ); 1435 } 1436 } 1437 1438 return asReturn; 1439 }, 1440 1441 1442 /** 1443 * Decode HTML entities 1444 * @method _fnHtmlDecode 1445 * @param {String} sData encoded string 1446 * @returns {String} decoded string 1447 * @private 1448 */ 1449 _fnHtmlDecode: function ( sData ) 1450 { 1451 if ( sData.indexOf('&') == -1 ) 1452 { 1453 return sData; 1454 } 1455 1456 var 1457 aData = this._fnChunkData( sData, 2048 ), 1458 n = document.createElement('div'), 1459 i, iLen, iIndex, 1460 sReturn = "", sInner; 1461 1462 /* nodeValue has a limit in browsers - so we chunk the data into smaller segments to build 1463 * up the string. Note that the 'trick' here is to remember than we might have split over 1464 * an HTML entity, so we backtrack a little to make sure this doesn't happen 1465 */ 1466 for ( i=0, iLen=aData.length ; i<iLen ; i++ ) 1467 { 1468 /* Magic number 8 is because no entity is longer then strlen 8 in ISO 8859-1 */ 1469 iIndex = aData[i].lastIndexOf( '&' ); 1470 if ( iIndex != -1 && aData[i].length >= 8 && iIndex > aData[i].length - 8 ) 1471 { 1472 sInner = aData[i].substr( iIndex ); 1473 aData[i] = aData[i].substr( 0, iIndex ); 1474 } 1475 1476 n.innerHTML = aData[i]; 1477 sReturn += n.childNodes[0].nodeValue; 1478 } 1479 1480 return sReturn; 1481 }, 1482 1483 1484 1485 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1486 * Printing functions 1487 */ 1488 1489 /** 1490 * Configure a button for printing 1491 * @method _fnPrintConfig 1492 * @param {Node} nButton Button element which is being considered 1493 * @param {Object} oConfig Button configuration object 1494 * @returns void 1495 * @private 1496 */ 1497 _fnPrintConfig: function ( nButton, oConfig ) 1498 { 1499 var that = this; 1500 1501 if ( oConfig.fnInit !== null ) 1502 { 1503 oConfig.fnInit.call( this, nButton, oConfig ); 1504 } 1505 1506 $(nButton).hover( function () { 1507 $(nButton).removeClass( oConfig.sButtonClass ). 1508 addClass(oConfig.sButtonClassHover ); 1509 }, function () { 1510 $(nButton).removeClass( oConfig.sButtonClassHover ). 1511 addClass(oConfig.sButtonClass ); 1512 } ); 1513 1514 if ( oConfig.fnSelect !== null ) 1515 { 1516 TableTools._fnEventListen( this, 'select', function (n) { 1517 oConfig.fnSelect.call( that, nButton, oConfig, n ); 1518 } ); 1519 } 1520 1521 $(nButton).click( function (e) { 1522 e.preventDefault(); 1523 1524 that._fnPrintStart.call( that, e, oConfig); 1525 1526 if ( oConfig.fnClick !== null ) 1527 { 1528 oConfig.fnClick.call( that, nButton, oConfig, null ); 1529 } 1530 1531 /* Provide a complete function to match the behaviour of the flash elements */ 1532 if ( oConfig.fnComplete !== null ) 1533 { 1534 oConfig.fnComplete.call( that, nButton, oConfig, null, null ); 1535 } 1536 1537 that._fnCollectionHide( nButton, oConfig ); 1538 } ); 1539 }, 1540 1541 /** 1542 * Show print display 1543 * @method _fnPrintStart 1544 * @param {Event} e Event object 1545 * @param {Object} oConfig Button configuration object 1546 * @returns void 1547 * @private 1548 */ 1549 _fnPrintStart: function ( e, oConfig ) 1550 { 1551 var that = this; 1552 var oSetDT = this.s.dt; 1553 1554 /* Parse through the DOM hiding everything that isn't needed for the table */ 1555 this._fnPrintHideNodes( oSetDT.nTable ); 1556 1557 /* Show the whole table */ 1558 this.s.print.saveStart = oSetDT._iDisplayStart; 1559 this.s.print.saveLength = oSetDT._iDisplayLength; 1560 1561 if ( oConfig.bShowAll ) 1562 { 1563 oSetDT._iDisplayStart = 0; 1564 oSetDT._iDisplayLength = -1; 1565 oSetDT.oApi._fnCalculateEnd( oSetDT ); 1566 oSetDT.oApi._fnDraw( oSetDT ); 1567 } 1568 1569 /* Adjust the display for scrolling which might be done by DataTables */ 1570 if ( oSetDT.oScroll.sX !== "" || oSetDT.oScroll.sY !== "" ) 1571 { 1572 this._fnPrintScrollStart( oSetDT ); 1573 } 1574 1575 /* Remove the other DataTables feature nodes - but leave the table! and info div */ 1576 var anFeature = oSetDT.aanFeatures; 1577 for ( var cFeature in anFeature ) 1578 { 1579 if ( cFeature != 'i' && cFeature != 't' && cFeature.length == 1 ) 1580 { 1581 for ( var i=0, iLen=anFeature[cFeature].length ; i<iLen ; i++ ) 1582 { 1583 this.dom.print.hidden.push( { 1584 node: anFeature[cFeature][i], 1585 display: "block" 1586 } ); 1587 anFeature[cFeature][i].style.display = "none"; 1588 } 1589 } 1590 } 1591 1592 /* Print class can be used for styling */ 1593 $(document.body).addClass( 'DTTT_Print' ); 1594 1595 /* Add a node telling the user what is going on */ 1596 if ( oConfig.sInfo !== "" ) 1597 { 1598 var nInfo = document.createElement( "div" ); 1599 nInfo.className = "DTTT_print_info"; 1600 nInfo.innerHTML = oConfig.sInfo; 1601 document.body.appendChild( nInfo ); 1602 1603 setTimeout( function() { 1604 $(nInfo).fadeOut( "normal", function() { 1605 document.body.removeChild( nInfo ); 1606 } ); 1607 }, 2000 ); 1608 } 1609 1610 /* Add a message at the top of the page */ 1611 if ( oConfig.sMessage !== "" ) 1612 { 1613 this.dom.print.message = document.createElement( "div" ); 1614 this.dom.print.message.className = "DTTT_PrintMessage"; 1615 this.dom.print.message.innerHTML = oConfig.sMessage; 1616 document.body.insertBefore( this.dom.print.message, document.body.childNodes[0] ); 1617 } 1618 1619 /* Cache the scrolling and the jump to the top of the t=page */ 1620 this.s.print.saveScroll = $(window).scrollTop(); 1621 window.scrollTo( 0, 0 ); 1622 1623 this.s.print.funcEnd = function(e) { 1624 that._fnPrintEnd.call( that, e ); 1625 }; 1626 $(document).bind( "keydown", null, this.s.print.funcEnd ); 1627 }, 1628 1629 1630 /** 1631 * Printing is finished, resume normal display 1632 * @method _fnPrintEnd 1633 * @param {Event} e Event object 1634 * @returns void 1635 * @private 1636 */ 1637 _fnPrintEnd: function ( e ) 1638 { 1639 /* Only interested in the escape key */ 1640 if ( e.keyCode == 27 ) 1641 { 1642 e.preventDefault(); 1643 1644 var that = this; 1645 var oSetDT = this.s.dt; 1646 var oSetPrint = this.s.print; 1647 var oDomPrint = this.dom.print; 1648 1649 /* Show all hidden nodes */ 1650 this._fnPrintShowNodes(); 1651 1652 /* Restore DataTables' scrolling */ 1653 if ( oSetDT.oScroll.sX !== "" || oSetDT.oScroll.sY !== "" ) 1654 { 1655 this._fnPrintScrollEnd(); 1656 } 1657 1658 /* Restore the scroll */ 1659 window.scrollTo( 0, oSetPrint.saveScroll ); 1660 1661 /* Drop the print message */ 1662 if ( oDomPrint.message !== null ) 1663 { 1664 document.body.removeChild( oDomPrint.message ); 1665 oDomPrint.message = null; 1666 } 1667 1668 /* Styling class */ 1669 $(document.body).removeClass( 'DTTT_Print' ); 1670 1671 /* Restore the table length */ 1672 oSetDT._iDisplayStart = oSetPrint.saveStart; 1673 oSetDT._iDisplayLength = oSetPrint.saveLength; 1674 oSetDT.oApi._fnCalculateEnd( oSetDT ); 1675 oSetDT.oApi._fnDraw( oSetDT ); 1676 1677 $(document).unbind( "keydown", this.s.print.funcEnd ); 1678 this.s.print.funcEnd = null; 1679 } 1680 }, 1681 1682 1683 /** 1684 * Take account of scrolling in DataTables by showing the full table 1685 * @returns void 1686 * @private 1687 */ 1688 _fnPrintScrollStart: function () 1689 { 1690 var 1691 oSetDT = this.s.dt, 1692 nScrollHeadInner = oSetDT.nScrollHead.getElementsByTagName('div')[0], 1693 nScrollHeadTable = nScrollHeadInner.getElementsByTagName('table')[0], 1694 nScrollBody = oSetDT.nTable.parentNode; 1695 1696 /* Copy the header in the thead in the body table, this way we show one single table when 1697 * in print view. Note that this section of code is more or less verbatim from DT 1.7.0 1698 */ 1699 var nTheadSize = oSetDT.nTable.getElementsByTagName('thead'); 1700 if ( nTheadSize.length > 0 ) 1701 { 1702 oSetDT.nTable.removeChild( nTheadSize[0] ); 1703 } 1704 1705 if ( oSetDT.nTFoot !== null ) 1706 { 1707 var nTfootSize = oSetDT.nTable.getElementsByTagName('tfoot'); 1708 if ( nTfootSize.length > 0 ) 1709 { 1710 oSetDT.nTable.removeChild( nTfootSize[0] ); 1711 } 1712 } 1713 1714 nTheadSize = oSetDT.nTHead.cloneNode(true); 1715 oSetDT.nTable.insertBefore( nTheadSize, oSetDT.nTable.childNodes[0] ); 1716 1717 if ( oSetDT.nTFoot !== null ) 1718 { 1719 nTfootSize = oSetDT.nTFoot.cloneNode(true); 1720 oSetDT.nTable.insertBefore( nTfootSize, oSetDT.nTable.childNodes[1] ); 1721 } 1722 1723 /* Now adjust the table's viewport so we can actually see it */ 1724 if ( oSetDT.oScroll.sX !== "" ) 1725 { 1726 oSetDT.nTable.style.width = $(oSetDT.nTable).outerWidth()+"px"; 1727 nScrollBody.style.width = $(oSetDT.nTable).outerWidth()+"px"; 1728 nScrollBody.style.overflow = "visible"; 1729 } 1730 1731 if ( oSetDT.oScroll.sY !== "" ) 1732 { 1733 nScrollBody.style.height = $(oSetDT.nTable).outerHeight()+"px"; 1734 nScrollBody.style.overflow = "visible"; 1735 } 1736 }, 1737 1738 1739 /** 1740 * Take account of scrolling in DataTables by showing the full table. Note that the redraw of 1741 * the DataTable that we do will actually deal with the majority of the hardword here 1742 * @returns void 1743 * @private 1744 */ 1745 _fnPrintScrollEnd: function () 1746 { 1747 var 1748 oSetDT = this.s.dt, 1749 nScrollBody = oSetDT.nTable.parentNode; 1750 1751 if ( oSetDT.oScroll.sX !== "" ) 1752 { 1753 nScrollBody.style.width = oSetDT.oApi._fnStringToCss( oSetDT.oScroll.sX ); 1754 nScrollBody.style.overflow = "auto"; 1755 } 1756 1757 if ( oSetDT.oScroll.sY !== "" ) 1758 { 1759 nScrollBody.style.height = oSetDT.oApi._fnStringToCss( oSetDT.oScroll.sY ); 1760 nScrollBody.style.overflow = "auto"; 1761 } 1762 }, 1763 1764 1765 /** 1766 * Resume the display of all TableTools hidden nodes 1767 * @method _fnPrintShowNodes 1768 * @returns void 1769 * @private 1770 */ 1771 _fnPrintShowNodes: function ( ) 1772 { 1773 var anHidden = this.dom.print.hidden; 1774 1775 for ( var i=0, iLen=anHidden.length ; i<iLen ; i++ ) 1776 { 1777 anHidden[i].node.style.display = anHidden[i].display; 1778 } 1779 anHidden.splice( 0, anHidden.length ); 1780 }, 1781 1782 1783 /** 1784 * Hide nodes which are not needed in order to display the table. Note that this function is 1785 * recursive 1786 * @method _fnPrintHideNodes 1787 * @param {Node} nNode Element which should be showing in a 'print' display 1788 * @returns void 1789 * @private 1790 */ 1791 _fnPrintHideNodes: function ( nNode ) 1792 { 1793 var anHidden = this.dom.print.hidden; 1794 1795 var nParent = nNode.parentNode; 1796 var nChildren = nParent.childNodes; 1797 for ( var i=0, iLen=nChildren.length ; i<iLen ; i++ ) 1798 { 1799 if ( nChildren[i] != nNode && nChildren[i].nodeType == 1 ) 1800 { 1801 /* If our node is shown (don't want to show nodes which were previously hidden) */ 1802 var sDisplay = $(nChildren[i]).css("display"); 1803 if ( sDisplay != "none" ) 1804 { 1805 /* Cache the node and it's previous state so we can restore it */ 1806 anHidden.push( { 1807 node: nChildren[i], 1808 display: sDisplay 1809 } ); 1810 nChildren[i].style.display = "none"; 1811 } 1812 } 1813 } 1814 1815 if ( nParent.nodeName != "BODY" ) 1816 { 1817 this._fnPrintHideNodes( nParent ); 1818 } 1819 } 1820 }; 1821 1822 1823 1824 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1825 * Static variables 1826 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 1827 1828 /** 1829 * Store of all instances that have been created of TableTools, so one can look up other (when 1830 * there is need of a master) 1831 * @property _aInstances 1832 * @type Array 1833 * @default [] 1834 * @private 1835 */ 1836 TableTools._aInstances = []; 1837 1838 1839 /** 1840 * Store of all listeners and their callback functions 1841 * @property _aListeners 1842 * @type Array 1843 * @default [] 1844 */ 1845 TableTools._aListeners = []; 1846 1847 1848 1849 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1850 * Static methods 1851 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 1852 1853 /** 1854 * Get an array of all the master instances 1855 * @method fnGetMasters 1856 * @returns {Array} List of master TableTools instances 1857 * @static 1858 */ 1859 TableTools.fnGetMasters = function () 1860 { 1861 var a = []; 1862 for ( var i=0, iLen=TableTools._aInstances.length ; i<iLen ; i++ ) 1863 { 1864 if ( TableTools._aInstances[i].s.master ) 1865 { 1866 a.push( TableTools._aInstances[i].s ); 1867 } 1868 } 1869 return a; 1870 }; 1871 1872 /** 1873 * Get the master instance for a table node (or id if a string is given) 1874 * @method fnGetInstance 1875 * @returns {Object} ID of table OR table node, for which we want the TableTools instance 1876 * @static 1877 */ 1878 TableTools.fnGetInstance = function ( node ) 1879 { 1880 if ( typeof node != 'object' ) 1881 { 1882 node = document.getElementById(node); 1883 } 1884 1885 for ( var i=0, iLen=TableTools._aInstances.length ; i<iLen ; i++ ) 1886 { 1887 if ( TableTools._aInstances[i].s.master && TableTools._aInstances[i].dom.table == node ) 1888 { 1889 return TableTools._aInstances[i]; 1890 } 1891 } 1892 return null; 1893 }; 1894 1895 1896 /** 1897 * Add a listener for a specific event 1898 * @method _fnEventListen 1899 * @param {Object} that Scope of the listening function (i.e. 'this' in the caller) 1900 * @param {String} type Event type 1901 * @param {Function} fn Function 1902 * @returns void 1903 * @private 1904 * @static 1905 */ 1906 TableTools._fnEventListen = function ( that, type, fn ) 1907 { 1908 TableTools._aListeners.push( { 1909 that: that, 1910 type: type, 1911 fn: fn 1912 } ); 1913 }; 1914 1915 1916 /** 1917 * An event has occured - look up every listener and fire it off. We check that the event we are 1918 * going to fire is attached to the same table (using the table node as reference) before firing 1919 * @method _fnEventDispatch 1920 * @param {Object} that Scope of the listening function (i.e. 'this' in the caller) 1921 * @param {String} type Event type 1922 * @param {Node} node Element that the event occured on (may be null) 1923 * @returns void 1924 * @private 1925 * @static 1926 */ 1927 TableTools._fnEventDispatch = function ( that, type, node ) 1928 { 1929 var listeners = TableTools._aListeners; 1930 for ( var i=0, iLen=listeners.length ; i<iLen ; i++ ) 1931 { 1932 if ( that.dom.table == listeners[i].that.dom.table && listeners[i].type == type ) 1933 { 1934 listeners[i].fn( node ); 1935 } 1936 } 1937 }; 1938 1939 1940 1941 1942 1943 1944 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1945 * Constants 1946 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 1947 1948 1949 /** 1950 * @namespace Default button configurations 1951 */ 1952 TableTools.BUTTONS = { 1953 csv: { 1954 sAction: "flash_save", 1955 sCharSet: "utf8", 1956 bBomInc: false, 1957 sFileName: "*.csv", 1958 sFieldBoundary: "'", 1959 sFieldSeperator: ",", 1960 sNewLine: "auto", 1961 sTitle: "", 1962 sToolTip: "", 1963 sButtonClass: "DTTT_button_csv", 1964 sButtonClassHover: "DTTT_button_csv_hover", 1965 sButtonText: "CSV", 1966 mColumns: "all", /* "all", "visible", "hidden" or array of column integers */ 1967 bHeader: true, 1968 bFooter: true, 1969 fnMouseover: null, 1970 fnMouseout: null, 1971 fnClick: function( nButton, oConfig, flash ) { 1972 this.fnSetText( flash, this.fnGetTableData(oConfig) ); 1973 }, 1974 fnSelect: null, 1975 fnComplete: null, 1976 fnInit: null 1977 }, 1978 xls: { 1979 sAction: "flash_save", 1980 sCharSet: "utf16le", 1981 bBomInc: true, 1982 sFileName: "*.csv", 1983 sFieldBoundary: "", 1984 sFieldSeperator: "\t", 1985 sNewLine: "auto", 1986 sTitle: "", 1987 sToolTip: "", 1988 sButtonClass: "DTTT_button_xls", 1989 sButtonClassHover: "DTTT_button_xls_hover", 1990 sButtonText: "Excel", 1991 mColumns: "all", 1992 bHeader: true, 1993 bFooter: true, 1994 fnMouseover: null, 1995 fnMouseout: null, 1996 fnClick: function( nButton, oConfig, flash ) { 1997 this.fnSetText( flash, this.fnGetTableData(oConfig) ); 1998 }, 1999 fnSelect: null, 2000 fnComplete: null, 2001 fnInit: null 2002 }, 2003 copy: { 2004 sAction: "flash_copy", 2005 sFieldBoundary: "", 2006 sFieldSeperator: "\t", 2007 sNewLine: "auto", 2008 sToolTip: "", 2009 sButtonClass: "DTTT_button_copy", 2010 sButtonClassHover: "DTTT_button_copy_hover", 2011 sButtonText: "Copy", 2012 mColumns: "all", 2013 bHeader: true, 2014 bFooter: true, 2015 fnMouseover: null, 2016 fnMouseout: null, 2017 fnClick: function( nButton, oConfig, flash ) { 2018 this.fnSetText( flash, this.fnGetTableData(oConfig) ); 2019 }, 2020 fnSelect: null, 2021 fnComplete: function(nButton, oConfig, flash, text) { 2022 var 2023 len = text.split('\n').length - 1, 2024 plural = (len==1) ? "" : "s"; 2025 alert( 'Copied '+len+' row'+plural+' to the clipboard' ); 2026 }, 2027 fnInit: null 2028 }, 2029 pdf: { 2030 sAction: "flash_pdf", 2031 sFieldBoundary: "", 2032 sFieldSeperator: "\t", 2033 sNewLine: "\n", 2034 sFileName: "*.pdf", 2035 sToolTip: "", 2036 sTitle: "", 2037 sButtonClass: "DTTT_button_pdf", 2038 sButtonClassHover: "DTTT_button_pdf_hover", 2039 sButtonText: "PDF", 2040 mColumns: "all", 2041 bHeader: true, 2042 bFooter: true, 2043 fnMouseover: null, 2044 fnMouseout: null, 2045 fnClick: function( nButton, oConfig, flash ) { 2046 this.fnSetText( flash, 2047 "title:"+ this.fnGetTitle(oConfig) +"\n"+ 2048 "colWidth:"+ this.fnCalcColRatios(oConfig) +"\n"+ 2049 "--/TableToolsOpts--\n" + 2050 this.fnGetTableData(oConfig) 2051 ); 2052 }, 2053 fnSelect: null, 2054 fnComplete: null, 2055 fnInit: null 2056 }, 2057 print: { 2058 sAction: "print", 2059 sInfo: "<h6>Print view</h6><p>Please use your browser's print function to "+ 2060 "print this table. Press escape when finished.", 2061 sMessage: "", 2062 bShowAll: true, 2063 sToolTip: "View print view", 2064 sButtonClass: "DTTT_button_print", 2065 sButtonClassHover: "DTTT_button_print_hover", 2066 sButtonText: "Print", 2067 fnMouseover: null, 2068 fnMouseout: null, 2069 fnClick: null, 2070 fnSelect: null, 2071 fnComplete: null, 2072 fnInit: null 2073 }, 2074 text: { 2075 sAction: "text", 2076 sToolTip: "", 2077 sButtonClass: "DTTT_button_text", 2078 sButtonClassHover: "DTTT_button_text_hover", 2079 sButtonText: "Text button", 2080 mColumns: "all", 2081 bHeader: true, 2082 bFooter: true, 2083 fnMouseover: null, 2084 fnMouseout: null, 2085 fnClick: null, 2086 fnSelect: null, 2087 fnComplete: null, 2088 fnInit: null 2089 }, 2090 select: { 2091 sAction: "text", 2092 sToolTip: "", 2093 sButtonClass: "DTTT_button_text", 2094 sButtonClassHover: "DTTT_button_text_hover", 2095 sButtonText: "Select button", 2096 mColumns: "all", 2097 bHeader: true, 2098 bFooter: true, 2099 fnMouseover: null, 2100 fnMouseout: null, 2101 fnClick: null, 2102 fnSelect: function( nButton, oConfig ) { 2103 if ( this.fnGetSelected().length !== 0 ) { 2104 $(nButton).removeClass('DTTT_disabled'); 2105 } else { 2106 $(nButton).addClass('DTTT_disabled'); 2107 } 2108 }, 2109 fnComplete: null, 2110 fnInit: function( nButton, oConfig ) { 2111 $(nButton).addClass('DTTT_disabled'); 2112 } 2113 }, 2114 select_single: { 2115 sAction: "text", 2116 sToolTip: "", 2117 sButtonClass: "DTTT_button_text", 2118 sButtonClassHover: "DTTT_button_text_hover", 2119 sButtonText: "Select button", 2120 mColumns: "all", 2121 bHeader: true, 2122 bFooter: true, 2123 fnMouseover: null, 2124 fnMouseout: null, 2125 fnClick: null, 2126 fnSelect: function( nButton, oConfig ) { 2127 var iSelected = this.fnGetSelected().length; 2128 if ( iSelected == 1 ) { 2129 $(nButton).removeClass('DTTT_disabled'); 2130 } else { 2131 $(nButton).addClass('DTTT_disabled'); 2132 } 2133 }, 2134 fnComplete: null, 2135 fnInit: function( nButton, oConfig ) { 2136 $(nButton).addClass('DTTT_disabled'); 2137 } 2138 }, 2139 select_all: { 2140 sAction: "text", 2141 sToolTip: "", 2142 sButtonClass: "DTTT_button_text", 2143 sButtonClassHover: "DTTT_button_text_hover", 2144 sButtonText: "Select all", 2145 mColumns: "all", 2146 bHeader: true, 2147 bFooter: true, 2148 fnMouseover: null, 2149 fnMouseout: null, 2150 fnClick: function( nButton, oConfig ) { 2151 this.fnSelectAll(); 2152 }, 2153 fnSelect: function( nButton, oConfig ) { 2154 if ( this.fnGetSelected().length == this.s.dt.fnRecordsDisplay() ) { 2155 $(nButton).addClass('DTTT_disabled'); 2156 } else { 2157 $(nButton).removeClass('DTTT_disabled'); 2158 } 2159 }, 2160 fnComplete: null, 2161 fnInit: null 2162 }, 2163 select_none: { 2164 sAction: "text", 2165 sToolTip: "", 2166 sButtonClass: "DTTT_button_text", 2167 sButtonClassHover: "DTTT_button_text_hover", 2168 sButtonText: "Deselect all", 2169 mColumns: "all", 2170 bHeader: true, 2171 bFooter: true, 2172 fnMouseover: null, 2173 fnMouseout: null, 2174 fnClick: function( nButton, oConfig ) { 2175 this.fnSelectNone(); 2176 }, 2177 fnSelect: function( nButton, oConfig ) { 2178 if ( this.fnGetSelected().length !== 0 ) { 2179 $(nButton).removeClass('DTTT_disabled'); 2180 } else { 2181 $(nButton).addClass('DTTT_disabled'); 2182 } 2183 }, 2184 fnComplete: null, 2185 fnInit: function( nButton, oConfig ) { 2186 $(nButton).addClass('DTTT_disabled'); 2187 } 2188 }, 2189 ajax: { 2190 sAction: "text", 2191 sFieldBoundary: "", 2192 sFieldSeperator: "\t", 2193 sAjaxUrl: "/xhr.php", 2194 sToolTip: "", 2195 sButtonClass: "DTTT_button_text", 2196 sButtonClassHover: "DTTT_button_text_hover", 2197 sButtonText: "Ajax button", 2198 mColumns: "all", 2199 bHeader: true, 2200 bFooter: true, 2201 fnMouseover: null, 2202 fnMouseout: null, 2203 fnClick: function( nButton, oConfig ) { 2204 var sData = this.fnGetTableData(oConfig); 2205 $.ajax( { 2206 url: oConfig.sAjaxUrl, 2207 data: [ 2208 { name: "tableData", value: sData } 2209 ], 2210 success: oConfig.fnAjaxComplete, 2211 dataType: "json", 2212 type: "POST", 2213 cache: false, 2214 error: function () { 2215 alert( "Error detected when sending table data to server" ); 2216 } 2217 } ); 2218 }, 2219 fnSelect: null, 2220 fnComplete: null, 2221 fnInit: null, 2222 fnAjaxComplete: function( json ) { 2223 alert( 'Ajax complete' ); 2224 } 2225 }, 2226 collection: { 2227 sAction: "collection", 2228 sToolTip: "", 2229 sButtonClass: "DTTT_button_collection", 2230 sButtonClassHover: "DTTT_button_collection_hover", 2231 sButtonText: "Collection", 2232 fnMouseover: null, 2233 fnMouseout: null, 2234 fnClick: function( nButton, oConfig ) { 2235 this._fnCollectionShow(nButton, oConfig); 2236 }, 2237 fnSelect: null, 2238 fnComplete: null, 2239 fnInit: null 2240 } 2241 }; 2242 /* 2243 * on* callback parameters: 2244 * 1. node - button element 2245 * 2. object - configuration object for this button 2246 * 3. object - ZeroClipboard reference (flash button only) 2247 * 4. string - Returned string from Flash (flash button only - and only on 'complete') 2248 */ 2249 2250 2251 /** 2252 * @namespace TableTools default settings for initialisation 2253 */ 2254 TableTools.DEFAULTS = { 2255 sSwfPath: "media/swf/copy_cvs_xls_pdf.swf", 2256 sRowSelect: "none", 2257 fnPreRowSelect: null, 2258 fnRowSelected: null, 2259 fnRowDeselected: null, 2260 aButtons: [ "copy", "csv", "xls", "pdf", "print" ] 2261 }; 2262 2263 2264 /** 2265 * Name of this class 2266 * @constant CLASS 2267 * @type String 2268 * @default TableTools 2269 */ 2270 TableTools.prototype.CLASS = "TableTools"; 2271 2272 2273 /** 2274 * TableTools version 2275 * @constant VERSION 2276 * @type String 2277 * @default 2.0.0 2278 */ 2279 TableTools.VERSION = "2.0.0"; 2280 TableTools.prototype.VERSION = TableTools.VERSION; 2281 2282 2283 2284 2285 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2286 * Initialisation 2287 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 2288 2289 /* 2290 * Register a new feature with DataTables 2291 */ 2292 if ( typeof $.fn.dataTable == "function" && 2293 typeof $.fn.dataTableExt.fnVersionCheck == "function" && 2294 $.fn.dataTableExt.fnVersionCheck('1.7.0') ) 2295 { 2296 $.fn.dataTableExt.aoFeatures.push( { 2297 fnInit: function( oDTSettings ) { 2298 var oOpts = typeof oDTSettings.oInit.oTableTools != 'undefined' ? 2299 oDTSettings.oInit.oTableTools : {}; 2300 2301 var oTT = new TableTools( oDTSettings.oInstance, oOpts ); 2302 TableTools._aInstances.push( oTT ); 2303 2304 return oTT.dom.container; 2305 }, 2306 cFeature: "T", 2307 sFeature: "TableTools" 2308 } ); 2309 } 2310 else 2311 { 2312 alert( "Warning: TableTools 2 requires DataTables 1.7 or greater - www.datatables.net/download"); 2313 } 2314 2315 })(jQuery, window, document); 2316