      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