/* * File:        TableTools.js * Version:     2.1.4 * Description: Tools and buttons for DataTables * Author:      Allan Jardine (www.sprymedia.co.uk) * Language:    Javascript * License:	    GPL v2 or BSD 3 point style * Project:	    DataTables *  * Copyright 2009-2012 Allan Jardine, all rights reserved. * * This source file is free software, under either the GPL v2 license or a * BSD style license, available at: *   http://datatables.net/license_gpl2 *   http://datatables.net/license_bsd *//* Global scope for TableTools */var TableTools;(function($, window, document) {/**  * TableTools provides flexible buttons and other tools for a DataTables enhanced table * @class TableTools * @constructor * @param {Object} oDT DataTables instance * @param {Object} oOpts TableTools options * @param {String} oOpts.sSwfPath ZeroClipboard SWF path * @param {String} oOpts.sRowSelect Row selection options - 'none', 'single' or 'multi' * @param {Function} oOpts.fnPreRowSelect Callback function just prior to row selection * @param {Function} oOpts.fnRowSelected Callback function just after row selection * @param {Function} oOpts.fnRowDeselected Callback function when row is deselected * @param {Array} oOpts.aButtons List of buttons to be used */TableTools = function( oDT, oOpts ){	/* Santiy check that we are a new instance */	if ( ! this instanceof TableTools )	{		alert( "Warning: TableTools must be initialised with the keyword 'new'" );	}			/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *	 * Public class variables	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */		/**	 * @namespace Settings object which contains customisable information for TableTools instance	 */	this.s = {		/**		 * Store 'this' so the instance can be retrieved from the settings object		 * @property that		 * @type	 object		 * @default  this		 */		"that": this,				/** 		 * DataTables settings objects		 * @property dt		 * @type	 object		 * @default  <i>From the oDT init option</i>		 */		"dt": oDT.fnSettings(),				/**		 * @namespace Print specific information		 */		"print": {			/** 			 * DataTables draw 'start' point before the printing display was shown			 *  @property saveStart			 *  @type	 int			 *  @default  -1		 	 */		  "saveStart": -1,						/** 			 * DataTables draw 'length' point before the printing display was shown			 *  @property saveLength			 *  @type	 int			 *  @default  -1		 	 */		  "saveLength": -1,					/** 			 * Page scrolling point before the printing display was shown so it can be restored			 *  @property saveScroll			 *  @type	 int			 *  @default  -1		 	 */		  "saveScroll": -1,					/** 			 * Wrapped function to end the print display (to maintain scope)			 *  @property funcEnd		 	 *  @type	 Function			 *  @default  function () {}		 	 */		  "funcEnd": function () {}	  },			/**		 * A unique ID is assigned to each button in each instance		 * @property buttonCounter		 *  @type	 int		 * @default  0		 */	  "buttonCounter": 0,				/**		 * @namespace Select rows specific information		 */		"select": {			/**			 * Select type - can be 'none', 'single' or 'multi'			 * @property type			 *  @type	 string			 * @default  ""			 */			"type": "",						/**			 * Array of nodes which are currently selected			 *  @property selected			 *  @type	 array			 *  @default  []			 */			"selected": [],						/**			 * Function to run before the selection can take place. Will cancel the select if the			 * function returns false			 *  @property preRowSelect			 *  @type	 Function			 *  @default  null			 */			"preRowSelect": null,						/**			 * Function to run when a row is selected			 *  @property postSelected			 *  @type	 Function			 *  @default  null			 */			"postSelected": null,						/**			 * Function to run when a row is deselected			 *  @property postDeselected			 *  @type	 Function			 *  @default  null			 */			"postDeselected": null,						/**			 * Indicate if all rows are selected (needed for server-side processing)			 *  @property all			 *  @type	 boolean			 *  @default  false			 */			"all": false,						/**			 * Class name to add to selected TR nodes			 *  @property selectedClass			 *  @type	 String			 *  @default  ""			 */			"selectedClass": ""		},				/**		 * Store of the user input customisation object		 *  @property custom		 *  @type	 object		 *  @default  {}		 */		"custom": {},				/**		 * SWF movie path		 *  @property swfPath		 *  @type	 string		 *  @default  ""		 */		"swfPath": "",				/**		 * Default button set		 *  @property buttonSet		 *  @type	 array		 *  @default  []		 */		"buttonSet": [],				/**		 * When there is more than one TableTools instance for a DataTable, there must be a 		 * master which controls events (row selection etc)		 *  @property master		 *  @type	 boolean		 *  @default  false		 */		"master": false,				/**		 * Tag names that are used for creating collections and buttons		 *  @namesapce		 */		"tags": {}	};			/**	 * @namespace Common and useful DOM elements for the class instance	 */	this.dom = {		/**		 * DIV element that is create and all TableTools buttons (and their children) put into		 *  @property container		 *  @type	 node		 *  @default  null		 */		"container": null,				/**		 * The table node to which TableTools will be applied		 *  @property table		 *  @type	 node		 *  @default  null		 */		"table": null,				/**		 * @namespace Nodes used for the print display		 */		"print": {			/**			 * Nodes which have been removed from the display by setting them to display none			 *  @property hidden			 *  @type	 array		 	 *  @default  []			 */		  "hidden": [],						/**			 * The information display saying telling the user about the print display			 *  @property message			 *  @type	 node		 	 *  @default  null			 */		  "message": null	  },				/**		 * @namespace Nodes used for a collection display. This contains the currently used collection		 */		"collection": {			/**			 * The div wrapper containing the buttons in the collection (i.e. the menu)			 *  @property collection			 *  @type	 node		 	 *  @default  null			 */			"collection": null,						/**			 * Background display to provide focus and capture events			 *  @property background			 *  @type	 node		 	 *  @default  null			 */			"background": null		}	};	/**	 * @namespace Name space for the classes that this TableTools instance will use	 * @extends TableTools.classes	 */	this.classes = $.extend( true, {}, TableTools.classes );	if ( this.s.dt.bJUI )	{		$.extend( true, this.classes, TableTools.classes_themeroller );	}			/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *	 * Public class methods	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */		/**	 * Retreieve the settings object from an instance	 *  @method fnSettings	 *  @returns {object} TableTools settings object	 */	this.fnSettings = function () {		return this.s;	};			/* Constructor logic */	if ( typeof oOpts == 'undefined' )	{		oOpts = {};	}		this._fnConstruct( oOpts );		return this;};TableTools.prototype = {	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *	 * Public methods	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */		/**	 * Retreieve the settings object from an instance	 *  @returns {array} List of TR nodes which are currently selected	 *  @param {boolean} [filtered=false] Get only selected rows which are  	 *    available given the filtering applied to the table. By default	 *    this is false -  i.e. all rows, regardless of filtering are 	      selected.	 */	"fnGetSelected": function ( filtered )	{		var			out = [],			data = this.s.dt.aoData,			displayed = this.s.dt.aiDisplay,			i, iLen;		if ( filtered )		{			// Only consider filtered rows			for ( i=0, iLen=displayed.length ; i<iLen ; i++ )			{				if ( data[ displayed[i] ]._DTTT_selected )				{					out.push( data[ displayed[i] ].nTr );				}			}		}		else		{			// Use all rows			for ( i=0, iLen=data.length ; i<iLen ; i++ )			{				if ( data[i]._DTTT_selected )				{					out.push( data[i].nTr );				}			}		}		return out;	},	/**	 * Get the data source objects/arrays from DataTables for the selected rows (same as	 * fnGetSelected followed by fnGetData on each row from the table)	 *  @returns {array} Data from the TR nodes which are currently selected	 */	"fnGetSelectedData": function ()	{		var out = [];		var data=this.s.dt.aoData;		var i, iLen;		for ( i=0, iLen=data.length ; i<iLen ; i++ )		{			if ( data[i]._DTTT_selected )			{				out.push( this.s.dt.oInstance.fnGetData(i) );			}		}		return out;	},			/**	 * Check to see if a current row is selected or not	 *  @param {Node} n TR node to check if it is currently selected or not	 *  @returns {Boolean} true if select, false otherwise	 */	"fnIsSelected": function ( n )	{		var pos = this.s.dt.oInstance.fnGetPosition( n );		return (this.s.dt.aoData[pos]._DTTT_selected===true) ? true : false;	},		/**	 * Select all rows in the table	 *  @param {boolean} [filtered=false] Select only rows which are available 	 *    given the filtering applied to the table. By default this is false - 	 *    i.e. all rows, regardless of filtering are selected.	 */	"fnSelectAll": function ( filtered )	{		var s = this._fnGetMasterSettings();				this._fnRowSelect( (filtered === true) ?			s.dt.aiDisplay :			s.dt.aoData		);	},		/**	 * Deselect all rows in the table	 *  @param {boolean} [filtered=false] Deselect only rows which are available 	 *    given the filtering applied to the table. By default this is false - 	 *    i.e. all rows, regardless of filtering are deselected.	 */	"fnSelectNone": function ( filtered )	{		var s = this._fnGetMasterSettings();		this._fnRowDeselect( this.fnGetSelected(filtered) );	},		/**	 * Select row(s)	 *  @param {node|object|array} n The row(s) to select. Can be a single DOM	 *    TR node, an array of TR nodes or a jQuery object.	 */	"fnSelect": function ( n )	{		if ( this.s.select.type == "single" )		{			this.fnSelectNone();			this._fnRowSelect( n );		}		else if ( this.s.select.type == "multi" )		{			this._fnRowSelect( n );		}	},		/**	 * Deselect row(s)	 *  @param {node|object|array} n The row(s) to deselect. Can be a single DOM	 *    TR node, an array of TR nodes or a jQuery object.	 */	"fnDeselect": function ( n )	{		this._fnRowDeselect( n );	},			/**	 * Get the title of the document - useful for file names. The title is retrieved from either	 * the configuration object's 'title' parameter, or the HTML document title	 *  @param   {Object} oConfig Button configuration object	 *  @returns {String} Button title	 */	"fnGetTitle": function( oConfig )	{		var sTitle = "";		if ( typeof oConfig.sTitle != 'undefined' && oConfig.sTitle !== "" ) {			sTitle = oConfig.sTitle;		} else {			var anTitle = document.getElementsByTagName('title');			if ( anTitle.length > 0 )			{				sTitle = anTitle[0].innerHTML;			}		}				/* Strip characters which the OS will object to - checking for UTF8 support in the scripting		 * engine		 */		if ( "\u00A1".toString().length < 4 ) {			return sTitle.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, "");		} else {			return sTitle.replace(/[^a-zA-Z0-9_\.,\-_ !\(\)]/g, "");		}	},			/**	 * Calculate a unity array with the column width by proportion for a set of columns to be	 * included for a button. This is particularly useful for PDF creation, where we can use the	 * column widths calculated by the browser to size the columns in the PDF.	 *  @param   {Object} oConfig Button configuration object	 *  @returns {Array} Unity array of column ratios	 */	"fnCalcColRatios": function ( oConfig )	{		var			aoCols = this.s.dt.aoColumns,			aColumnsInc = this._fnColumnTargets( oConfig.mColumns ),			aColWidths = [],			iWidth = 0, iTotal = 0, i, iLen;				for ( i=0, iLen=aColumnsInc.length ; i<iLen ; i++ )		{			if ( aColumnsInc[i] )			{				iWidth = aoCols[i].nTh.offsetWidth;				iTotal += iWidth;				aColWidths.push( iWidth );			}		}				for ( i=0, iLen=aColWidths.length ; i<iLen ; i++ )		{			aColWidths[i] = aColWidths[i] / iTotal;		}				return aColWidths.join('\t');	},			/**	 * Get the information contained in a table as a string	 *  @param   {Object} oConfig Button configuration object	 *  @returns {String} Table data as a string	 */	"fnGetTableData": function ( oConfig )	{		/* In future this could be used to get data from a plain HTML source as well as DataTables */		if ( this.s.dt )		{			return this._fnGetDataTablesData( oConfig );		}	},			/**	 * Pass text to a flash button instance, which will be used on the button's click handler	 *  @param   {Object} clip Flash button object	 *  @param   {String} text Text to set	 */	"fnSetText": function ( clip, text )	{		this._fnFlashSetText( clip, text );	},			/**	 * Resize the flash elements of the buttons attached to this TableTools instance - this is	 * useful for when initialising TableTools when it is hidden (display:none) since sizes can't	 * be calculated at that time.	 */	"fnResizeButtons": function ()	{		for ( var cli in ZeroClipboard_TableTools.clients )		{			if ( cli )			{				var client = ZeroClipboard_TableTools.clients[cli];				if ( typeof client.domElement != 'undefined' &&					 client.domElement.parentNode )				{					client.positionElement();				}			}		}	},			/**	 * Check to see if any of the ZeroClipboard client's attached need to be resized	 */	"fnResizeRequired": function ()	{		for ( var cli in ZeroClipboard_TableTools.clients )		{			if ( cli )			{				var client = ZeroClipboard_TableTools.clients[cli];				if ( typeof client.domElement != 'undefined' &&					 client.domElement.parentNode == this.dom.container &&					 client.sized === false )				{					return true;				}			}		}		return false;	},			/**	 * Programmatically enable or disable the print view	 *  @param {boolean} [bView=true] Show the print view if true or not given. If false, then	 *    terminate the print view and return to normal.	 *  @param {object} [oConfig={}] Configuration for the print view	 *  @param {boolean} [oConfig.bShowAll=false] Show all rows in the table if true	 *  @param {string} [oConfig.sInfo] Information message, displayed as an overlay to the	 *    user to let them know what the print view is.	 *  @param {string} [oConfig.sMessage] HTML string to show at the top of the document - will	 *    be included in the printed document.	 */	"fnPrint": function ( bView, oConfig )	{		if ( oConfig === undefined )		{			oConfig = {};		}		if ( bView === undefined || bView )		{			this._fnPrintStart( oConfig );		}		else		{			this._fnPrintEnd();		}	},			/**	 * Show a message to the end user which is nicely styled	 *  @param {string} message The HTML string to show to the user	 *  @param {int} time The duration the message is to be shown on screen for (mS)	 */	"fnInfo": function ( message, time ) {		var nInfo = document.createElement( "div" );		nInfo.className = this.classes.print.info;		nInfo.innerHTML = message;		document.body.appendChild( nInfo );				setTimeout( function() {			$(nInfo).fadeOut( "normal", function() {				document.body.removeChild( nInfo );			} );		}, time );	},				/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *	 * Private methods (they are of course public in JS, but recommended as private)	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */		/**	 * Constructor logic	 *  @method  _fnConstruct	 *  @param   {Object} oOpts Same as TableTools constructor	 *  @returns void	 *  @private 	 */	"_fnConstruct": function ( oOpts )	{		var that = this;				this._fnCustomiseSettings( oOpts );				/* Container element */		this.dom.container = document.createElement( this.s.tags.container );		this.dom.container.className = this.classes.container;				/* Row selection config */		if ( this.s.select.type != 'none' )		{			this._fnRowSelectConfig();		}				/* Buttons */		this._fnButtonDefinations( this.s.buttonSet, this.dom.container );				/* Destructor - need to wipe the DOM for IE's garbage collector */		this.s.dt.aoDestroyCallback.push( {			"sName": "TableTools",			"fn": function () {				that.dom.container.innerHTML = "";			}		} );	},			/**	 * Take the user defined settings and the default settings and combine them.	 *  @method  _fnCustomiseSettings	 *  @param   {Object} oOpts Same as TableTools constructor	 *  @returns void	 *  @private 	 */	"_fnCustomiseSettings": function ( oOpts )	{		/* Is this the master control instance or not? */		if ( typeof this.s.dt._TableToolsInit == 'undefined' )		{			this.s.master = true;			this.s.dt._TableToolsInit = true;		}				/* We can use the table node from comparisons to group controls */		this.dom.table = this.s.dt.nTable;				/* Clone the defaults and then the user options */		this.s.custom = $.extend( {}, TableTools.DEFAULTS, oOpts );				/* Flash file location */		this.s.swfPath = this.s.custom.sSwfPath;		if ( typeof ZeroClipboard_TableTools != 'undefined' )		{			ZeroClipboard_TableTools.moviePath = this.s.swfPath;		}				/* Table row selecting */		this.s.select.type = this.s.custom.sRowSelect;		this.s.select.preRowSelect = this.s.custom.fnPreRowSelect;		this.s.select.postSelected = this.s.custom.fnRowSelected;		this.s.select.postDeselected = this.s.custom.fnRowDeselected;		// Backwards compatibility - allow the user to specify a custom class in the initialiser		if ( this.s.custom.sSelectedClass )		{			this.classes.select.row = this.s.custom.sSelectedClass;		}		this.s.tags = this.s.custom.oTags;		/* Button set */		this.s.buttonSet = this.s.custom.aButtons;	},			/**	 * Take the user input arrays and expand them to be fully defined, and then add them to a given	 * DOM element	 *  @method  _fnButtonDefinations	 *  @param {array} buttonSet Set of user defined buttons	 *  @param {node} wrapper Node to add the created buttons to	 *  @returns void	 *  @private 	 */	"_fnButtonDefinations": function ( buttonSet, wrapper )	{		var buttonDef;				for ( var i=0, iLen=buttonSet.length ; i<iLen ; i++ )		{			if ( typeof buttonSet[i] == "string" )			{				if ( typeof TableTools.BUTTONS[ buttonSet[i] ] == 'undefined' )				{					alert( "TableTools: Warning - unknown button type: "+buttonSet[i] );					continue;				}				buttonDef = $.extend( {}, TableTools.BUTTONS[ buttonSet[i] ], true );			}			else			{				if ( typeof TableTools.BUTTONS[ buttonSet[i].sExtends ] == 'undefined' )				{					alert( "TableTools: Warning - unknown button type: "+buttonSet[i].sExtends );					continue;				}				var o = $.extend( {}, TableTools.BUTTONS[ buttonSet[i].sExtends ], true );				buttonDef = $.extend( o, buttonSet[i], true );			}						wrapper.appendChild( this._fnCreateButton( 				buttonDef, 				$(wrapper).hasClass(this.classes.collection.container)			) );		}	},			/**	 * Create and configure a TableTools button	 *  @method  _fnCreateButton	 *  @param   {Object} oConfig Button configuration object	 *  @returns {Node} Button element	 *  @private 	 */	"_fnCreateButton": function ( oConfig, bCollectionButton )	{	  var nButton = this._fnButtonBase( oConfig, bCollectionButton );				if ( oConfig.sAction.match(/flash/) )		{			this._fnFlashConfig( nButton, oConfig );		}		else if ( oConfig.sAction == "text" )		{			this._fnTextConfig( nButton, oConfig );		}		else if ( oConfig.sAction == "div" )		{			this._fnTextConfig( nButton, oConfig );		}		else if ( oConfig.sAction == "collection" )		{			this._fnTextConfig( nButton, oConfig );			this._fnCollectionConfig( nButton, oConfig );		}				return nButton;	},			/**	 * Create the DOM needed for the button and apply some base properties. All buttons start here	 *  @method  _fnButtonBase	 *  @param   {o} oConfig Button configuration object	 *  @returns {Node} DIV element for the button	 *  @private 	 */	"_fnButtonBase": function ( o, bCollectionButton )	{		var sTag, sLiner, sClass;		if ( bCollectionButton )		{			sTag = o.sTag !== "default" ? o.sTag : this.s.tags.collection.button;			sLiner = o.sLinerTag !== "default" ? o.sLiner : this.s.tags.collection.liner;			sClass = this.classes.collection.buttons.normal;		}		else		{			sTag = o.sTag !== "default" ? o.sTag : this.s.tags.button;			sLiner = o.sLinerTag !== "default" ? o.sLiner : this.s.tags.liner;			sClass = this.classes.buttons.normal;		}		var		  nButton = document.createElement( sTag ),		  nSpan = document.createElement( sLiner ),		  masterS = this._fnGetMasterSettings();				nButton.className = sClass+" "+o.sButtonClass;		nButton.setAttribute('id', "ToolTables_"+this.s.dt.sInstance+"_"+masterS.buttonCounter );		nButton.appendChild( nSpan );		nSpan.innerHTML = o.sButtonText;				masterS.buttonCounter++;				return nButton;	},			/**	 * Get the settings object for the master instance. When more than one TableTools instance is	 * assigned to a DataTable, only one of them can be the 'master' (for the select rows). As such,	 * we will typically want to interact with that master for global properties.	 *  @method  _fnGetMasterSettings	 *  @returns {Object} TableTools settings object	 *  @private 	 */	"_fnGetMasterSettings": function ()	{		if ( this.s.master )		{			return this.s;		}		else		{			/* Look for the master which has the same DT as this one */			var instances = TableTools._aInstances;			for ( var i=0, iLen=instances.length ; i<iLen ; i++ )			{				if ( this.dom.table == instances[i].s.dt.nTable )				{					return instances[i].s;				}			}		}	},				/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *	 * Button collection functions	 */		/**	 * Create a collection button, when activated will present a drop down list of other buttons	 *  @param   {Node} nButton Button to use for the collection activation	 *  @param   {Object} oConfig Button configuration object	 *  @returns void	 *  @private	 */	"_fnCollectionConfig": function ( nButton, oConfig )	{		var nHidden = document.createElement( this.s.tags.collection.container );		nHidden.style.display = "none";		nHidden.className = this.classes.collection.container;		oConfig._collection = nHidden;		document.body.appendChild( nHidden );				this._fnButtonDefinations( oConfig.aButtons, nHidden );	},			/**	 * Show a button collection	 *  @param   {Node} nButton Button to use for the collection	 *  @param   {Object} oConfig Button configuration object	 *  @returns void	 *  @private	 */	"_fnCollectionShow": function ( nButton, oConfig )	{		var			that = this,			oPos = $(nButton).offset(),			nHidden = oConfig._collection,			iDivX = oPos.left,			iDivY = oPos.top + $(nButton).outerHeight(),			iWinHeight = $(window).height(), iDocHeight = $(document).height(),		 	iWinWidth = $(window).width(), iDocWidth = $(document).width();				nHidden.style.position = "absolute";		nHidden.style.left = iDivX+"px";		nHidden.style.top = iDivY+"px";		nHidden.style.display = "block";		$(nHidden).css('opacity',0);				var nBackground = document.createElement('div');		nBackground.style.position = "absolute";		nBackground.style.left = "0px";		nBackground.style.top = "0px";		nBackground.style.height = ((iWinHeight>iDocHeight)? iWinHeight : iDocHeight) +"px";		nBackground.style.width = ((iWinWidth>iDocWidth)? iWinWidth : iDocWidth) +"px";		nBackground.className = this.classes.collection.background;		$(nBackground).css('opacity',0);				document.body.appendChild( nBackground );		document.body.appendChild( nHidden );				/* Visual corrections to try and keep the collection visible */		var iDivWidth = $(nHidden).outerWidth();		var iDivHeight = $(nHidden).outerHeight();				if ( iDivX + iDivWidth > iDocWidth )		{			nHidden.style.left = (iDocWidth-iDivWidth)+"px";		}				if ( iDivY + iDivHeight > iDocHeight )		{			nHidden.style.top = (iDivY-iDivHeight-$(nButton).outerHeight())+"px";		}			this.dom.collection.collection = nHidden;		this.dom.collection.background = nBackground;				/* This results in a very small delay for the end user but it allows the animation to be		 * much smoother. If you don't want the animation, then the setTimeout can be removed		 */		setTimeout( function () {			$(nHidden).animate({"opacity": 1}, 500);			$(nBackground).animate({"opacity": 0.25}, 500);		}, 10 );		/* Resize the buttons to the Flash contents fit */		this.fnResizeButtons();				/* Event handler to remove the collection display */		$(nBackground).click( function () {			that._fnCollectionHide.call( that, null, null );		} );	},			/**	 * Hide a button collection	 *  @param   {Node} nButton Button to use for the collection	 *  @param   {Object} oConfig Button configuration object	 *  @returns void	 *  @private	 */	"_fnCollectionHide": function ( nButton, oConfig )	{		if ( oConfig !== null && oConfig.sExtends == 'collection' )		{			return;		}				if ( this.dom.collection.collection !== null )		{			$(this.dom.collection.collection).animate({"opacity": 0}, 500, function (e) {				this.style.display = "none";			} );						$(this.dom.collection.background).animate({"opacity": 0}, 500, function (e) {				this.parentNode.removeChild( this );			} );						this.dom.collection.collection = null;			this.dom.collection.background = null;		}	},				/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *	 * Row selection functions	 */		/**	 * Add event handlers to a table to allow for row selection	 *  @method  _fnRowSelectConfig	 *  @returns void	 *  @private 	 */	"_fnRowSelectConfig": function ()	{		if ( this.s.master )		{			var				that = this, 				i, iLen, 				dt = this.s.dt,				aoOpenRows = this.s.dt.aoOpenRows;						$(dt.nTable).addClass( this.classes.select.table );						$('tr', dt.nTBody).live( 'click', function(e) {				/* Sub-table must be ignored (odd that the selector won't do this with >) */				if ( this.parentNode != dt.nTBody )				{					return;				}								/* Check that we are actually working with a DataTables controlled row */				if ( dt.oInstance.fnGetData(this) === null )				{				    return;				}				if ( that.fnIsSelected( this ) )				{					that._fnRowDeselect( this, e );				}				else if ( that.s.select.type == "single" )				{					that.fnSelectNone();					that._fnRowSelect( this, e );				}				else if ( that.s.select.type == "multi" )				{					that._fnRowSelect( this, e );				}			} );			// Bind a listener to the DataTable for when new rows are created.			// This allows rows to be visually selected when they should be and			// deferred rendering is used.			dt.oApi._fnCallbackReg( dt, 'aoRowCreatedCallback', function (tr, data, index) {				if ( dt.aoData[index]._DTTT_selected ) {					$(tr).addClass( that.classes.select.row );				}			}, 'TableTools-SelectAll' );		}	},	/**	 * Select rows	 *  @param   {*} src Rows to select - see _fnSelectData for a description of valid inputs	 *  @private 	 */	"_fnRowSelect": function ( src, e )	{		var			that = this,			data = this._fnSelectData( src ),			firstTr = data.length===0 ? null : data[0].nTr,			anSelected = [],			i, len;		// Get all the rows that will be selected		for ( i=0, len=data.length ; i<len ; i++ )		{			if ( data[i].nTr )			{				anSelected.push( data[i].nTr );			}		}				// User defined pre-selection function		if ( this.s.select.preRowSelect !== null && !this.s.select.preRowSelect.call(this, e, anSelected, true) )		{			return;		}		// Mark them as selected		for ( i=0, len=data.length ; i<len ; i++ )		{			data[i]._DTTT_selected = true;			if ( data[i].nTr )			{				$(data[i].nTr).addClass( that.classes.select.row );			}		}		// Post-selection function		if ( this.s.select.postSelected !== null )		{			this.s.select.postSelected.call( this, anSelected );		}		TableTools._fnEventDispatch( this, 'select', anSelected, true );	},	/**	 * Deselect rows	 *  @param   {*} src Rows to deselect - see _fnSelectData for a description of valid inputs	 *  @private 	 */	"_fnRowDeselect": function ( src, e )	{		var			that = this,			data = this._fnSelectData( src ),			firstTr = data.length===0 ? null : data[0].nTr,			anDeselectedTrs = [],			i, len;		// Get all the rows that will be deselected		for ( i=0, len=data.length ; i<len ; i++ )		{			if ( data[i].nTr )			{				anDeselectedTrs.push( data[i].nTr );			}		}		// User defined pre-selection function		if ( this.s.select.preRowSelect !== null && !this.s.select.preRowSelect.call(this, e, anDeselectedTrs, false) )		{			return;		}		// Mark them as deselected		for ( i=0, len=data.length ; i<len ; i++ )		{			data[i]._DTTT_selected = false;			if ( data[i].nTr )			{				$(data[i].nTr).removeClass( that.classes.select.row );			}		}		// Post-deselection function		if ( this.s.select.postDeselected !== null )		{			this.s.select.postDeselected.call( this, anDeselectedTrs );		}		TableTools._fnEventDispatch( this, 'select', anDeselectedTrs, false );	},		/**	 * Take a data source for row selection and convert it into aoData points for the DT	 *   @param {*} src Can be a single DOM TR node, an array of TR nodes (including a	 *     a jQuery object), a single aoData point from DataTables, an array of aoData	 *     points or an array of aoData indexes	 *   @returns {array} An array of aoData points	 */	"_fnSelectData": function ( src )	{		var out = [], pos, i, iLen;		if ( src.nodeName )		{			// Single node			pos = this.s.dt.oInstance.fnGetPosition( src );			out.push( this.s.dt.aoData[pos] );		}		else if ( typeof src.length !== 'undefined' )		{			// jQuery object or an array of nodes, or aoData points			for ( i=0, iLen=src.length ; i<iLen ; i++ )			{				if ( src[i].nodeName )				{					pos = this.s.dt.oInstance.fnGetPosition( src[i] );					out.push( this.s.dt.aoData[pos] );				}				else if ( typeof src[i] === 'number' )				{					out.push( this.s.dt.aoData[ src[i] ] );				}				else				{					out.push( src[i] );				}			}			return out;		}		else		{			// A single aoData point			out.push( src );		}		return out;	},			/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *	 * Text button functions	 */		/**	 * Configure a text based button for interaction events	 *  @method  _fnTextConfig	 *  @param   {Node} nButton Button element which is being considered	 *  @param   {Object} oConfig Button configuration object	 *  @returns void	 *  @private 	 */	"_fnTextConfig": function ( nButton, oConfig )	{		var that = this;				if ( oConfig.fnInit !== null )		{			oConfig.fnInit.call( this, nButton, oConfig );		}				if ( oConfig.sToolTip !== "" )		{			nButton.title = oConfig.sToolTip;		}				$(nButton).hover( function () {			if ( oConfig.fnMouseover !== null )			{				oConfig.fnMouseover.call( this, nButton, oConfig, null );			}		}, function () {			if ( oConfig.fnMouseout !== null )			{				oConfig.fnMouseout.call( this, nButton, oConfig, null );			}		} );				if ( oConfig.fnSelect !== null )		{			TableTools._fnEventListen( this, 'select', function (n) {				oConfig.fnSelect.call( that, nButton, oConfig, n );			} );		}				$(nButton).click( function (e) {			//e.preventDefault();						if ( oConfig.fnClick !== null )			{				oConfig.fnClick.call( that, nButton, oConfig, null );			}						/* Provide a complete function to match the behaviour of the flash elements */			if ( oConfig.fnComplete !== null )			{				oConfig.fnComplete.call( that, nButton, oConfig, null, null );			}						that._fnCollectionHide( nButton, oConfig );		} );	},				/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *	 * Flash button functions	 */		/**	 * Configure a flash based button for interaction events	 *  @method  _fnFlashConfig	 *  @param   {Node} nButton Button element which is being considered	 *  @param   {o} oConfig Button configuration object	 *  @returns void	 *  @private 	 */	"_fnFlashConfig": function ( nButton, oConfig )	{		var that = this;		var flash = new ZeroClipboard_TableTools.Client();				if ( oConfig.fnInit !== null )		{			oConfig.fnInit.call( this, nButton, oConfig );		}				flash.setHandCursor( true );				if ( oConfig.sAction == "flash_save" )		{			flash.setAction( 'save' );			flash.setCharSet( (oConfig.sCharSet=="utf16le") ? 'UTF16LE' : 'UTF8' );			flash.setBomInc( oConfig.bBomInc );			flash.setFileName( oConfig.sFileName.replace('*', this.fnGetTitle(oConfig)) );		}		else if ( oConfig.sAction == "flash_pdf" )		{			flash.setAction( 'pdf' );			flash.setFileName( oConfig.sFileName.replace('*', this.fnGetTitle(oConfig)) );		}		else		{			flash.setAction( 'copy' );		}				flash.addEventListener('mouseOver', function(client) {			if ( oConfig.fnMouseover !== null )			{				oConfig.fnMouseover.call( that, nButton, oConfig, flash );			}		} );				flash.addEventListener('mouseOut', function(client) {			if ( oConfig.fnMouseout !== null )			{				oConfig.fnMouseout.call( that, nButton, oConfig, flash );			}		} );				flash.addEventListener('mouseDown', function(client) {			if ( oConfig.fnClick !== null )			{				oConfig.fnClick.call( that, nButton, oConfig, flash );			}		} );				flash.addEventListener('complete', function (client, text) {			if ( oConfig.fnComplete !== null )			{				oConfig.fnComplete.call( that, nButton, oConfig, flash, text );			}			that._fnCollectionHide( nButton, oConfig );		} );				this._fnFlashGlue( flash, nButton, oConfig.sToolTip );	},			/**	 * Wait until the id is in the DOM before we "glue" the swf. Note that this function will call	 * itself (using setTimeout) until it completes successfully	 *  @method  _fnFlashGlue	 *  @param   {Object} clip Zero clipboard object	 *  @param   {Node} node node to glue swf to	 *  @param   {String} text title of the flash movie	 *  @returns void	 *  @private 	 */	"_fnFlashGlue": function ( flash, node, text )	{		var that = this;		var id = node.getAttribute('id');				if ( document.getElementById(id) )		{			flash.glue( node, text );		}		else		{			setTimeout( function () {				that._fnFlashGlue( flash, node, text );			}, 100 );		}	},			/**	 * Set the text for the flash clip to deal with	 * 	 * This function is required for large information sets. There is a limit on the 	 * amount of data that can be transferred between Javascript and Flash in a single call, so	 * we use this method to build up the text in Flash by sending over chunks. It is estimated	 * that the data limit is around 64k, although it is undocumented, and appears to be different	 * between different flash versions. We chunk at 8KiB.	 *  @method  _fnFlashSetText	 *  @param   {Object} clip the ZeroClipboard object	 *  @param   {String} sData the data to be set	 *  @returns void	 *  @private 	 */	"_fnFlashSetText": function ( clip, sData )	{		var asData = this._fnChunkData( sData, 8192 );				clip.clearText();		for ( var i=0, iLen=asData.length ; i<iLen ; i++ )		{			clip.appendText( asData[i] );		}	},				/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *	 * Data retrieval functions	 */		/**	 * Convert the mixed columns variable into a boolean array the same size as the columns, which	 * indicates which columns we want to include	 *  @method  _fnColumnTargets	 *  @param   {String|Array} mColumns The columns to be included in data retrieval. If a string	 *			 then it can take the value of "visible" or "hidden" (to include all visible or	 *			 hidden columns respectively). Or an array of column indexes	 *  @returns {Array} A boolean array the length of the columns of the table, which each value	 *			 indicating if the column is to be included or not	 *  @private 	 */	"_fnColumnTargets": function ( mColumns )	{		var aColumns = [];		var dt = this.s.dt;				if ( typeof mColumns == "object" )		{			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )			{				aColumns.push( false );			}						for ( i=0, iLen=mColumns.length ; i<iLen ; i++ )			{				aColumns[ mColumns[i] ] = true;			}		}		else if ( mColumns == "visible" )		{			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )			{				aColumns.push( dt.aoColumns[i].bVisible ? true : false );			}		}		else if ( mColumns == "hidden" )		{			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )			{				aColumns.push( dt.aoColumns[i].bVisible ? false : true );			}		}		else if ( mColumns == "sortable" )		{			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )			{				aColumns.push( dt.aoColumns[i].bSortable ? true : false );			}		}		else /* all */		{			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )			{				aColumns.push( true );			}		}				return aColumns;	},			/**	 * New line character(s) depend on the platforms	 *  @method  method	 *  @param   {Object} oConfig Button configuration object - only interested in oConfig.sNewLine	 *  @returns {String} Newline character	 */	"_fnNewline": function ( oConfig )	{		if ( oConfig.sNewLine == "auto" )		{			return navigator.userAgent.match(/Windows/) ? "\r\n" : "\n";		}		else		{			return oConfig.sNewLine;		}	},			/**	 * Get data from DataTables' internals and format it for output	 *  @method  _fnGetDataTablesData	 *  @param   {Object} oConfig Button configuration object	 *  @param   {String} oConfig.sFieldBoundary Field boundary for the data cells in the string	 *  @param   {String} oConfig.sFieldSeperator Field separator for the data cells	 *  @param   {String} oConfig.sNewline New line options	 *  @param   {Mixed} oConfig.mColumns Which columns should be included in the output	 *  @param   {Boolean} oConfig.bHeader Include the header	 *  @param   {Boolean} oConfig.bFooter Include the footer	 *  @param   {Boolean} oConfig.bSelectedOnly Include only the selected rows in the output	 *  @returns {String} Concatenated string of data	 *  @private 	 */	"_fnGetDataTablesData": function ( oConfig )	{		var i, iLen, j, jLen;		var aRow, aData=[], sLoopData='', arr;		var dt = this.s.dt, tr, child;		var regex = new RegExp(oConfig.sFieldBoundary, "g"); /* Do it here for speed */		var aColumnsInc = this._fnColumnTargets( oConfig.mColumns );		var bSelectedOnly = (typeof oConfig.bSelectedOnly != 'undefined') ? oConfig.bSelectedOnly : false;				/*		 * Header		 */		if ( oConfig.bHeader )		{			aRow = [];						for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )			{				if ( aColumnsInc[i] )				{					sLoopData = dt.aoColumns[i].sTitle.replace(/\n/g," ").replace( /<.*?>/g, "" ).replace(/^\s+|\s+$/g,"");					sLoopData = this._fnHtmlDecode( sLoopData );										aRow.push( this._fnBoundData( sLoopData, oConfig.sFieldBoundary, regex ) );				}			}			aData.push( aRow.join(oConfig.sFieldSeperator) );		}				/*		 * Body		 */		var aDataIndex = dt.aiDisplay;		var aSelected = this.fnGetSelected();		if ( this.s.select.type !== "none" && bSelectedOnly && aSelected.length !== 0 )		{			aDataIndex = [];			for ( i=0, iLen=aSelected.length ; i<iLen ; i++ )			{				aDataIndex.push( dt.oInstance.fnGetPosition( aSelected[i] ) );			}		}				for ( j=0, jLen=aDataIndex.length ; j<jLen ; j++ )		{			tr = dt.aoData[ aDataIndex[j] ].nTr;			aRow = [];						/* Columns */			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )			{				if ( aColumnsInc[i] )				{					/* Convert to strings (with small optimisation) */					var mTypeData = dt.oApi._fnGetCellData( dt, aDataIndex[j], i, 'display' );					if ( oConfig.fnCellRender )					{						sLoopData = oConfig.fnCellRender( mTypeData, i, tr, aDataIndex[j] )+"";					}					else if ( typeof mTypeData == "string" )					{						/* Strip newlines, replace img tags with alt attr. and finally strip html... */						sLoopData = mTypeData.replace(/\n/g," ");						sLoopData =						 	sLoopData.replace(/<img.*?\s+alt\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+)).*?>/gi,						 		'$1$2$3');						sLoopData = sLoopData.replace( /<.*?>/g, "" );					}					else					{						sLoopData = mTypeData+"";					}										/* Trim and clean the data */					sLoopData = sLoopData.replace(/^\s+/, '').replace(/\s+$/, '');					sLoopData = this._fnHtmlDecode( sLoopData );										/* Bound it and add it to the total data */					aRow.push( this._fnBoundData( sLoopData, oConfig.sFieldBoundary, regex ) );				}			}      			aData.push( aRow.join(oConfig.sFieldSeperator) );      			/* Details rows from fnOpen */			if ( oConfig.bOpenRows )			{				arr = $.grep(dt.aoOpenRows, function(o) { return o.nParent === tr; });								if ( arr.length === 1 )				{					sLoopData = this._fnBoundData( $('td', arr[0].nTr).html(), oConfig.sFieldBoundary, regex );					aData.push( sLoopData );				}			}		}				/*		 * Footer		 */		if ( oConfig.bFooter && dt.nTFoot !== null )		{			aRow = [];						for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )			{				if ( aColumnsInc[i] && dt.aoColumns[i].nTf !== null )				{					sLoopData = dt.aoColumns[i].nTf.innerHTML.replace(/\n/g," ").replace( /<.*?>/g, "" );					sLoopData = this._fnHtmlDecode( sLoopData );										aRow.push( this._fnBoundData( sLoopData, oConfig.sFieldBoundary, regex ) );				}			}						aData.push( aRow.join(oConfig.sFieldSeperator) );		}				_sLastData = aData.join( this._fnNewline(oConfig) );		return _sLastData;	},			/**	 * Wrap data up with a boundary string	 *  @method  _fnBoundData	 *  @param   {String} sData data to bound	 *  @param   {String} sBoundary bounding char(s)	 *  @param   {RegExp} regex search for the bounding chars - constructed outside for efficiency	 *			 in the loop	 *  @returns {String} bound data	 *  @private 	 */	"_fnBoundData": function ( sData, sBoundary, regex )	{		if ( sBoundary === "" )		{			return sData;		}		else		{			return sBoundary + sData.replace(regex, sBoundary+sBoundary) + sBoundary;		}	},			/**	 * Break a string up into an array of smaller strings	 *  @method  _fnChunkData	 *  @param   {String} sData data to be broken up	 *  @param   {Int} iSize chunk size	 *  @returns {Array} String array of broken up text	 *  @private 	 */	"_fnChunkData": function ( sData, iSize )	{		var asReturn = [];		var iStrlen = sData.length;				for ( var i=0 ; i<iStrlen ; i+=iSize )		{			if ( i+iSize < iStrlen )			{				asReturn.push( sData.substring( i, i+iSize ) );			}			else			{				asReturn.push( sData.substring( i, iStrlen ) );			}		}				return asReturn;	},			/**	 * Decode HTML entities	 *  @method  _fnHtmlDecode	 *  @param   {String} sData encoded string	 *  @returns {String} decoded string	 *  @private 	 */	"_fnHtmlDecode": function ( sData )	{		if ( sData.indexOf('&') === -1 )		{			return sData;		}				var n = document.createElement('div');		return sData.replace( /&([^\s]*);/g, function( match, match2 ) {			if ( match.substr(1, 1) === '#' )			{				return String.fromCharCode( Number(match2.substr(1)) );			}			else			{				n.innerHTML = match;				return n.childNodes[0].nodeValue;			}		} );	},				/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *	 * Printing functions	 */		/**	 * Show print display	 *  @method  _fnPrintStart	 *  @param   {Event} e Event object	 *  @param   {Object} oConfig Button configuration object	 *  @returns void	 *  @private 	 */	"_fnPrintStart": function ( oConfig )	{	  var that = this;	  var oSetDT = this.s.dt;	  		/* Parse through the DOM hiding everything that isn't needed for the table */		this._fnPrintHideNodes( oSetDT.nTable );				/* Show the whole table */		this.s.print.saveStart = oSetDT._iDisplayStart;		this.s.print.saveLength = oSetDT._iDisplayLength;		if ( oConfig.bShowAll )		{			oSetDT._iDisplayStart = 0;			oSetDT._iDisplayLength = -1;			oSetDT.oApi._fnCalculateEnd( oSetDT );			oSetDT.oApi._fnDraw( oSetDT );		}				/* Adjust the display for scrolling which might be done by DataTables */		if ( oSetDT.oScroll.sX !== "" || oSetDT.oScroll.sY !== "" )		{			this._fnPrintScrollStart( oSetDT );			// If the table redraws while in print view, the DataTables scrolling			// setup would hide the header, so we need to readd it on draw			$(this.s.dt.nTable).bind('draw.DTTT_Print', function () {				that._fnPrintScrollStart( oSetDT );			} );		}				/* Remove the other DataTables feature nodes - but leave the table! and info div */		var anFeature = oSetDT.aanFeatures;		for ( var cFeature in anFeature )		{			if ( cFeature != 'i' && cFeature != 't' && cFeature.length == 1 )			{				for ( var i=0, iLen=anFeature[cFeature].length ; i<iLen ; i++ )				{					this.dom.print.hidden.push( {						"node": anFeature[cFeature][i],						"display": "block"					} );					anFeature[cFeature][i].style.display = "none";				}			}		}				/* Print class can be used for styling */		$(document.body).addClass( this.classes.print.body );		/* Show information message to let the user know what is happening */		if ( oConfig.sInfo !== "" )		{			this.fnInfo( oConfig.sInfo, 3000 );		}		/* Add a message at the top of the page */		if ( oConfig.sMessage )		{			this.dom.print.message = document.createElement( "div" );			this.dom.print.message.className = this.classes.print.message;			this.dom.print.message.innerHTML = oConfig.sMessage;			document.body.insertBefore( this.dom.print.message, document.body.childNodes[0] );		}				/* Cache the scrolling and the jump to the top of the page */		this.s.print.saveScroll = $(window).scrollTop();		window.scrollTo( 0, 0 );		/* Bind a key event listener to the document for the escape key -		 * it is removed in the callback		 */		$(document).bind( "http://disabilitycompendium.org/datatables/tabletools/media/js/keydown.dttt", function(e) {			/* Only interested in the escape key */			if ( e.keyCode == 27 )			{				e.preventDefault();				that._fnPrintEnd.call( that, e );			}		} );	},			/**	 * Printing is finished, resume normal display	 *  @method  _fnPrintEnd	 *  @param   {Event} e Event object	 *  @returns void	 *  @private 	 */	"_fnPrintEnd": function ( e )	{		var that = this;		var oSetDT = this.s.dt;		var oSetPrint = this.s.print;		var oDomPrint = this.dom.print;				/* Show all hidden nodes */		this._fnPrintShowNodes();				/* Restore DataTables' scrolling */		if ( oSetDT.oScroll.sX !== "" || oSetDT.oScroll.sY !== "" )		{			$(this.s.dt.nTable).unbind('draw.DTTT_Print');			this._fnPrintScrollEnd();		}				/* Restore the scroll */		window.scrollTo( 0, oSetPrint.saveScroll );				/* Drop the print message */		if ( oDomPrint.message !== null )		{			document.body.removeChild( oDomPrint.message );			oDomPrint.message = null;		}				/* Styling class */		$(document.body).removeClass( 'DTTT_Print' );				/* Restore the table length */		oSetDT._iDisplayStart = oSetPrint.saveStart;		oSetDT._iDisplayLength = oSetPrint.saveLength;		oSetDT.oApi._fnCalculateEnd( oSetDT );		oSetDT.oApi._fnDraw( oSetDT );				$(document).unbind( "http://disabilitycompendium.org/datatables/tabletools/media/js/keydown.dttt" );	},			/**	 * Take account of scrolling in DataTables by showing the full table	 *  @returns void	 *  @private 	 */	"_fnPrintScrollStart": function ()	{		var 			oSetDT = this.s.dt,			nScrollHeadInner = oSetDT.nScrollHead.getElementsByTagName('div')[0],			nScrollHeadTable = nScrollHeadInner.getElementsByTagName('table')[0],			nScrollBody = oSetDT.nTable.parentNode;		/* Copy the header in the thead in the body table, this way we show one single table when		 * in print view. Note that this section of code is more or less verbatim from DT 1.7.0		 */		var nTheadSize = oSetDT.nTable.getElementsByTagName('thead');		if ( nTheadSize.length > 0 )		{			oSetDT.nTable.removeChild( nTheadSize[0] );		}				if ( oSetDT.nTFoot !== null )		{			var nTfootSize = oSetDT.nTable.getElementsByTagName('tfoot');			if ( nTfootSize.length > 0 )			{				oSetDT.nTable.removeChild( nTfootSize[0] );			}		}				nTheadSize = oSetDT.nTHead.cloneNode(true);		oSetDT.nTable.insertBefore( nTheadSize, oSetDT.nTable.childNodes[0] );				if ( oSetDT.nTFoot !== null )		{			nTfootSize = oSetDT.nTFoot.cloneNode(true);			oSetDT.nTable.insertBefore( nTfootSize, oSetDT.nTable.childNodes[1] );		}				/* Now adjust the table's viewport so we can actually see it */		if ( oSetDT.oScroll.sX !== "" )		{			oSetDT.nTable.style.width = $(oSetDT.nTable).outerWidth()+"px";			nScrollBody.style.width = $(oSetDT.nTable).outerWidth()+"px";			nScrollBody.style.overflow = "visible";		}				if ( oSetDT.oScroll.sY !== "" )		{			nScrollBody.style.height = $(oSetDT.nTable).outerHeight()+"px";			nScrollBody.style.overflow = "visible";		}	},			/**	 * Take account of scrolling in DataTables by showing the full table. Note that the redraw of	 * the DataTable that we do will actually deal with the majority of the hard work here	 *  @returns void	 *  @private 	 */	"_fnPrintScrollEnd": function ()	{		var 			oSetDT = this.s.dt,			nScrollBody = oSetDT.nTable.parentNode;				if ( oSetDT.oScroll.sX !== "" )		{			nScrollBody.style.width = oSetDT.oApi._fnStringToCss( oSetDT.oScroll.sX );			nScrollBody.style.overflow = "auto";		}				if ( oSetDT.oScroll.sY !== "" )		{			nScrollBody.style.height = oSetDT.oApi._fnStringToCss( oSetDT.oScroll.sY );			nScrollBody.style.overflow = "auto";		}	},			/**	 * Resume the display of all TableTools hidden nodes	 *  @method  _fnPrintShowNodes	 *  @returns void	 *  @private 	 */	"_fnPrintShowNodes": function ( )	{	  var anHidden = this.dom.print.hidden;	  		for ( var i=0, iLen=anHidden.length ; i<iLen ; i++ )		{			anHidden[i].node.style.display = anHidden[i].display;		}		anHidden.splice( 0, anHidden.length );	},			/**	 * Hide nodes which are not needed in order to display the table. Note that this function is	 * recursive	 *  @method  _fnPrintHideNodes	 *  @param   {Node} nNode Element which should be showing in a 'print' display	 *  @returns void	 *  @private 	 */	"_fnPrintHideNodes": function ( nNode )	{	  var anHidden = this.dom.print.hidden;	  		var nParent = nNode.parentNode;		var nChildren = nParent.childNodes;		for ( var i=0, iLen=nChildren.length ; i<iLen ; i++ )		{			if ( nChildren[i] != nNode && nChildren[i].nodeType == 1 )			{				/* If our node is shown (don't want to show nodes which were previously hidden) */				var sDisplay = $(nChildren[i]).css("display");			 	if ( sDisplay != "none" )				{					/* Cache the node and it's previous state so we can restore it */					anHidden.push( {						"node": nChildren[i],						"display": sDisplay					} );					nChildren[i].style.display = "none";				}			}		}				if ( nParent.nodeName != "BODY" )		{			this._fnPrintHideNodes( nParent );		}	}};/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Static variables * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *//** * Store of all instances that have been created of TableTools, so one can look up other (when * there is need of a master) *  @property _aInstances *  @type	 Array *  @default  [] *  @private */TableTools._aInstances = [];/** * Store of all listeners and their callback functions *  @property _aListeners *  @type	 Array *  @default  [] */TableTools._aListeners = [];/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Static methods * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *//** * Get an array of all the master instances *  @method  fnGetMasters *  @returns {Array} List of master TableTools instances *  @static */TableTools.fnGetMasters = function (){	var a = [];	for ( var i=0, iLen=TableTools._aInstances.length ; i<iLen ; i++ )	{		if ( TableTools._aInstances[i].s.master )		{			a.push( TableTools._aInstances[i] );		}	}	return a;};/** * Get the master instance for a table node (or id if a string is given) *  @method  fnGetInstance *  @returns {Object} ID of table OR table node, for which we want the TableTools instance *  @static */TableTools.fnGetInstance = function ( node ){	if ( typeof node != 'object' )	{		node = document.getElementById(node);	}		for ( var i=0, iLen=TableTools._aInstances.length ; i<iLen ; i++ )	{		if ( TableTools._aInstances[i].s.master && TableTools._aInstances[i].dom.table == node )		{			return TableTools._aInstances[i];		}	}	return null;};/** * Add a listener for a specific event *  @method  _fnEventListen *  @param   {Object} that Scope of the listening function (i.e. 'this' in the caller) *  @param   {String} type Event type *  @param   {Function} fn Function *  @returns void *  @private *  @static */TableTools._fnEventListen = function ( that, type, fn ){	TableTools._aListeners.push( {		"that": that,		"type": type,		"fn": fn	} );};	/** * An event has occurred - look up every listener and fire it off. We check that the event we are * going to fire is attached to the same table (using the table node as reference) before firing *  @method  _fnEventDispatch *  @param   {Object} that Scope of the listening function (i.e. 'this' in the caller) *  @param   {String} type Event type *  @param   {Node} node Element that the event occurred on (may be null) *  @param   {boolean} [selected] Indicate if the node was selected (true) or deselected (false) *  @returns void *  @private *  @static */TableTools._fnEventDispatch = function ( that, type, node, selected ){	var listeners = TableTools._aListeners;	for ( var i=0, iLen=listeners.length ; i<iLen ; i++ )	{		if ( that.dom.table == listeners[i].that.dom.table && listeners[i].type == type )		{			listeners[i].fn( node, selected );		}	}};/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constants * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */TableTools.buttonBase = {	// Button base	"sAction": "text",	"sTag": "default",	"sLinerTag": "default",	"sButtonClass": "DTTT_button_text",	"sButtonText": "Button text",	"sTitle": "",	"sToolTip": "",	// Common button specific options	"sCharSet": "utf8",	"bBomInc": false,	"sFileName": "*.csv",	"sFieldBoundary": "",	"sFieldSeperator": "\t",	"sNewLine": "auto",	"mColumns": "all", /* "all", "visible", "hidden" or array of column integers */	"bHeader": true,	"bFooter": true,	"bOpenRows": false,	"bSelectedOnly": false,	// Callbacks	"fnMouseover": null,	"fnMouseout": null,	"fnClick": null,	"fnSelect": null,	"fnComplete": null,	"fnInit": null,	"fnCellRender": null};/** * @namespace Default button configurations */TableTools.BUTTONS = {	"csv": $.extend( {}, TableTools.buttonBase, {		"sAction": "flash_save",		"sButtonClass": "DTTT_button_csv",		"sButtonText": "CSV",		"sFieldBoundary": '"',		"sFieldSeperator": ",",		"fnClick": function( nButton, oConfig, flash ) {			this.fnSetText( flash, this.fnGetTableData(oConfig) );		}	} ),	"xls": $.extend( {}, TableTools.buttonBase, {		"sAction": "flash_save",		"sCharSet": "utf16le",		"bBomInc": true,		"sButtonClass": "DTTT_button_xls",		"sButtonText": "Excel",		"fnClick": function( nButton, oConfig, flash ) {			this.fnSetText( flash, this.fnGetTableData(oConfig) );		}	} ),	"copy": $.extend( {}, TableTools.buttonBase, {		"sAction": "flash_copy",		"sButtonClass": "DTTT_button_copy",		"sButtonText": "Copy",		"fnClick": function( nButton, oConfig, flash ) {			this.fnSetText( flash, this.fnGetTableData(oConfig) );		},		"fnComplete": function(nButton, oConfig, flash, text) {			var				lines = text.split('\n').length,				len = this.s.dt.nTFoot === null ? lines-1 : lines-2,				plural = (len==1) ? "" : "s";			this.fnInfo( '<h6>Table copied</h6>'+				'<p>Copied '+len+' row'+plural+' to the clipboard.</p>',				1500			);		}	} ),	"pdf": $.extend( {}, TableTools.buttonBase, {		"sAction": "flash_pdf",		"sNewLine": "\n",		"sFileName": "*.pdf",		"sButtonClass": "DTTT_button_pdf",		"sButtonText": "PDF",		"sPdfOrientation": "portrait",		"sPdfSize": "A4",		"sPdfMessage": "",		"fnClick": function( nButton, oConfig, flash ) {			this.fnSetText( flash, 				"title:"+ this.fnGetTitle(oConfig) +"\n"+				"message:"+ oConfig.sPdfMessage +"\n"+				"colWidth:"+ this.fnCalcColRatios(oConfig) +"\n"+				"orientation:"+ oConfig.sPdfOrientation +"\n"+				"size:"+ oConfig.sPdfSize +"\n"+				"--/TableToolsOpts--\n" +				this.fnGetTableData(oConfig)			);		}	} ),	"print": $.extend( {}, TableTools.buttonBase, {		"sInfo": "<h6>Print view</h6><p>Please use your browser's print function to "+		  "print this table. Press escape when finished.",		"sMessage": null,		"bShowAll": true,		"sToolTip": "View print view",		"sButtonClass": "DTTT_button_print",		"sButtonText": "Print",		"fnClick": function ( nButton, oConfig ) {			this.fnPrint( true, oConfig );		}	} ),	"text": $.extend( {}, TableTools.buttonBase ),	"select": $.extend( {}, TableTools.buttonBase, {		"sButtonText": "Select button",		"fnSelect": function( nButton, oConfig ) {			if ( this.fnGetSelected().length !== 0 ) {				$(nButton).removeClass( this.classes.buttons.disabled );			} else {				$(nButton).addClass( this.classes.buttons.disabled );			}		},		"fnInit": function( nButton, oConfig ) {			$(nButton).addClass( this.classes.buttons.disabled );		}	} ),	"select_single": $.extend( {}, TableTools.buttonBase, {		"sButtonText": "Select button",		"fnSelect": function( nButton, oConfig ) {			var iSelected = this.fnGetSelected().length;			if ( iSelected == 1 ) {				$(nButton).removeClass( this.classes.buttons.disabled );			} else {				$(nButton).addClass( this.classes.buttons.disabled );			}		},		"fnInit": function( nButton, oConfig ) {			$(nButton).addClass( this.classes.buttons.disabled );		}	} ),	"select_all": $.extend( {}, TableTools.buttonBase, {		"sButtonText": "Select all",		"fnClick": function( nButton, oConfig ) {			this.fnSelectAll();		},		"fnSelect": function( nButton, oConfig ) {			if ( this.fnGetSelected().length == this.s.dt.fnRecordsDisplay() ) {				$(nButton).addClass( this.classes.buttons.disabled );			} else {				$(nButton).removeClass( this.classes.buttons.disabled );			}		}	} ),	"select_none": $.extend( {}, TableTools.buttonBase, {		"sButtonText": "Deselect all",		"fnClick": function( nButton, oConfig ) {			this.fnSelectNone();		},		"fnSelect": function( nButton, oConfig ) {			if ( this.fnGetSelected().length !== 0 ) {				$(nButton).removeClass( this.classes.buttons.disabled );			} else {				$(nButton).addClass( this.classes.buttons.disabled );			}		},		"fnInit": function( nButton, oConfig ) {			$(nButton).addClass( this.classes.buttons.disabled );		}	} ),	"ajax": $.extend( {}, TableTools.buttonBase, {		"sAjaxUrl": "http://disabilitycompendium.org/xhr.php",		"sButtonText": "Ajax button",		"fnClick": function( nButton, oConfig ) {			var sData = this.fnGetTableData(oConfig);			$.ajax( {				"url": oConfig.sAjaxUrl,				"data": [					{ "name": "tableData", "value": sData }				],				"success": oConfig.fnAjaxComplete,				"dataType": "json",				"type": "POST", 				"cache": false,				"error": function () {					alert( "Error detected when sending table data to server" );				}			} );		},		"fnAjaxComplete": function( json ) {			alert( 'Ajax complete' );		}	} ),	"div": $.extend( {}, TableTools.buttonBase, {		"sAction": "div",		"sTag": "div",		"sButtonClass": "DTTT_nonbutton",		"sButtonText": "Text button"	} ),	"collection": $.extend( {}, TableTools.buttonBase, {		"sAction": "collection",		"sButtonClass": "DTTT_button_collection",		"sButtonText": "Collection",		"fnClick": function( nButton, oConfig ) {			this._fnCollectionShow(nButton, oConfig);		}	} )};/* *  on* callback parameters: *  	1. node - button element *  	2. object - configuration object for this button *  	3. object - ZeroClipboard reference (flash button only) *  	4. string - Returned string from Flash (flash button only - and only on 'complete') *//** * @namespace Classes used by TableTools - allows the styles to be override easily. *   Note that when TableTools initialises it will take a copy of the classes object *   and will use its internal copy for the remainder of its run time. */TableTools.classes = {	"container": "DTTT_container",	"buttons": {		"normal": "DTTT_button",		"disabled": "DTTT_disabled"	},	"collection": {		"container": "DTTT_collection",		"background": "DTTT_collection_background",		"buttons": {			"normal": "DTTT_button",			"disabled": "DTTT_disabled"		}	},	"select": {		"table": "DTTT_selectable",		"row": "DTTT_selected"	},	"print": {		"body": "DTTT_Print",		"info": "DTTT_print_info",		"message": "DTTT_PrintMessage"	}};/** * @namespace ThemeRoller classes - built in for compatibility with DataTables'  *   bJQueryUI option. */TableTools.classes_themeroller = {	"container": "DTTT_container ui-buttonset ui-buttonset-multi",	"buttons": {		"normal": "DTTT_button ui-button ui-state-default"	},	"collection": {		"container": "DTTT_collection ui-buttonset ui-buttonset-multi"	}};/** * @namespace TableTools default settings for initialisation */TableTools.DEFAULTS = {	"sSwfPath":        "copy_csv_xls_pdf-2.swf"/*tpa=http://disabilitycompendium.org/datatables/tabletools/media/js/media/swf/copy_csv_xls_pdf.swf*/,	"sRowSelect":      "none",	"sSelectedClass":  null,	"fnPreRowSelect":  null,	"fnRowSelected":   null,	"fnRowDeselected": null,	"aButtons":        [ "copy", "csv", "xls", "pdf", "print" ],	"oTags": {		"container": "div",		"button": "a", // We really want to use buttons here, but Firefox and IE ignore the		                 // click on the Flash element in the button (but not mouse[in|out]).		"liner": "span",		"collection": {			"container": "div",			"button": "a",			"liner": "span"		}	}};/** * Name of this class *  @constant CLASS *  @type	 String *  @default  TableTools */TableTools.prototype.CLASS = "TableTools";/** * TableTools version *  @constant  VERSION *  @type	  String *  @default   See code */TableTools.VERSION = "2.1.4";TableTools.prototype.VERSION = TableTools.VERSION;/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Initialisation * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *//* * Register a new feature with DataTables */if ( typeof $.fn.dataTable == "function" &&	 typeof $.fn.dataTableExt.fnVersionCheck == "function" &&	 $.fn.dataTableExt.fnVersionCheck('1.9.0') ){	$.fn.dataTableExt.aoFeatures.push( {		"fnInit": function( oDTSettings ) {			var oOpts = typeof oDTSettings.oInit.oTableTools != 'undefined' ? 				oDTSettings.oInit.oTableTools : {};						var oTT = new TableTools( oDTSettings.oInstance, oOpts );			TableTools._aInstances.push( oTT );						return oTT.dom.container;		},		"cFeature": "T",		"sFeature": "TableTools"	} );}else{	alert( "Warning: TableTools 2 requires DataTables 1.9.0 or newer - www.datatables.net/download");}$.fn.DataTable.TableTools = TableTools;})(jQuery, window, document);