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$
10 * License: GPL v2 or BSD 3 point style
11 * Project: Just a little bit of fun :-)
12 * Contact: www.sprymedia.co.uk/contact
14 * Copyright 2010-2011 Allan Jardine, all rights reserved.
16 * This source file is free software, under either the GPL v2 license or a
17 * BSD style license, available at:
18 * http://datatables.net/license_gpl2
19 * http://datatables.net/license_bsd
25 * ColVis provides column visiblity control for DataTables
28 * @param {object} DataTables settings object
30 ColVis = function( oDTSettings, oInit )
32 /* Santiy check that we are a new instance */
33 if ( !this.CLASS || this.CLASS != "ColVis" )
35 alert( "Warning: ColVis must be initialised with the keyword 'new'" );
38 if ( typeof oInit == 'undefined' )
44 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
45 * Public class variables
46 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
49 * @namespace Settings object which contains customisable information for ColVis instance
53 * DataTables settings object
61 * Customisation object
69 * Callback function to tell the user when the state has changed
70 * @property fnStateChange
74 "fnStateChange": null,
77 * Mode of activation. Can be 'click' or 'mouseover'
85 * Position of the collection menu when shown - align "left" or "right"
93 * Text used for the button
94 * @property buttonText
96 * @default Show / hide columns
98 "buttonText": "Show / hide columns",
101 * Flag to say if the collection is hidden
109 * List of columns (integers) which should be excluded from the list
110 * @property aiExclude
117 * Store the original viisbility settings so they could be restored
118 * @property abOriginal
125 * Show Show-All button
133 * Show All button text
136 * @default Restore original
138 "sShowAll": "Show All",
141 * Show restore button
149 * Restore button text
152 * @default Restore original
154 "sRestore": "Restore original",
157 * Overlay animation duration in mS
158 * @property iOverlayFade
165 * Label callback for column names. Takes three parameters: 1. the column index, 2. the column
166 * title detected by DataTables and 3. the TH node for the column
174 * Indicate if ColVis should automatically calculate the size of buttons or not. The default
175 * is for it to do so. Set to "css" to disable the automatic sizing
183 * Indicate if the column list should be positioned by Javascript, visually below the button
184 * or allow CSS to do the positioning
185 * @property bCssPosition
189 "bCssPosition": false
194 * @namespace Common and useful DOM elements for the class instance
198 * Wrapper for the button - given back to DataTables as the node to insert
214 * Collection list node
215 * @property collection
222 * Background node used for shading the display and event capturing
223 * @property background
230 * Element to position over the activation button to catch mouse events when using mouseover
238 * List of button elements
254 /* Store global reference */
255 ColVis.aInstances.push( this );
257 /* Constructor logic */
258 this.s.dt = oDTSettings;
266 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
268 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
271 * Rebuild the list of buttons for this instance (i.e. if there is a column header update)
275 "fnRebuild": function ()
277 /* Remove the old buttons */
278 for ( var i=this.dom.buttons.length-1 ; i>=0 ; i-- )
280 if ( this.dom.buttons[i] !== null )
282 this.dom.collection.removeChild( this.dom.buttons[i] );
285 this.dom.buttons.splice( 0, this.dom.buttons.length );
287 if ( this.dom.restore )
289 this.dom.restore.parentNode( this.dom.restore );
292 /* Re-add them (this is not the optimal way of doing this, it is fast and effective) */
293 this._fnAddButtons();
295 /* Update the checkboxes */
296 this._fnDrawCallback();
301 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
302 * Private methods (they are of course public in JS, but recommended as private)
303 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
307 * @method _fnConstruct
311 "_fnConstruct": function ()
313 this._fnApplyCustomisation();
317 this.dom.wrapper = document.createElement('div');
318 this.dom.wrapper.className = "ColVis TableTools";
320 this.dom.button = this._fnDomBaseButton( this.s.buttonText );
321 this.dom.button.className += " ColVis_MasterButton";
322 this.dom.wrapper.appendChild( this.dom.button );
324 this.dom.catcher = this._fnDomCatcher();
325 this.dom.collection = this._fnDomCollection();
326 this.dom.background = this._fnDomBackground();
328 this._fnAddButtons();
330 /* Store the original visbility information */
331 for ( i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ )
333 this.s.abOriginal.push( this.s.dt.aoColumns[i].bVisible );
336 /* Update on each draw */
337 this.s.dt.aoDrawCallback.push( {
339 that._fnDrawCallback.call( that );
344 /* If columns are reordered, then we need to update our exclude list and
345 * rebuild the displayed list
347 $(this.s.dt.oInstance).bind( 'column-reorder', function ( e, oSettings, oReorder ) {
348 for ( i=0, iLen=that.s.aiExclude.length ; i<iLen ; i++ ) {
349 that.s.aiExclude[i] = oReorder.aiInvertMapping[ that.s.aiExclude[i] ];
352 var mStore = that.s.abOriginal.splice( oReorder.iFrom, 1 )[0];
353 that.s.abOriginal.splice( oReorder.iTo, 0, mStore );
361 * Apply any customisation to the settings from the DataTables initialisation
362 * @method _fnApplyCustomisation
366 "_fnApplyCustomisation": function ()
368 var oConfig = this.s.oInit;
370 if ( typeof oConfig.activate != 'undefined' )
372 this.s.activate = oConfig.activate;
375 if ( typeof oConfig.buttonText != 'undefined' )
377 this.s.buttonText = oConfig.buttonText;
380 if ( typeof oConfig.aiExclude != 'undefined' )
382 this.s.aiExclude = oConfig.aiExclude;
385 if ( typeof oConfig.bRestore != 'undefined' )
387 this.s.bRestore = oConfig.bRestore;
390 if ( typeof oConfig.sRestore != 'undefined' )
392 this.s.sRestore = oConfig.sRestore;
395 if ( typeof oConfig.bShowAll != 'undefined' )
397 this.s.bShowAll = oConfig.bShowAll;
400 if ( typeof oConfig.sShowAll != 'undefined' )
402 this.s.sShowAll = oConfig.sShowAll;
405 if ( typeof oConfig.sAlign != 'undefined' )
407 this.s.sAlign = oConfig.sAlign;
410 if ( typeof oConfig.fnStateChange != 'undefined' )
412 this.s.fnStateChange = oConfig.fnStateChange;
415 if ( typeof oConfig.iOverlayFade != 'undefined' )
417 this.s.iOverlayFade = oConfig.iOverlayFade;
420 if ( typeof oConfig.fnLabel != 'undefined' )
422 this.s.fnLabel = oConfig.fnLabel;
425 if ( typeof oConfig.sSize != 'undefined' )
427 this.s.sSize = oConfig.sSize;
430 if ( typeof oConfig.bCssPosition != 'undefined' )
432 this.s.bCssPosition = oConfig.bCssPosition;
438 * On each table draw, check the visibility checkboxes as needed. This allows any process to
439 * update the table's column visibility and ColVis will still be accurate.
440 * @method _fnDrawCallback
444 "_fnDrawCallback": function ()
446 var aoColumns = this.s.dt.aoColumns;
448 for ( var i=0, iLen=aoColumns.length ; i<iLen ; i++ )
450 if ( this.dom.buttons[i] !== null )
452 if ( aoColumns[i].bVisible )
454 $('input', this.dom.buttons[i]).attr('checked','checked');
458 $('input', this.dom.buttons[i]).removeAttr('checked');
466 * Loop through the columns in the table and as a new button for each one.
467 * @method _fnAddButtons
471 "_fnAddButtons": function ()
475 sExclude = ","+this.s.aiExclude.join(',')+",";
477 for ( var i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ )
479 if ( sExclude.indexOf( ","+i+"," ) == -1 )
481 nButton = this._fnDomColumnButton( i );
482 this.dom.buttons.push( nButton );
483 this.dom.collection.appendChild( nButton );
487 this.dom.buttons.push( null );
491 if ( this.s.bRestore )
493 nButton = this._fnDomRestoreButton();
494 nButton.className += " ColVis_Restore";
495 this.dom.buttons.push( nButton );
496 this.dom.collection.appendChild( nButton );
499 if ( this.s.bShowAll )
501 nButton = this._fnDomShowAllButton();
502 nButton.className += " ColVis_ShowAll";
503 this.dom.buttons.push( nButton );
504 this.dom.collection.appendChild( nButton );
510 * Create a button which allows a "restore" action
511 * @method _fnDomRestoreButton
512 * @returns {Node} Created button
515 "_fnDomRestoreButton": function ()
519 nButton = document.createElement('button'),
520 nSpan = document.createElement('span');
522 nButton.className = !this.s.dt.bJUI ? "ColVis_Button TableTools_Button" :
523 "ColVis_Button TableTools_Button ui-button ui-state-default";
524 nButton.appendChild( nSpan );
525 $(nSpan).html( '<span class="ColVis_title">'+this.s.sRestore+'</span>' );
527 $(nButton).click( function (e) {
528 for ( var i=0, iLen=that.s.abOriginal.length ; i<iLen ; i++ )
530 that.s.dt.oInstance.fnSetColumnVis( i, that.s.abOriginal[i], false );
532 that._fnAdjustOpenRows();
533 that.s.dt.oInstance.fnAdjustColumnSizing( false );
534 that.s.dt.oInstance.fnDraw( false );
542 * Create a button which allows a "show all" action
543 * @method _fnDomShowAllButton
544 * @returns {Node} Created button
547 "_fnDomShowAllButton": function ()
551 nButton = document.createElement('button'),
552 nSpan = document.createElement('span');
554 nButton.className = !this.s.dt.bJUI ? "ColVis_Button TableTools_Button" :
555 "ColVis_Button TableTools_Button ui-button ui-state-default";
556 nButton.appendChild( nSpan );
557 $(nSpan).html( '<span class="ColVis_title">'+this.s.sShowAll+'</span>' );
559 $(nButton).click( function (e) {
560 for ( var i=0, iLen=that.s.abOriginal.length ; i<iLen ; i++ )
562 if (that.s.aiExclude.indexOf(i) === -1)
564 that.s.dt.oInstance.fnSetColumnVis( i, true, false );
567 that._fnAdjustOpenRows();
568 that.s.dt.oInstance.fnAdjustColumnSizing( false );
569 that.s.dt.oInstance.fnDraw( false );
577 * Create the DOM for a show / hide button
578 * @method _fnDomColumnButton
579 * @param {int} i Column in question
580 * @returns {Node} Created button
583 "_fnDomColumnButton": function ( i )
587 oColumn = this.s.dt.aoColumns[i],
588 nButton = document.createElement('button'),
589 nSpan = document.createElement('span'),
592 nButton.className = !dt.bJUI ? "ColVis_Button TableTools_Button" :
593 "ColVis_Button TableTools_Button ui-button ui-state-default";
594 nButton.appendChild( nSpan );
595 var sTitle = this.s.fnLabel===null ? oColumn.sTitle : this.s.fnLabel( i, oColumn.sTitle, oColumn.nTh );
597 '<span class="ColVis_radio"><input type="checkbox"/></span>'+
598 '<span class="ColVis_title">'+sTitle+'</span>' );
600 $(nButton).click( function (e) {
601 var showHide = !$('input', this).is(":checked");
602 if ( e.target.nodeName.toLowerCase() == "input" )
604 showHide = $('input', this).is(":checked");
607 /* Need to consider the case where the initialiser created more than one table - change the
608 * API index that DataTables is using
610 var oldIndex = $.fn.dataTableExt.iApiIndex;
611 $.fn.dataTableExt.iApiIndex = that._fnDataTablesApiIndex.call(that);
613 // Optimisation for server-side processing when scrolling - don't do a full redraw
614 if ( dt.oFeatures.bServerSide && (dt.oScroll.sX !== "" || dt.oScroll.sY !== "" ) )
616 that.s.dt.oInstance.fnSetColumnVis( i, showHide, false );
617 that.s.dt.oInstance.fnAdjustColumnSizing( false );
618 that.s.dt.oInstance.oApi._fnScrollDraw( that.s.dt );
619 that._fnDrawCallback();
623 that.s.dt.oInstance.fnSetColumnVis( i, showHide );
626 $.fn.dataTableExt.iApiIndex = oldIndex; /* Restore */
628 if ( that.s.fnStateChange !== null )
630 that.s.fnStateChange.call( that, i, showHide );
639 * Get the position in the DataTables instance array of the table for this instance of ColVis
640 * @method _fnDataTablesApiIndex
641 * @returns {int} Index
644 "_fnDataTablesApiIndex": function ()
646 for ( var i=0, iLen=this.s.dt.oInstance.length ; i<iLen ; i++ )
648 if ( this.s.dt.oInstance[i] == this.s.dt.nTable )
658 * Create the DOM needed for the button and apply some base properties. All buttons start here
659 * @method _fnDomBaseButton
660 * @param {String} text Button text
661 * @returns {Node} DIV element for the button
664 "_fnDomBaseButton": function ( text )
668 nButton = document.createElement('button'),
669 nSpan = document.createElement('span'),
670 sEvent = this.s.activate=="mouseover" ? "mouseover" : "click";
672 nButton.className = !this.s.dt.bJUI ? "ColVis_Button TableTools_Button" :
673 "ColVis_Button TableTools_Button ui-button ui-state-default";
674 nButton.appendChild( nSpan );
675 nSpan.innerHTML = text;
677 $(nButton).bind( sEvent, function (e) {
678 that._fnCollectionShow();
687 * Create the element used to contain list the columns (it is shown and hidden as needed)
688 * @method _fnDomCollection
689 * @returns {Node} div container for the collection
692 "_fnDomCollection": function ()
695 var nHidden = document.createElement('div');
696 nHidden.style.display = "none";
697 nHidden.className = !this.s.dt.bJUI ? "ColVis_collection TableTools_collection" :
698 "ColVis_collection TableTools_collection ui-buttonset ui-buttonset-multi";
700 if ( !this.s.bCssPosition )
702 nHidden.style.position = "absolute";
704 $(nHidden).css('opacity', 0);
711 * An element to be placed on top of the activate button to catch events
712 * @method _fnDomCatcher
713 * @returns {Node} div container for the collection
716 "_fnDomCatcher": function ()
720 nCatcher = document.createElement('div');
721 nCatcher.className = "ColVis_catcher TableTools_catcher";
723 $(nCatcher).click( function () {
724 that._fnCollectionHide.call( that, null, null );
732 * Create the element used to shade the background, and capture hide events (it is shown and
734 * @method _fnDomBackground
735 * @returns {Node} div container for the background
738 "_fnDomBackground": function ()
742 var nBackground = document.createElement('div');
743 nBackground.style.position = "absolute";
744 nBackground.style.left = "0px";
745 nBackground.style.top = "0px";
746 nBackground.className = "ColVis_collectionBackground TableTools_collectionBackground";
747 $(nBackground).css('opacity', 0);
749 $(nBackground).click( function () {
750 that._fnCollectionHide.call( that, null, null );
753 /* When considering a mouse over action for the activation, we also consider a mouse out
754 * which is the same as a mouse over the background - without all the messing around of
755 * bubbling events. Use the catcher element to avoid messing around with bubbling
757 if ( this.s.activate == "mouseover" )
759 $(nBackground).mouseover( function () {
760 that.s.overcollection = false;
761 that._fnCollectionHide.call( that, null, null );
770 * Show the show / hide list and the background
771 * @method _fnCollectionShow
775 "_fnCollectionShow": function ()
777 var that = this, i, iLen;
778 var oPos = $(this.dom.button).offset();
779 var nHidden = this.dom.collection;
780 var nBackground = this.dom.background;
781 var iDivX = parseInt(oPos.left, 10);
782 var iDivY = parseInt(oPos.top + $(this.dom.button).outerHeight(), 10);
784 if ( !this.s.bCssPosition )
786 nHidden.style.top = iDivY+"px";
787 nHidden.style.left = iDivX+"px";
789 nHidden.style.display = "block";
790 $(nHidden).css('opacity',0);
792 var iWinHeight = $(window).height(), iDocHeight = $(document).height(),
793 iWinWidth = $(window).width(), iDocWidth = $(document).width();
795 nBackground.style.height = ((iWinHeight>iDocHeight)? iWinHeight : iDocHeight) +"px";
796 nBackground.style.width = ((iWinWidth<iDocWidth)? iWinWidth : iDocWidth) +"px";
798 var oStyle = this.dom.catcher.style;
799 oStyle.height = $(this.dom.button).outerHeight()+"px";
800 oStyle.width = $(this.dom.button).outerWidth()+"px";
801 oStyle.top = oPos.top+"px";
802 oStyle.left = iDivX+"px";
804 document.body.appendChild( nBackground );
805 document.body.appendChild( nHidden );
806 document.body.appendChild( this.dom.catcher );
808 /* Resize the buttons */
809 if ( this.s.sSize == "auto" )
812 this.dom.collection.style.width = "auto";
813 for ( i=0, iLen=this.dom.buttons.length ; i<iLen ; i++ )
815 if ( this.dom.buttons[i] !== null )
817 this.dom.buttons[i].style.width = "auto";
818 aiSizes.push( $(this.dom.buttons[i]).outerWidth() );
821 iMax = Math.max.apply(window, aiSizes);
822 for ( i=0, iLen=this.dom.buttons.length ; i<iLen ; i++ )
824 if ( this.dom.buttons[i] !== null )
826 this.dom.buttons[i].style.width = iMax+"px";
829 this.dom.collection.style.width = iMax+"px";
832 /* Visual corrections to try and keep the collection visible */
833 if ( !this.s.bCssPosition )
835 nHidden.style.left = this.s.sAlign=="left" ?
836 iDivX+"px" : (iDivX-$(nHidden).outerWidth()+$(this.dom.button).outerWidth())+"px";
838 var iDivWidth = $(nHidden).outerWidth();
839 var iDivHeight = $(nHidden).outerHeight();
841 if ( iDivX + iDivWidth > iDocWidth )
843 nHidden.style.left = (iDocWidth-iDivWidth)+"px";
847 /* This results in a very small delay for the end user but it allows the animation to be
848 * much smoother. If you don't want the animation, then the setTimeout can be removed
850 setTimeout( function () {
851 $(nHidden).animate({"opacity": 1}, that.s.iOverlayFade);
852 $(nBackground).animate({"opacity": 0.1}, that.s.iOverlayFade, 'linear', function () {
853 /* In IE6 if you set the checked attribute of a hidden checkbox, then this is not visually
854 * reflected. As such, we need to do it here, once it is visible. Unbelievable.
856 if ( jQuery.browser.msie && jQuery.browser.version == "6.0" )
858 that._fnDrawCallback();
863 this.s.hidden = false;
868 * Hide the show / hide list and the background
869 * @method _fnCollectionHide
873 "_fnCollectionHide": function ( )
877 if ( !this.s.hidden && this.dom.collection !== null )
879 this.s.hidden = true;
881 $(this.dom.collection).animate({"opacity": 0}, that.s.iOverlayFade, function (e) {
882 this.style.display = "none";
885 $(this.dom.background).animate({"opacity": 0}, that.s.iOverlayFade, function (e) {
886 document.body.removeChild( that.dom.background );
887 document.body.removeChild( that.dom.catcher );
894 * Alter the colspan on any fnOpen rows
896 "_fnAdjustOpenRows": function ()
898 var aoOpen = this.s.dt.aoOpenRows;
899 var iVisible = this.s.dt.oApi._fnVisbleColumns( this.s.dt );
901 for ( var i=0, iLen=aoOpen.length ; i<iLen ; i++ ) {
902 aoOpen[i].nTr.getElementsByTagName('td')[0].colSpan = iVisible;
911 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
912 * Static object methods
913 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
916 * Rebuild the collection for a given table, or all tables if no parameter given
917 * @method ColVis.fnRebuild
919 * @param object oTable DataTable instance to consider - optional
922 ColVis.fnRebuild = function ( oTable )
925 if ( typeof oTable != 'undefined' )
927 nTable = oTable.fnSettings().nTable;
930 for ( var i=0, iLen=ColVis.aInstances.length ; i<iLen ; i++ )
932 if ( typeof oTable == 'undefined' || nTable == ColVis.aInstances[i].s.dt.nTable )
934 ColVis.aInstances[i].fnRebuild();
943 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
944 * Static object properties
945 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
948 * Collection of all ColVis instances
949 * @property ColVis.aInstances
954 ColVis.aInstances = [];
960 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
962 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
970 ColVis.prototype.CLASS = "ColVis";
979 ColVis.VERSION = "1.0.8";
980 ColVis.prototype.VERSION = ColVis.VERSION;
986 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
988 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
991 * Register a new feature with DataTables
993 if ( typeof $.fn.dataTable == "function" &&
994 typeof $.fn.dataTableExt.fnVersionCheck == "function" &&
995 $.fn.dataTableExt.fnVersionCheck('1.7.0') )
997 $.fn.dataTableExt.aoFeatures.push( {
998 "fnInit": function( oDTSettings ) {
999 var init = (typeof oDTSettings.oInit.oColVis == 'undefined') ?
1000 {} : oDTSettings.oInit.oColVis;
1001 var oColvis = new ColVis( oDTSettings, init );
1002 return oColvis.dom.wrapper;
1005 "sFeature": "ColVis"
1010 alert( "Warning: ColVis requires DataTables 1.7 or greater - www.datatables.net/download");