/*
 * File:        TableTools.js
 * Version:     1.0.2
 * CVS:         $Id$
 * Description: Copy, save and print functions for DataTables
 * Author:      Allan Jardine (www.sprymedia.co.uk)
 * Created:     Wed  1 Apr 2009 08:41:58 BST
 * Modified:    $Date$ by $Author$
 * Language:    Javascript
 * License:     LGPL
 * Project:     Just a little bit of fun :-)
 * Contact:     www.sprymedia.co.uk/contact
 * 
 * Copyright 2009 Allan Jardine, all rights reserved.
 *
 */

/*
 * Variable: TableToolsInit
 * Purpose:  Parameters for TableTools customisation
 * Scope:    global
 */
var TableToolsInit = {
	"oFeatures": {
		"bCsv": true,
		"bXls": true,
		"bCopy": true,
		"bPrint": true
	},
	"sPrintMessage": "",
	"sTitle": "",
	"sDom": 'T<"clear">lfrtip',
	"sSwfPath": "http://shopping.netsuite.com/c.935369/site/swf/copy_cvs_xls.swf",
	//"sSwfPath": "site/swf/ZeroClipboard.swf",
	"iButtonHeight": 30,
	"iButtonWidth": 30,
	"_iNextId": 1 /* Internal useage - but needs to be global */
};


(function($) {
/*
 * Function: TableTools
 * Purpose:  TableTools "class"
 * Returns:  same as _fnInit
 * Inputs:   same as _fnInit
 */
function TableTools ( oInit )
{
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Private parameters
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	var _oSettings;
	var nTools = null;
	var _nTableWrapper;
	var _aoPrintHidden = [];
	var _iPrintScroll = 0;
	var _nPrintMessage = null;
	var _DTSettings;
	var _sLastData;
	var _iId;
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Initialisation
	 */
	
	/*
	 * Function: _fnInit
	 * Purpose:  Initialise the table tools
	 * Returns:  node: - The created node for the table tools wrapping
 	 * Inputs:   object:oInit - object with:
 	 *             oDTSettings - DataTables settings
	 */
	function _fnInit( oInit )
	{
		_nTools = document.createElement('div');
		_nTools.className = "TableTools";
		_iId = TableToolsInit._iNextId++;
		
		/* Copy the init object */
		_oSettings = $.extend( true, {}, TableToolsInit );
		
		_DTSettings = oInit.oDTSettings;
		
		_nTableWrapper = fnFindParentClass( _DTSettings.nTable, "dataTables_wrapper" );
		
		ZeroClipboard.moviePath = _oSettings.sSwfPath;
		
		if ( _oSettings.oFeatures.bCopy ) {
			fnFeatureClipboard();
		}
		if ( _oSettings.oFeatures.bCsv ) {
			fnFeatureSaveCSV();
		}
		if ( _oSettings.oFeatures.bXls ) {
			fnFeatureSaveXLS();
		}
		if ( _oSettings.oFeatures.bPrint ) {
			fnFeaturePrint();
		}
		
		return _nTools;
	}
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Feature buttons
	 */
	
	/*
	 * Function: fnFeatureSaveCSV
	 * Purpose:  Add a button for saving a CSV file
	 * Returns:  -
	 * Inputs:   -
	 */
	function fnFeatureSaveCSV ()
	{
		
		var sBaseClass = "TableTools_button TableTools_csv";
		var nButton = document.createElement( 'div' );
		nButton.id = "ToolTables_CSV_"+_iId;
		nButton.style.height = _oSettings.iButtonHeight+'px';
		nButton.style.width = _oSettings.iButtonWidth+'px';
		nButton.className = sBaseClass;
		_nTools.appendChild( nButton );
		
		var clip = new ZeroClipboard.Client();
		clip.setHandCursor( true );
		clip.setAction( 'save' );
		clip.setFileName( fnGetTitle()+'.csv' );
		
		clip.addEventListener('mouseOver', function(client) {
			nButton.className = sBaseClass+'_hover';
		} );
		
		clip.addEventListener('mouseOut', function(client) {
			nButton.className = sBaseClass;
		} );
		
		clip.addEventListener('mouseDown', function(client) {
			clip.setText( fnGetDataTablesData(",") );
		} );
		
		fnGlue( clip, nButton, "ToolTables_CSV_"+_iId, "Save as CSV" );
	}
	
	
	/*
	 * Function: fnFeatureSaveXLS
	 * Purpose:  Add a button for saving an XLS file
	 * Returns:  -
	 * Inputs:   -
	 */
	function fnFeatureSaveXLS ()
	{
		var sBaseClass = "TableTools_button TableTools_xls";
		var nButton = document.createElement( 'div' );
		nButton.id = "ToolTables_XLS_"+_iId;
		nButton.style.height = _oSettings.iButtonHeight+'px';
		nButton.style.width = _oSettings.iButtonWidth+'px';
		nButton.className = sBaseClass;
		_nTools.appendChild( nButton );
		
		var clip = new ZeroClipboard.Client();
		clip.setHandCursor( true );
		clip.setAction( 'save' );
		clip.setFileName( fnGetTitle()+'.xls' );
		
		clip.addEventListener('mouseOver', function(client) {
			nButton.className = sBaseClass+'_hover';
		} );
		
		clip.addEventListener('mouseOut', function(client) {
			nButton.className = sBaseClass;
		} );
		
		clip.addEventListener('mouseDown', function(client) {
			clip.setText( fnGetDataTablesData("\t") );
		} );
		
		fnGlue( clip, nButton, "ToolTables_XLS_"+_iId, "Save for Excel" );
	}
	
	
	/*
	 * Function: fnFeatureClipboard
	 * Purpose:  Add a button for copying data to clipboard
	 * Returns:  -
	 * Inputs:   -
	 */
	function fnFeatureClipboard ()
	{
		var sBaseClass = "TableTools_button TableTools_clipboard";
		var nButton = document.createElement( 'div' );
		nButton.id = "ToolTables_Copy_"+_iId;
		nButton.style.height = _oSettings.iButtonHeight+'px';
		nButton.style.width = _oSettings.iButtonWidth+'px';
		nButton.className = sBaseClass;
		_nTools.appendChild( nButton );
		
		var clip = new ZeroClipboard.Client();
		clip.setHandCursor( true );
		clip.setAction( 'copy' );
		
		clip.addEventListener('mouseOver', function(client) {
			nButton.className = sBaseClass+'_hover';
		} );
		
		clip.addEventListener('mouseOut', function(client) {
			nButton.className = sBaseClass;
		} );
		
		clip.addEventListener('mouseDown', function(client) {
			clip.setText( fnGetDataTablesData("\t") );
		} );
		
		clip.addEventListener('complete', function (client, text) {
			var aData = _sLastData.split('\n');
			alert( 'Copied '+(aData.length-2)+' rows to the clipboard' );
		} );
		
		fnGlue( clip, nButton, "ToolTables_Copy_"+_iId, "Copy to clipboard" );
	}
	
	
	/*
	 * Function: fnFeaturePrint
	 * Purpose:  Add a button for printing data
	 * Returns:  -
	 * Inputs:   -
	 * Notes:    Fun one this function. In order to print the table, we want the table to retain
	 *   it's position in the DOM, so all styles still apply, but we don't want to print all the
	 *   other nonesense. So we hide that nonesese and add an event handler for 'esc' which will
	 *   restore a normal view.
	 */
	function fnFeaturePrint ()
	{
		var sBaseClass = "TableTools_button TableTools_print";
		var nButton = document.createElement( 'div' );
		nButton.style.height = _oSettings.iButtonHeight+'px';
		nButton.style.width = _oSettings.iButtonWidth+'px';
		nButton.className = sBaseClass;
		nButton.title = "Print table";
		_nTools.appendChild( nButton );
		
		/* Could do this in CSS - but might as well be consistent with the flash buttons */
		$(nButton).hover( function(client) {
			nButton.className = sBaseClass+'_hover';
		}, function(client) {
			nButton.className = sBaseClass;
		} );
		
		$(nButton).click( function() {
			/* Parse through the DOM hiding everything that isn't needed for the table */
			fnPrintHideNodes( _DTSettings.nTable );
			
			/* Show the whole table */
			_iPrintSaveLength = _DTSettings._iDisplayLength;
			_DTSettings._iDisplayLength = -1;
			_DTSettings.oApi._fnCalculateEnd( _DTSettings );
			_DTSettings.oApi._fnDraw( _DTSettings );
			
			/* Remove the other DataTables feature nodes - but leave the table! and info div */
			var anFeature = _DTSettings.anFeatures;
			for ( var cFeature in anFeature )
			{
				if ( cFeature != 'i' && cFeature != 't' )
				{
					_aoPrintHidden.push( {
						"node": anFeature[cFeature],
						"display": "block"
					} );
					anFeature[cFeature].style.display = "none";
				}
			}
			
			/* Add a node telling the user what is going on */
			var nInfo = document.createElement( "div" );
			nInfo.className = "TableTools_PrintInfo";
			nInfo.innerHTML = "<h6>Print view</h6><p>Please use your browser's print function to "+
				"print this table. Press escape when finished.";
			document.body.appendChild( nInfo );
			
			/* Add a message at the top of the page */
			if ( _oSettings.sPrintMessage != "" )
			{
				_nPrintMessage = document.createElement( "p" );
				_nPrintMessage.className = "TableTools_PrintMessage";
				_nPrintMessage.innerHTML = _oSettings.sPrintMessage;
				document.body.insertBefore( _nPrintMessage, document.body.childNodes[0] );
			}
			
			/* Cache the scrolling and the jump to the top of the t=page */
			_iPrintScroll = $(window).scrollTop();
			window.scrollTo( 0, 0 );
			
			$(document).bind( "keypress", null, fnPrintEnd );
			
			setTimeout( function() {
				$(nInfo).fadeOut( "normal", function() {
					document.body.removeChild( nInfo );
				} );
			}, 2000 );
		} );
	}
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Printing functions
	 */
	
	/*
	 * Function: fnPrintEnd
	 * Purpose:  Printing is finished, resume normal display
	 * Returns:  -
	 * Inputs:   event
	 */
	function fnPrintEnd ( e )
	{
		/* Only interested in the escape key */
		if ( e.keyCode == 27 )
		{
			/* Show all hidden nodes */
			fnPrintShowNodes();
			
			/* Restore the scroll */
			window.scrollTo( 0, _iPrintScroll );
			
			/* Drop the print message */
			if ( _nPrintMessage )
			{
				document.body.removeChild( _nPrintMessage );
				_nPrintMessage = null;
			}
			
			/* Restore the table length */
			_DTSettings._iDisplayLength = _iPrintSaveLength;
			_DTSettings.oApi._fnCalculateEnd( _DTSettings );
			_DTSettings.oApi._fnDraw( _DTSettings );
			
			$(document).unbind( "keypress", fnPrintEnd );
		}
	}
	
	
	/*
	 * Function: fnPrintShowNodes
	 * Purpose:  Resume the display of all TableTools hidden nodes
	 * Returns:  -
	 * Inputs:   -
	 */
	function fnPrintShowNodes( )
	{
		for ( var i=0, iLen=_aoPrintHidden.length ; i<iLen ; i++ )
		{
			_aoPrintHidden[i].node.style.display = _aoPrintHidden[i].display;
		}
		_aoPrintHidden.splice( 0, _aoPrintHidden.length );
	}
	
	
	/*
	 * Function: fnPrintHideNodes
	 * Purpose:  Hide nodes which are not needed in order to display the table
	 * Returns:  -
	 * Inputs:   node:nNode - the table node - we parse back up
	 * Notes:    Recursive
	 */
	function fnPrintHideNodes( nNode )
	{
		var nParent = nNode.parentNode;
		var nChildren = nParent.childNodes;
		for ( var i=0, iLen=nChildren.length ; i<iLen ; i++ )
		{
			if ( nChildren[i] != nNode && nChildren[i].nodeType == 1 )
			{
				/* If our node is shown (don't want to show nodes which were previously hidden) */
				var sDisplay = $(nChildren[i]).css("display");
			 	if ( sDisplay != "none" )
				{
					/* Cache the node and it's previous state so we can restore it */
					_aoPrintHidden.push( {
						"node": nChildren[i],
						"display": sDisplay
					} );
					nChildren[i].style.display = "none";
				}
			}
		}
		
		if ( nParent.nodeName != "BODY" )
		{
			fnPrintHideNodes( nParent );
		}
	}
	
	
	
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Support functions
	 */
	
	/*
	 * Function: fnGlue
	 * Purpose:  Wait until the id is in the DOM before we "glue" the swf
	 * Returns:  -
	 * Inputs:   object:clip - Zero clipboard object
	 *           node:node - node to glue swf to
	 *           string:id - id of the element to look for
	 *           string:text - title of the flash movie
	 * Notes:    Recursive (setTimeout)
	 */
	function fnGlue ( clip, node, id, text )
	{
		
		if ( document.getElementById(id) )
		{
			clip.glue( node, text );
		}
		else
		{
			setTimeout( function () {
				fnGlue( clip, node, id, text );
			}, 100 );
		}
	}
	
	
	/*
	 * Function: fnGetTitle
	 * Purpose:  Get the title of the page (from DOM or user set) for file saving
	 * Returns:  
	 * Inputs:   
	 */
	function fnGetTitle( )
	{
		if ( _oSettings.sTitle != "" )
			return _oSettings.sTitle;
		else
			return document.getElementsByTagName('title')[0].innerHTML;
	}
	
	
	/*
	 * Function: fnFindParentClass
	 * Purpose:  Parse back up the DOM to a node with a particular node
	 * Returns:  node: - found node
	 * Inputs:   node:n - Node to test
	 *           string:sClass - class to find
	 * Notes:    Recursive
	 */
	function fnFindParentClass ( n, sClass )
	{
		if ( n.className.match(sClass) || n.nodeName == "BODY" )
			return n;
		else
			return fnFindParentClass( n.parentNode, sClass );
	}
	
	
	/*
	 * Function: fnGetDataTablesData
	 * Purpose:  Get data from DataTables' internals and format it for output
	 * Returns:  
	 * Inputs:   
	 */
	function fnGetDataTablesData( sSeperator )
	{
		
		
		var i, iLen;
		var j, jLen;
		var sData = '';
		var sNewline = navigator.userAgent.match(/Windows/) ? "\r\n" : "\n";
		
		/* Titles */
		for ( i=0, iLen=_DTSettings.aoColumns.length ; i<iLen ; i++ )
		{
			if ( _DTSettings.aoColumns[i].bVisible )
			{
				sData += _DTSettings.aoColumns[i].sTitle.replace(/\n/g," ").replace( /<.*?>/g, "" ) +sSeperator;
			}
		}
		sData = sData.slice( 0, sSeperator.length*-1 );
		sData += sNewline;
		
		/* Rows */
		for ( j=0, jLen=_DTSettings.aiDisplay.length ; j<jLen ; j++ )
		{
			/* Columns */
			for ( i=0, iLen=_DTSettings.aoColumns.length ; i<iLen ; i++ )
			{
				if ( _DTSettings.aoColumns[i].bVisible )
				{
					sData += _DTSettings.aoData[ _DTSettings.aiDisplay[j] ]._aData[ i ].replace(/\n/g," ").replace( /<.*?>/g, "" ) +sSeperator;
				}
			}
			sData = sData.slice( 0, sSeperator.length*-1 );
			sData += sNewline;
		}
		
		/* Remove the last new line */
		sData.slice( 0, -1 );
		
		_sLastData = sData;
		return sData;
	}
	
	
	/* Initialise our new object */
	return _fnInit( oInit );
}


/*
 * Register a new feature with DataTables
 */
if ( typeof $.fn.dataTable == "function" && typeof $.fn.dataTableExt.sVersion != "undefined" )
{
	$.fn.dataTableExt.aoFeatures.push( {
		"fnInit": function( oSettings ) {
			return new TableTools( { "oDTSettings": oSettings } );
		},
		"cFeature": "T",
		"sFeature": "TableTools"
	} );
}
else
{
	alert( "Warning: TableTools requires DataTables 1.5 beta 9 or greater - "+
		"www.datatables.net/download");
}
})(jQuery);

