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