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