text: /*! FixedColumns 3.3.0
   * ©2010-2018 SpryMedia Ltd - datatables.net/license
   */
  
  /**
   * @summary     FixedColumns
   * @description Freeze columns in place on a scrolling DataTable
   * @version     3.3.0
   * @file        dataTables.fixedColumns.js
   * @author      SpryMedia Ltd (www.sprymedia.co.uk)
   * @contact     www.sprymedia.co.uk/contact
   * @copyright   Copyright 2010-2018 SpryMedia Ltd.
   *
   * This source file is free software, available under the following license:
   *   MIT license - http://datatables.net/license/mit
   *
   * This source file is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
   *
   * For details please refer to: http://www.datatables.net
   */
  (function( factory ){
  	if ( typeof define === 'function' && define.amd ) {
  		// AMD
  		define( ['jquery', 'datatables.net'], function ( $ ) {
  			return factory( $, window, document );
  		} );
  	}
  	else if ( typeof exports === 'object' ) {
  		// CommonJS
  		module.exports = function (root, $) {
  			if ( ! root ) {
  				root = window;
  			}
  
  			if ( ! $ || ! $.fn.dataTable ) {
  				$ = require('datatables.net')(root, $).$;
  			}
  
  			return factory( $, root, root.document );
  		};
  	}
  	else {
  		// Browser
  		factory( jQuery, window, document );
  	}
  }(function( $, window, document, undefined ) {
  'use strict';
  var DataTable = $.fn.dataTable;
  var _firefoxScroll;
  
  /**
   * When making use of DataTables' x-axis scrolling feature, you may wish to
   * fix the left most column in place. This plug-in for DataTables provides
   * exactly this option (note for non-scrolling tables, please use the
   * FixedHeader plug-in, which can fix headers and footers). Key
   * features include:
   *
   * * Freezes the left or right most columns to the side of the table
   * * Option to freeze two or more columns
   * * Full integration with DataTables' scrolling options
   * * Speed - FixedColumns is fast in its operation
   *
   *  @class
   *  @constructor
   *  @global
   *  @param {object} dt DataTables instance. With DataTables 1.10 this can also
   *    be a jQuery collection, a jQuery selector, DataTables API instance or
   *    settings object.
   *  @param {object} [init={}] Configuration object for FixedColumns. Options are
   *    defined by {@link FixedColumns.defaults}
   *
   *  @requires jQuery 1.7+
   *  @requires DataTables 1.8.0+
   *
   *  @example
   *      var table = $('#example').dataTable( {
   *        "scrollX": "100%"
   *      } );
   *      new $.fn.dataTable.fixedColumns( table );
   */
  var FixedColumns = function ( dt, init ) {
  	var that = this;
  
  	/* Sanity check - you just know it will happen */
  	if ( ! ( this instanceof FixedColumns ) ) {
  		/*---*/console.log( "FixedColumns warning: FixedColumns must be initialised with the 'new' keyword." );
  		return;
  	}
  
  	if ( init === undefined || init === true ) {
  		init = {};
  	}
  
  	// Use the DataTables Hungarian notation mapping method, if it exists to
  	// provide forwards compatibility for camel case variables
  	var camelToHungarian = $.fn.dataTable.camelToHungarian;
  	if ( camelToHungarian ) {
  		camelToHungarian( FixedColumns.defaults, FixedColumns.defaults, true );
  		camelToHungarian( FixedColumns.defaults, init );
  	}
  
  	// v1.10 allows the settings object to be got form a number of sources
  	var dtSettings = new $.fn.dataTable.Api( dt ).settings()[0];
  
  	/**
  	 * Settings object which contains customisable information for FixedColumns instance
  	 * @namespace
  	 * @extends FixedColumns.defaults
  	 * @private
  	 */
  	this.s = {
  		/**
  		 * DataTables settings objects
  		 *  @type     object
  		 *  @default  Obtained from DataTables instance
  		 */
  		"dt": dtSettings,
  
  		/**
  		 * Number of columns in the DataTable - stored for quick access
  		 *  @type     int
  		 *  @default  Obtained from DataTables instance
  		 */
  		"iTableColumns": dtSettings.aoColumns.length,
  
  		/**
  		 * Original outer widths of the columns as rendered by DataTables - used to calculate
  		 * the FixedColumns grid bounding box
  		 *  @type     array.<int>
  		 *  @default  []
  		 */
  		"aiOuterWidths": [],
  
  		/**
  		 * Original inner widths of the columns as rendered by DataTables - used to apply widths
  		 * to the columns
  		 *  @type     array.<int>
  		 *  @default  []
  		 */
  		"aiInnerWidths": [],
  
  
  		/**
  		 * Is the document layout right-to-left
  		 * @type boolean
  		 */
  		rtl: $(dtSettings.nTable).css('direction') === 'rtl'
  	};
  
  
  	/**
  	 * DOM elements used by the class instance
  	 * @namespace
  	 * @private
  	 *
  	 */
  	this.dom = {
  		/**
  		 * DataTables scrolling element
  		 *  @type     node
  		 *  @default  null
  		 */
  		"scroller": null,
  
  		/**
  		 * DataTables header table
  		 *  @type     node
  		 *  @default  null
  		 */
  		"header": null,
  
  		/**
  		 * DataTables body table
  		 *  @type     node
  		 *  @default  null
  		 */
  		"body": null,
  
  		/**
  		 * DataTables footer table
  		 *  @type     node
  		 *  @default  null
  		 */
  		"footer": null,
  
  		/**
  		 * Display grid elements
  		 * @namespace
  		 */
  		"grid": {
  			/**
  			 * Grid wrapper. This is the container element for the 3x3 grid
  			 *  @type     node
  			 *  @default  null
  			 */
  			"wrapper": null,
  
  			/**
  			 * DataTables scrolling element. This element is the DataTables
  			 * component in the display grid (making up the main table - i.e.
  			 * not the fixed columns).
  			 *  @type     node
  			 *  @default  null
  			 */
  			"dt": null,
  
  			/**
  			 * Left fixed column grid components
  			 * @namespace
  			 */
  			"left": {
  				"wrapper": null,
  				"head": null,
  				"body": null,
  				"foot": null
  			},
  
  			/**
  			 * Right fixed column grid components
  			 * @namespace
  			 */
  			"right": {
  				"wrapper": null,
  				"head": null,
  				"body": null,
  				"foot": null
  			}
  		},
  
  		/**
  		 * Cloned table nodes
  		 * @namespace
  		 */
  		"clone": {
  			/**
  			 * Left column cloned table nodes
  			 * @namespace
  			 */
  			"left": {
  				/**
  				 * Cloned header table
  				 *  @type     node
  				 *  @default  null
  				 */
  				"header": null,
  
  				/**
  				 * Cloned body table
  				 *  @type     node
  				 *  @default  null
  				 */
  				"body": null,
  
  				/**
  				 * Cloned footer table
  				 *  @type     node
  				 *  @default  null
  				 */
  				"footer": null
  			},
  
  			/**
  			 * Right column cloned table nodes
  			 * @namespace
  			 */
  			"right": {
  				/**
  				 * Cloned header table
  				 *  @type     node
  				 *  @default  null
  				 */
  				"header": null,
  
  				/**
  				 * Cloned body table
  				 *  @type     node
  				 *  @default  null
  				 */
  				"body": null,
  
  				/**
  				 * Cloned footer table
  				 *  @type     node
  				 *  @default  null
  				 */
  				"footer": null
  			}
  		}
  	};
  
  	if ( dtSettings._oFixedColumns ) {
  		throw 'FixedColumns already initialised on this table';
  	}
  
  	/* Attach the instance to the DataTables instance so it can be accessed easily */
  	dtSettings._oFixedColumns = this;
  
  	/* Let's do it */
  	if ( ! dtSettings._bInitComplete )
  	{
  		dtSettings.oApi._fnCallbackReg( dtSettings, 'aoInitComplete', function () {
  			that._fnConstruct( init );
  		}, 'FixedColumns' );
  	}
  	else
  	{
  		this._fnConstruct( init );
  	}
  };
  
  
  
  $.extend( FixedColumns.prototype , {
  	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  	 * Public methods
  	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  
  	/**
  	 * Update the fixed columns - including headers and footers. Note that FixedColumns will
  	 * automatically update the display whenever the host DataTable redraws.
  	 *  @returns {void}
  	 *  @example
  	 *      var table = $('#example').dataTable( {
  	 *          "scrollX": "100%"
  	 *      } );
  	 *      var fc = new $.fn.dataTable.fixedColumns( table );
  	 *
  	 *      // at some later point when the table has been manipulated....
  	 *      fc.fnUpdate();
  	 */
  	"fnUpdate": function ()
  	{
  		this._fnDraw( true );
  	},
  
  
  	/**
  	 * Recalculate the resizes of the 3x3 grid that FixedColumns uses for display of the table.
  	 * This is useful if you update the width of the table container. Note that FixedColumns will
  	 * perform this function automatically when the window.resize event is fired.
  	 *  @returns {void}
  	 *  @example
  	 *      var table = $('#example').dataTable( {
  	 *          "scrollX": "100%"
  	 *      } );
  	 *      var fc = new $.fn.dataTable.fixedColumns( table );
  	 *
  	 *      // Resize the table container and then have FixedColumns adjust its layout....
  	 *      $('#content').width( 1200 );
  	 *      fc.fnRedrawLayout();
  	 */
  	"fnRedrawLayout": function ()
  	{
  		this._fnColCalc();
  		this._fnGridLayout();
  		this.fnUpdate();
  	},
  
  
  	/**
  	 * Mark a row such that it's height should be recalculated when using 'semiauto' row
  	 * height matching. This function will have no effect when 'none' or 'auto' row height
  	 * matching is used.
  	 *  @param   {Node} nTr TR element that should have it's height recalculated
  	 *  @returns {void}
  	 *  @example
  	 *      var table = $('#example').dataTable( {
  	 *          "scrollX": "100%"
  	 *      } );
  	 *      var fc = new $.fn.dataTable.fixedColumns( table );
  	 *
  	 *      // manipulate the table - mark the row as needing an update then update the table
  	 *      // this allows the redraw performed by DataTables fnUpdate to recalculate the row
  	 *      // height
  	 *      fc.fnRecalculateHeight();
  	 *      table.fnUpdate( $('#example tbody tr:eq(0)')[0], ["insert date", 1, 2, 3 ... ]);
  	 */
  	"fnRecalculateHeight": function ( nTr )
  	{
  		delete nTr._DTTC_iHeight;
  		nTr.style.height = 'auto';
  	},
  
  
  	/**
  	 * Set the height of a given row - provides cross browser compatibility
  	 *  @param   {Node} nTarget TR element that should have it's height recalculated
  	 *  @param   {int} iHeight Height in pixels to set
  	 *  @returns {void}
  	 *  @example
  	 *      var table = $('#example').dataTable( {
  	 *          "scrollX": "100%"
  	 *      } );
  	 *      var fc = new $.fn.dataTable.fixedColumns( table );
  	 *
  	 *      // You may want to do this after manipulating a row in the fixed column
  	 *      fc.fnSetRowHeight( $('#example tbody tr:eq(0)')[0], 50 );
  	 */
  	"fnSetRowHeight": function ( nTarget, iHeight )
  	{
  		nTarget.style.height = iHeight+"px";
  	},
  
  
  	/**
  	 * Get data index information about a row or cell in the table body.
  	 * This function is functionally identical to fnGetPosition in DataTables,
  	 * taking the same parameter (TH, TD or TR node) and returning exactly the
  	 * the same information (data index information). THe difference between
  	 * the two is that this method takes into account the fixed columns in the
  	 * table, so you can pass in nodes from the master table, or the cloned
  	 * tables and get the index position for the data in the main table.
  	 *  @param {node} node TR, TH or TD element to get the information about
  	 *  @returns {int} If nNode is given as a TR, then a single index is 
  	 *    returned, or if given as a cell, an array of [row index, column index
  	 *    (visible), column index (all)] is given.
  	 */
  	"fnGetPosition": function ( node )
  	{
  		var idx;
  		var inst = this.s.dt.oInstance;
  
  		if ( ! $(node).parents('.DTFC_Cloned').length )
  		{
  			// Not in a cloned table
  			return inst.fnGetPosition( node );
  		}
  		else
  		{
  			// Its in the cloned table, so need to look up position
  			if ( node.nodeName.toLowerCase() === 'tr' ) {
  				idx = $(node).index();
  				return inst.fnGetPosition( $('tr', this.s.dt.nTBody)[ idx ] );
  			}
  			else
  			{
  				var colIdx = $(node).index();
  				idx = $(node.parentNode).index();
  				var row = inst.fnGetPosition( $('tr', this.s.dt.nTBody)[ idx ] );
  
  				return [
  					row,
  					colIdx,
  					inst.oApi._fnVisibleToColumnIndex( this.s.dt, colIdx )
  				];
  			}
  		}
  	},
  
  	fnToFixedNode: function ( rowIdx, colIdx )
  	{
  		var found;
  
  		if ( colIdx < this.s.iLeftColumns ) {
  			found = $(this.dom.clone.left.body).find('[data-dt-row='+rowIdx+'][data-dt-column='+colIdx+']');
  		}
  		else if ( colIdx >= this.s.iRightColumns ) {
  			found = $(this.dom.clone.right.body).find('[data-dt-row='+rowIdx+'][data-dt-column='+colIdx+']');
  		}
  
  		if ( found && found.length ) {
  			return found[0];
  		}
  
  		// Fallback - non-fixed node
  		var table = new $.fn.dataTable.Api(this.s.dt);
  		return table.cell(rowIdx, colIdx).node();
  	},
  
  
  	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  	 * Private methods (they are of course public in JS, but recommended as private)
  	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  
  	/**
  	 * Initialisation for FixedColumns
  	 *  @param   {Object} oInit User settings for initialisation
  	 *  @returns {void}
  	 *  @private
  	 */
  	"_fnConstruct": function ( oInit )
  	{
  		var i, iLen, iWidth,
  			that = this;
  
  		/* Sanity checking */
  		if ( typeof this.s.dt.oInstance.fnVersionCheck != 'function' ||
  		     this.s.dt.oInstance.fnVersionCheck( '1.8.0' ) !== true )
  		{
  			/*---*/console.log( "FixedColumns "+FixedColumns.VERSION+" required DataTables 1.8.0 or later. "+
  				"Please upgrade your DataTables installation" );
  			return;
  		}
  
  		if ( this.s.dt.oScroll.sX === "" )
  		{
  			this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "FixedColumns is not needed (no "+
  				"x-scrolling in DataTables enabled), so no action will be taken. Use 'FixedHeader' for "+
  				"column fixing when scrolling is not enabled" );
  			return;
  		}
  
  		/* Apply the settings from the user / defaults */
  		this.s = $.extend( true, this.s, FixedColumns.defaults, oInit );
  
  		/* Set up the DOM as we need it and cache nodes */
  		var classes = this.s.dt.oClasses;
  		this.dom.grid.dt = $(this.s.dt.nTable).parents('div.'+classes.sScrollWrapper)[0];
  		this.dom.scroller = $('div.'+classes.sScrollBody, this.dom.grid.dt )[0];
  
  		/* Set up the DOM that we want for the fixed column layout grid */
  		this._fnColCalc();
  		this._fnGridSetup();
  
  		/* Event handlers */
  		var mouseController;
  		var mouseDown = false;
  
  		// When the mouse is down (drag scroll) the mouse controller cannot
  		// change, as the browser keeps the original element as the scrolling one
  		$(this.s.dt.nTableWrapper).on( 'mousedown.DTFC', function (e) {
  			if ( e.button === 0 ) {
  				mouseDown = true;
  
  				$(document).one( 'mouseup', function () {
  					mouseDown = false;
  				} );
  			}
  		} );
  
  		// When the body is scrolled - scroll the left and right columns
  		$(this.dom.scroller)
  			.on( 'mouseover.DTFC touchstart.DTFC', function () {
  				if ( ! mouseDown ) {
  					mouseController = 'main';
  				}
  			} )
  			.on( 'scroll.DTFC', function (e) {
  				if ( ! mouseController && e.originalEvent ) {
  					mouseController = 'main';
  				}
  
  				if ( mouseController === 'main' ) {
  					if ( that.s.iLeftColumns > 0 ) {
  						that.dom.grid.left.liner.scrollTop = that.dom.scroller.scrollTop;
  					}
  					if ( that.s.iRightColumns > 0 ) {
  						that.dom.grid.right.liner.scrollTop = that.dom.scroller.scrollTop;
  					}
  				}
  			} );
  
  		var wheelType = 'onwheel' in document.createElement('div') ?
  			'wheel.DTFC' :
  			'mousewheel.DTFC';
  
  		if ( that.s.iLeftColumns > 0 ) {
  			// When scrolling the left column, scroll the body and right column
  			$(that.dom.grid.left.liner)
  				.on( 'mouseover.DTFC touchstart.DTFC', function () {
  					if ( ! mouseDown ) {
  						mouseController = 'left';
  					}
  				} )
  				.on( 'scroll.DTFC', function ( e ) {
  					if ( ! mouseController && e.originalEvent ) {
  						mouseController = 'left';
  					}
  
  					if ( mouseController === 'left' ) {
  						that.dom.scroller.scrollTop = that.dom.grid.left.liner.scrollTop;
  						if ( that.s.iRightColumns > 0 ) {
  							that.dom.grid.right.liner.scrollTop = that.dom.grid.left.liner.scrollTop;
  						}
  					}
  				} )
  				.on( wheelType, function(e) {
  					// Pass horizontal scrolling through
  					var xDelta = e.type === 'wheel' ?
  						-e.originalEvent.deltaX :
  						e.originalEvent.wheelDeltaX;
  					that.dom.scroller.scrollLeft -= xDelta;
  				} );
  		}
  
  		if ( that.s.iRightColumns > 0 ) {
  			// When scrolling the right column, scroll the body and the left column
  			$(that.dom.grid.right.liner)
  				.on( 'mouseover.DTFC touchstart.DTFC', function () {
  					if ( ! mouseDown ) {
  						mouseController = 'right';
  					}
  				} )
  				.on( 'scroll.DTFC', function ( e ) {
  					if ( ! mouseController && e.originalEvent ) {
  						mouseController = 'right';
  					}
  
  					if ( mouseController === 'right' ) {
  						that.dom.scroller.scrollTop = that.dom.grid.right.liner.scrollTop;
  						if ( that.s.iLeftColumns > 0 ) {
  							that.dom.grid.left.liner.scrollTop = that.dom.grid.right.liner.scrollTop;
  						}
  					}
  				} )
  				.on( wheelType, function(e) {
  					// Pass horizontal scrolling through
  					var xDelta = e.type === 'wheel' ?
  						-e.originalEvent.deltaX :
  						e.originalEvent.wheelDeltaX;
  					that.dom.scroller.scrollLeft -= xDelta;
  				} );
  		}
  
  		$(window).on( 'resize.DTFC', function () {
  			that._fnGridLayout.call( that );
  		} );
  
  		var bFirstDraw = true;
  		var jqTable = $(this.s.dt.nTable);
  
  		jqTable
  			.on( 'draw.dt.DTFC', function () {
  				that._fnColCalc();
  				that._fnDraw.call( that, bFirstDraw );
  				bFirstDraw = false;
  			} )
  			.on( 'column-sizing.dt.DTFC', function () {
  				that._fnColCalc();
  				that._fnGridLayout( that );
  			} )
  			.on( 'column-visibility.dt.DTFC', function ( e, settings, column, vis, recalc ) {
  				if ( recalc === undefined || recalc ) {
  					that._fnColCalc();
  					that._fnGridLayout( that );
  					that._fnDraw( true );
  				}
  			} )
  			.on( 'select.dt.DTFC deselect.dt.DTFC', function ( e, dt, type, indexes ) {
  				if ( e.namespace === 'dt' ) {
  					that._fnDraw( false );
  				}
  			} )
  			.on( 'destroy.dt.DTFC', function () {
  				jqTable.off( '.DTFC' );
  
  				$(that.dom.scroller).off( '.DTFC' );
  				$(window).off( '.DTFC' );
  				$(that.s.dt.nTableWrapper).off( '.DTFC' );
  
  				$(that.dom.grid.left.liner).off( '.DTFC '+wheelType );
  				$(that.dom.grid.left.wrapper).remove();
  
  				$(that.dom.grid.right.liner).off( '.DTFC '+wheelType );
  				$(that.dom.grid.right.wrapper).remove();
  			} );
  
  		/* Get things right to start with - note that due to adjusting the columns, there must be
  		 * another redraw of the main table. It doesn't need to be a full redraw however.
  		 */
  		this._fnGridLayout();
  		this.s.dt.oInstance.fnDraw(false);
  	},
  
  
  	/**
  	 * Calculate the column widths for the grid layout
  	 *  @returns {void}
  	 *  @private
  	 */
  	"_fnColCalc": function ()
  	{
  		var that = this;
  		var iLeftWidth = 0;
  		var iRightWidth = 0;
  
  		this.s.aiInnerWidths = [];
  		this.s.aiOuterWidths = [];
  
  		$.each( this.s.dt.aoColumns, function (i, col) {
  			var th = $(col.nTh);
  			var border;
  
  			if ( ! th.filter(':visible').length ) {
  				that.s.aiInnerWidths.push( 0 );
  				that.s.aiOuterWidths.push( 0 );
  			}
  			else
  			{
  				// Inner width is used to assign widths to cells
  				// Outer width is used to calculate the container
  				var iWidth = th.outerWidth();
  
  				// When working with the left most-cell, need to add on the
  				// table's border to the outerWidth, since we need to take
  				// account of it, but it isn't in any cell
  				if ( that.s.aiOuterWidths.length === 0 ) {
  					border = $(that.s.dt.nTable).css('border-left-width');
  					iWidth += typeof border === 'string' && border.indexOf('px') === -1 ?
  						1 :
  						parseInt( border, 10 );
  				}
  
  				// Likewise with the final column on the right
  				if ( that.s.aiOuterWidths.length === that.s.dt.aoColumns.length-1 ) {
  					border = $(that.s.dt.nTable).css('border-right-width');
  					iWidth += typeof border === 'string' && border.indexOf('px') === -1 ?
  						1 :
  						parseInt( border, 10 );
  				}
  
  				that.s.aiOuterWidths.push( iWidth );
  				that.s.aiInnerWidths.push( th.width() );
  
  				if ( i < that.s.iLeftColumns )
  				{
  					iLeftWidth += iWidth;
  				}
  
  				if ( that.s.iTableColumns-that.s.iRightColumns <= i )
  				{
  					iRightWidth += iWidth;
  				}
  			}
  		} );
  
  		this.s.iLeftWidth = iLeftWidth;
  		this.s.iRightWidth = iRightWidth;
  	},
  
  
  	/**
  	 * Set up the DOM for the fixed column. The way the layout works is to create a 1x3 grid
  	 * for the left column, the DataTable (for which we just reuse the scrolling element DataTable
  	 * puts into the DOM) and the right column. In each of he two fixed column elements there is a
  	 * grouping wrapper element and then a head, body and footer wrapper. In each of these we then
  	 * place the cloned header, body or footer tables. This effectively gives as 3x3 grid structure.
  	 *  @returns {void}
  	 *  @private
  	 */
  	"_fnGridSetup": function ()
  	{
  		var that = this;
  		var oOverflow = this._fnDTOverflow();
  		var block;
  
  		this.dom.body = this.s.dt.nTable;
  		this.dom.header = this.s.dt.nTHead.parentNode;
  		this.dom.header.parentNode.parentNode.style.position = "relative";
  
  		var nSWrapper =
  			$('<div class="DTFC_ScrollWrapper" style="position:relative; clear:both;">'+
  				'<div class="DTFC_LeftWrapper" style="position:absolute; top:0; left:0;" aria-hidden="true">'+
  					'<div class="DTFC_LeftHeadWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+
  					'<div class="DTFC_LeftBodyWrapper" style="position:relative; top:0; left:0; height:0; overflow:hidden;">'+
  						'<div class="DTFC_LeftBodyLiner" style="position:relative; top:0; left:0; overflow-y:scroll;"></div>'+
  					'</div>'+
  					'<div class="DTFC_LeftFootWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+
  				'</div>'+
  				'<div class="DTFC_RightWrapper" style="position:absolute; top:0; right:0;" aria-hidden="true">'+
  					'<div class="DTFC_RightHeadWrapper" style="position:relative; top:0; left:0;">'+
  						'<div class="DTFC_RightHeadBlocker DTFC_Blocker" style="position:absolute; top:0; bottom:0;"></div>'+
  					'</div>'+
  					'<div class="DTFC_RightBodyWrapper" style="position:relative; top:0; left:0; height:0; overflow:hidden;">'+
  						'<div class="DTFC_RightBodyLiner" style="position:relative; top:0; left:0; overflow-y:scroll;"></div>'+
  					'</div>'+
  					'<div class="DTFC_RightFootWrapper" style="position:relative; top:0; left:0;">'+
  						'<div class="DTFC_RightFootBlocker DTFC_Blocker" style="position:absolute; top:0; bottom:0;"></div>'+
  					'</div>'+
  				'</div>'+
  			'</div>')[0];
  		var nLeft = nSWrapper.childNodes[0];
  		var nRight = nSWrapper.childNodes[1];
  
  		this.dom.grid.dt.parentNode.insertBefore( nSWrapper, this.dom.grid.dt );
  		nSWrapper.appendChild( this.dom.grid.dt );
  
  		this.dom.grid.wrapper = nSWrapper;
  
  		if ( this.s.iLeftColumns > 0 )
  		{
  			this.dom.grid.left.wrapper = nLeft;
  			this.dom.grid.left.head = nLeft.childNodes[0];
  			this.dom.grid.left.body = nLeft.childNodes[1];
  			this.dom.grid.left.liner = $('div.DTFC_LeftBodyLiner', nSWrapper)[0];
  
  			nSWrapper.appendChild( nLeft );
  		}
  
  		if ( this.s.iRightColumns > 0 )
  		{
  			this.dom.grid.right.wrapper = nRight;
  			this.dom.grid.right.head = nRight.childNodes[0];
  			this.dom.grid.right.body = nRight.childNodes[1];
  			this.dom.grid.right.liner = $('div.DTFC_RightBodyLiner', nSWrapper)[0];
  
  			nRight.style.right = oOverflow.bar+"px";
  
  			block = $('div.DTFC_RightHeadBlocker', nSWrapper)[0];
  			block.style.width = oOverflow.bar+"px";
  			block.style.right = -oOverflow.bar+"px";
  			this.dom.grid.right.headBlock = block;
  
  			block = $('div.DTFC_RightFootBlocker', nSWrapper)[0];
  			block.style.width = oOverflow.bar+"px";
  			block.style.right = -oOverflow.bar+"px";
  			this.dom.grid.right.footBlock = block;
  
  			nSWrapper.appendChild( nRight );
  		}
  
  		if ( this.s.dt.nTFoot )
  		{
  			this.dom.footer = this.s.dt.nTFoot.parentNode;
  			if ( this.s.iLeftColumns > 0 )
  			{
  				this.dom.grid.left.foot = nLeft.childNodes[2];
  			}
  			if ( this.s.iRightColumns > 0 )
  			{
  				this.dom.grid.right.foot = nRight.childNodes[2];
  			}
  		}
  
  		// RTL support - swap the position of the left and right columns (#48)
  		if ( this.s.rtl ) {
  			$('div.DTFC_RightHeadBlocker', nSWrapper).css( {
  				left: -oOverflow.bar+'px',
  				right: ''
  			} );
  		}
  	},
  
  
  	/**
  	 * Style and position the grid used for the FixedColumns layout
  	 *  @returns {void}
  	 *  @private
  	 */
  	"_fnGridLayout": function ()
  	{
  		var that = this;
  		var oGrid = this.dom.grid;
  		var iWidth = $(oGrid.wrapper).width();
  		var iBodyHeight = this.s.dt.nTable.parentNode.offsetHeight;
  		var iFullHeight = this.s.dt.nTable.parentNode.parentNode.offsetHeight;
  		var oOverflow = this._fnDTOverflow();
  		var iLeftWidth = this.s.iLeftWidth;
  		var iRightWidth = this.s.iRightWidth;
  		var rtl = $(this.dom.body).css('direction') === 'rtl';
  		var wrapper;
  		var scrollbarAdjust = function ( node, width ) {
  			if ( ! oOverflow.bar ) {
  				// If there is no scrollbar (Macs) we need to hide the auto scrollbar
  				node.style.width = (width+20)+"px";
  				node.style.paddingRight = "20px";
  				node.style.boxSizing = "border-box";
  			}
  			else if ( that._firefoxScrollError() ) {
  				// See the above function for why this is required
  				if ( $(node).height() > 34 ) {
  					node.style.width = (width+oOverflow.bar)+"px";
  				}
  			}
  			else {
  				// Otherwise just overflow by the scrollbar
  				node.style.width = (width+oOverflow.bar)+"px";
  			}
  		};
  
  		// When x scrolling - don't paint the fixed columns over the x scrollbar
  		if ( oOverflow.x )
  		{
  			iBodyHeight -= oOverflow.bar;
  		}
  
  		oGrid.wrapper.style.height = iFullHeight+"px";
  
  		if ( this.s.iLeftColumns > 0 )
  		{
  			wrapper = oGrid.left.wrapper;
  			wrapper.style.width = iLeftWidth+'px';
  			wrapper.style.height = '1px';
  
  			// Swap the position of the left and right columns for rtl (#48)
  			// This is always up against the edge, scrollbar on the far side
  			if ( rtl ) {
  				wrapper.style.left = '';
  				wrapper.style.right = 0;
  			}
  			else {
  				wrapper.style.left = 0;
  				wrapper.style.right = '';
  			}
  
  			oGrid.left.body.style.height = iBodyHeight+"px";
  			if ( oGrid.left.foot ) {
  				oGrid.left.foot.style.top = (oOverflow.x ? oOverflow.bar : 0)+"px"; // shift footer for scrollbar
  			}
  
  			scrollbarAdjust( oGrid.left.liner, iLeftWidth );
  			oGrid.left.liner.style.height = iBodyHeight+"px";
  			oGrid.left.liner.style.maxHeight = iBodyHeight+"px";
  		}
  
  		if ( this.s.iRightColumns > 0 )
  		{
  			wrapper = oGrid.right.wrapper;
  			wrapper.style.width = iRightWidth+'px';
  			wrapper.style.height = '1px';
  
  			// Need to take account of the vertical scrollbar
  			if ( this.s.rtl ) {
  				wrapper.style.left = oOverflow.y ? oOverflow.bar+'px' : 0;
  				wrapper.style.right = '';
  			}
  			else {
  				wrapper.style.left = '';
  				wrapper.style.right = oOverflow.y ? oOverflow.bar+'px' : 0;
  			}
  
  			oGrid.right.body.style.height = iBodyHeight+"px";
  			if ( oGrid.right.foot ) {
  				oGrid.right.foot.style.top = (oOverflow.x ? oOverflow.bar : 0)+"px";
  			}
  
  			scrollbarAdjust( oGrid.right.liner, iRightWidth );
  			oGrid.right.liner.style.height = iBodyHeight+"px";
  			oGrid.right.liner.style.maxHeight = iBodyHeight+"px";
  
  			oGrid.right.headBlock.style.display = oOverflow.y ? 'block' : 'none';
  			oGrid.right.footBlock.style.display = oOverflow.y ? 'block' : 'none';
  		}
  	},
  
  
  	/**
  	 * Get information about the DataTable's scrolling state - specifically if the table is scrolling
  	 * on either the x or y axis, and also the scrollbar width.
  	 *  @returns {object} Information about the DataTables scrolling state with the properties:
  	 *    'x', 'y' and 'bar'
  	 *  @private
  	 */
  	"_fnDTOverflow": function ()
  	{
  		var nTable = this.s.dt.nTable;
  		var nTableScrollBody = nTable.parentNode;
  		var out = {
  			"x": false,
  			"y": false,
  			"bar": this.s.dt.oScroll.iBarWidth
  		};
  
  		if ( nTable.offsetWidth > nTableScrollBody.clientWidth )
  		{
  			out.x = true;
  		}
  
  		if ( nTable.offsetHeight > nTableScrollBody.clientHeight )
  		{
  			out.y = true;
  		}
  
  		return out;
  	},
  
  
  	/**
  	 * Clone and position the fixed columns
  	 *  @returns {void}
  	 *  @param   {Boolean} bAll Indicate if the header and footer should be updated as well (true)
  	 *  @private
  	 */
  	"_fnDraw": function ( bAll )
  	{
  		this._fnGridLayout();
  		this._fnCloneLeft( bAll );
  		this._fnCloneRight( bAll );
  
  		/* Draw callback function */
  		if ( this.s.fnDrawCallback !== null )
  		{
  			this.s.fnDrawCallback.call( this, this.dom.clone.left, this.dom.clone.right );
  		}
  
  		/* Event triggering */
  		$(this).trigger( 'draw.dtfc', {
  			"leftClone": this.dom.clone.left,
  			"rightClone": this.dom.clone.right
  		} );
  	},
  
  
  	/**
  	 * Clone the right columns
  	 *  @returns {void}
  	 *  @param   {Boolean} bAll Indicate if the header and footer should be updated as well (true)
  	 *  @private
  	 */
  	"_fnCloneRight": function ( bAll )
  	{
  		if ( this.s.iRightColumns <= 0 ) {
  			return;
  		}
  
  		var that = this,
  			i, jq,
  			aiColumns = [];
  
  		for ( i=this.s.iTableColumns-this.s.iRightColumns ; i<this.s.iTableColumns ; i++ ) {
  			if ( this.s.dt.aoColumns[i].bVisible ) {
  				aiColumns.push( i );
  			}
  		}
  
  		this._fnClone( this.dom.clone.right, this.dom.grid.right, aiColumns, bAll );
  	},
  
  
  	/**
  	 * Clone the left columns
  	 *  @returns {void}
  	 *  @param   {Boolean} bAll Indicate if the header and footer should be updated as well (true)
  	 *  @private
  	 */
  	"_fnCloneLeft": function ( bAll )
  	{
  		if ( this.s.iLeftColumns <= 0 ) {
  			return;
  		}
  
  		var that = this,
  			i, jq,
  			aiColumns = [];
  
  		for ( i=0 ; i<this.s.iLeftColumns ; i++ ) {
  			if ( this.s.dt.aoColumns[i].bVisible ) {
  				aiColumns.push( i );
  			}
  		}
  
  		this._fnClone( this.dom.clone.left, this.dom.grid.left, aiColumns, bAll );
  	},
  
  
  	/**
  	 * Make a copy of the layout object for a header or footer element from DataTables. Note that
  	 * this method will clone the nodes in the layout object.
  	 *  @returns {Array} Copy of the layout array
  	 *  @param   {Object} aoOriginal Layout array from DataTables (aoHeader or aoFooter)
  	 *  @param   {Object} aiColumns Columns to copy
  	 *  @param   {boolean} events Copy cell events or not
  	 *  @private
  	 */
  	"_fnCopyLayout": function ( aoOriginal, aiColumns, events )
  	{
  		var aReturn = [];
  		var aClones = [];
  		var aCloned = [];
  
  		for ( var i=0, iLen=aoOriginal.length ; i<iLen ; i++ )
  		{
  			var aRow = [];
  			aRow.nTr = $(aoOriginal[i].nTr).clone(events, false)[0];
  
  			for ( var j=0, jLen=this.s.iTableColumns ; j<jLen ; j++ )
  			{
  				if ( $.inArray( j, aiColumns ) === -1 )
  				{
  					continue;
  				}
  
  				var iCloned = $.inArray( aoOriginal[i][j].cell, aCloned );
  				if ( iCloned === -1 )
  				{
  					var nClone = $(aoOriginal[i][j].cell).clone(events, false)[0];
  					aClones.push( nClone );
  					aCloned.push( aoOriginal[i][j].cell );
  
  					aRow.push( {
  						"cell": nClone,
  						"unique": aoOriginal[i][j].unique
  					} );
  				}
  				else
  				{
  					aRow.push( {
  						"cell": aClones[ iCloned ],
  						"unique": aoOriginal[i][j].unique
  					} );
  				}
  			}
  
  			aReturn.push( aRow );
  		}
  
  		return aReturn;
  	},
  
  
  	/**
  	 * Clone the DataTable nodes and place them in the DOM (sized correctly)
  	 *  @returns {void}
  	 *  @param   {Object} oClone Object containing the header, footer and body cloned DOM elements
  	 *  @param   {Object} oGrid Grid object containing the display grid elements for the cloned
  	 *                    column (left or right)
  	 *  @param   {Array} aiColumns Column indexes which should be operated on from the DataTable
  	 *  @param   {Boolean} bAll Indicate if the header and footer should be updated as well (true)
  	 *  @private
  	 */
  	"_fnClone": function ( oClone, oGrid, aiColumns, bAll )
  	{
  		var that = this,
  			i, iLen, j, jLen, jq, nTarget, iColumn, nClone, iIndex, aoCloneLayout,
  			jqCloneThead, aoFixedHeader,
  			dt = this.s.dt;
  
  		/*
  		 * Header
  		 */
  		if ( bAll )
  		{
  			$(oClone.header).remove();
  
  			oClone.header = $(this.dom.header).clone(true, false)[0];
  			oClone.header.className += " DTFC_Cloned";
  			oClone.header.style.width = "100%";
  			oGrid.head.appendChild( oClone.header );
  
  			/* Copy the DataTables layout cache for the header for our floating column */
  			aoCloneLayout = this._fnCopyLayout( dt.aoHeader, aiColumns, true );
  			jqCloneThead = $('>thead', oClone.header);
  			jqCloneThead.empty();
  
  			/* Add the created cloned TR elements to the table */
  			for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
  			{
  				jqCloneThead[0].appendChild( aoCloneLayout[i].nTr );
  			}
  
  			/* Use the handy _fnDrawHead function in DataTables to do the rowspan/colspan
  			 * calculations for us
  			 */
  			dt.oApi._fnDrawHead( dt, aoCloneLayout, true );
  		}
  		else
  		{
  			/* To ensure that we copy cell classes exactly, regardless of colspan, multiple rows
  			 * etc, we make a copy of the header from the DataTable again, but don't insert the
  			 * cloned cells, just copy the classes across. To get the matching layout for the
  			 * fixed component, we use the DataTables _fnDetectHeader method, allowing 1:1 mapping
  			 */
  			aoCloneLayout = this._fnCopyLayout( dt.aoHeader, aiColumns, false );
  			aoFixedHeader=[];
  
  			dt.oApi._fnDetectHeader( aoFixedHeader, $('>thead', oClone.header)[0] );
  
  			for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
  			{
  				for ( j=0, jLen=aoCloneLayout[i].length ; j<jLen ; j++ )
  				{
  					aoFixedHeader[i][j].cell.className = aoCloneLayout[i][j].cell.className;
  
  					// If jQuery UI theming is used we need to copy those elements as well
  					$('span.DataTables_sort_icon', aoFixedHeader[i][j].cell).each( function () {
  						this.className = $('span.DataTables_sort_icon', aoCloneLayout[i][j].cell)[0].className;
  					} );
  				}
  			}
  		}
  		this._fnEqualiseHeights( 'thead', this.dom.header, oClone.header );
  
  		/*
  		 * Body
  		 */
  		if ( this.s.sHeightMatch == 'auto' )
  		{
  			/* Remove any heights which have been applied already and let the browser figure it out */
  			$('>tbody>tr', that.dom.body).css('height', 'auto');
  		}
  
  		if ( oClone.body !== null )
  		{
  			$(oClone.body).remove();
  			oClone.body = null;
  		}
  
  		oClone.body = $(this.dom.body).clone(true)[0];
  		oClone.body.className += " DTFC_Cloned";
  		oClone.body.style.paddingBottom = dt.oScroll.iBarWidth+"px";
  		oClone.body.style.marginBottom = (dt.oScroll.iBarWidth*2)+"px"; /* For IE */
  		if ( oClone.body.getAttribute('id') !== null )
  		{
  			oClone.body.removeAttribute('id');
  		}
  
  		$('>thead>tr', oClone.body).empty();
  		$('>tfoot', oClone.body).remove();
  
  		var nBody = $('tbody', oClone.body)[0];
  		$(nBody).empty();
  		if ( dt.aiDisplay.length > 0 )
  		{
  			/* Copy the DataTables' header elements to force the column width in exactly the
  			 * same way that DataTables does it - have the header element, apply the width and
  			 * colapse it down
  			 */
  			var nInnerThead = $('>thead>tr', oClone.body)[0];
  			for ( iIndex=0 ; iIndex<aiColumns.length ; iIndex++ )
  			{
  				iColumn = aiColumns[iIndex];
  
  				nClone = $(dt.aoColumns[iColumn].nTh).clone(true)[0];
  				nClone.innerHTML = "";
  
  				var oStyle = nClone.style;
  				oStyle.paddingTop = "0";
  				oStyle.paddingBottom = "0";
  				oStyle.borderTopWidth = "0";
  				oStyle.borderBottomWidth = "0";
  				oStyle.height = 0;
  				oStyle.width = that.s.aiInnerWidths[iColumn]+"px";
  
  				nInnerThead.appendChild( nClone );
  			}
  
  			/* Add in the tbody elements, cloning form the master table */
  			$('>tbody>tr', that.dom.body).each( function (z) {
  				var i = that.s.dt.oFeatures.bServerSide===false ?
  					that.s.dt.aiDisplay[ that.s.dt._iDisplayStart+z ] : z;
  				var aTds = that.s.dt.aoData[ i ].anCells || $(this).children('td, th');
  
  				var n = this.cloneNode(false);
  				n.removeAttribute('id');
  				n.setAttribute( 'data-dt-row', i );
  
  				for ( iIndex=0 ; iIndex<aiColumns.length ; iIndex++ )
  				{
  					iColumn = aiColumns[iIndex];
  
  					if ( aTds.length > 0 )
  					{
  						nClone = $( aTds[iColumn] ).clone(true, true)[0];
  						nClone.removeAttribute( 'id' );
  						nClone.setAttribute( 'data-dt-row', i );
  						nClone.setAttribute( 'data-dt-column', iColumn );
  						n.appendChild( nClone );
  					}
  				}
  				nBody.appendChild( n );
  			} );
  		}
  		else
  		{
  			$('>tbody>tr', that.dom.body).each( function (z) {
  				nClone = this.cloneNode(true);
  				nClone.className += ' DTFC_NoData';
  				$('td', nClone).html('');
  				nBody.appendChild( nClone );
  			} );
  		}
  
  		oClone.body.style.width = "100%";
  		oClone.body.style.margin = "0";
  		oClone.body.style.padding = "0";
  
  		// Interop with Scroller - need to use a height forcing element in the
  		// scrolling area in the same way that Scroller does in the body scroll.
  		if ( dt.oScroller !== undefined )
  		{
  			var scrollerForcer = dt.oScroller.dom.force;
  
  			if ( ! oGrid.forcer ) {
  				oGrid.forcer = scrollerForcer.cloneNode( true );
  				oGrid.liner.appendChild( oGrid.forcer );
  			}
  			else {
  				oGrid.forcer.style.height = scrollerForcer.style.height;
  			}
  		}
  
  		oGrid.liner.appendChild( oClone.body );
  
  		this._fnEqualiseHeights( 'tbody', that.dom.body, oClone.body );
  
  		/*
  		 * Footer
  		 */
  		if ( dt.nTFoot !== null )
  		{
  			if ( bAll )
  			{
  				if ( oClone.footer !== null )
  				{
  					oClone.footer.parentNode.removeChild( oClone.footer );
  				}
  				oClone.footer = $(this.dom.footer).clone(true, true)[0];
  				oClone.footer.className += " DTFC_Cloned";
  				oClone.footer.style.width = "100%";
  				oGrid.foot.appendChild( oClone.footer );
  
  				/* Copy the footer just like we do for the header */
  				aoCloneLayout = this._fnCopyLayout( dt.aoFooter, aiColumns, true );
  				var jqCloneTfoot = $('>tfoot', oClone.footer);
  				jqCloneTfoot.empty();
  
  				for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
  				{
  					jqCloneTfoot[0].appendChild( aoCloneLayout[i].nTr );
  				}
  				dt.oApi._fnDrawHead( dt, aoCloneLayout, true );
  			}
  			else
  			{
  				aoCloneLayout = this._fnCopyLayout( dt.aoFooter, aiColumns, false );
  				var aoCurrFooter=[];
  
  				dt.oApi._fnDetectHeader( aoCurrFooter, $('>tfoot', oClone.footer)[0] );
  
  				for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
  				{
  					for ( j=0, jLen=aoCloneLayout[i].length ; j<jLen ; j++ )
  					{
  						aoCurrFooter[i][j].cell.className = aoCloneLayout[i][j].cell.className;
  					}
  				}
  			}
  			this._fnEqualiseHeights( 'tfoot', this.dom.footer, oClone.footer );
  		}
  
  		/* Equalise the column widths between the header footer and body - body get's priority */
  		var anUnique = dt.oApi._fnGetUniqueThs( dt, $('>thead', oClone.header)[0] );
  		$(anUnique).each( function (i) {
  			iColumn = aiColumns[i];
  			this.style.width = that.s.aiInnerWidths[iColumn]+"px";
  		} );
  
  		if ( that.s.dt.nTFoot !== null )
  		{
  			anUnique = dt.oApi._fnGetUniqueThs( dt, $('>tfoot', oClone.footer)[0] );
  			$(anUnique).each( function (i) {
  				iColumn = aiColumns[i];
  				this.style.width = that.s.aiInnerWidths[iColumn]+"px";
  			} );
  		}
  	},
  
  
  	/**
  	 * From a given table node (THEAD etc), get a list of TR direct child elements
  	 *  @param   {Node} nIn Table element to search for TR elements (THEAD, TBODY or TFOOT element)
  	 *  @returns {Array} List of TR elements found
  	 *  @private
  	 */
  	"_fnGetTrNodes": function ( nIn )
  	{
  		var aOut = [];
  		for ( var i=0, iLen=nIn.childNodes.length ; i<iLen ; i++ )
  		{
  			if ( nIn.childNodes[i].nodeName.toUpperCase() == "TR" )
  			{
  				aOut.push( nIn.childNodes[i] );
  			}
  		}
  		return aOut;
  	},
  
  
  	/**
  	 * Equalise the heights of the rows in a given table node in a cross browser way
  	 *  @returns {void}
  	 *  @param   {String} nodeName Node type - thead, tbody or tfoot
  	 *  @param   {Node} original Original node to take the heights from
  	 *  @param   {Node} clone Copy the heights to
  	 *  @private
  	 */
  	"_fnEqualiseHeights": function ( nodeName, original, clone )
  	{
  		if ( this.s.sHeightMatch == 'none' && nodeName !== 'thead' && nodeName !== 'tfoot' )
  		{
  			return;
  		}
  
  		var that = this,
  			i, iLen, iHeight, iHeight2, iHeightOriginal, iHeightClone,
  			rootOriginal = original.getElementsByTagName(nodeName)[0],
  			rootClone    = clone.getElementsByTagName(nodeName)[0],
  			jqBoxHack    = $('>'+nodeName+'>tr:eq(0)', original).children(':first'),
  			iBoxHack     = jqBoxHack.outerHeight() - jqBoxHack.height(),
  			anOriginal   = this._fnGetTrNodes( rootOriginal ),
  			anClone      = this._fnGetTrNodes( rootClone ),
  			heights      = [];
  
  		for ( i=0, iLen=anClone.length ; i<iLen ; i++ )
  		{
  			iHeightOriginal = anOriginal[i].offsetHeight;
  			iHeightClone = anClone[i].offsetHeight;
  			iHeight = iHeightClone > iHeightOriginal ? iHeightClone : iHeightOriginal;
  
  			if ( this.s.sHeightMatch == 'semiauto' )
  			{
  				anOriginal[i]._DTTC_iHeight = iHeight;
  			}
  
  			heights.push( iHeight );
  		}
  
  		for ( i=0, iLen=anClone.length ; i<iLen ; i++ )
  		{
  			anClone[i].style.height = heights[i]+"px";
  			anOriginal[i].style.height = heights[i]+"px";
  		}
  	},
  
  	/**
  	 * Determine if the UA suffers from Firefox's overflow:scroll scrollbars
  	 * not being shown bug.
  	 *
  	 * Firefox doesn't draw scrollbars, even if it is told to using
  	 * overflow:scroll, if the div is less than 34px height. See bugs 292284 and
  	 * 781885. Using UA detection here since this is particularly hard to detect
  	 * using objects - its a straight up rendering error in Firefox.
  	 *
  	 * @return {boolean} True if Firefox error is present, false otherwise
  	 */
  	_firefoxScrollError: function () {
  		if ( _firefoxScroll === undefined ) {
  			var test = $('<div/>')
  				.css( {
  					position: 'absolute',
  					top: 0,
  					left: 0,
  					height: 10,
  					width: 50,
  					overflow: 'scroll'
  				} )
  				.appendTo( 'body' );
  
  			// Make sure this doesn't apply on Macs with 0 width scrollbars
  			_firefoxScroll = (
  				test[0].clientWidth === test[0].offsetWidth && this._fnDTOverflow().bar !== 0
  			);
  
  			test.remove();
  		}
  
  		return _firefoxScroll;
  	}
  } );
  
  
  
  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   * Statics
   * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  
  /**
   * FixedColumns default settings for initialisation
   *  @name FixedColumns.defaults
   *  @namespace
   *  @static
   */
  FixedColumns.defaults = /** @lends FixedColumns.defaults */{
  	/**
  	 * Number of left hand columns to fix in position
  	 *  @type     int
  	 *  @default  1
  	 *  @static
  	 *  @example
  	 *      var  = $('#example').dataTable( {
  	 *          "scrollX": "100%"
  	 *      } );
  	 *      new $.fn.dataTable.fixedColumns( table, {
  	 *          "leftColumns": 2
  	 *      } );
  	 */
  	"iLeftColumns": 1,
  
  	/**
  	 * Number of right hand columns to fix in position
  	 *  @type     int
  	 *  @default  0
  	 *  @static
  	 *  @example
  	 *      var table = $('#example').dataTable( {
  	 *          "scrollX": "100%"
  	 *      } );
  	 *      new $.fn.dataTable.fixedColumns( table, {
  	 *          "rightColumns": 1
  	 *      } );
  	 */
  	"iRightColumns": 0,
  
  	/**
  	 * Draw callback function which is called when FixedColumns has redrawn the fixed assets
  	 *  @type     function(object, object):void
  	 *  @default  null
  	 *  @static
  	 *  @example
  	 *      var table = $('#example').dataTable( {
  	 *          "scrollX": "100%"
  	 *      } );
  	 *      new $.fn.dataTable.fixedColumns( table, {
  	 *          "drawCallback": function () {
  	 *	            /*---*/console.log( "FixedColumns redraw" );
  	 *	        }
  	 *      } );
  	 */
  	"fnDrawCallback": null,
  
  	/**
  	 * Height matching algorthim to use. This can be "none" which will result in no height
  	 * matching being applied by FixedColumns (height matching could be forced by CSS in this
  	 * case), "semiauto" whereby the height calculation will be performed once, and the result
  	 * cached to be used again (fnRecalculateHeight can be used to force recalculation), or
  	 * "auto" when height matching is performed on every draw (slowest but must accurate)
  	 *  @type     string
  	 *  @default  semiauto
  	 *  @static
  	 *  @example
  	 *      var table = $('#example').dataTable( {
  	 *          "scrollX": "100%"
  	 *      } );
  	 *      new $.fn.dataTable.fixedColumns( table, {
  	 *          "heightMatch": "auto"
  	 *      } );
  	 */
  	"sHeightMatch": "semiauto"
  };
  
  
  
  
  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   * Constants
   * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  
  /**
   * FixedColumns version
   *  @name      FixedColumns.version
   *  @type      String
   *  @default   See code
   *  @static
   */
  FixedColumns.version = "3.3.0";
  
  
  
  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   * DataTables API integration
   * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  
  DataTable.Api.register( 'fixedColumns()', function () {
  	return this;
  } );
  
  DataTable.Api.register( 'fixedColumns().update()', function () {
  	return this.iterator( 'table', function ( ctx ) {
  		if ( ctx._oFixedColumns ) {
  			ctx._oFixedColumns.fnUpdate();
  		}
  	} );
  } );
  
  DataTable.Api.register( 'fixedColumns().relayout()', function () {
  	return this.iterator( 'table', function ( ctx ) {
  		if ( ctx._oFixedColumns ) {
  			ctx._oFixedColumns.fnRedrawLayout();
  		}
  	} );
  } );
  
  DataTable.Api.register( 'rows().recalcHeight()', function () {
  	return this.iterator( 'row', function ( ctx, idx ) {
  		if ( ctx._oFixedColumns ) {
  			ctx._oFixedColumns.fnRecalculateHeight( this.row(idx).node() );
  		}
  	} );
  } );
  
  DataTable.Api.register( 'fixedColumns().rowIndex()', function ( row ) {
  	row = $(row);
  
  	return row.parents('.DTFC_Cloned').length ?
  		this.rows( { page: 'current' } ).indexes()[ row.index() ] :
  		this.row( row ).index();
  } );
  
  DataTable.Api.register( 'fixedColumns().cellIndex()', function ( cell ) {
  	cell = $(cell);
  
  	if ( cell.parents('.DTFC_Cloned').length ) {
  		var rowClonedIdx = cell.parent().index();
  		var rowIdx = this.rows( { page: 'current' } ).indexes()[ rowClonedIdx ];
  		var columnIdx;
  
  		if ( cell.parents('.DTFC_LeftWrapper').length ) {
  			columnIdx = cell.index();
  		}
  		else {
  			var columns = this.columns().flatten().length;
  			columnIdx = columns - this.context[0]._oFixedColumns.s.iRightColumns + cell.index();
  		}
  
  		return {
  			row: rowIdx,
  			column: this.column.index( 'toData', columnIdx ),
  			columnVisible: columnIdx
  		};
  	}
  	else {
  		return this.cell( cell ).index();
  	}
  } );
  
  DataTable.Api.registerPlural( 'cells().fixedNodes()', 'cell().fixedNode()', function () {
  	return this.iterator( 'cell', function ( settings, row, column ) {
  		return settings._oFixedColumns
  			? settings._oFixedColumns.fnToFixedNode( row, column )
  			: this.node();
  	}, 1 );
  } );
  
  
  
  
  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   * Initialisation
   * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  
  // Attach a listener to the document which listens for DataTables initialisation
  // events so we can automatically initialise
  $(document).on( 'init.dt.fixedColumns', function (e, settings) {
  	if ( e.namespace !== 'dt' ) {
  		return;
  	}
  
  	var init = settings.oInit.fixedColumns;
  	var defaults = DataTable.defaults.fixedColumns;
  
  	if ( init || defaults ) {
  		var opts = $.extend( {}, init, defaults );
  
  		if ( init !== false ) {
  			new FixedColumns( settings, opts );
  		}
  	}
  } );
  
  
  
  // Make FixedColumns accessible from the DataTables instance
  $.fn.dataTable.FixedColumns = FixedColumns;
  $.fn.DataTable.FixedColumns = FixedColumns;
  
  return FixedColumns;
  }));
  