1 /*
  2  * File:        TableTools.js
  3  * Version:     2.0.0
  4  * Description: Tools and buttons for DataTables
  5  * Author:      Allan Jardine (www.sprymedia.co.uk)
  6  * Language:    Javascript
  7  * License:     LGPL / 3 point BSD
  8  * Project:     DataTables
  9  * 
 10  * Copyright 2009-2010 Allan Jardine, all rights reserved.
 11  */
 12 
 13 /* Global scope for TableTools */
 14 var TableTools;
 15 
 16 (function($, window, document) {
 17 
 18 /** 
 19  * TableTools provides flexible buttons and other tools for a DataTables enhanced table
 20  * @class TableTools
 21  * @constructor
 22  * @param {Object} oDT DataTables instance
 23  * @param {Object} oOpts TableTools options
 24  * @param {String} oOpts.sSwfPath ZeroClipboard SWF path
 25  * @param {String} oOpts.sRowSelect Row selection options - 'none', 'single' or 'multi'
 26  * @param {Function} oOpts.fnPreRowSelect Callback function just prior to row selection
 27  * @param {Function} oOpts.fnRowSelected Callback function just after row selection
 28  * @param {Function} oOpts.fnRowDeselected Callback function when row is deselected
 29  * @param {Array} oOpts.aButtons List of buttons to be used
 30  */
 31 TableTools = function( oDT, oOpts )
 32 {
 33 	/* Santiy check that we are a new instance */
 34 	if ( !this.CLASS || this.CLASS != "TableTools" )
 35 	{
 36 		alert( "Warning: TableTools must be initialised with the keyword 'new'" );
 37 	}
 38 	
 39 	
 40 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 41 	 * Public class variables
 42 	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 43 	
 44 	/**
 45 	 * @namespace Settings object which contains customisable information for TableTools instance
 46 	 */
 47 	this.s = {
 48     /**
 49      * Store 'this' so the instance can be retreieved from the settings object
 50      *  @property that
 51      *  @type     object
 52      *  @default  this
 53      */
 54 		that: this,
 55 		
 56 		/** 
 57 		 * DataTables settings objects
 58      *  @property dt
 59      *  @type     object
 60      *  @default  null
 61 		 */
 62 		dt: null,
 63 		
 64 		/**
 65 		 * @namespace Print specific information
 66 		 */
 67 		print: {
 68 			/** 
 69 			 * DataTables draw 'start' point before the printing display was shown
 70        *  @property saveStart
 71 			 *  @type     int
 72        *  @default  -1
 73 		 	 */
 74 		  saveStart: -1,
 75 			
 76 			/** 
 77 			 * DataTables draw 'length' point before the printing display was shown
 78        *  @property saveLength
 79 			 *  @type     int
 80        *  @default  -1
 81 		 	 */
 82 		  saveLength: -1,
 83 		
 84 			/** 
 85 			 * Page scrolling point before the printing display was shown so it can be restored
 86        *  @property saveScroll
 87 			 *  @type     int
 88        *  @default  -1
 89 		 	 */
 90 		  saveScroll: -1,
 91 		
 92 			/** 
 93 			 * Wrapped function to end the print display (to maintain scope)
 94        *  @property funcEnd
 95 		 	 *  @type     Function
 96        *  @default  function () {}
 97 		 	 */
 98 		  funcEnd: function () {}
 99 	  },
100 	
101 		/**
102 		 * A unique ID is assigned to each button in each instance
103      *  @property buttonCounter
104 		 *  @type     int
105      *  @default  0
106 		 */
107 	  buttonCounter: 0,
108 		
109 		/**
110 		 * @namespace Select rows specific information
111 		 */
112 		select: {
113 			/**
114 			 * Select type - can be 'none', 'single' or 'multi'
115        *  @property type
116 			 *  @type     string
117        *  @default  ""
118 			 */
119 			type: "",
120 			
121 			/**
122 			 * Array of nodes which are currently selected
123        *  @property selected
124 			 *  @type     array
125        *  @default  []
126 			 */
127 			selected: [],
128 			
129 			/**
130 			 * Function to run before the selection can take place. Will cancel the select if the
131 			 * function returns false
132        *  @property preRowSelect
133 			 *  @type     Function
134        *  @default  null
135 			 */
136 			preRowSelect: null,
137 			
138 			/**
139 			 * Function to run when a row is selected
140        *  @property postSelected
141 			 *  @type     Function
142        *  @default  null
143 			 */
144 			postSelected: null,
145 			
146 			/**
147 			 * Function to run when a row is deselected
148        *  @property postDeselected
149 			 *  @type     Function
150        *  @default  null
151 			 */
152 			postDeselected: null,
153 			
154 			/**
155 			 * Indicate if all rows are selected (needed for server-side processing)
156        *  @property all
157 			 *  @type     boolean
158        *  @default  false
159 			 */
160 			all: false
161 		},
162 		
163 		/**
164 		 * Store of the user input customisation object
165      *  @property custom
166 		 *  @type     object
167      *  @default  {}
168 		 */
169 		custom: {},
170 		
171 		/**
172 		 * SWF movie path
173      *  @property swfPath
174 		 *  @type     string
175      *  @default  ""
176 		 */
177 		swfPath: "",
178 		
179 		/**
180 		 * Default button set
181      *  @property buttonSet
182 		 *  @type     array
183      *  @default  []
184 		 */
185 		buttonSet: [],
186 		
187 		/**
188 		 * When there is more than one TableTools instance for a DataTable, there must be a 
189 		 * master which controls events (row selection etc)
190      *  @property master
191 		 *  @type     boolean
192      *  @default  false
193 		 */
194 		master: false
195 	};
196 	
197 	
198 	/**
199 	 * @namespace Common and useful DOM elements for the class instance
200 	 */
201 	this.dom = {
202 		/**
203 		 * DIV element that is create and all TableTools buttons (and their children) put into
204 		 *  @property container
205 		 *  @type     node
206 		 *  @default  null
207 		 */
208 		container: null,
209 		
210 		/**
211 		 * The table node to which TableTools will be applied
212 		 *  @property table
213 		 *  @type     node
214 		 *  @default  null
215 		 */
216 		table: null,
217 		
218 		/**
219 		 * @namespace Nodes used for the print display
220 		 */
221 		print: {
222 			/**
223 			 * Nodes which have been removed from the display by setting them to display none
224 			 *  @property hidden
225 			 *  @type     array
226 		 	 *  @default  []
227 			 */
228 		  hidden: [],
229 			
230 			/**
231 			 * The information display saying tellng the user about the print display
232 			 *  @property message
233 			 *  @type     node
234 		 	 *  @default  null
235 			 */
236 		  message: null
237 	  },
238 		
239 		/**
240 		 * @namespace Nodes used for a collection display. This contains the currently used collection
241 		 */
242 		collection: {
243 			/**
244 			 * The div wrapper containing the buttons in the collection (i.e. the menu)
245 			 *  @property collection
246 			 *  @type     node
247 		 	 *  @default  null
248 			 */
249 			collection: null,
250 			
251 			/**
252 			 * Background display to provide focus and capture events
253 			 *  @property background
254 			 *  @type     node
255 		 	 *  @default  null
256 			 */
257 			background: null
258 		}
259 	};
260 	
261 	
262 	
263 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
264 	 * Public class methods
265 	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
266 	
267 	/**
268 	 * Retreieve the settings object from an instance
269 	 *  @method fnSettings
270 	 *  @returns {object} TableTools settings object
271 	 */
272 	this.fnSettings = function () {
273 		return this.s;
274 	};
275 	
276 	
277 	/* Constructor logic */
278 	if ( typeof oOpts == 'undefined' )
279 	{
280 		oOpts = {};
281 	}
282 	
283 	this.s.dt = oDT.fnSettings();
284 	this._fnConstruct( oOpts );
285 	
286 	return this;
287 };
288 
289 
290 
291 TableTools.prototype = {
292 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
293 	 * Public methods
294 	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
295 	
296 	/**
297 	 * Retreieve the settings object from an instance
298 	 *  @method fnGetSelected
299 	 *  @returns {array} List of TR nodes which are currently selected
300 	 */
301 	fnGetSelected: function ()
302 	{
303 		var masterS = this._fnGetMasterSettings();
304 		return masterS.select.selected;
305 	},
306 	
307 	/**
308 	 * Check to see if a current row is selected or not
309 	 *  @method fnGetSelected
310 	 *  @param {Node} n TR node to check if it is currently selected or not
311 	 *  @returns {Boolean} true if select, false otherwise
312 	 */
313 	fnIsSelected: function ( n )
314 	{
315 		var selected = this.fnGetSelected();
316 		for ( var i=0, iLen=selected.length ; i<iLen ; i++ )
317 		{
318 			if ( n == selected[i] )
319 			{
320 				return true;
321 			}
322 		}
323 		return false;
324 	},
325 	
326 	/**
327 	 * Select all rows in the table
328 	 *  @method  fnSelectAll
329 	 *  @returns void
330 	 */
331 	fnSelectAll: function ()
332 	{
333 		var masterS = this._fnGetMasterSettings();
334 		masterS.that._fnRowSelectAll();
335 	},
336 	
337 	
338 	/**
339 	 * Deselect all rows in the table
340 	 *  @method  fnSelectNone
341 	 *  @returns void
342 	 */
343 	fnSelectNone: function ()
344 	{
345 		var masterS = this._fnGetMasterSettings();
346 		masterS.that._fnRowDeselectAll();
347 	},
348 	
349 	
350 	/**
351 	 * Get the title of the document - useful for file names. The title is retrieved from either
352 	 * the configuration object's 'title' parameter, or the HTML document title
353 	 *  @method  fnGetTitle
354 	 *  @param   {Object} oConfig Button configuration object
355 	 *  @returns {String} Button title
356 	 */
357 	fnGetTitle: function( oConfig )
358 	{
359 		var sTitle = "";
360 		if ( typeof oConfig.sTitle != 'undefined' && oConfig.sTitle !== "" ) {
361 			sTitle = oConfig.sTitle;
362 		} else {
363 			var anTitle = document.getElementsByTagName('title');
364 			if ( anTitle.length > 0 )
365 			{
366 				sTitle = anTitle[0].innerHTML;
367 			}
368 		}
369 		
370 		/* Strip characters which the OS will object to - checking for UTF8 support in the scripting
371 		 * engine
372 		 */
373 		if ( "\u00A1".toString().length < 4 ) {
374 			return sTitle.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, "");
375 		} else {
376 			return sTitle.replace(/[^a-zA-Z0-9_\.,\-_ !\(\)]/g, "");
377 		}
378 	},
379 	
380 	
381 	/**
382 	 * Calculate a unity array with the column width by proportion for a set of columns to be
383 	 * included for a button. This is particularly useful for PDF creation, where we can use the
384 	 * column widths calculated by the browser to size the columns in the PDF.
385 	 *  @method  fnCalcColRations
386 	 *  @param   {Object} oConfig Button configuration object
387 	 *  @returns {Array} Unity array of column ratios
388 	 */
389 	fnCalcColRatios: function ( oConfig )
390 	{
391 		var
392 			aoCols = this.s.dt.aoColumns,
393 			aColumnsInc = this._fnColumnTargets( oConfig.mColumns ),
394 			aColWidths = [],
395 			iWidth = 0, iTotal = 0, i, iLen;
396 		
397 		for ( i=0, iLen=aColumnsInc.length ; i<iLen ; i++ )
398 		{
399 			if ( aColumnsInc[i] )
400 			{
401 				iWidth = aoCols[i].nTh.offsetWidth;
402 				iTotal += iWidth;
403 				aColWidths.push( iWidth );
404 			}
405 		}
406 		
407 		for ( i=0, iLen=aColWidths.length ; i<iLen ; i++ )
408 		{
409 			aColWidths[i] = aColWidths[i] / iTotal;
410 		}
411 		
412 		return aColWidths.join('\t');
413 	},
414 	
415 	
416 	/**
417 	 * Get the information contained in a table as a string
418 	 *  @method  fnGetTableData
419 	 *  @param   {Object} oConfig Button configuration object
420 	 *  @returns {String} Table data as a string
421 	 */
422 	fnGetTableData: function ( oConfig )
423 	{
424 		/* In future this could be used to get data from a plain HTML source as well as DataTables */
425 		if ( this.s.dt )
426 		{
427 			return this._fnGetDataTablesData( oConfig );
428 		}
429 	},
430 	
431 	
432 	/**
433 	 * Pass text to a flash button instance, which will be used on the button's click handler
434 	 *  @method  fnSetText
435 	 *  @param   {Object} clip Flash button object
436 	 *  @param   {String} text Text to set
437 	 *  @returns void
438 	 */
439 	fnSetText: function ( clip, text )
440 	{
441 		this._fnFlashSetText( clip, text );
442 	},
443 	
444 	
445 	
446 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
447 	 * Private methods (they are of course public in JS, but recommended as private)
448 	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
449 	
450 	/**
451 	 * Constructor logic
452 	 *  @method  _fnConstruct
453 	 *  @param   {Object} oOpts Same as TableTools constructor
454 	 *  @returns void
455 	 *  @private 
456 	 */
457 	_fnConstruct: function ( oOpts )
458 	{
459 		this._fnCustomiseSettings( oOpts );
460 		
461 		/* Container element */
462 		this.dom.container = document.createElement('div');
463 		this.dom.container.style.position = "relative";
464 		this.dom.container.className = !this.s.dt.bJUI ? "DTTT_container" :
465 			"DTTT_container ui-buttonset ui-buttonset-multi";
466 		
467 		/* Row selection config */
468 		if ( this.s.select.type != 'none' )
469 		{
470 			this._fnRowSelectConfig();
471 		}
472 		
473 		/* Buttons */
474 		this._fnButtonDefinations( this.s.buttonSet, this.dom.container );
475 	},
476 	
477 	
478 	/**
479 	 * Take the user defined settings and the default settings and combine them.
480 	 *  @method  _fnCustomiseSettings
481 	 *  @param   {Object} oOpts Same as TableTools constructor
482 	 *  @returns void
483 	 *  @private 
484 	 */
485 	_fnCustomiseSettings: function ( oOpts )
486 	{
487 		/* Is this the master control instance or not? */
488 		if ( typeof this.s.dt._TableToolsInit == 'undefined' )
489 		{
490 			this.s.master = true;
491 			this.s.dt._TableToolsInit = true;
492 		}
493 		
494 		/* We can use the table node from comparisons to group controls */
495 		this.dom.table = this.s.dt.nTable;
496 		
497 		/* Clone the defaults and then the user options */
498 		this.s.custom = $.extend( {}, TableTools.DEFAULTS, oOpts );
499 		
500 		/* Flash file location */
501 		this.s.swfPath = this.s.custom.sSwfPath;
502 		if ( typeof ZeroClipboard != 'undefined' )
503 		{
504 			ZeroClipboard.moviePath = this.s.swfPath;
505 		}
506 		
507 		/* Table row selecting */
508 		this.s.select.type = this.s.custom.sRowSelect;
509 		this.s.select.preRowSelect = this.s.custom.fnPreRowSelect;
510 		this.s.select.postSelected = this.s.custom.fnRowSelected;
511 		this.s.select.postDeselected = this.s.custom.fnRowDeselected;
512 		
513 		/* Button set */
514 		this.s.buttonSet = this.s.custom.aButtons;
515 	},
516 	
517 	
518 	/**
519 	 * Take the user input arrays and expand them to be fully defined, and then add them to a given
520 	 * DOM element
521 	 *  @method  _fnButtonDefinations
522 	 *  @param {array} buttonSet Set of user defined buttons
523 	 *  @param {node} wrapper Node to add the created buttons to
524 	 *  @returns void
525 	 *  @private 
526 	 */
527 	_fnButtonDefinations: function ( buttonSet, wrapper )
528 	{
529 		var buttonDef;
530 		
531 		for ( var i=0, iLen=buttonSet.length ; i<iLen ; i++ )
532 		{
533 			if ( typeof buttonSet[i] == "string" )
534 			{
535 				if ( typeof TableTools.BUTTONS[ buttonSet[i] ] == 'undefined' )
536 				{
537 					alert( "TableTools: Warning - unknown button type: "+buttonSet[i] );
538 					continue;
539 				}
540 				buttonDef = $.extend( {}, TableTools.BUTTONS[ buttonSet[i] ], true );
541 			}
542 			else
543 			{
544 				if ( typeof TableTools.BUTTONS[ buttonSet[i].sExtends ] == 'undefined' )
545 				{
546 					alert( "TableTools: Warning - unknown button type: "+buttonSet[i].sExtends );
547 					continue;
548 				}
549 				var o = $.extend( {}, TableTools.BUTTONS[ buttonSet[i].sExtends ], true );
550 				buttonDef = $.extend( o, buttonSet[i], true );
551 			}
552 			
553 			if ( this.s.dt.bJUI )
554 			{
555 				buttonDef.sButtonClass += " ui-button ui-state-default";
556 				buttonDef.sButtonClassHover += " ui-button ui-state-default ui-state-hover";
557 			}
558 			
559 			wrapper.appendChild( this._fnCreateButton( buttonDef ) );
560 		}
561 	},
562 	
563 	
564 	/**
565 	 * Create and configure a TableTools button
566 	 *  @method  _fnCreateButton
567 	 *  @param   {Object} oConfig Button configuration object
568 	 *  @returns {Node} Button element
569 	 *  @private 
570 	 */
571 	_fnCreateButton: function ( oConfig )
572 	{
573 	  var nButton = this._fnButtonBase( oConfig );
574 		
575     if ( oConfig.sAction == "print" )
576     {
577       this._fnPrintConfig( nButton, oConfig );
578     }
579     else if ( oConfig.sAction.match(/flash/) )
580     {
581       this._fnFlashConfig( nButton, oConfig );
582     }
583     else if ( oConfig.sAction == "text" )
584     {
585       this._fnTextConfig( nButton, oConfig );
586     }
587     else if ( oConfig.sAction == "collection" )
588     {
589       this._fnTextConfig( nButton, oConfig );
590 			this._fnCollectionConfig( nButton, oConfig );
591     }
592 		
593 	  return nButton;
594   },
595 	
596 	
597 	/**
598 	 * Create the DOM needed for the button and apply some base properties. All buttons start here
599 	 *  @method  _fnButtonBase
600 	 *  @param   {o} oConfig Button configuration object
601 	 *  @returns {Node} DIV element for the button
602 	 *  @private 
603 	 */
604 	_fnButtonBase: function ( o )
605 	{
606 		var
607 		  nButton = document.createElement('button'),
608 		  nSpan = document.createElement('span'),
609 			masterS = this._fnGetMasterSettings();
610 		
611 		nButton.className = "DTTT_button "+o.sButtonClass;
612 		nButton.setAttribute('id', "ToolTables_"+this.s.dt.sInstance+"_"+masterS.buttonCounter );
613 		nButton.appendChild( nSpan );
614 		nSpan.innerHTML = o.sButtonText;
615 		
616 		masterS.buttonCounter++;
617 		
618 		return nButton;
619 	},
620 	
621 	
622 	/**
623 	 * Get the settings object for the master instance. When more than one TableTools instance is
624 	 * assigned to a DataTable, only one of them can be the 'master' (for the select rows). As such,
625 	 * we will typically want to interact with that master for global properties.
626 	 *  @method  _fnGetMasterSettings
627 	 *  @returns {Object} TableTools settings object
628 	 *  @private 
629 	 */
630 	_fnGetMasterSettings: function ()
631 	{
632 		if ( this.s.master )
633 		{
634 			return this.s;
635 		}
636 		else
637 		{
638 			/* Look for the master which has the same DT as this one */
639 			var instances = TableTools._aInstances;
640 			for ( var i=0, iLen=instances.length ; i<iLen ; i++ )
641 			{
642 				if ( this.dom.table == instances[i].s.dt.nTable )
643 				{
644 					return instances[i].s;
645 				}
646 			}
647 		}
648 	},
649 	
650 	
651 	
652 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
653 	 * Button collection functions
654 	 */
655 	
656 	/**
657 	 * Create a collection button, when activated will present a drop downlist of other buttons
658 	 *  @param   {Node} nButton Button to use for the collection activation
659 	 *  @param   {Object} oConfig Button configuration object
660 	 *  @returns void
661 	 *  @private
662 	 */
663 	_fnCollectionConfig: function ( nButton, oConfig )
664 	{
665 		var nHidden = document.createElement('div');
666 		nHidden.style.display = "none";
667 		nHidden.className = !this.s.dt.bJUI ? "DTTT_collection" :
668 			"DTTT_collection ui-buttonset ui-buttonset-multi";
669 		oConfig._collection = nHidden;
670 		
671 		this._fnButtonDefinations( oConfig.aButtons, nHidden );
672 	},
673 	
674 	
675 	/**
676 	 * Show a button collection
677 	 *  @param   {Node} nButton Button to use for the collection
678 	 *  @param   {Object} oConfig Button configuration object
679 	 *  @returns void
680 	 *  @private
681 	 */
682 	_fnCollectionShow: function ( nButton, oConfig )
683 	{
684 		var
685 			that = this,
686 			oPos = $(nButton).offset(),
687 			nHidden = oConfig._collection,
688 			iDivX = oPos.left,
689 			iDivY = oPos.top + $(nButton).outerHeight(),
690 			iWinHeight = $(window).height(), iDocHeight = $(document).height(),
691 		 	iWinWidth = $(window).width(), iDocWidth = $(document).width();
692 		
693 		nHidden.style.position = "absolute";
694 		nHidden.style.left = iDivX+"px";
695 		nHidden.style.top = iDivY+"px";
696 		nHidden.style.display = "block";
697 		$(nHidden).css('opacity',0);
698 		
699 		var nBackground = document.createElement('div');
700 		nBackground.style.position = "absolute";
701 		nBackground.style.left = "0px";
702 		nBackground.style.top = "0px";
703 		nBackground.style.height = ((iWinHeight>iDocHeight)? iWinHeight : iDocHeight) +"px";
704 		nBackground.style.width = ((iWinWidth>iDocWidth)? iWinWidth : iDocWidth) +"px";
705 		nBackground.className = "DTTT_collection_background";
706 		$(nBackground).css('opacity',0);
707 		
708 		document.body.appendChild( nBackground );
709 		document.body.appendChild( nHidden );
710 		
711 		/* Visual corrections to try and keep the collection visible */
712 		var iDivWidth = $(nHidden).outerWidth();
713 		var iDivHeight = $(nHidden).outerHeight();
714 		
715 		if ( iDivX + iDivWidth > iDocWidth )
716 		{
717 			nHidden.style.left = (iDocWidth-iDivWidth)+"px";
718 		}
719 		
720 		if ( iDivY + iDivHeight > iDocHeight )
721 		{
722 			nHidden.style.top = (iDivY-iDivHeight-$(nButton).outerHeight())+"px";
723 		}
724 	
725 		this.dom.collection.collection = nHidden;
726 		this.dom.collection.background = nBackground;
727 		
728 		/* This results in a very small delay for the end user but it allows the animation to be
729 		 * much smoother. If you don't want the animation, then the setTimeout can be removed
730 		 */
731 		setTimeout( function () {
732 			$(nHidden).animate({opacity: 1}, 500);
733 			$(nBackground).animate({opacity: 0.25}, 500);
734 		}, 10 );
735 		
736 		/* Event handler to remove the collection display */
737 		$(nBackground).click( function () {
738 			that._fnCollectionHide.call( that, null, null );
739 		} );
740 	},
741 	
742 	
743 	/**
744 	 * Hide a button collection
745 	 *  @param   {Node} nButton Button to use for the collection
746 	 *  @param   {Object} oConfig Button configuration object
747 	 *  @returns void
748 	 *  @private
749 	 */
750 	_fnCollectionHide: function ( nButton, oConfig )
751 	{
752 		if ( oConfig !== null && oConfig.sExtends == 'collection' )
753 		{
754 			return;
755 		}
756 		
757 		if ( this.dom.collection.collection !== null )
758 		{
759 			$(this.dom.collection.collection).animate({opacity: 0}, 500, function (e) {
760 				this.style.display = "none";
761 			} );
762 			
763 			$(this.dom.collection.background).animate({opacity: 0}, 500, function (e) {
764 				this.parentNode.removeChild( this );
765 			} );
766 			
767 			this.dom.collection.collection = null;
768 			this.dom.collection.background = null;
769 		}
770 	},
771 	
772 	
773 	
774 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
775 	 * Row selection functions
776 	 */
777 	
778 	/**
779 	 * Add event handlers to a table to allow for row selection
780 	 *  @method  _fnRowSelectConfig
781 	 *  @returns void
782 	 *  @private 
783 	 */
784 	_fnRowSelectConfig: function ()
785 	{
786 		if ( this.s.master )
787 		{
788 			var
789 				that = this, 
790 				i, iLen, 
791 				aoOpenRows = this.s.dt.aoOpenRows;
792 			
793 			$(that.s.dt.nTable).addClass( 'DTTT_selectable' );
794 			
795 			$('tr', that.s.dt.nTBody).live( 'click', function(e) {
796 				/* Sub-table must be ignored (odd that the selector won't do this with >) */
797 				if ( this.parentNode != that.s.dt.nTBody )
798 				{
799 					return;
800 				}
801 				
802 				/* Not interested in selecting 'opened' rows */
803 				for ( i=0, iLen=aoOpenRows.length ; i<iLen ; i++ )
804 				{
805 					if ( this == aoOpenRows[i].nTr )
806 					{
807 						return;
808 					}
809 				}
810 				
811 				/* User defined selection function */
812 				if ( that.s.select.preRowSelect !== null && !that.s.select.preRowSelect.call(that, e) )
813 				{
814 					return;
815 				}
816 				
817 				/* And go */
818 				if ( that.s.select.type == "single" )
819 				{
820 					that._fnRowSelectSingle.call( that, this );
821 				}
822 				else
823 				{
824 					that._fnRowSelectMulti.call( that, this );
825 				}
826 			} );
827 			
828 			/* Add a draw callback handler for when 'select' all is active and we are using server-side
829 			 * processing, so TableTools will automatically select the new rows for us
830 			 */
831 			that.s.dt.aoDrawCallback.push( {
832 				fn: function () {
833 					if ( that.s.select.all && that.s.dt.oFeatures.bServerSide )
834 					{
835 						that.fnSelectAll();
836 					}
837 				},
838 				sName: "TableTools_select"
839 			} );
840 		}
841 	},
842 	
843 	
844 	/**
845 	 * Select or deselect a row based on its current state when only one row is allowed to be
846 	 * selected at a time (i.e. if there is a row already selected, deselect it). If the selected
847 	 * row is the one being passed in, just deselect and take no further action.
848 	 *  @method  _fnRowSelectSingle
849 	 *  @param   {Node} nNode TR element which is being 'activated' in some way
850 	 *  @returns void
851 	 *  @private 
852 	 */
853 	_fnRowSelectSingle: function ( nNode )
854 	{
855 		if ( this.s.master )
856 		{
857 			/* Do nothing on the DataTables 'empty' result set row */
858 			if ( $('td', nNode).hasClass(this.s.dt.oClasses.sRowEmpty) )
859 			{
860 				return;
861 			}
862 			
863 			if ( $(nNode).hasClass('DTTT_selected') )
864 			{
865 				this._fnRowDeselect( nNode );
866 			}
867 			else
868 			{
869 				if ( this.s.select.selected.length !== 0 )
870 				{
871 					this._fnRowDeselectAll();
872 				}
873 				
874 				this.s.select.selected.push( nNode );
875 				$(nNode).addClass( 'DTTT_selected' );
876 				
877 				if ( this.s.select.postSelected !== null )
878 				{
879 					this.s.select.postSelected.call( this, nNode );
880 				}
881 			}
882 			
883 			TableTools._fnEventDispatch( this, 'select', nNode );
884 		}
885 	},
886 	
887 	
888 	/**
889 	 * Select or deselect a row based on its current state when multiple rows are allowed to be
890 	 * selected.
891 	 *  @method  _fnRowSelectMulti
892 	 *  @param   {Node} nNode TR element which is being 'activated' in some way
893 	 *  @returns void
894 	 *  @private 
895 	 */
896 	_fnRowSelectMulti: function ( nNode )
897 	{
898 		if ( this.s.master )
899 		{
900 			/* Do nothing on the DataTables 'empty' result set row */
901 			if ( $('td', nNode).hasClass(this.s.dt.oClasses.sRowEmpty) )
902 			{
903 				return;
904 			}
905 			
906 			if ( $(nNode).hasClass('DTTT_selected') )
907 			{
908 				this._fnRowDeselect( nNode );
909 			}
910 			else
911 			{
912 				this.s.select.selected.push( nNode );
913 				$(nNode).addClass( 'DTTT_selected' );
914 				
915 				if ( this.s.select.postSelected !== null )
916 				{
917 					this.s.select.postSelected.call( this, nNode );
918 				}
919 			}
920 			
921 			TableTools._fnEventDispatch( this, 'select', nNode );
922 		}
923 	},
924 	
925 	
926 	/**
927 	 * Select all TR elements in the table. Note that this function will still operate in 'single'
928 	 * select mode, which might not be what you desire (in which case, don't call this function!)
929 	 *  @method  _fnRowSelectAll
930 	 *  @returns void
931 	 *  @private 
932 	 */
933 	_fnRowSelectAll: function ( )
934 	{
935 		if ( this.s.master )
936 		{
937 			var n;
938 			for ( var i=0, iLen=this.s.dt.aiDisplayMaster.length ; i<iLen ; i++ )
939 			{
940 				n = this.s.dt.aoData[ this.s.dt.aiDisplayMaster[i] ].nTr;
941 				
942 				if ( !$(n).hasClass('DTTT_selected') )
943 				{
944 					this.s.select.selected.push( n );
945 					$(n).addClass( 'DTTT_selected' );
946 				}
947 			}
948 			
949 			this.s.select.all = true;
950 			TableTools._fnEventDispatch( this, 'select', null );
951 		}
952 	},
953 	
954 	
955 	/**
956 	 * Deselect all TR elements in the table. If nothing is currently selected, then no action is
957 	 * taken.
958 	 *  @method  _fnRowDeselectAll
959 	 *  @returns void
960 	 *  @private 
961 	 */
962 	_fnRowDeselectAll: function ( )
963 	{
964 		if ( this.s.master )
965 		{
966 			for ( var i=this.s.select.selected.length-1 ; i>=0 ; i-- )
967 			{
968 				this._fnRowDeselect( i );
969 			}
970 			
971 			this.s.select.all = false;
972 			TableTools._fnEventDispatch( this, 'select', null );
973 		}
974 	},
975 	
976 	
977 	/**
978 	 * Deselect a single row, based on its index in the selected array, or a TR node (when the
979 	 * index is then computed)
980 	 *  @method  _fnRowDeselect
981 	 *  @param   {int|Node} i Node or index of node in selected array, which is to be deselected
982 	 *  @returns void
983 	 *  @private 
984 	 */
985 	_fnRowDeselect: function ( i )
986 	{
987 		if ( typeof i.nodeName != 'undefined' )
988 		{
989 			i = $.inArray( i, this.s.select.selected );
990 		}
991 		
992 		var nNode = this.s.select.selected[i];
993 		$(nNode).removeClass('DTTT_selected');
994 		this.s.select.selected.splice( i, 1 );
995 		
996 		if ( this.s.select.postDeselected !== null )
997 		{
998 			this.s.select.postDeselected.call( this, nNode );
999 		}
1000 		
1001 		this.s.select.all = false;
1002 	},
1003 	
1004 	
1005 	
1006 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1007 	 * Text button functions
1008 	 */
1009 	
1010 	/**
1011 	 * Configure a text based button for interaction events
1012 	 *  @method  _fnTextConfig
1013 	 *  @param   {Node} nButton Button element which is being considered
1014 	 *  @param   {Object} oConfig Button configuration object
1015 	 *  @returns void
1016 	 *  @private 
1017 	 */
1018 	_fnTextConfig: function ( nButton, oConfig )
1019 	{
1020 		var that = this;
1021 		
1022 		if ( oConfig.fnInit !== null )
1023 		{
1024 			oConfig.fnInit.call( this, nButton, oConfig );
1025 		}
1026 		
1027 		if ( oConfig.sToolTip != "" )
1028 		{
1029 			nButton.title = oConfig.sToolTip;
1030 		}
1031 		
1032 	  $(nButton).hover( function () {
1033 			$(nButton).removeClass( oConfig.sButtonClass ).
1034 				addClass(oConfig.sButtonClassHover );
1035 			if ( oConfig.fnMouseover !== null )
1036 			{
1037 				oConfig.fnMouseover.call( this, nButton, oConfig, null );
1038 			}
1039 		}, function () {
1040 			$(nButton).removeClass( oConfig.sButtonClassHover ).
1041 				addClass( oConfig.sButtonClass );
1042 			if ( oConfig.fnMouseout !== null )
1043 			{
1044 				oConfig.fnMouseout.call( this, nButton, oConfig, null );
1045 			}
1046 		} );
1047 		
1048 		if ( oConfig.fnSelect !== null )
1049 		{
1050 			TableTools._fnEventListen( this, 'select', function (n) {
1051 				oConfig.fnSelect.call( that, nButton, oConfig, n );
1052 			} );
1053 		}
1054 		
1055 		$(nButton).click( function (e) {
1056 			e.preventDefault();
1057 			
1058 			if ( oConfig.fnClick !== null )
1059 			{
1060 				oConfig.fnClick.call( that, nButton, oConfig, null );
1061 			}
1062 			
1063 			/* Provide a complete function to match the behaviour of the flash elements */
1064 			if ( oConfig.fnComplete !== null )
1065 			{
1066 				oConfig.fnComplete.call( that, nButton, oConfig, null, null );
1067 			}
1068 			
1069 			that._fnCollectionHide( nButton, oConfig );
1070 		} );
1071 	},
1072 	
1073 	
1074 	
1075 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1076 	 * Flash button functions
1077 	 */
1078 	
1079 	/**
1080 	 * Configure a flash based button for interaction events
1081 	 *  @method  _fnFlashConfig
1082 	 *  @param   {Node} nButton Button element which is being considered
1083 	 *  @param   {o} oConfig Button configuration object
1084 	 *  @returns void
1085 	 *  @private 
1086 	 */
1087 	_fnFlashConfig: function ( nButton, oConfig )
1088 	{
1089 	  var that = this;
1090 		var flash = new ZeroClipboard.Client();
1091 		
1092 		if ( oConfig.fnInit !== null )
1093 		{
1094 			oConfig.fnInit.call( this, nButton, oConfig );
1095 		}
1096 		
1097 		flash.setHandCursor( true );
1098 		
1099 		if ( oConfig.sAction == "flash_save" )
1100 		{
1101 			flash.setAction( 'save' );
1102 			flash.setCharSet( (oConfig.sCharSet=="utf16le") ? 'UTF16LE' : 'UTF8' );
1103 			flash.setBomInc( oConfig.bBomInc );
1104 			flash.setFileName( oConfig.sFileName.replace('*', this.fnGetTitle(oConfig)) );
1105 		}
1106 		else if ( oConfig.sAction == "flash_pdf" )
1107 		{
1108 			flash.setAction( 'pdf' );
1109 			flash.setFileName( oConfig.sFileName.replace('*', this.fnGetTitle(oConfig)) );
1110 		}
1111 		else
1112 		{
1113 			flash.setAction( 'copy' );
1114 		}
1115 		
1116 		flash.addEventListener('mouseOver', function(client) {
1117 			$(nButton).removeClass( oConfig.sButtonClass ).
1118 				addClass(oConfig.sButtonClassHover );
1119 			
1120 			if ( oConfig.fnMouseover !== null )
1121 			{
1122 				oConfig.fnMouseover.call( that, nButton, oConfig, flash );
1123 			}
1124 		} );
1125 		
1126 		flash.addEventListener('mouseOut', function(client) {
1127 			$(nButton).removeClass( oConfig.sButtonClassHover ).
1128 				addClass(oConfig.sButtonClass );
1129 			
1130 			if ( oConfig.fnMouseout !== null )
1131 			{
1132 				oConfig.fnMouseout.call( that, nButton, oConfig, flash );
1133 			}
1134 		} );
1135 		
1136 		flash.addEventListener('mouseDown', function(client) {
1137 			if ( oConfig.fnClick !== null )
1138 			{
1139 				oConfig.fnClick.call( that, nButton, oConfig, flash );
1140 			}
1141 		} );
1142 		
1143 		flash.addEventListener('complete', function (client, text) {
1144 			if ( oConfig.fnComplete !== null )
1145 			{
1146 				oConfig.fnComplete.call( that, nButton, oConfig, flash, text );
1147 			}
1148 			that._fnCollectionHide( nButton, oConfig );
1149 		} );
1150 		
1151 		this._fnFlashGlue( flash, nButton, oConfig.sToolTip );
1152 	},
1153 	
1154 	
1155 	/**
1156 	 * Wait until the id is in the DOM before we "glue" the swf. Note that this function will call
1157 	 * itself (using setTimeout) until it completes successfully
1158 	 *  @method  _fnFlashGlue
1159 	 *  @param   {Object} clip Zero clipboard object
1160 	 *  @param   {Node} node node to glue swf to
1161 	 *  @param   {String} text title of the flash movie
1162 	 *  @returns void
1163 	 *  @private 
1164 	 */
1165 	_fnFlashGlue: function ( flash, node, text )
1166 	{
1167 	  var that = this;
1168 	  var id = node.getAttribute('id');
1169 	  
1170 		if ( document.getElementById(id) )
1171 		{
1172 			flash.glue( node, text );
1173 		}
1174 		else
1175 		{
1176 			setTimeout( function () {
1177 				that._fnFlashGlue( flash, node, text );
1178 			}, 100 );
1179 		}
1180 	},
1181 	
1182 	
1183 	/**
1184 	 * Set the text for the flash clip to deal with
1185 	 * 
1186 	 * This function is required for large information sets. There is a limit on the 
1187 	 * amount of data that can be transfered between Javascript and Flash in a single call, so
1188 	 * we use this method to build up the text in Flash by sending over chunks. It is estimated
1189 	 * that the data limit is around 64k, although it is undocuments, and appears to be different
1190 	 * between different flash versions. We chunk at 8KiB.
1191 	 *  @method  _fnFlashSetText
1192 	 *  @param   {Object} clip the ZeroClipboard object
1193 	 *  @param   {String} sData the data to be set
1194 	 *  @returns void
1195 	 *  @private 
1196 	 */
1197 	_fnFlashSetText: function ( clip, sData )
1198 	{
1199 		var asData = this._fnChunkData( sData, 8192 );
1200 		
1201 		clip.clearText();
1202 		for ( var i=0, iLen=asData.length ; i<iLen ; i++ )
1203 		{
1204 			clip.appendText( asData[i] );
1205 		}
1206 	},
1207 	
1208 	
1209 	
1210 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1211 	 * Data retrieval functions
1212 	 */
1213 	
1214 	/**
1215 	 * Convert the mixed columns variable into a boolean array the same size as the columns, which
1216 	 * indicates which columns we want to include
1217 	 *  @method  _fnColumnTargets
1218 	 *  @param   {String|Array} mColumns The columns to be included in data retreieval. If a string
1219 	 *             then it can take the value of "visible" or "hidden" (to include all visible or
1220 	 *             hidden columns respectively). Or an array of column indexes
1221 	 *  @returns {Array} A boolean array the length of the columns of the table, which each value
1222 	 *             indicating if the column is to be included or not
1223 	 *  @private 
1224 	 */
1225 	_fnColumnTargets: function ( mColumns )
1226 	{
1227 		var aColumns = [];
1228 		var dt = this.s.dt;
1229 		
1230 		if ( typeof mColumns == "object" )
1231 		{
1232 			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )
1233 			{
1234 				aColumns.push( false );
1235 			}
1236 			
1237 			for ( i=0, iLen=mColumns.length ; i<iLen ; i++ )
1238 			{
1239 				aColumns[ mColumns[i] ] = true;
1240 			}
1241 		}
1242 		else if ( mColumns == "visible" )
1243 		{
1244 			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )
1245 			{
1246 				aColumns.push( dt.aoColumns[i].bVisible ? true : false );
1247 			}
1248 		}
1249 		else if ( mColumns == "hidden" )
1250 		{
1251 			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )
1252 			{
1253 				aColumns.push( dt.aoColumns[i].bVisible ? false : true );
1254 			}
1255 		}
1256 		else /* all */
1257 		{
1258 			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )
1259 			{
1260 				aColumns.push( true );
1261 			}
1262 		}
1263 		
1264 		return aColumns;
1265 	},
1266 	
1267 	
1268 	/**
1269 	 * New line character(s) depend on the platforms
1270 	 *  @method  method
1271 	 *  @param   {Object} oConfig Button configuration object - only interested in oConfig.sNewLine
1272 	 *  @returns {String} Newline character
1273 	 */
1274 	_fnNewline: function ( oConfig )
1275 	{
1276 		if ( oConfig.sNewLine == "auto" )
1277 		{
1278 			return navigator.userAgent.match(/Windows/) ? "\r\n" : "\n";
1279 		}
1280 		else
1281 		{
1282 			return oConfig.sNewLine;
1283 		}
1284 	},
1285 	
1286 	
1287 	/**
1288 	 * Get data from DataTables' internals and format it for output
1289 	 *  @method  _fnGetDataTablesData
1290 	 *  @param   {Object} oConfig Button configuration object
1291 	 *  @returns {String} Concatinated string of data
1292 	 *  @private 
1293 	 */
1294 	_fnGetDataTablesData: function ( oConfig )
1295 	{
1296 		var i, iLen, j, jLen;
1297 		var sData = '', sLoopData = '';
1298 		var dt = this.s.dt;
1299 		var regex = new RegExp(oConfig.sFieldBoundary, "g"); /* Do it here for speed */
1300 		var aColumnsInc = this._fnColumnTargets( oConfig.mColumns );
1301 		var sNewline = this._fnNewline( oConfig );
1302 		
1303 		/*
1304 		 * Header
1305 		 */
1306 		if ( oConfig.bHeader )
1307 		{
1308 			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )
1309 			{
1310 				if ( aColumnsInc[i] )
1311 				{
1312 					sLoopData = dt.aoColumns[i].sTitle.replace(/\n/g," ").replace( /<.*?>/g, "" );
1313 					sLoopData = this._fnHtmlDecode( sLoopData );
1314 					
1315 					sData += this._fnBoundData( sLoopData, oConfig.sFieldBoundary, regex ) +
1316 					 	oConfig.sFieldSeperator;
1317 				}
1318 			}
1319 			sData = sData.slice( 0, oConfig.sFieldSeperator.length*-1 );
1320 			sData += sNewline;
1321 		}
1322 		
1323 		/*
1324 		 * Body
1325 		 */
1326 		for ( j=0, jLen=dt.aiDisplay.length ; j<jLen ; j++ )
1327 		{
1328 			/* Columns */
1329 			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )
1330 			{
1331 				if ( aColumnsInc[i] )
1332 				{
1333 					/* Convert to strings (with small optimisation) */
1334 					var mTypeData = dt.aoData[ dt.aiDisplay[j] ]._aData[ i ];
1335 					if ( typeof mTypeData == "string" )
1336 					{
1337 						/* Strip newlines, replace img tags with alt attr. and finally strip html... */
1338 						sLoopData = mTypeData.replace(/\n/g," ");
1339 						sLoopData =
1340 						 	sLoopData.replace(/<img.*?\s+alt\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+)).*?>/gi,
1341 						 		'$1$2$3');
1342 						sLoopData = sLoopData.replace( /<.*?>/g, "" );
1343 					}
1344 					else
1345 					{
1346 						sLoopData = mTypeData+"";
1347 					}
1348 					
1349 					/* Trim and clean the data */
1350 					sLoopData = sLoopData.replace(/^\s+/, '').replace(/\s+$/, '');
1351 					sLoopData = this._fnHtmlDecode( sLoopData );
1352 					
1353 					/* Bound it and add it to the total data */
1354 					sData += this._fnBoundData( sLoopData, oConfig.sFieldBoundary, regex ) +
1355 					 	oConfig.sFieldSeperator;
1356 				}
1357 			}
1358 			sData = sData.slice( 0, oConfig.sFieldSeperator.length*-1 );
1359 			sData += sNewline;
1360 		}
1361 		
1362 		/* Remove the last new line */
1363 		sData.slice( 0, -1 );
1364 		
1365 		/*
1366 		 * Footer
1367 		 */
1368 		if ( oConfig.bFooter )
1369 		{
1370 			for ( i=0, iLen=dt.aoColumns.length ; i<iLen ; i++ )
1371 			{
1372 				if ( aColumnsInc[i] && dt.aoColumns[i].nTf !== null )
1373 				{
1374 					sLoopData = dt.aoColumns[i].nTf.innerHTML.replace(/\n/g," ").replace( /<.*?>/g, "" );
1375 					sLoopData = this._fnHtmlDecode( sLoopData );
1376 					
1377 					sData += this._fnBoundData( sLoopData, oConfig.sFieldBoundary, regex ) +
1378 					 	oConfig.sFieldSeperator;
1379 				}
1380 			}
1381 			sData = sData.slice( 0, oConfig.sFieldSeperator.length*-1 );
1382 		}
1383 		
1384 		/* No pointers here - this is a string copy :-) */
1385 		_sLastData = sData;
1386 		return sData;
1387 	},
1388 	
1389 	
1390 	/**
1391 	 * Wrap data up with a boundary string
1392 	 *  @method  _fnBoundData
1393 	 *  @param   {String} sData data to bound
1394 	 *  @param   {String} sBoundary bounding char(s)
1395 	 *  @param   {RegExp} regex search for the bounding chars - constructed outside for efficincy
1396 	 *             in the loop
1397 	 *  @returns {String} bound data
1398 	 *  @private 
1399 	 */
1400 	_fnBoundData: function ( sData, sBoundary, regex )
1401 	{
1402 		if ( sBoundary === "" )
1403 		{
1404 			return sData;
1405 		}
1406 		else
1407 		{
1408 			return sBoundary + sData.replace(regex, "\\"+sBoundary) + sBoundary;
1409 		}
1410 	},
1411 	
1412 	
1413 	/**
1414 	 * Break a string up into an array of smaller strings
1415 	 *  @method  _fnChunkData
1416 	 *  @param   {String} sData data to be broken up
1417 	 *  @param   {Int} iSize chunk size
1418 	 *  @returns {Array} String array of broken up text
1419 	 *  @private 
1420 	 */
1421 	_fnChunkData: function ( sData, iSize )
1422 	{
1423 		var asReturn = [];
1424 		var iStrlen = sData.length;
1425 		
1426 		for ( var i=0 ; i<iStrlen ; i+=iSize )
1427 		{
1428 			if ( i+iSize < iStrlen )
1429 			{
1430 				asReturn.push( sData.substring( i, i+iSize ) );
1431 			}
1432 			else
1433 			{
1434 				asReturn.push( sData.substring( i, iStrlen ) );
1435 			}
1436 		}
1437 		
1438 		return asReturn;
1439 	},
1440 	
1441 	
1442 	/**
1443 	 * Decode HTML entities
1444 	 *  @method  _fnHtmlDecode
1445 	 *  @param   {String} sData encoded string
1446 	 *  @returns {String} decoded string
1447 	 *  @private 
1448 	 */
1449 	_fnHtmlDecode: function ( sData )
1450 	{
1451 		if ( sData.indexOf('&') == -1 )
1452 		{
1453 			return sData;
1454 		}
1455 		
1456 		var 
1457 			aData = this._fnChunkData( sData, 2048 ),
1458 			n = document.createElement('div'),
1459 			i, iLen, iIndex,
1460 			sReturn = "", sInner;
1461 		
1462 		/* nodeValue has a limit in browsers - so we chunk the data into smaller segments to build
1463 		 * up the string. Note that the 'trick' here is to remember than we might have split over
1464 		 * an HTML entity, so we backtrack a little to make sure this doesn't happen
1465 		 */
1466 		for ( i=0, iLen=aData.length ; i<iLen ; i++ )
1467 		{
1468 			/* Magic number 8 is because no entity is longer then strlen 8 in ISO 8859-1 */
1469 			iIndex = aData[i].lastIndexOf( '&' );
1470 			if ( iIndex != -1 && aData[i].length >= 8 && iIndex > aData[i].length - 8 )
1471 			{
1472 				sInner = aData[i].substr( iIndex );
1473 				aData[i] = aData[i].substr( 0, iIndex );
1474 			}
1475 			
1476 			n.innerHTML = aData[i];
1477 			sReturn += n.childNodes[0].nodeValue;
1478 		}
1479 		
1480 		return sReturn;
1481 	},
1482 	
1483 	
1484 	
1485 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1486 	 * Printing functions
1487 	 */
1488 	
1489 	/**
1490 	 * Configure a button for printing
1491 	 *  @method  _fnPrintConfig
1492 	 *  @param   {Node} nButton Button element which is being considered
1493 	 *  @param   {Object} oConfig Button configuration object
1494 	 *  @returns void
1495 	 *  @private 
1496 	 */
1497 	_fnPrintConfig: function ( nButton, oConfig )
1498 	{
1499 	  var that = this;
1500 		
1501 		if ( oConfig.fnInit !== null )
1502 		{
1503 			oConfig.fnInit.call( this, nButton, oConfig );
1504 		}
1505 
1506 	  $(nButton).hover( function () {
1507 			$(nButton).removeClass( oConfig.sButtonClass ).
1508 				addClass(oConfig.sButtonClassHover );
1509 		}, function () {
1510 			$(nButton).removeClass( oConfig.sButtonClassHover ).
1511 				addClass(oConfig.sButtonClass );
1512 		} );
1513 		
1514 		if ( oConfig.fnSelect !== null )
1515 		{
1516 			TableTools._fnEventListen( this, 'select', function (n) {
1517 				oConfig.fnSelect.call( that, nButton, oConfig, n );
1518 			} );
1519 		}
1520 		
1521 		$(nButton).click( function (e) {
1522 			e.preventDefault();
1523 			
1524 			that._fnPrintStart.call( that, e, oConfig);
1525 			
1526 			if ( oConfig.fnClick !== null )
1527 			{
1528 				oConfig.fnClick.call( that, nButton, oConfig, null );
1529 			}
1530 			
1531 			/* Provide a complete function to match the behaviour of the flash elements */
1532 			if ( oConfig.fnComplete !== null )
1533 			{
1534 				oConfig.fnComplete.call( that, nButton, oConfig, null, null );
1535 			}
1536 			
1537 			that._fnCollectionHide( nButton, oConfig );
1538 		} );
1539   },
1540   
1541   /**
1542    * Show print display
1543    *  @method  _fnPrintStart
1544    *  @param   {Event} e Event object
1545 	 *  @param   {Object} oConfig Button configuration object
1546    *  @returns void
1547 	 *  @private 
1548    */
1549   _fnPrintStart: function ( e, oConfig )
1550 	{
1551 	  var that = this;
1552 	  var oSetDT = this.s.dt;
1553 	  
1554     /* Parse through the DOM hiding everything that isn't needed for the table */
1555     this._fnPrintHideNodes( oSetDT.nTable );
1556 		
1557     /* Show the whole table */
1558     this.s.print.saveStart = oSetDT._iDisplayStart;
1559     this.s.print.saveLength = oSetDT._iDisplayLength;
1560 
1561 		if ( oConfig.bShowAll )
1562 		{
1563     	oSetDT._iDisplayStart = 0;
1564     	oSetDT._iDisplayLength = -1;
1565     	oSetDT.oApi._fnCalculateEnd( oSetDT );
1566     	oSetDT.oApi._fnDraw( oSetDT );
1567 		}
1568 		
1569 		/* Adjust the display for scrolling which might be done by DataTables */
1570 		if ( oSetDT.oScroll.sX !== "" || oSetDT.oScroll.sY !== "" )
1571 		{
1572 			this._fnPrintScrollStart( oSetDT );
1573 		}
1574 		
1575 		/* Remove the other DataTables feature nodes - but leave the table! and info div */
1576 		var anFeature = oSetDT.aanFeatures;
1577 		for ( var cFeature in anFeature )
1578 		{
1579 			if ( cFeature != 'i' && cFeature != 't' && cFeature.length == 1 )
1580 			{
1581 			  for ( var i=0, iLen=anFeature[cFeature].length ; i<iLen ; i++ )
1582 			  {
1583 				  this.dom.print.hidden.push( {
1584 				  	node: anFeature[cFeature][i],
1585 				  	display: "block"
1586 				  } );
1587 				  anFeature[cFeature][i].style.display = "none";
1588 			  }
1589 			}
1590 		}
1591 		
1592 		/* Print class can be used for styling */
1593 		$(document.body).addClass( 'DTTT_Print' );
1594     
1595     /* Add a node telling the user what is going on */
1596     if ( oConfig.sInfo !== "" )
1597     {
1598       var nInfo = document.createElement( "div" );
1599       nInfo.className = "DTTT_print_info";
1600       nInfo.innerHTML = oConfig.sInfo;
1601       document.body.appendChild( nInfo );
1602       
1603       setTimeout( function() {
1604       	$(nInfo).fadeOut( "normal", function() {
1605       		document.body.removeChild( nInfo );
1606       	} );
1607       }, 2000 );
1608     }
1609     
1610     /* Add a message at the top of the page */
1611     if ( oConfig.sMessage !== "" )
1612     {
1613     	this.dom.print.message = document.createElement( "div" );
1614     	this.dom.print.message.className = "DTTT_PrintMessage";
1615     	this.dom.print.message.innerHTML = oConfig.sMessage;
1616     	document.body.insertBefore( this.dom.print.message, document.body.childNodes[0] );
1617     }
1618     
1619     /* Cache the scrolling and the jump to the top of the t=page */
1620     this.s.print.saveScroll = $(window).scrollTop();
1621     window.scrollTo( 0, 0 );
1622     
1623     this.s.print.funcEnd = function(e) {
1624      that._fnPrintEnd.call( that, e ); 
1625     };
1626     $(document).bind( "keydown", null, this.s.print.funcEnd );
1627   },
1628   
1629 	
1630 	/**
1631 	 * Printing is finished, resume normal display
1632 	 *  @method  _fnPrintEnd
1633 	 *  @param   {Event} e Event object
1634 	 *  @returns void
1635 	 *  @private 
1636 	 */
1637   _fnPrintEnd: function ( e )
1638 	{
1639 		/* Only interested in the escape key */
1640 		if ( e.keyCode == 27 )
1641 		{
1642 			e.preventDefault();
1643 			
1644 		  var that = this;
1645 	    var oSetDT = this.s.dt;
1646 	    var oSetPrint = this.s.print;
1647 	    var oDomPrint = this.dom.print;
1648 	    
1649 			/* Show all hidden nodes */
1650 			this._fnPrintShowNodes();
1651 			
1652 			/* Restore DataTables' scrolling */
1653 			if ( oSetDT.oScroll.sX !== "" || oSetDT.oScroll.sY !== "" )
1654 			{
1655 				this._fnPrintScrollEnd();
1656 			}
1657 			
1658 			/* Restore the scroll */
1659 			window.scrollTo( 0, oSetPrint.saveScroll );
1660 			
1661 			/* Drop the print message */
1662 			if ( oDomPrint.message !== null )
1663 			{
1664 				document.body.removeChild( oDomPrint.message );
1665 				oDomPrint.message = null;
1666 			}
1667 			
1668 			/* Styling class */
1669 			$(document.body).removeClass( 'DTTT_Print' );
1670 			
1671 			/* Restore the table length */
1672 			oSetDT._iDisplayStart = oSetPrint.saveStart;
1673 			oSetDT._iDisplayLength = oSetPrint.saveLength;
1674 			oSetDT.oApi._fnCalculateEnd( oSetDT );
1675 			oSetDT.oApi._fnDraw( oSetDT );
1676 			
1677 			$(document).unbind( "keydown", this.s.print.funcEnd );
1678 			this.s.print.funcEnd = null;
1679 		}
1680 	},
1681 	
1682 	
1683 	/**
1684 	 * Take account of scrolling in DataTables by showing the full table
1685 	 *  @returns void
1686 	 *  @private 
1687 	 */
1688 	_fnPrintScrollStart: function ()
1689 	{
1690 		var 
1691 			oSetDT = this.s.dt,
1692 			nScrollHeadInner = oSetDT.nScrollHead.getElementsByTagName('div')[0],
1693 			nScrollHeadTable = nScrollHeadInner.getElementsByTagName('table')[0],
1694 			nScrollBody = oSetDT.nTable.parentNode;
1695 
1696 		/* Copy the header in the thead in the body table, this way we show one single table when
1697 		 * in print view. Note that this section of code is more or less verbatim from DT 1.7.0
1698 		 */
1699 		var nTheadSize = oSetDT.nTable.getElementsByTagName('thead');
1700 		if ( nTheadSize.length > 0 )
1701 		{
1702 			oSetDT.nTable.removeChild( nTheadSize[0] );
1703 		}
1704 		
1705 		if ( oSetDT.nTFoot !== null )
1706 		{
1707 			var nTfootSize = oSetDT.nTable.getElementsByTagName('tfoot');
1708 			if ( nTfootSize.length > 0 )
1709 			{
1710 				oSetDT.nTable.removeChild( nTfootSize[0] );
1711 			}
1712 		}
1713 		
1714 		nTheadSize = oSetDT.nTHead.cloneNode(true);
1715 		oSetDT.nTable.insertBefore( nTheadSize, oSetDT.nTable.childNodes[0] );
1716 		
1717 		if ( oSetDT.nTFoot !== null )
1718 		{
1719 			nTfootSize = oSetDT.nTFoot.cloneNode(true);
1720 			oSetDT.nTable.insertBefore( nTfootSize, oSetDT.nTable.childNodes[1] );
1721 		}
1722 		
1723 		/* Now adjust the table's viewport so we can actually see it */
1724 		if ( oSetDT.oScroll.sX !== "" )
1725 		{
1726 			oSetDT.nTable.style.width = $(oSetDT.nTable).outerWidth()+"px";
1727 			nScrollBody.style.width = $(oSetDT.nTable).outerWidth()+"px";
1728 			nScrollBody.style.overflow = "visible";
1729 		}
1730 		
1731 		if ( oSetDT.oScroll.sY !== "" )
1732 		{
1733 			nScrollBody.style.height = $(oSetDT.nTable).outerHeight()+"px";
1734 			nScrollBody.style.overflow = "visible";
1735     }
1736 	},
1737 	
1738 	
1739 	/**
1740 	 * Take account of scrolling in DataTables by showing the full table. Note that the redraw of
1741 	 * the DataTable that we do will actually deal with the majority of the hardword here
1742 	 *  @returns void
1743 	 *  @private 
1744 	 */
1745 	_fnPrintScrollEnd: function ()
1746 	{
1747 		var 
1748 			oSetDT = this.s.dt,
1749 			nScrollBody = oSetDT.nTable.parentNode;
1750 		
1751 		if ( oSetDT.oScroll.sX !== "" )
1752 		{
1753 			nScrollBody.style.width = oSetDT.oApi._fnStringToCss( oSetDT.oScroll.sX );
1754 			nScrollBody.style.overflow = "auto";
1755 		}
1756 		
1757 		if ( oSetDT.oScroll.sY !== "" )
1758 		{
1759 			nScrollBody.style.height = oSetDT.oApi._fnStringToCss( oSetDT.oScroll.sY );
1760 			nScrollBody.style.overflow = "auto";
1761 		}
1762 	},
1763 	
1764 	
1765 	/**
1766 	 * Resume the display of all TableTools hidden nodes
1767 	 *  @method  _fnPrintShowNodes
1768 	 *  @returns void
1769 	 *  @private 
1770 	 */
1771   _fnPrintShowNodes: function ( )
1772 	{
1773 	  var anHidden = this.dom.print.hidden;
1774 	  
1775 		for ( var i=0, iLen=anHidden.length ; i<iLen ; i++ )
1776 		{
1777 			anHidden[i].node.style.display = anHidden[i].display;
1778 		}
1779 		anHidden.splice( 0, anHidden.length );
1780 	},
1781 	
1782 	
1783 	/**
1784 	 * Hide nodes which are not needed in order to display the table. Note that this function is
1785 	 * recursive
1786 	 *  @method  _fnPrintHideNodes
1787 	 *  @param   {Node} nNode Element which should be showing in a 'print' display
1788 	 *  @returns void
1789 	 *  @private 
1790 	 */
1791   _fnPrintHideNodes: function ( nNode )
1792 	{
1793 	  var anHidden = this.dom.print.hidden;
1794 	  
1795 		var nParent = nNode.parentNode;
1796 		var nChildren = nParent.childNodes;
1797 		for ( var i=0, iLen=nChildren.length ; i<iLen ; i++ )
1798 		{
1799 			if ( nChildren[i] != nNode && nChildren[i].nodeType == 1 )
1800 			{
1801 				/* If our node is shown (don't want to show nodes which were previously hidden) */
1802 				var sDisplay = $(nChildren[i]).css("display");
1803 			 	if ( sDisplay != "none" )
1804 				{
1805 					/* Cache the node and it's previous state so we can restore it */
1806 					anHidden.push( {
1807 						node: nChildren[i],
1808 						display: sDisplay
1809 					} );
1810 					nChildren[i].style.display = "none";
1811 				}
1812 			}
1813 		}
1814 		
1815 		if ( nParent.nodeName != "BODY" )
1816 		{
1817 			this._fnPrintHideNodes( nParent );
1818 		}
1819 	}
1820 };
1821 
1822 
1823 
1824 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1825  * Static variables
1826  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1827 
1828 /**
1829  * Store of all instances that have been created of TableTools, so one can look up other (when
1830  * there is need of a master)
1831  *  @property _aInstances
1832  *  @type     Array
1833  *  @default  []
1834  *  @private
1835  */
1836 TableTools._aInstances = [];
1837 
1838 
1839 /**
1840  * Store of all listeners and their callback functions
1841  *  @property _aListeners
1842  *  @type     Array
1843  *  @default  []
1844  */
1845 TableTools._aListeners = [];
1846 
1847 
1848 
1849 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1850  * Static methods
1851  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1852 
1853 /**
1854  * Get an array of all the master instances
1855  *  @method  fnGetMasters
1856  *  @returns {Array} List of master TableTools instances
1857  *  @static
1858  */
1859 TableTools.fnGetMasters = function ()
1860 {
1861 	var a = [];
1862 	for ( var i=0, iLen=TableTools._aInstances.length ; i<iLen ; i++ )
1863 	{
1864 		if ( TableTools._aInstances[i].s.master )
1865 		{
1866 			a.push( TableTools._aInstances[i].s );
1867 		}
1868 	}
1869 	return a;
1870 };
1871 
1872 /**
1873  * Get the master instance for a table node (or id if a string is given)
1874  *  @method  fnGetInstance
1875  *  @returns {Object} ID of table OR table node, for which we want the TableTools instance
1876  *  @static
1877  */
1878 TableTools.fnGetInstance = function ( node )
1879 {
1880 	if ( typeof node != 'object' )
1881 	{
1882 		node = document.getElementById(node);
1883 	}
1884 	
1885 	for ( var i=0, iLen=TableTools._aInstances.length ; i<iLen ; i++ )
1886 	{
1887 		if ( TableTools._aInstances[i].s.master && TableTools._aInstances[i].dom.table == node )
1888 		{
1889 			return TableTools._aInstances[i];
1890 		}
1891 	}
1892 	return null;
1893 };
1894 
1895 
1896 /**
1897  * Add a listener for a specific event
1898  *  @method  _fnEventListen
1899  *  @param   {Object} that Scope of the listening function (i.e. 'this' in the caller)
1900  *  @param   {String} type Event type
1901  *  @param   {Function} fn Function
1902  *  @returns void
1903  *  @private
1904  *  @static
1905  */
1906 TableTools._fnEventListen = function ( that, type, fn )
1907 {
1908 	TableTools._aListeners.push( {
1909 		that: that,
1910 		type: type,
1911 		fn: fn
1912 	} );
1913 };
1914 	
1915 
1916 /**
1917  * An event has occured - look up every listener and fire it off. We check that the event we are
1918  * going to fire is attached to the same table (using the table node as reference) before firing
1919  *  @method  _fnEventDispatch
1920  *  @param   {Object} that Scope of the listening function (i.e. 'this' in the caller)
1921  *  @param   {String} type Event type
1922  *  @param   {Node} node Element that the event occured on (may be null)
1923  *  @returns void
1924  *  @private
1925  *  @static
1926  */
1927 TableTools._fnEventDispatch = function ( that, type, node )
1928 {
1929 	var listeners = TableTools._aListeners;
1930 	for ( var i=0, iLen=listeners.length ; i<iLen ; i++ )
1931 	{
1932 		if ( that.dom.table == listeners[i].that.dom.table && listeners[i].type == type )
1933 		{
1934 			listeners[i].fn( node );
1935 		}
1936 	}
1937 };
1938 
1939 
1940 
1941 
1942 
1943 
1944 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1945  * Constants
1946  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1947 
1948 
1949 /**
1950  * @namespace Default button configurations
1951  */
1952 TableTools.BUTTONS = {
1953 	csv: {
1954 		sAction: "flash_save",
1955 		sCharSet: "utf8",
1956 		bBomInc: false,
1957 		sFileName: "*.csv",
1958 		sFieldBoundary: "'",
1959 		sFieldSeperator: ",",
1960 		sNewLine: "auto",
1961 		sTitle: "",
1962 		sToolTip: "",
1963 		sButtonClass: "DTTT_button_csv",
1964 		sButtonClassHover: "DTTT_button_csv_hover",
1965 		sButtonText: "CSV",
1966 		mColumns: "all", /* "all", "visible", "hidden" or array of column integers */
1967 		bHeader: true,
1968 		bFooter: true,
1969 		fnMouseover: null,
1970 		fnMouseout: null,
1971 		fnClick: function( nButton, oConfig, flash ) {
1972 			this.fnSetText( flash, this.fnGetTableData(oConfig) );
1973 		},
1974 		fnSelect: null,
1975 		fnComplete: null,
1976 		fnInit: null
1977 	},
1978 	xls: {
1979 		sAction: "flash_save",
1980 		sCharSet: "utf16le",
1981 		bBomInc: true,
1982 		sFileName: "*.csv",
1983 		sFieldBoundary: "",
1984 		sFieldSeperator: "\t",
1985 		sNewLine: "auto",
1986 		sTitle: "",
1987 		sToolTip: "",
1988 		sButtonClass: "DTTT_button_xls",
1989 		sButtonClassHover: "DTTT_button_xls_hover",
1990 		sButtonText: "Excel",
1991 		mColumns: "all",
1992 		bHeader: true,
1993 		bFooter: true,
1994 		fnMouseover: null,
1995 		fnMouseout: null,
1996 		fnClick: function( nButton, oConfig, flash ) {
1997 			this.fnSetText( flash, this.fnGetTableData(oConfig) );
1998 		},
1999 		fnSelect: null,
2000 		fnComplete: null,
2001 		fnInit: null
2002 	},
2003 	copy: {
2004 		sAction: "flash_copy",
2005 		sFieldBoundary: "",
2006 		sFieldSeperator: "\t",
2007 		sNewLine: "auto",
2008 		sToolTip: "",
2009 		sButtonClass: "DTTT_button_copy",
2010 		sButtonClassHover: "DTTT_button_copy_hover",
2011 		sButtonText: "Copy",
2012 		mColumns: "all",
2013 		bHeader: true,
2014 		bFooter: true,
2015 		fnMouseover: null,
2016 		fnMouseout: null,
2017 		fnClick: function( nButton, oConfig, flash ) {
2018 			this.fnSetText( flash, this.fnGetTableData(oConfig) );
2019 		},
2020 		fnSelect: null,
2021 		fnComplete: function(nButton, oConfig, flash, text) {
2022 			var
2023 				len = text.split('\n').length - 1,
2024 				plural = (len==1) ? "" : "s";
2025 			alert( 'Copied '+len+' row'+plural+' to the clipboard' );
2026 		},
2027 		fnInit: null
2028 	},
2029 	pdf: {
2030 		sAction: "flash_pdf",
2031 		sFieldBoundary: "",
2032 		sFieldSeperator: "\t",
2033 		sNewLine: "\n",
2034 		sFileName: "*.pdf",
2035 		sToolTip: "",
2036 		sTitle: "",
2037 		sButtonClass: "DTTT_button_pdf",
2038 		sButtonClassHover: "DTTT_button_pdf_hover",
2039 		sButtonText: "PDF",
2040 		mColumns: "all",
2041 		bHeader: true,
2042 		bFooter: true,
2043 		fnMouseover: null,
2044 		fnMouseout: null,
2045 		fnClick: function( nButton, oConfig, flash ) {
2046 			this.fnSetText( flash, 
2047 				"title:"+ this.fnGetTitle(oConfig) +"\n"+
2048 				"colWidth:"+ this.fnCalcColRatios(oConfig) +"\n"+
2049 				"--/TableToolsOpts--\n" +
2050 				this.fnGetTableData(oConfig)
2051 			);
2052 		},
2053 		fnSelect: null,
2054 		fnComplete: null,
2055 		fnInit: null
2056 	},
2057 	print: {
2058 		sAction: "print",
2059 		sInfo: "<h6>Print view</h6><p>Please use your browser's print function to "+
2060 		  "print this table. Press escape when finished.",
2061 		sMessage: "",
2062 		bShowAll: true,
2063 		sToolTip: "View print view",
2064 		sButtonClass: "DTTT_button_print",
2065 		sButtonClassHover: "DTTT_button_print_hover",
2066 		sButtonText: "Print",
2067 		fnMouseover: null,
2068 		fnMouseout: null,
2069 		fnClick: null,
2070 		fnSelect: null,
2071 		fnComplete: null,
2072 		fnInit: null
2073 	},
2074 	text: {
2075 		sAction: "text",
2076 		sToolTip: "",
2077 		sButtonClass: "DTTT_button_text",
2078 		sButtonClassHover: "DTTT_button_text_hover",
2079 		sButtonText: "Text button",
2080 		mColumns: "all",
2081 		bHeader: true,
2082 		bFooter: true,
2083 		fnMouseover: null,
2084 		fnMouseout: null,
2085 		fnClick: null,
2086 		fnSelect: null,
2087 		fnComplete: null,
2088 		fnInit: null
2089 	},
2090 	select: {
2091 		sAction: "text",
2092 		sToolTip: "",
2093 		sButtonClass: "DTTT_button_text",
2094 		sButtonClassHover: "DTTT_button_text_hover",
2095 		sButtonText: "Select button",
2096 		mColumns: "all",
2097 		bHeader: true,
2098 		bFooter: true,
2099 		fnMouseover: null,
2100 		fnMouseout: null,
2101 		fnClick: null,
2102 		fnSelect: function( nButton, oConfig ) {
2103 			if ( this.fnGetSelected().length !== 0 ) {
2104 				$(nButton).removeClass('DTTT_disabled');
2105 			} else {
2106 				$(nButton).addClass('DTTT_disabled');
2107 			}
2108 		},
2109 		fnComplete: null,
2110 		fnInit: function( nButton, oConfig ) {
2111 			$(nButton).addClass('DTTT_disabled');
2112 		}
2113 	},
2114 	select_single: {
2115 		sAction: "text",
2116 		sToolTip: "",
2117 		sButtonClass: "DTTT_button_text",
2118 		sButtonClassHover: "DTTT_button_text_hover",
2119 		sButtonText: "Select button",
2120 		mColumns: "all",
2121 		bHeader: true,
2122 		bFooter: true,
2123 		fnMouseover: null,
2124 		fnMouseout: null,
2125 		fnClick: null,
2126 		fnSelect: function( nButton, oConfig ) {
2127 			var iSelected = this.fnGetSelected().length;
2128 			if ( iSelected == 1 ) {
2129 				$(nButton).removeClass('DTTT_disabled');
2130 			} else {
2131 				$(nButton).addClass('DTTT_disabled');
2132 			}
2133 		},
2134 		fnComplete: null,
2135 		fnInit: function( nButton, oConfig ) {
2136 			$(nButton).addClass('DTTT_disabled');
2137 		}
2138 	},
2139 	select_all: {
2140 		sAction: "text",
2141 		sToolTip: "",
2142 		sButtonClass: "DTTT_button_text",
2143 		sButtonClassHover: "DTTT_button_text_hover",
2144 		sButtonText: "Select all",
2145 		mColumns: "all",
2146 		bHeader: true,
2147 		bFooter: true,
2148 		fnMouseover: null,
2149 		fnMouseout: null,
2150 		fnClick: function( nButton, oConfig ) {
2151 			this.fnSelectAll();
2152 		},
2153 		fnSelect: function( nButton, oConfig ) {
2154 			if ( this.fnGetSelected().length == this.s.dt.fnRecordsDisplay() ) {
2155 				$(nButton).addClass('DTTT_disabled');
2156 			} else {
2157 				$(nButton).removeClass('DTTT_disabled');
2158 			}
2159 		},
2160 		fnComplete: null,
2161 		fnInit: null
2162 	},
2163 	select_none: {
2164 		sAction: "text",
2165 		sToolTip: "",
2166 		sButtonClass: "DTTT_button_text",
2167 		sButtonClassHover: "DTTT_button_text_hover",
2168 		sButtonText: "Deselect all",
2169 		mColumns: "all",
2170 		bHeader: true,
2171 		bFooter: true,
2172 		fnMouseover: null,
2173 		fnMouseout: null,
2174 		fnClick: function( nButton, oConfig ) {
2175 			this.fnSelectNone();
2176 		},
2177 		fnSelect: function( nButton, oConfig ) {
2178 			if ( this.fnGetSelected().length !== 0 ) {
2179 				$(nButton).removeClass('DTTT_disabled');
2180 			} else {
2181 				$(nButton).addClass('DTTT_disabled');
2182 			}
2183 		},
2184 		fnComplete: null,
2185 		fnInit: function( nButton, oConfig ) {
2186 			$(nButton).addClass('DTTT_disabled');
2187 		}
2188 	},
2189 	ajax: {
2190 		sAction: "text",
2191 		sFieldBoundary: "",
2192 		sFieldSeperator: "\t",
2193 		sAjaxUrl: "/xhr.php",
2194 		sToolTip: "",
2195 		sButtonClass: "DTTT_button_text",
2196 		sButtonClassHover: "DTTT_button_text_hover",
2197 		sButtonText: "Ajax button",
2198 		mColumns: "all",
2199 		bHeader: true,
2200 		bFooter: true,
2201 		fnMouseover: null,
2202 		fnMouseout: null,
2203 		fnClick: function( nButton, oConfig ) {
2204 			var sData = this.fnGetTableData(oConfig);
2205 			$.ajax( {
2206 				url: oConfig.sAjaxUrl,
2207 				data: [
2208 					{ name: "tableData", value: sData }
2209 				],
2210 				success: oConfig.fnAjaxComplete,
2211 				dataType: "json",
2212 				type: "POST", 
2213 				cache: false,
2214 				error: function () {
2215 					alert( "Error detected when sending table data to server" );
2216 				}
2217 			} );
2218 		},
2219 		fnSelect: null,
2220 		fnComplete: null,
2221 		fnInit: null,
2222 		fnAjaxComplete: function( json ) {
2223 			alert( 'Ajax complete' );
2224 		}
2225 	},
2226 	collection: {
2227 		sAction: "collection",
2228 		sToolTip: "",
2229 		sButtonClass: "DTTT_button_collection",
2230 		sButtonClassHover: "DTTT_button_collection_hover",
2231 		sButtonText: "Collection",
2232 		fnMouseover: null,
2233 		fnMouseout: null,
2234 		fnClick: function( nButton, oConfig ) {
2235 			this._fnCollectionShow(nButton, oConfig);
2236 		},
2237 		fnSelect: null,
2238 		fnComplete: null,
2239 		fnInit: null
2240 	}
2241 };
2242 /*
2243  *  on* callback parameters:
2244  *  	1. node - button element
2245  *  	2. object - configuration object for this button
2246  *  	3. object - ZeroClipboard reference (flash button only)
2247  *  	4. string - Returned string from Flash (flash button only - and only on 'complete')
2248  */
2249 
2250 
2251 /**
2252  * @namespace TableTools default settings for initialisation
2253  */
2254 TableTools.DEFAULTS = {
2255 	sSwfPath:         "media/swf/copy_cvs_xls_pdf.swf",
2256 	sRowSelect:       "none",
2257 	fnPreRowSelect:   null,
2258 	fnRowSelected:    null,
2259 	fnRowDeselected:  null,
2260 	aButtons:         [ "copy", "csv", "xls", "pdf", "print" ]
2261 };
2262 
2263 
2264 /**
2265  * Name of this class
2266  *  @constant CLASS
2267  *  @type     String
2268  *  @default  TableTools
2269  */
2270 TableTools.prototype.CLASS = "TableTools";
2271 
2272 
2273 /**
2274  * TableTools version
2275  *  @constant  VERSION
2276  *  @type      String
2277  *  @default   2.0.0
2278  */
2279 TableTools.VERSION = "2.0.0";
2280 TableTools.prototype.VERSION = TableTools.VERSION;
2281 
2282 
2283 
2284 
2285 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2286  * Initialisation
2287  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2288 
2289 /*
2290  * Register a new feature with DataTables
2291  */
2292 if ( typeof $.fn.dataTable == "function" &&
2293      typeof $.fn.dataTableExt.fnVersionCheck == "function" &&
2294      $.fn.dataTableExt.fnVersionCheck('1.7.0') )
2295 {
2296 	$.fn.dataTableExt.aoFeatures.push( {
2297 		fnInit: function( oDTSettings ) {
2298 			var oOpts = typeof oDTSettings.oInit.oTableTools != 'undefined' ? 
2299 				oDTSettings.oInit.oTableTools : {};
2300 			
2301 			var oTT = new TableTools( oDTSettings.oInstance, oOpts );
2302 			TableTools._aInstances.push( oTT );
2303 			
2304 			return oTT.dom.container;
2305 		},
2306 		cFeature: "T",
2307 		sFeature: "TableTools"
2308 	} );
2309 }
2310 else
2311 {
2312 	alert( "Warning: TableTools 2 requires DataTables 1.7 or greater - www.datatables.net/download");
2313 }
2314 
2315 })(jQuery, window, document);
2316