1 /* 2 * File: ColVis.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: LGPL 11 * Project: Just a little bit of fun :-) 12 * Contact: www.sprymedia.co.uk/contact 13 * 14 * Copyright 2010 Allan Jardine, all rights reserved. 15 * 16 */ 17 18 (function($) { 19 20 /** 21 * ColVis provides column visiblity control for DataTables 22 * @class ColVis 23 * @constructor 24 * @param {object} DataTables settings object 25 */ 26 ColVis = function( oDTSettings, oInit ) 27 { 28 /* Santiy check that we are a new instance */ 29 if ( !this.CLASS || this.CLASS != "ColVis" ) 30 { 31 alert( "Warning: ColVis must be initialised with the keyword 'new'" ); 32 } 33 34 if ( typeof oInit == 'undefined' ) 35 { 36 oInit = {}; 37 } 38 39 40 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 41 * Public class variables 42 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 43 44 /** 45 * @namespace Settings object which contains customisable information for ColVis instance 46 */ 47 this.s = { 48 /** 49 * DataTables settings object 50 * @property dt 51 * @type Object 52 * @default null 53 */ 54 dt: null, 55 56 /** 57 * Customisation object 58 * @property oInit 59 * @type Object 60 * @default passed in 61 */ 62 oInit: oInit, 63 64 /** 65 * Callback function to tell the user when the state has changed 66 * @property fnStateChange 67 * @type function 68 * @default null 69 */ 70 fnStateChange: null, 71 72 /** 73 * Mode of activation. Can be 'click' or 'mouseover' 74 * @property activate 75 * @type String 76 * @default click 77 */ 78 activate: "click", 79 80 /** 81 * Position of the collection menu when shown - align "left" or "right" 82 * @property sAlign 83 * @type String 84 * @default right 85 */ 86 sAlign: "left", 87 88 /** 89 * Text used for the button 90 * @property buttonText 91 * @type String 92 * @default Show / hide columns 93 */ 94 buttonText: "Show / hide columns", 95 96 /** 97 * Flag to say if the collection is hidden 98 * @property hidden 99 * @type boolean 100 * @default true 101 */ 102 hidden: true, 103 104 /** 105 * List of columns (integers) which should be excluded from the list 106 * @property aiExclude 107 * @type Array 108 * @default [] 109 */ 110 aiExclude: [], 111 112 /** 113 * Store the original viisbility settings so they could be restored 114 * @property abOriginal 115 * @type Array 116 * @default [] 117 */ 118 abOriginal: [], 119 120 /** 121 * Show restore button 122 * @property bRestore 123 * @type Array 124 * @default [] 125 */ 126 bRestore: false, 127 128 /** 129 * Restore button text 130 * @property sRestore 131 * @type String 132 * @default Restore original 133 */ 134 sRestore: "Restore original" 135 }; 136 137 138 /** 139 * @namespace Common and useful DOM elements for the class instance 140 */ 141 this.dom = { 142 /** 143 * Wrapper for the button - given back to DataTables as the node to insert 144 * @property wrapper 145 * @type Node 146 * @default null 147 */ 148 wrapper: null, 149 150 /** 151 * Activation button 152 * @property button 153 * @type Node 154 * @default null 155 */ 156 button: null, 157 158 /** 159 * Collection list node 160 * @property collection 161 * @type Node 162 * @default null 163 */ 164 collection: null, 165 166 /** 167 * Background node used for shading the display and event capturing 168 * @property background 169 * @type Node 170 * @default null 171 */ 172 background: null, 173 174 /** 175 * Element to position over the activation button to catch mouse events when using mouseover 176 * @property catcher 177 * @type Node 178 * @default null 179 */ 180 catcher: null, 181 182 /** 183 * List of button elements 184 * @property buttons 185 * @type Array 186 * @default [] 187 */ 188 buttons: [], 189 190 /** 191 * Restore button 192 * @property restore 193 * @type Node 194 * @default null 195 */ 196 restore: null 197 }; 198 199 /* Store global reference */ 200 ColVis.aInstances.push( this ); 201 202 /* Constructor logic */ 203 this.s.dt = oDTSettings; 204 this._fnConstruct(); 205 return this; 206 }; 207 208 209 210 ColVis.prototype = { 211 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 212 * Public methods 213 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 214 215 /** 216 * Rebuild the list of buttons for this instance (i.e. if there is a column header update) 217 * @method fnRebuild 218 * @returns void 219 */ 220 fnRebuild: function () 221 { 222 /* Remove the old buttons */ 223 for ( var i=this.dom.buttons.length-1 ; i>=0 ; i-- ) 224 { 225 if ( this.dom.buttons[i] !== null ) 226 { 227 this.dom.collection.removeChild( this.dom.buttons[i] ); 228 } 229 } 230 this.dom.buttons.splice( 0, this.dom.buttons.length ); 231 232 if ( this.dom.restore ) 233 { 234 this.dom.restore.parentNode( this.dom.restore ); 235 } 236 237 /* Re-add them (this is not the optimal way of doing this, it is fast and effective) */ 238 this._fnAddButtons(); 239 240 /* Update the checkboxes */ 241 this._fnDrawCallback(); 242 }, 243 244 245 246 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 247 * Private methods (they are of course public in JS, but recommended as private) 248 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 249 250 /** 251 * Constructor logic 252 * @method _fnConstruct 253 * @returns void 254 * @private 255 */ 256 _fnConstruct: function () 257 { 258 this._fnApplyCustomisation(); 259 260 var that = this; 261 this.dom.wrapper = document.createElement('div'); 262 this.dom.wrapper.className = "ColVis TableTools"; 263 264 this.dom.button = this._fnDomBaseButton( this.s.buttonText ); 265 this.dom.button.className += " ColVis_MasterButton"; 266 this.dom.wrapper.appendChild( this.dom.button ); 267 268 this.dom.catcher = this._fnDomCatcher(); 269 this.dom.collection = this._fnDomCollection(); 270 this.dom.background = this._fnDomBackground(); 271 272 this._fnAddButtons(); 273 274 /* Store the original visbility information */ 275 for ( var i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ ) 276 { 277 this.s.abOriginal.push( this.s.dt.aoColumns[i].bVisible ); 278 } 279 280 /* Update on each draw */ 281 this.s.dt.aoDrawCallback.push( { 282 fn: function () { 283 that._fnDrawCallback.call( that ); 284 }, 285 sName: "ColVis" 286 } ); 287 }, 288 289 290 /** 291 * Apply any customisation to the settings from the DataTables initialisation 292 * @method _fnApplyCustomisation 293 * @returns void 294 * @private 295 */ 296 _fnApplyCustomisation: function () 297 { 298 var oConfig = this.s.oInit; 299 300 if ( typeof oConfig.activate != 'undefined' ) 301 { 302 this.s.activate = oConfig.activate; 303 } 304 305 if ( typeof oConfig.buttonText != 'undefined' ) 306 { 307 this.s.buttonText = oConfig.buttonText; 308 } 309 310 if ( typeof oConfig.aiExclude != 'undefined' ) 311 { 312 this.s.aiExclude = oConfig.aiExclude; 313 } 314 315 if ( typeof oConfig.bRestore != 'undefined' ) 316 { 317 this.s.bRestore = oConfig.bRestore; 318 } 319 320 if ( typeof oConfig.sRestore != 'undefined' ) 321 { 322 this.s.sRestore = oConfig.sRestore; 323 } 324 325 if ( typeof oConfig.sAlign != 'undefined' ) 326 { 327 this.s.sAlign = oConfig.sAlign; 328 } 329 330 if ( typeof oConfig.fnStateChange != 'undefined' ) 331 { 332 this.s.fnStateChange = oConfig.fnStateChange; 333 } 334 }, 335 336 337 /** 338 * On each table draw, check the visiblity checkboxes as needed. This allows any process to 339 * update the table's column visiblity and ColVis will still be accurate. 340 * @method _fnDrawCallback 341 * @returns void 342 * @private 343 */ 344 _fnDrawCallback: function () 345 { 346 var aoColumns = this.s.dt.aoColumns; 347 348 for ( var i=0, iLen=aoColumns.length ; i<iLen ; i++ ) 349 { 350 if ( this.dom.buttons[i] !== null ) 351 { 352 if ( aoColumns[i].bVisible ) 353 { 354 $('input', this.dom.buttons[i]).attr('checked','checked'); 355 } 356 else 357 { 358 $('input', this.dom.buttons[i]).removeAttr('checked'); 359 } 360 } 361 } 362 }, 363 364 365 /** 366 * Loop through the columns in the table and as a new button for each one. 367 * @method _fnAddButtons 368 * @returns void 369 * @private 370 */ 371 _fnAddButtons: function () 372 { 373 var 374 nButton, 375 sExclude = ","+this.s.aiExclude.join(',')+","; 376 377 for ( var i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ ) 378 { 379 if ( sExclude.indexOf( ","+i+"," ) == -1 ) 380 { 381 nButton = this._fnDomColumnButton( i ); 382 this.dom.buttons.push( nButton ); 383 this.dom.collection.appendChild( nButton ); 384 } 385 else 386 { 387 this.dom.buttons.push( null ); 388 } 389 } 390 391 if ( this.s.bRestore ) 392 { 393 nButton = this._fnDomRestoreButton(); 394 nButton.className += " ColVis_Restore"; 395 this.dom.buttons.push( nButton ); 396 this.dom.collection.appendChild( nButton ); 397 } 398 }, 399 400 401 /** 402 * Create a button which allows a "restore" action 403 * @method _fnDomRestoreButton 404 * @returns {Node} Created button 405 * @private 406 */ 407 _fnDomRestoreButton: function () 408 { 409 var 410 that = this, 411 nButton = document.createElement('button'), 412 nSpan = document.createElement('span'); 413 414 nButton.className = !this.s.dt.bJUI ? "ColVis_Button TableTools_Button" : 415 "ColVis_Button TableTools_Button ui-button ui-state-default"; 416 nButton.appendChild( nSpan ); 417 $(nSpan).html( '<span class="ColVis_title">'+this.s.sRestore+'</span>' ); 418 419 $(nButton).click( function (e) { 420 for ( var i=0, iLen=that.s.abOriginal.length ; i<iLen ; i++ ) 421 { 422 that.s.dt.oInstance.fnSetColumnVis( i, that.s.abOriginal[i], false ); 423 } 424 that.s.dt.oInstance.fnDraw( false ); 425 } ); 426 427 return nButton; 428 }, 429 430 431 /** 432 * Create the DOM for a show / hide button 433 * @method _fnDomColumnButton 434 * @param {int} i Column in question 435 * @returns {Node} Created button 436 * @private 437 */ 438 _fnDomColumnButton: function ( i ) 439 { 440 var 441 that = this, 442 oColumn = this.s.dt.aoColumns[i], 443 nButton = document.createElement('button'), 444 nSpan = document.createElement('span'); 445 446 nButton.className = !this.s.dt.bJUI ? "ColVis_Button TableTools_Button" : 447 "ColVis_Button TableTools_Button ui-button ui-state-default"; 448 nButton.appendChild( nSpan ); 449 $(nSpan).html( 450 '<span class="ColVis_radio"><input type="checkbox"></span>'+ 451 '<span class="ColVis_title">'+oColumn.sTitle+'</span>' ); 452 453 $(nButton).click( function (e) { 454 var showHide = $('input',this).attr('checked')===true ? false : true; 455 if ( e.target.nodeName.toLowerCase() == "input" ) 456 { 457 showHide = $('input',this).attr('checked'); 458 } 459 460 /* Need to consider the case where the initialiser created more than one table - change the 461 * API index that DataTables is using 462 */ 463 var oldIndex = $.fn.dataTableExt.iApiIndex; 464 $.fn.dataTableExt.iApiIndex = that._fnDataTablesApiIndex.call(that); 465 that.s.dt.oInstance.fnSetColumnVis( i, showHide ); 466 $.fn.dataTableExt.iApiIndex = oldIndex; /* Restore */ 467 468 if ( that.s.fnStateChange !== null ) 469 { 470 that.s.fnStateChange.call( that, i, showHide ); 471 } 472 } ); 473 474 return nButton; 475 }, 476 477 478 /** 479 * Get the position in the DataTables instance array of the table for this instance of ColVis 480 * @method _fnDataTablesApiIndex 481 * @returns {int} Index 482 * @private 483 */ 484 _fnDataTablesApiIndex: function () 485 { 486 for ( var i=0, iLen=this.s.dt.oInstance.length ; i<iLen ; i++ ) 487 { 488 if ( this.s.dt.oInstance[i] == this.s.dt.nTable ) 489 { 490 return i; 491 } 492 } 493 return 0; 494 }, 495 496 497 /** 498 * Create the DOM needed for the button and apply some base properties. All buttons start here 499 * @method _fnDomBaseButton 500 * @param {String} text Button text 501 * @returns {Node} DIV element for the button 502 * @private 503 */ 504 _fnDomBaseButton: function ( text ) 505 { 506 var 507 that = this, 508 nButton = document.createElement('button'), 509 nSpan = document.createElement('span'), 510 sEvent = this.s.activate=="mouseover" ? "mouseover" : "click"; 511 512 nButton.className = !this.s.dt.bJUI ? "ColVis_Button TableTools_Button" : 513 "ColVis_Button TableTools_Button ui-button ui-state-default"; 514 nButton.appendChild( nSpan ); 515 nSpan.innerHTML = text; 516 517 $(nButton).bind( sEvent, function (e) { 518 that._fnCollectionShow(); 519 e.preventDefault(); 520 } ); 521 522 return nButton; 523 }, 524 525 526 /** 527 * Create the element used to contain list the columns (it is shown and hidden as needed) 528 * @method _fnDomCollection 529 * @returns {Node} div container for the collection 530 * @private 531 */ 532 _fnDomCollection: function () 533 { 534 var that = this; 535 var nHidden = document.createElement('div'); 536 nHidden.style.display = "none"; 537 nHidden.className = !this.s.dt.bJUI ? "ColVis_collection TableTools_collection" : 538 "ColVis_collection TableTools_collection ui-buttonset ui-buttonset-multi"; 539 nHidden.style.position = "absolute"; 540 $(nHidden).css('opacity', 0); 541 542 return nHidden; 543 }, 544 545 546 /** 547 * An element to be placed on top of the activate button to catch events 548 * @method _fnDomCatcher 549 * @returns {Node} div container for the collection 550 * @private 551 */ 552 _fnDomCatcher: function () 553 { 554 var 555 that = this, 556 nCatcher = document.createElement('div'); 557 nCatcher.className = "ColVis_catcher TableTools_catcher"; 558 559 $(nCatcher).click( function () { 560 that._fnCollectionHide.call( that, null, null ); 561 } ); 562 563 return nCatcher; 564 }, 565 566 567 /** 568 * Create the element used to shade the background, and capture hide events (it is shown and 569 * hidden as needed) 570 * @method _fnDomBackground 571 * @returns {Node} div container for the background 572 * @private 573 */ 574 _fnDomBackground: function () 575 { 576 var that = this; 577 578 var nBackground = document.createElement('div'); 579 nBackground.style.position = "absolute"; 580 nBackground.style.left = "0px"; 581 nBackground.style.top = "0px"; 582 nBackground.className = "ColVis_collectionBackground TableTools_collectionBackground"; 583 $(nBackground).css('opacity', 0); 584 585 $(nBackground).click( function () { 586 that._fnCollectionHide.call( that, null, null ); 587 } ); 588 589 /* When considering a mouse over action for the activation, we also consider a mouse out 590 * which is the same as a mouse over the background - without all the messing around of 591 * bubbling events. Use the catcher element to avoid messing around with bubbling 592 */ 593 if ( this.s.activate == "mouseover" ) 594 { 595 $(nBackground).mouseover( function () { 596 that.s.overcollection = false; 597 that._fnCollectionHide.call( that, null, null ); 598 } ); 599 } 600 601 return nBackground; 602 }, 603 604 605 /** 606 * Show the show / hide list and the background 607 * @method _fnCollectionShow 608 * @returns void 609 * @private 610 */ 611 _fnCollectionShow: function () 612 { 613 var that = this; 614 var oPos = $(this.dom.button).offset(); 615 var nHidden = this.dom.collection; 616 var nBackground = this.dom.background; 617 var iDivX = parseInt(oPos.left, 10); 618 var iDivY = parseInt(oPos.top + $(this.dom.button).outerHeight(), 10); 619 620 nHidden.style.top = iDivY+"px"; 621 nHidden.style.left = iDivX+"px"; 622 nHidden.style.display = "block"; 623 $(nHidden).css('opacity',0); 624 625 var iWinHeight = $(window).height(), iDocHeight = $(document).height(), 626 iWinWidth = $(window).width(), iDocWidth = $(document).width(); 627 628 nBackground.style.height = ((iWinHeight>iDocHeight)? iWinHeight : iDocHeight) +"px"; 629 nBackground.style.width = ((iWinWidth<iDocWidth)? iWinWidth : iDocWidth) +"px"; 630 631 var oStyle = this.dom.catcher.style; 632 oStyle.height = $(this.dom.button).outerHeight()+"px"; 633 oStyle.width = $(this.dom.button).outerWidth()+"px"; 634 oStyle.top = oPos.top+"px"; 635 oStyle.left = iDivX+"px"; 636 637 document.body.appendChild( nBackground ); 638 document.body.appendChild( nHidden ); 639 document.body.appendChild( this.dom.catcher ); 640 641 /* Visual corrections to try and keep the collection visible */ 642 nHidden.style.left = this.s.sAlign=="left" ? 643 iDivX+"px" : (iDivX-$(nHidden).outerWidth()+$(this.dom.button).outerWidth())+"px"; 644 645 var iDivWidth = $(nHidden).outerWidth(); 646 var iDivHeight = $(nHidden).outerHeight(); 647 648 if ( iDivX + iDivWidth > iDocWidth ) 649 { 650 nHidden.style.left = (iDocWidth-iDivWidth)+"px"; 651 } 652 653 if ( iDivY + iDivHeight > iDocHeight ) 654 { 655 nHidden.style.top = (iDivY-iDivHeight-$(this.dom.button).outerHeight())+"px"; 656 } 657 658 659 /* This results in a very small delay for the end user but it allows the animation to be 660 * much smoother. If you don't want the animation, then the setTimeout can be removed 661 */ 662 setTimeout( function () { 663 $(nHidden).animate({opacity: 1}, 500); 664 $(nBackground).animate({opacity: 0.1}, 500, 'linear', function () { 665 /* In IE6 if you set the checked attribute of a hidden checkbox, then this is not visually 666 * reflected. As such, we need to do it here, once it is visible. Unbelievable. 667 */ 668 if ( jQuery.browser.msie && jQuery.browser.version == "6.0" ) 669 { 670 that._fnDrawCallback(); 671 } 672 }); 673 }, 10 ); 674 675 this.s.hidden = false; 676 }, 677 678 679 /** 680 * Hide the show / hide list and the background 681 * @method _fnCollectionHide 682 * @returns void 683 * @private 684 */ 685 _fnCollectionHide: function ( ) 686 { 687 var that = this; 688 689 if ( !this.s.hidden && this.dom.collection !== null ) 690 { 691 this.s.hidden = true; 692 693 $(this.dom.collection).animate({opacity: 0}, 500, function (e) { 694 this.style.display = "none"; 695 } ); 696 697 $(this.dom.background).animate({opacity: 0}, 500, function (e) { 698 document.body.removeChild( that.dom.background ); 699 document.body.removeChild( that.dom.catcher ); 700 } ); 701 } 702 } 703 }; 704 705 706 707 708 709 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 710 * Static object methods 711 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 712 713 /** 714 * Rebuild the collection for a given table, or all tables if no parameter given 715 * @method ColVis.fnRebuild 716 * @static 717 * @param object oTable DataTable instance to consider - optional 718 * @returns void 719 */ 720 ColVis.fnRebuild = function ( oTable ) 721 { 722 var nTable = null; 723 if ( typeof oTable != 'undefined' ) 724 { 725 nTable = oTable.fnSettings().nTable; 726 } 727 728 for ( var i=0, iLen=ColVis.aInstances.length ; i<iLen ; i++ ) 729 { 730 if ( typeof oTable == 'undefined' || nTable == ColVis.aInstances[i].s.dt.nTable ) 731 { 732 ColVis.aInstances[i].fnRebuild(); 733 } 734 } 735 }; 736 737 738 739 740 741 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 742 * Static object propterties 743 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 744 745 /** 746 * Collection of all ColVis instances 747 * @property ColVis.aInstances 748 * @static 749 * @type Array 750 * @default [] 751 */ 752 ColVis.aInstances = []; 753 754 755 756 757 758 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 759 * Constants 760 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 761 762 /** 763 * Name of this class 764 * @constant CLASS 765 * @type String 766 * @default ColVis 767 */ 768 ColVis.prototype.CLASS = "ColVis"; 769 770 771 /** 772 * ColVis version 773 * @constant VERSION 774 * @type String 775 * @default 1.0.4.dev 776 */ 777 ColVis.VERSION = "1.0.4"; 778 ColVis.prototype.VERSION = ColVis.VERSION; 779 780 781 782 783 784 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 785 * Initialisation 786 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 787 788 /* 789 * Register a new feature with DataTables 790 */ 791 if ( typeof $.fn.dataTable == "function" && 792 typeof $.fn.dataTableExt.fnVersionCheck == "function" && 793 $.fn.dataTableExt.fnVersionCheck('1.7.0') ) 794 { 795 $.fn.dataTableExt.aoFeatures.push( { 796 fnInit: function( oDTSettings ) { 797 var init = (typeof oDTSettings.oInit.oColVis == 'undefined') ? 798 {} : oDTSettings.oInit.oColVis; 799 var oColvis = new ColVis( oDTSettings, init ); 800 return oColvis.dom.wrapper; 801 }, 802 cFeature: "C", 803 sFeature: "ColVis" 804 } ); 805 } 806 else 807 { 808 alert( "Warning: ColVis requires DataTables 1.7 or greater - www.datatables.net/download"); 809 } 810 811 })(jQuery); 812