1 /*
  2  * File:        ColVis.js
  3  * Version:     1.0.1
  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 	
157 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
158 	 * Private methods (they are of course public in JS, but recommended as private)
159 	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
160 	
161 	/**
162 	 * Constructor logic
163 	 *  @method  _fnConstruct
164 	 *  @returns void
165 	 *  @private 
166 	 */
167 	_fnConstruct: function ()
168 	{
169 		this._fnApplyCustomisation();
170 		
171 		var that = this;
172 		this.dom.wrapper = document.createElement('div');
173 		this.dom.wrapper.className = "ColVis TableTools";
174 		
175 		this.dom.button = this._fnDomBaseButton( this.s.buttonText );
176 		this.dom.wrapper.appendChild( this.dom.button );
177 		
178 		this.dom.catcher = this._fnDomCatcher();
179 		this.dom.collection = this._fnDomCollection();
180 		this.dom.background = this._fnDomBackground();
181 		
182 		this._fnAddButtons();
183 		
184 		this.s.dt.aoDrawCallback.push( {
185 			fn: function () {
186 				that._fnDrawCallback.call( that );
187 			},
188 			sName: "ColVis"
189 		} );
190 	},
191 	
192 	
193 	/**
194 	 * Apply any customisation to the settings from the DataTables initialisation
195 	 *  @method  _fnApplyCustomisation
196 	 *  @returns void
197 	 *  @private 
198 	 */
199 	_fnApplyCustomisation: function ()
200 	{
201 		if ( typeof this.s.dt.oInit.oColVis != 'undefined' )
202 		{
203 			var oConfig = this.s.dt.oInit.oColVis;
204 			
205 			if ( typeof oConfig.activate != 'undefined' )
206 			{
207 				this.s.activate = oConfig.activate;
208 			}
209 			
210 			if ( typeof oConfig.buttonText != 'undefined' )
211 			{
212 				this.s.buttonText = oConfig.buttonText;
213 			}
214 			
215 			if ( typeof oConfig.aiExclude != 'undefined' )
216 			{
217 				this.s.aiExclude = oConfig.aiExclude;
218 			}
219 		}
220 	},
221 	
222 	
223 	/**
224 	 * On each table draw, check the visiblity checkboxes as needed. This allows any process to
225 	 * update the table's column visiblity and ColVis will still be accurate.
226 	 *  @method  _fnDrawCallback
227 	 *  @returns void
228 	 *  @private 
229 	 */
230 	_fnDrawCallback: function ()
231 	{
232 		var aoColumns = this.s.dt.aoColumns;
233 		
234 		for ( var i=0, iLen=aoColumns.length ; i<iLen ; i++ )
235 		{
236 			if ( this.dom.buttons[i] !== null )
237 			{
238 				if ( aoColumns[i].bVisible )
239 				{
240 					$('input', this.dom.buttons[i]).attr('checked','checked');
241 				}
242 				else
243 				{
244 					$('input', this.dom.buttons[i]).removeAttr('checked');
245 				}
246 			}
247 		}
248 	},
249 	
250 	
251 	/**
252 	 * Loop through the columns in the table and as a new button for each one.
253 	 *  @method  _fnAddButtons
254 	 *  @returns void
255 	 *  @private 
256 	 */
257 	_fnAddButtons: function ()
258 	{
259 		var
260 			nButton,
261 			sExclude = ","+this.s.aiExclude.join(',')+",";
262 		
263 		for ( var i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ )
264 		{
265 			if ( sExclude.indexOf( ","+i+"," ) == -1 )
266 			{
267 				nButton = this._fnDomColumnButton( i );
268 				this.dom.buttons.push( nButton );
269 				this.dom.collection.appendChild( nButton );
270 			}
271 			else
272 			{
273 				this.dom.buttons.push( null );
274 			}
275 		}
276 	},
277 	
278 	
279 	/**
280 	 * Create the DOM for a show / hide button
281 	 *  @method  _fnDomColumnButton
282 	 *  @param {int} i Column in question
283 	 *  @returns {Node} Created button
284 	 *  @private 
285 	 */
286 	_fnDomColumnButton: function ( i )
287 	{
288 		var
289 			that = this,
290 			oColumn = this.s.dt.aoColumns[i],
291 		  nButton = document.createElement('button'),
292 		  nSpan = document.createElement('span');
293 		
294 		nButton.className = !this.s.dt.bJUI ? "ColVis_Button TableTools_Button" :
295 			"ColVis_Button TableTools_Button ui-button ui-state-default";
296 		nButton.appendChild( nSpan );
297 		$(nSpan).html(
298 			'<span class="ColVis_radio"><input type="checkbox"></span>'+
299 			'<span class="ColVis_title">'+oColumn.sTitle+'</span>' );
300 		
301 		$(nButton).click( function (e) {
302 			var showHide = $('input',this).attr('checked')===true ? false : true;
303 			if ( e.target.nodeName.toLowerCase() == "input" )
304 			{
305 				showHide = $('input',this).attr('checked');
306 			}
307 			that.s.dt.oInstance.fnSetColumnVis( i, showHide );
308 		} );
309 		
310 		return nButton;
311 	},
312 	
313 	
314 	/**
315 	 * Create the DOM needed for the button and apply some base properties. All buttons start here
316 	 *  @method  _fnDomBaseButton
317 	 *  @param   {String} text Button text
318 	 *  @returns {Node} DIV element for the button
319 	 *  @private 
320 	 */
321 	_fnDomBaseButton: function ( text )
322 	{
323 		var
324 			that = this,
325 		  nButton = document.createElement('button'),
326 		  nSpan = document.createElement('span'),
327 			sEvent = this.s.activate=="mouseover" ? "mouseover" : "click";
328 		
329 		nButton.className = !this.s.dt.bJUI ? "ColVis_Button TableTools_Button" :
330 			"ColVis_Button TableTools_Button ui-button ui-state-default";
331 		nButton.appendChild( nSpan );
332 		nSpan.innerHTML = text;
333 		
334 		$(nButton).bind( sEvent, function (e) {
335 			that._fnCollectionShow();
336 			e.preventDefault();
337 		} );
338 		
339 		return nButton;
340 	},
341 	
342 	
343 	/**
344 	 * Create the element used to contain list the columns (it is shown and hidden as needed)
345 	 *  @method  _fnDomCollection
346 	 *  @returns {Node} div container for the collection
347 	 *  @private 
348 	 */
349 	_fnDomCollection: function ()
350 	{
351 		var that = this;
352 		var nHidden = document.createElement('div');
353 		nHidden.style.display = "none";
354 		nHidden.className = !this.s.dt.bJUI ? "ColVis_collection TableTools_collection" :
355 			"ColVis_collection TableTools_collection ui-buttonset ui-buttonset-multi";
356 		nHidden.style.position = "absolute";
357 		$(nHidden).css('opacity', 0);
358 		
359 		return nHidden;
360 	},
361 	
362 	
363 	/**
364 	 * An element to be placed on top of the activate button to catch events
365 	 *  @method  _fnDomCatcher
366 	 *  @returns {Node} div container for the collection
367 	 *  @private 
368 	 */
369 	_fnDomCatcher: function ()
370 	{
371 		var 
372 			that = this,
373 			nCatcher = document.createElement('div');
374 		nCatcher.className = "ColVis_catcher TableTools_catcher";
375 		
376 		$(nCatcher).click( function () {
377 			that._fnCollectionHide.call( that, null, null );
378 		} );
379 		
380 		return nCatcher;
381 	},
382 	
383 	
384 	/**
385 	 * Create the element used to shade the background, and capture hide events (it is shown and 
386 	 * hidden as needed)
387 	 *  @method  _fnDomBackground
388 	 *  @returns {Node} div container for the background
389 	 *  @private 
390 	 */
391 	_fnDomBackground: function ()
392 	{
393 		var that = this;
394 		
395 		var nBackground = document.createElement('div');
396 		nBackground.style.position = "absolute";
397 		nBackground.style.left = "0px";
398 		nBackground.style.top = "0px";
399 		nBackground.className = "TableTools_collectionBackground";
400 		$(nBackground).css('opacity', 0);
401 		
402 		$(nBackground).click( function () {
403 			that._fnCollectionHide.call( that, null, null );
404 		} );
405 		
406 		/* When considering a mouse over action for the activation, we also consider a mouse out
407 		 * which is the same as a mouse over the background - without all the messing around of
408 		 * bubbling events. Use the catcher element to avoid messing around with bubbling
409 		 */
410 		if ( this.s.activate == "mouseover" )
411 		{
412 			$(nBackground).mouseover( function () {
413 				that.s.overcollection = false;
414 				that._fnCollectionHide.call( that, null, null );
415 			} );
416 		}
417 		
418 		return nBackground;
419 	},
420 	
421 	
422 	/**
423 	 * Show the show / hide list and the background
424 	 *  @method  _fnCollectionShow
425 	 *  @returns void
426 	 *  @private 
427 	 */
428 	_fnCollectionShow: function ()
429 	{
430 		var that = this;
431 		var oPos = $(this.dom.button).offset();
432 		var nHidden = this.dom.collection;
433 		var nBackground = this.dom.background;
434 		var iDivX = parseInt(oPos.left, 10);
435 		var iDivY = parseInt(oPos.top + $(this.dom.button).outerHeight(), 10);
436 		
437 		nHidden.style.left = iDivX+"px";
438 		nHidden.style.top = iDivY+"px";
439 		nHidden.style.display = "block";
440 		$(nHidden).css('opacity',0);
441 		
442 		var iWinHeight = $(window).height(), iDocHeight = $(document).height(),
443 		 	iWinWidth = $(window).width(), iDocWidth = $(document).width();
444 		
445 		nBackground.style.height = ((iWinHeight>iDocHeight)? iWinHeight : iDocHeight) +"px";
446 		nBackground.style.width = ((iWinWidth<iDocWidth)? iWinWidth : iDocWidth) +"px";
447 		
448 		var oStyle = this.dom.catcher.style;
449 		oStyle.height = $(this.dom.button).outerHeight()+"px";
450 		oStyle.width = $(this.dom.button).outerWidth()+"px";
451 		oStyle.top = oPos.top+"px";
452 		oStyle.left = iDivX+"px";
453 		
454 		document.body.appendChild( nBackground );
455 		document.body.appendChild( nHidden );
456 		document.body.appendChild( this.dom.catcher );
457 		
458 		/* Visual corrections to try and keep the collection visible */
459 		var iDivWidth = $(nHidden).outerWidth();
460 		var iDivHeight = $(nHidden).outerHeight();
461 		
462 		if ( iDivX + iDivWidth > iDocWidth )
463 		{
464 			nHidden.style.left = (iDocWidth-iDivWidth)+"px";
465 		}
466 		
467 		if ( iDivY + iDivHeight > iDocHeight )
468 		{
469 			nHidden.style.top = (iDivY-iDivHeight-$(nButton).outerHeight())+"px";
470 		}
471 		
472 		
473 		/* This results in a very small delay for the end user but it allows the animation to be
474 		 * much smoother. If you don't want the animation, then the setTimeout can be removed
475 		 */
476 		setTimeout( function () {
477 			$(nHidden).animate({opacity: 1}, 500);
478 			$(nBackground).animate({opacity: 0.1}, 500);
479 		}, 10 );
480 		
481 		this.s.hidden = false;
482 	},
483 	
484 	
485 	/**
486 	 * Hide the show / hide list and the background
487 	 *  @method  _fnCollectionHide
488 	 *  @returns void
489 	 *  @private 
490 	 */
491 	_fnCollectionHide: function (  )
492 	{
493 		var that = this;
494 		
495 		if ( !this.s.hidden && this.dom.collection !== null )
496 		{
497 			this.s.hidden = true;
498 			
499 			$(this.dom.collection).animate({opacity: 0}, 500, function (e) {
500 				this.style.display = "none";
501 			} );
502 			
503 			$(this.dom.background).animate({opacity: 0}, 500, function (e) {
504 				document.body.removeChild( that.dom.background );
505 				document.body.removeChild( that.dom.catcher );
506 			} );
507 		}
508 	}
509 };
510 
511 
512 
513 
514 
515 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
516  * Constants
517  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
518 
519 /**
520  * Name of this class
521  *  @constant CLASS
522  *  @type     String
523  *  @default  ColVis
524  */
525 ColVis.prototype.CLASS = "ColVis";
526 
527 
528 /**
529  * ColVis version
530  *  @constant  VERSION
531  *  @type      String
532  *  @default   1.0.0
533  */
534 ColVis.VERSION = "1.0.1";
535 ColVis.prototype.VERSION = ColVis.VERSION;
536 
537 
538 
539 
540 
541 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
542  * Initialisation
543  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
544 
545 /*
546  * Register a new feature with DataTables
547  */
548 if ( typeof $.fn.dataTable == "function" &&
549      typeof $.fn.dataTableExt.fnVersionCheck == "function" &&
550      $.fn.dataTableExt.fnVersionCheck('1.7.0') )
551 {
552 	$.fn.dataTableExt.aoFeatures.push( {
553 		fnInit: function( oDTSettings ) {
554 			var tt = new ColVis( oDTSettings );
555 			return tt.dom.wrapper;
556 		},
557 		cFeature: "C",
558 		sFeature: "ColVis"
559 	} );
560 }
561 else
562 {
563 	alert( "Warning: ColVis requires DataTables 1.7 or greater - www.datatables.net/download");
564 }
565 })(jQuery);