/*! TableTools 2.2.4

 * 2009-2015 SpryMedia Ltd - datatables.net/license

 *

 * ZeroClipboard 1.0.4

 * Author: Joseph Huckaby - MIT licensed

 */



/**

 * @summary     TableTools

 * @description Tools and buttons for DataTables

 * @version     2.2.4

 * @file        dataTables.tableTools.js

 * @author      SpryMedia Ltd (www.sprymedia.co.uk)

 * @contact     www.sprymedia.co.uk/contact

 * @copyright   Copyright 2009-2015 SpryMedia Ltd.

 *

 * This source file is free software, available under the following license:

 *   MIT license - http://datatables.net/license/mit

 *

 * This source file is distributed in the hope that it will be useful, but

 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY

 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.

 *

 * For details please refer to: http://www.datatables.net

 */





/* Global scope for TableTools for backwards compatibility.

 * Will be removed in 2.3

 */

var TableTools;



(function(window, document, undefined) {





var factory = function( $, DataTable ) {

"use strict";





//include ZeroClipboard.js

/* ZeroClipboard 1.0.4

 * Author: Joseph Huckaby

 */



var ZeroClipboard_TableTools = {



	version: "1.0.4-TableTools2",

	clients: {}, // registered upload clients on page, indexed by id

	moviePath: '', // URL to movie

	nextId: 1, // ID of next movie



	$: function(thingy) {

		// simple DOM lookup utility function

		if (typeof(thingy) == 'string') {

			thingy = document.getElementById(thingy);

		}

		if (!thingy.addClass) {

			// extend element with a few useful methods

			thingy.hide = function() { this.style.display = 'none'; };

			thingy.show = function() { this.style.display = ''; };

			thingy.addClass = function(name) { this.removeClass(name); this.className += ' ' + name; };

			thingy.removeClass = function(name) {

				this.className = this.className.replace( new RegExp("\\s*" + name + "\\s*"), " ").replace(/^\s+/, '').replace(/\s+$/, '');

			};

			thingy.hasClass = function(name) {

				return !!this.className.match( new RegExp("\\s*" + name + "\\s*") );

			};

		}

		return thingy;

	},



	setMoviePath: function(path) {

		// set path to ZeroClipboard.swf

		this.moviePath = path;

	},



	dispatch: function(id, eventName, args) {

		// receive event from flash movie, send to client

		var client = this.clients[id];

		if (client) {

			client.receiveEvent(eventName, args);

		}

	},



	register: function(id, client) {

		// register new client to receive events

		this.clients[id] = client;

	},



	getDOMObjectPosition: function(obj) {

		// get absolute coordinates for dom element

		var info = {

			left: 0,

			top: 0,

			width: obj.width ? obj.width : obj.offsetWidth,

			height: obj.height ? obj.height : obj.offsetHeight

		};



		if ( obj.style.width !== "" ) {

			info.width = obj.style.width.replace("px","");

		}



		if ( obj.style.height !== "" ) {

			info.height = obj.style.height.replace("px","");

		}



		while (obj) {

			info.left += obj.offsetLeft;

			info.top += obj.offsetTop;

			obj = obj.offsetParent;

		}



		return info;

	},



	Client: function(elem) {

		// constructor for new simple upload client

		this.handlers = {};



		// unique ID

		this.id = ZeroClipboard_TableTools.nextId++;

		this.movieId = 'ZeroClipboard_TableToolsMovie_' + this.id;



		// register client with singleton to receive flash events

		ZeroClipboard_TableTools.register(this.id, this);



		// create movie

		if (elem) {

			this.glue(elem);

		}

	}

};



ZeroClipboard_TableTools.Client.prototype = {



	id: 0, // unique ID for us

	ready: false, // whether movie is ready to receive events or not

	movie: null, // reference to movie object

	clipText: '', // text to copy to clipboard

	fileName: '', // default file save name

	action: 'copy', // action to perform

	handCursorEnabled: true, // whether to show hand cursor, or default pointer cursor

	cssEffects: true, // enable CSS mouse effects on dom container

	handlers: null, // user event handlers

	sized: false,



	glue: function(elem, title) {

		// glue to DOM element

		// elem can be ID or actual DOM element object

		this.domElement = ZeroClipboard_TableTools.$(elem);



		// float just above object, or zIndex 99 if dom element isn't set

		var zIndex = 99;

		if (this.domElement.style.zIndex) {

			zIndex = parseInt(this.domElement.style.zIndex, 10) + 1;

		}



		// find X/Y position of domElement

		var box = ZeroClipboard_TableTools.getDOMObjectPosition(this.domElement);



		// create floating DIV above element

		this.div = document.createElement('div');

		var style = this.div.style;

		style.position = 'absolute';

		style.left = '0px';

		style.top = '0px';

		style.width = (box.width) + 'px';

		style.height = box.height + 'px';

		style.zIndex = zIndex;



		if ( typeof title != "undefined" && title !== "" ) {

			this.div.title = title;

		}

		if ( box.width !== 0 && box.height !== 0 ) {

			this.sized = true;

		}



		// style.backgroundColor = '#f00'; // debug

		if ( this.domElement ) {

			this.domElement.appendChild(this.div);

			this.div.innerHTML = this.getHTML( box.width, box.height ).replace(/&/g, '&amp;');

		}

	},



	positionElement: function() {

		var box = ZeroClipboard_TableTools.getDOMObjectPosition(this.domElement);

		var style = this.div.style;



		style.position = 'absolute';

		//style.left = (this.domElement.offsetLeft)+'px';

		//style.top = this.domElement.offsetTop+'px';

		style.width = box.width + 'px';

		style.height = box.height + 'px';



		if ( box.width !== 0 && box.height !== 0 ) {

			this.sized = true;

		} else {

			return;

		}



		var flash = this.div.childNodes[0];

		flash.width = box.width;

		flash.height = box.height;

	},



	getHTML: function(width, height) {

		// return HTML for movie

		var html = '';

		var flashvars = 'id=' + this.id +

			'&width=' + width +

			'&height=' + height;



		if (navigator.userAgent.match(/MSIE/)) {

			// IE gets an OBJECT tag

			var protocol = location.href.match(/^https/i) ? 'https://' : 'http://';

			html += '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="'+protocol+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=10,0,0,0" width="'+width+'" height="'+height+'" id="'+this.movieId+'" align="middle"><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="'+ZeroClipboard_TableTools.moviePath+'" /><param name="loop" value="false" /><param name="menu" value="false" /><param name="quality" value="best" /><param name="bgcolor" value="#ffffff" /><param name="flashvars" value="'+flashvars+'"/><param name="wmode" value="transparent"/></object>';

		}

		else {

			// all other browsers get an EMBED tag

			html += '<embed id="'+this.movieId+'" src="'+ZeroClipboard_TableTools.moviePath+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="'+width+'" height="'+height+'" name="'+this.movieId+'" align="middle" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+flashvars+'" wmode="transparent" />';

		}

		return html;

	},



	hide: function() {

		// temporarily hide floater offscreen

		if (this.div) {

			this.div.style.left = '-2000px';

		}

	},



	show: function() {

		// show ourselves after a call to hide()

		this.reposition();

	},



	destroy: function() {

		// destroy control and floater

		if (this.domElement && this.div) {

			this.hide();

			this.div.innerHTML = '';



			var body = document.getElementsByTagName('body')[0];

			try { body.removeChild( this.div ); } catch(e) {}



			this.domElement = null;

			this.div = null;

		}

	},



	reposition: function(elem) {

		// reposition our floating div, optionally to new container

		// warning: container CANNOT change size, only position

		if (elem) {

			this.domElement = ZeroClipboard_TableTools.$(elem);

			if (!this.domElement) {

				this.hide();

			}

		}



		if (this.domElement && this.div) {

			var box = ZeroClipboard_TableTools.getDOMObjectPosition(this.domElement);

			var style = this.div.style;

			style.left = '' + box.left + 'px';

			style.top = '' + box.top + 'px';

		}

	},



	clearText: function() {

		// clear the text to be copy / saved

		this.clipText = '';

		if (this.ready) {

			this.movie.clearText();

		}

	},



	appendText: function(newText) {

		// append text to that which is to be copied / saved

		this.clipText += newText;

		if (this.ready) { this.movie.appendText(newText) ;}

	},



	setText: function(newText) {

		// set text to be copied to be copied / saved

		this.clipText = newText;

		if (this.ready) { this.movie.setText(newText) ;}

	},



	setCharSet: function(charSet) {

		// set the character set (UTF16LE or UTF8)

		this.charSet = charSet;

		if (this.ready) { this.movie.setCharSet(charSet) ;}

	},



	setBomInc: function(bomInc) {

		// set if the BOM should be included or not

		this.incBom = bomInc;

		if (this.ready) { this.movie.setBomInc(bomInc) ;}

	},



	setFileName: function(newText) {

		// set the file name

		this.fileName = newText;

		if (this.ready) {

			this.movie.setFileName(newText);

		}

	},



	setAction: function(newText) {

		// set action (save or copy)

		this.action = newText;

		if (this.ready) {

			this.movie.setAction(newText);

		}

	},



	addEventListener: function(eventName, func) {

		// add user event listener for event

		// event types: load, queueStart, fileStart, fileComplete, queueComplete, progress, error, cancel

		eventName = eventName.toString().toLowerCase().replace(/^on/, '');

		if (!this.handlers[eventName]) {

			this.handlers[eventName] = [];

		}

		this.handlers[eventName].push(func);

	},



	setHandCursor: function(enabled) {

		// enable hand cursor (true), or default arrow cursor (false)

		this.handCursorEnabled = enabled;

		if (this.ready) {

			this.movie.setHandCursor(enabled);

		}

	},



	setCSSEffects: function(enabled) {

		// enable or disable CSS effects on DOM container

		this.cssEffects = !!enabled;

	},



	receiveEvent: function(eventName, args) {

		var self;



		// receive event from flash

		eventName = eventName.toString().toLowerCase().replace(/^on/, '');



		// special behavior for certain events

		switch (eventName) {

			case 'load':

				// movie claims it is ready, but in IE this isn't always the case...

				// bug fix: Cannot extend EMBED DOM elements in Firefox, must use traditional function

				this.movie = document.getElementById(this.movieId);

				if (!this.movie) {

					self = this;

					setTimeout( function() { self.receiveEvent('load', null); }, 1 );

					return;

				}



				// firefox on pc needs a "kick" in order to set these in certain cases

				if (!this.ready && navigator.userAgent.match(/Firefox/) && navigator.userAgent.match(/Windows/)) {

					self = this;

					setTimeout( function() { self.receiveEvent('load', null); }, 100 );

					this.ready = true;

					return;

				}



				this.ready = true;

				this.movie.clearText();

				this.movie.appendText( this.clipText );

				this.movie.setFileName( this.fileName );

				this.movie.setAction( this.action );

				this.movie.setCharSet( this.charSet );

				this.movie.setBomInc( this.incBom );

				this.movie.setHandCursor( this.handCursorEnabled );

				break;



			case 'mouseover':

				if (this.domElement && this.cssEffects) {

					//this.domElement.addClass('hover');

					if (this.recoverActive) {

						this.domElement.addClass('active');

					}

				}

				break;



			case 'mouseout':

				if (this.domElement && this.cssEffects) {

					this.recoverActive = false;

					if (this.domElement.hasClass('active')) {

						this.domElement.removeClass('active');

						this.recoverActive = true;

					}

					//this.domElement.removeClass('hover');

				}

				break;



			case 'mousedown':

				if (this.domElement && this.cssEffects) {

					this.domElement.addClass('active');

				}

				break;



			case 'mouseup':

				if (this.domElement && this.cssEffects) {

					this.domElement.removeClass('active');

					this.recoverActive = false;

				}

				break;

		} // switch eventName



		if (this.handlers[eventName]) {

			for (var idx = 0, len = this.handlers[eventName].length; idx < len; idx++) {

				var func = this.handlers[eventName][idx];



				if (typeof(func) == 'function') {

					// actual function reference

					func(this, args);

				}

				else if ((typeof(func) == 'object') && (func.length == 2)) {

					// PHP style object + method, i.e. [myObject, 'myMethod']

					func[0][ func[1] ](this, args);

				}

				else if (typeof(func) == 'string') {

					// name of function

					window[func](this, args);

				}

			} // foreach event handler defined

		} // user defined handler for event

	}



};



// For the Flash binding to work, ZeroClipboard_TableTools must be on the global

// object list

window.ZeroClipboard_TableTools = ZeroClipboard_TableTools;

//include TableTools.js

/* TableTools

 * 2009-2015 SpryMedia Ltd - datatables.net/license

 */



/*globals TableTools,ZeroClipboard_TableTools*/





(function($, window, document) {



/** 

 * TableTools provides flexible buttons and other tools for a DataTables enhanced table

 * @class TableTools

 * @constructor

 * @param {Object} oDT DataTables instance. When using DataTables 1.10 this can

 *   also be a jQuery collection, jQuery selector, table node, DataTables API

 *   instance or DataTables settings object.

 * @param {Object} oOpts TableTools options

 * @param {String} oOpts.sSwfPath ZeroClipboard SWF path

 * @param {String} oOpts.sRowSelect Row selection options - 'none', 'single', 'multi' or 'os'

 * @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'" );

	}



	// In 1.10 we can use the API to get the settings object from a number of

	// sources

	var dtSettings = $.fn.dataTable.Api ?

		new $.fn.dataTable.Api( oDT ).settings()[0] :

		oDT.fnSettings();





	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

	 * 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": dtSettings,



		/**

		 * @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 = {};

	}





	TableTools._aInstances.push( this );

	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;

	},





	/**

	 * Get the indexes of the selected rows

	 *  @returns {array} List of row indexes

	 *  @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.

	 */

	"fnGetSelectedIndexes": 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( displayed[i] );

				}

			}

		}

		else

		{

			// Use all rows

			for ( i=0, iLen=data.length ; i<iLen ; i++ )

			{

				if ( data[i]._DTTT_selected )

				{

					out.push( 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 )

	{

		this._fnRowSelect( filtered ?

			this.s.dt.aiDisplay :

			this.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 )

	{

		this._fnRowDeselect( this.fnGetSelectedIndexes(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

		{

			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 info = $('<div/>')

			.addClass( this.classes.print.info )

			.html( message )

			.appendTo( 'body' );



		setTimeout( function() {

			info.fadeOut( "normal", function() {

				info.remove();

			} );

		}, time );

	},







	/**

	 * Get the container element of the instance for attaching to the DOM

	 *   @returns {node} DOM node

	 */

	"fnContainer": function () {

		return this.dom.container;

	},







	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

	 * 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 */

		this.s.dt.aoDestroyCallback.push( {

			"sName": "TableTools",

			"fn": function () {

				$(that.s.dt.nTBody)

					.off( 'click.DTTT_Select', that.s.custom.sRowSelector )

					.off( 'mousedown.DTTT_Select', 'tr' )

					.off( 'mouseup.DTTT_Select', 'tr' );



				$(that.dom.container).empty();



				// Remove the instance

				var idx = $.inArray( that, TableTools._aInstances );

				if ( idx !== -1 ) {

					TableTools._aInstances.splice( idx, 1 );

				}

			}

		} );

	},





	/**

	 * 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 );

			}



			var button = this._fnCreateButton(

				buttonDef,

				$(wrapper).hasClass(this.classes.collection.container)

			);



			if ( button ) {

				wrapper.appendChild( button );

			}

		}

	},





	/**

	 * 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/) )

		{

			if ( ! this._fnHasFlash() ) {

				return false;

			}



			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 );

		}



		if ( this.s.dt.iTabIndex !== -1 ) {

			$(nButton)

				.attr( 'tabindex', this.s.dt.iTabIndex )

				.attr( 'aria-controls', this.s.dt.sTableId )

				.on( 'keyup.DTTT', function (e) {

					// Trigger the click event on return key when focused.

					// Note that for Flash buttons this has no effect since we

					// can't programmatically trigger the Flash export

					if ( e.keyCode === 13 ) {

						e.stopPropagation();



						$(this).trigger( 'click' );

					}

				} )

				.on( 'mousedown.DTTT', function (e) {

					// On mousedown we want to stop the focus occurring on the

					// button, focus is used only for the keyboard navigation.

					// But using preventDefault for the flash buttons stops the

					// flash action. However, it is not the button that gets

					// focused but the flash element for flash buttons, so this

					// works

					if ( ! oConfig.sAction.match(/flash/) ) {

						e.preventDefault();

					}

				} );

		}



		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 && o.sTag !== "default" ? o.sTag : this.s.tags.collection.button;

			sLiner = o.sLinerTag && o.sLinerTag !== "default" ? o.sLiner : this.s.tags.collection.liner;

			sClass = this.classes.collection.buttons.normal;

		}

		else

		{

			sTag = o.sTag && o.sTag !== "default" ? o.sTag : this.s.tags.button;

			sLiner = o.sLinerTag && 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 );



			// When using OS style selection, we want to cancel the shift text

			// selection, but only when the shift key is used (so you can

			// actually still select text in the table)

			if ( this.s.select.type === 'os' ) {

				$(dt.nTBody).on( 'mousedown.DTTT_Select', 'tr', function(e) {

					if ( e.shiftKey ) {



						$(dt.nTBody)

							.css( '-moz-user-select', 'none' )

							.one('selectstart.DTTT_Select', 'tr', function () {

								return false;

							} );

					}

				} );



				$(dt.nTBody).on( 'mouseup.DTTT_Select', 'tr', function(e) {

					$(dt.nTBody).css( '-moz-user-select', '' );

				} );

			}



			// Row selection

			$(dt.nTBody).on( 'click.DTTT_Select', this.s.custom.sRowSelector, function(e) {

				var row = this.nodeName.toLowerCase() === 'tr' ?

					this :

					$(this).parents('tr')[0];



				var select = that.s.select;

				var pos = that.s.dt.oInstance.fnGetPosition( row );



				/* Sub-table must be ignored (odd that the selector won't do this with >) */

				if ( row.parentNode != dt.nTBody ) {

					return;

				}



				/* Check that we are actually working with a DataTables controlled row */

				if ( dt.oInstance.fnGetData(row) === null ) {

				    return;

				}



				// Shift click, ctrl click and simple click handling to make

				// row selection a lot like a file system in desktop OSs

				if ( select.type == 'os' ) {

					if ( e.ctrlKey || e.metaKey ) {

						// Add or remove from the selection

						if ( that.fnIsSelected( row ) ) {

							that._fnRowDeselect( row, e );

						}

						else {

							that._fnRowSelect( row, e );

						}

					}

					else if ( e.shiftKey ) {

						// Add a range of rows, from the last selected row to

						// this one

						var rowIdxs = that.s.dt.aiDisplay.slice(); // visible rows

						var idx1 = $.inArray( select.lastRow, rowIdxs );

						var idx2 = $.inArray( pos, rowIdxs );



						if ( that.fnGetSelected().length === 0 || idx1 === -1 ) {

							// select from top to here - slightly odd, but both

							// Windows and Mac OS do this

							rowIdxs.splice( $.inArray( pos, rowIdxs )+1, rowIdxs.length );

						}

						else {

							// reverse so we can shift click 'up' as well as down

							if ( idx1 > idx2 ) {

								var tmp = idx2;

								idx2 = idx1;

								idx1 = tmp;

							}



							rowIdxs.splice( idx2+1, rowIdxs.length );

							rowIdxs.splice( 0, idx1 );

						}



						if ( ! that.fnIsSelected( row ) ) {

							// Select range

							that._fnRowSelect( rowIdxs, e );

						}

						else {

							// Deselect range - need to keep the clicked on row selected

							rowIdxs.splice( $.inArray( pos, rowIdxs ), 1 );

							that._fnRowDeselect( rowIdxs, e );

						}

					}

					else {

						// No cmd or shift click. Deselect current if selected,

						// or select this row only

						if ( that.fnIsSelected( row ) && that.fnGetSelected().length === 1 ) {

							that._fnRowDeselect( row, e );

						}

						else {

							that.fnSelectNone();

							that._fnRowSelect( row, e );

						}

					}

				}

				else if ( that.fnIsSelected( row ) ) {

					that._fnRowDeselect( row, e );

				}

				else if ( select.type == "single" ) {

					that.fnSelectNone();

					that._fnRowSelect( row, e );

				}

				else if ( select.type == "multi" ) {

					that._fnRowSelect( row, e );

				}



				select.lastRow = pos;

			} );//.on('selectstart', function () { return false; } );



			// 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 if ( typeof src === 'number' )

		{

			out.push(this.s.dt.aoData[src]);

		}

		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, e );

			}



			/* 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

	 */

	

	/**

	 * Check if the Flash plug-in is available

	 *  @method  _fnHasFlash

	 *  @returns {boolean} `true` if Flash available, `false` otherwise

	 *  @private 

	 */

	"_fnHasFlash": function ()

	{

		try {

			var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');

			if (fo) {

				return true;

			}

		}

		catch (e) {

			if (

				navigator.mimeTypes &&

				navigator.mimeTypes['application/x-shockwave-flash'] !== undefined &&

				navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin

			) {

				return true;

			}

		}



		return false;

	},





	/**

	 * 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 );

		} );



		if ( oConfig.fnSelect !== null )

		{

			TableTools._fnEventListen( this, 'select', function (n) {

				oConfig.fnSelect.call( that, nButton, oConfig, n );

			} );

		}



		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;

		var i, iLen;

		var columns = dt.aoColumns;

		var columnCount = columns.length;



		if ( typeof mColumns == "function" )

		{

			var a = mColumns.call( this, dt );



			for ( i=0, iLen=columnCount ; i<iLen ; i++ )

			{

				aColumns.push( $.inArray( i, a ) !== -1 ? true : false );

			}

		}

		else if ( typeof mColumns == "object" )

		{

			for ( i=0, iLen=columnCount ; 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=columnCount ; i<iLen ; i++ )

			{

				aColumns.push( columns[i].bVisible ? true : false );

			}

		}

		else if ( mColumns == "hidden" )

		{

			for ( i=0, iLen=columnCount ; i<iLen ; i++ )

			{

				aColumns.push( columns[i].bVisible ? false : true );

			}

		}

		else if ( mColumns == "sortable" )

		{

			for ( i=0, iLen=columnCount ; i<iLen ; i++ )

			{

				aColumns.push( columns[i].bSortable ? true : false );

			}

		}

		else /* all */

		{

			for ( i=0, iLen=columnCount ; 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) );

		}



		bSelectedOnly = true;



		/*

		 * Body

		 */

		var aDataIndex;

		var aSelected = this.fnGetSelectedIndexes();

		bSelectedOnly = this.s.select.type !== "none" && bSelectedOnly && aSelected.length !== 0;



		if ( bSelectedOnly ) {

			// Use the selected indexes

			aDataIndex = aSelected;

		}

		else if ( DataTable.Api ) {

			// 1.10+ style

			aDataIndex = new DataTable.Api( dt )

				.rows( oConfig.oSelectorOpts )

				.indexes()

				.flatten()

				.toArray();

		}

		else {

			// 1.9- style

			aDataIndex = dt.oInstance

				.$('tr', oConfig.oSelectorOpts)

				.map( function (id, row) {

					return dt.oInstance.fnGetPosition( row );

				} )

				.get();

		}



		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) );

		}



		var _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;

			if ( oSetDT.oApi._fnCalculateEnd ) {

				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 )

		{

			$('<div/>')

				.addClass( this.classes.print.message )

				.html( oConfig.sMessage )

				.prependTo( 'body' );

		}



		/* 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( "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 */

		$('div.'+this.classes.print.message).remove();



		/* Styling class */

		$(document.body).removeClass( 'DTTT_Print' );



		/* Restore the table length */

		oSetDT._iDisplayStart = oSetPrint.saveStart;

		oSetDT._iDisplayLength = oSetPrint.saveLength;

		if ( oSetDT.oApi._fnCalculateEnd ) {

			oSetDT.oApi._fnCalculateEnd( oSetDT );

		}

		oSetDT.oApi._fnDraw( oSetDT );



		$(document).unbind( "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,

			nTheadSize, nTfootSize;



		/* 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

		 */

		nTheadSize = oSetDT.nTable.getElementsByTagName('thead');

		if ( nTheadSize.length > 0 )

		{

			oSetDT.nTable.removeChild( nTheadSize[0] );

		}



		if ( oSetDT.nTFoot !== null )

		{

			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.toUpperCase() != "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,

	"oSelectorOpts": undefined, // See http://datatables.net/docs/DataTables/1.9.4/#$ for full options



	// 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;

            if (oConfig.bHeader) lines--;

            if (this.s.dt.nTFoot !== null && oConfig.bFooter) lines--;

			var plural = (lines==1) ? "" : "s";

			this.fnInfo( '<h6>Table copied</h6>'+

				'<p>Copied '+lines+' 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.</p>",

		"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": "/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')

 */



// Alias to match the other plug-ins styling

TableTools.buttons = TableTools.BUTTONS;





/**

 * @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 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":        "../swf/copy_csv_xls_pdf.swf",

	"sRowSelect":      "none",

	"sRowSelector":    "tr",

	"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"

		}

	}

};



// Alias to match the other plug-ins

TableTools.defaults = TableTools.DEFAULTS;





/**

 * 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.2.4";







// DataTables 1.10 API

// 

// This will be extended in a big way in in TableTools 3 to provide API methods

// such as rows().select() and rows.selected() etc, but for the moment the

// tabletools() method simply returns the instance.



if ( $.fn.dataTable.Api ) {

	$.fn.dataTable.Api.register( 'tabletools()', function () {

		var tt = null;



		if ( this.context.length > 0 ) {

			tt = TableTools.fnGetInstance( this.context[0].nTable );

		}



		return tt;

	} );

}









/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

 * 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 init = oDTSettings.oInit;

			var opts = init ?

				init.tableTools || init.oTableTools || {} :

				{};



			return new TableTools( oDTSettings.oInstance, opts ).dom.container;

		},

		"cFeature": "T",

		"sFeature": "TableTools"

	} );

}

else

{

	alert( "Warning: TableTools requires DataTables 1.9.0 or newer - www.datatables.net/download");

}



$.fn.DataTable.TableTools = TableTools;



})(jQuery, window, document);



/*

 * 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;

$.fn.DataTable.TableTools = TableTools;





return TableTools;

}; // /factory





// Define as an AMD module if possible

if ( typeof define === 'function' && define.amd ) {

	define( ['jquery', 'datatables'], factory );

}

else if ( typeof exports === 'object' ) {

    // Node/CommonJS

    factory( require('jquery'), require('datatables') );

}

else if ( jQuery && !jQuery.fn.dataTable.TableTools ) {

	// Otherwise simply initialise as normal, stopping multiple evaluation

	factory( jQuery, jQuery.fn.dataTable );

}





})(window, document);



