PageSourceSearch

https://portal.colocrossing.com/js/jquery.dataTables.js

js colocrossing.com collected 2026-10-03 21:19:09 UTC 226,137 bytes, 7,322 lines download raw bytes

1/*
2 * File:        jquery.dataTables.js
3 * Version:     1.8.0
4 * Description: Paginate, search and sort HTML tables
5 * Author:      Allan Jardine (www.sprymedia.co.uk)
6 * Created:     28/3/2008
7 * Language:    Javascript
8 * License:     GPL v2 or BSD 3 point style
9 * Project:     Mtaala
10 * Contact:     [email protected]
11 * 
12 * Copyright 2008-2011 Allan Jardine, all rights reserved.
13 *
14 * This source file is free software, under either the GPL v2 license or a
15 * BSD style license, as supplied with this software.
16 * 
17 * This source file is distributed in the hope that it will be useful, but 
18 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
19 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
20 * 
21 * For details please refer to: http://www.datatables.net
22 */
23
24/*
25 * When considering jsLint, we need to allow eval() as it it is used for reading cookies
26 */
27/*jslint evil: true, undef: true, browser: true */
28/*globals $, jQuery,_fnExternApiFunc,_fnInitalise,_fnInitComplete,_fnLanguageProcess,_fnAddColumn,_fnColumnOptions,_fnAddData,_fnCreateTr,_fnGatherData,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAjaxUpdate,_fnAjaxUpdateDraw,_fnAddOptionsHtml,_fnFeatureHtmlTable,_fnScrollDraw,_fnAjustColumnSizing,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnBuildSearchArray,_fnBuildSearchRow,_fnFilterCreateSearch,_fnDataToSearch,_fnSort,_fnSortAttachListener,_fnSortingClasses,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnFeatureHtmlLength,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnNodeToDataIndex,_fnVisbleColumns,_fnCalculateEnd,_fnConvertToWidth,_fnCalculateColumnWidths,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnArrayCmp,_fnDetectType,_fnSettingsFromNode,_fnGetDataMaster,_fnGetTrNodes,_fnGetTdNodes,_fnEscapeRegex,_fnDeleteIndex,_fnReOrderIndex,_fnColumnOrdering,_fnLog,_fnClearTable,_fnSaveState,_fnLoadState,_fnCreateCookie,_fnReadCookie,_fnDetectHeader,_fnGetUniqueThs,_fnScrollBarWidth,_fnApplyToChildren,_fnMap,_fnGetRowData,_fnGetCellData,_fnSetCellData,_fnGetObjectDataFn,_fnSetObjectDataFn*/
29
30(function($, window, document) {
31	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
32	 * Section - DataTables variables
33	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
34	
35	/*
36	 * Variable: dataTableSettings
37	 * Purpose:  Store the settings for each dataTables instance
38	 * Scope:    jQuery.fn
39	 */
40	$.fn.dataTableSettings = [];
41	var _aoSettings = $.fn.dataTableSettings; /* Short reference for fast internal lookup */
42	
43	/*
44	 * Variable: dataTableExt
45	 * Purpose:  Container for customisable parts of DataTables
46	 * Scope:    jQuery.fn
47	 */
48	$.fn.dataTableExt = {};
49	var _oExt = $.fn.dataTableExt;
50	
51	
52	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
53	 * Section - DataTables extensible objects
54	 * 
55	 * The _oExt object is used to provide an area where user dfined plugins can be 
56	 * added to DataTables. The following properties of the object are used:
57	 *   oApi - Plug-in API functions
58	 *   aTypes - Auto-detection of types
59	 *   oSort - Sorting functions used by DataTables (based on the type)
60	 *   oPagination - Pagination functions for different input styles
61	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
62	
63	/*
64	 * Variable: sVersion
65	 * Purpose:  Version string for plug-ins to check compatibility
66	 * Scope:    jQuery.fn.dataTableExt
67	 * Notes:    Allowed format is a.b.c.d.e where:
68	 *   a:int, b:int, c:int, d:string(dev|beta), e:int. d and e are optional
69	 */
70	_oExt.sVersion = "1.8.0";
71	
72	/*
73	 * Variable: sErrMode
74	 * Purpose:  How should DataTables report an error. Can take the value 'alert' or 'throw'
75	 * Scope:    jQuery.fn.dataTableExt
76	 */
77	_oExt.sErrMode = "alert";
78	
79	/*
80	 * Variable: iApiIndex
81	 * Purpose:  Index for what 'this' index API functions should use
82	 * Scope:    jQuery.fn.dataTableExt
83	 */
84	_oExt.iApiIndex = 0;
85	
86	/*
87	 * Variable: oApi
88	 * Purpose:  Container for plugin API functions
89	 * Scope:    jQuery.fn.dataTableExt
90	 */
91	_oExt.oApi = { };
92	
93	/*
94	 * Variable: aFiltering
95	 * Purpose:  Container for plugin filtering functions
96	 * Scope:    jQuery.fn.dataTableExt
97	 */
98	_oExt.afnFiltering = [ ];
99	
100	/*
101	 * Variable: aoFeatures
102	 * Purpose:  Container for plugin function functions
103	 * Scope:    jQuery.fn.dataTableExt
104	 * Notes:    Array of objects with the following parameters:
105	 *   fnInit: Function for initialisation of Feature. Takes oSettings and returns node
106	 *   cFeature: Character that will be matched in sDom - case sensitive
107	 *   sFeature: Feature name - just for completeness :-)
108	 */
109	_oExt.aoFeatures = [ ];
110	
111	/*
112	 * Variable: ofnSearch
113	 * Purpose:  Container for custom filtering functions
114	 * Scope:    jQuery.fn.dataTableExt
115	 * Notes:    This is an object (the name should match the type) for custom filtering function,
116	 *   which can be used for live DOM checking or formatted text filtering
117	 */
118	_oExt.ofnSearch = { };
119	
120	/*
121	 * Variable: afnSortData
122	 * Purpose:  Container for custom sorting data source functions
123	 * Scope:    jQuery.fn.dataTableExt
124	 * Notes:    Array (associative) of functions which is run prior to a column of this 
125	 *   'SortDataType' being sorted upon.
126	 *   Function input parameters:
127	 *     object:oSettings-  DataTables settings object
128	 *     int:iColumn - Target column number
129	 *   Return value: Array of data which exactly matched the full data set size for the column to
130	 *     be sorted upon
131	 */
132	_oExt.afnSortData = [ ];
133	
134	/*
135	 * Variable: oStdClasses
136	 * Purpose:  Storage for the various classes that DataTables uses
137	 * Scope:    jQuery.fn.dataTableExt
138	 */
139	_oExt.oStdClasses = {
140		/* Two buttons buttons */
141		"sPagePrevEnabled": "paginate_enabled_previous",
142		"sPagePrevDisabled": "paginate_disabled_previous",
143		"sPageNextEnabled": "paginate_enabled_next",
144		"sPageNextDisabled": "paginate_disabled_next",
145		"sPageJUINext": "",
146		"sPageJUIPrev": "",
147		
148		/* Full numbers paging buttons */
149		"sPageButton": "paginate_button",
150		"sPageButtonActive": "paginate_active",
151		"sPageButtonStaticDisabled": "paginate_button paginate_button_disabled",
152		"sPageFirst": "first",
153		"sPagePrevious": "previous",
154		"sPageNext": "next",
155		"sPageLast": "last",
156		
157		/* Stripping classes */
158		"sStripOdd": "odd",
159		"sStripEven": "even",
160		
161		/* Empty row */
162		"sRowEmpty": "dataTables_empty",
163		
164		/* Features */
165		"sWrapper": "dataTables_wrapper",
166		"sFilter": "dataTables_filter",
167		"sInfo": "dataTables_info",
168		"sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */
169		"sLength": "dataTables_length",
170		"sProcessing": "dataTables_processing",
171		
172		/* Sorting */
173		"sSortAsc": "sorting_asc",
174		"sSortDesc": "sorting_desc",
175		"sSortable": "sorting", /* Sortable in both directions */
176		"sSortableAsc": "sorting_asc_disabled",
177		"sSortableDesc": "sorting_desc_disabled",
178		"sSortableNone": "sorting_disabled",
179		"sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
180		"sSortJUIAsc": "",
181		"sSortJUIDesc": "",
182		"sSortJUI": "",
183		"sSortJUIAscAllowed": "",
184		"sSortJUIDescAllowed": "",
185		"sSortJUIWrapper": "",
186		"sSortIcon": "",
187		
188		/* Scrolling */
189		"sScrollWrapper": "dataTables_scroll",
190		"sScrollHead": "dataTables_scrollHead",
191		"sScrollHeadInner": "dataTables_scrollHeadInner",
192		"sScrollBody": "dataTables_scrollBody",
193		"sScrollFoot": "dataTables_scrollFoot",
194		"sScrollFootInner": "dataTables_scrollFootInner",
195		
196		/* Misc */
197		"sFooterTH": ""
198	};
199	
200	/*
201	 * Variable: oJUIClasses
202	 * Purpose:  Storage for the various classes that DataTables uses - jQuery UI suitable
203	 * Scope:    jQuery.fn.dataTableExt
204	 */
205	_oExt.oJUIClasses = {
206		/* Two buttons buttons */
207		"sPagePrevEnabled": "fg-button ui-button ui-state-default ui-corner-left",
208		"sPagePrevDisabled": "fg-button ui-button ui-state-default ui-corner-left ui-state-disabled",
209		"sPageNextEnabled": "fg-button ui-button ui-state-default ui-corner-right",
210		"sPageNextDisabled": "fg-button ui-button ui-state-default ui-corner-right ui-state-disabled",
211		"sPageJUINext": "ui-icon ui-icon-circle-arrow-e",
212		"sPageJUIPrev": "ui-icon ui-icon-circle-arrow-w",
213		
214		/* Full numbers paging buttons */
215		"sPageButton": "fg-button ui-button ui-state-default",
216		"sPageButtonActive": "fg-button ui-button ui-state-default ui-state-disabled",
217		"sPageButtonStaticDisabled": "fg-button ui-button ui-state-default ui-state-disabled",
218		"sPageFirst": "first ui-corner-tl ui-corner-bl",
219		"sPagePrevious": "previous",
220		"sPageNext": "next",
221		"sPageLast": "last ui-corner-tr ui-corner-br",
222		
223		/* Stripping classes */
224		"sStripOdd": "odd",
225		"sStripEven": "even",
226		
227		/* Empty row */
228		"sRowEmpty": "dataTables_empty",
229		
230		/* Features */
231		"sWrapper": "dataTables_wrapper",
232		"sFilter": "dataTables_filter",
233		"sInfo": "dataTables_info",
234		"sPaging": "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi "+
235			"ui-buttonset-multi paging_", /* Note that the type is postfixed */
236		"sLength": "dataTables_length",
237		"sProcessing": "dataTables_processing",
238		
239		/* Sorting */
240		"sSortAsc": "ui-state-default",
241		"sSortDesc": "ui-state-default",
242		"sSortable": "ui-state-default",
243		"sSortableAsc": "ui-state-default",
244		"sSortableDesc": "ui-state-default",
245		"sSortableNone": "ui-state-default",
246		"sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
247		"sSortJUIAsc": "css_right ui-icon ui-icon-triangle-1-n",
248		"sSortJUIDesc": "css_right ui-icon ui-icon-triangle-1-s",
249		"sSortJUI": "css_right ui-icon ui-icon-carat-2-n-s",
250		"sSortJUIAscAllowed": "css_right ui-icon ui-icon-carat-1-n",
251		"sSortJUIDescAllowed": "css_right ui-icon ui-icon-carat-1-s",
252		"sSortJUIWrapper": "DataTables_sort_wrapper",
253		"sSortIcon": "DataTables_sort_icon",
254		
255		/* Scrolling */
256		"sScrollWrapper": "dataTables_scroll",
257		"sScrollHead": "dataTables_scrollHead ui-state-default",
258		"sScrollHeadInner": "dataTables_scrollHeadInner",
259		"sScrollBody": "dataTables_scrollBody",
260		"sScrollFoot": "dataTables_scrollFoot ui-state-default",
261		"sScrollFootInner": "dataTables_scrollFootInner",
262		
263		/* Misc */
264		"sFooterTH": "ui-state-default"
265	};
266	
267	/*
268	 * Variable: oPagination
269	 * Purpose:  Container for the various type of pagination that dataTables supports
270	 * Scope:    jQuery.fn.dataTableExt
271	 */
272	_oExt.oPagination = {
273		/*
274		 * Variable: two_button
275		 * Purpose:  Standard two button (forward/back) pagination
276		 * Scope:    jQuery.fn.dataTableExt.oPagination
277		 */
278		"two_button": {
279			/*
280			 * Function: oPagination.two_button.fnInit
281			 * Purpose:  Initalise dom elements required for pagination with forward/back buttons only
282			 * Returns:  -
283			 * Inputs:   object:oSettings - dataTables settings object
284			 *           node:nPaging - the DIV which contains this pagination control
285			 *           function:fnCallbackDraw - draw function which must be called on update
286			 */
287			"fnInit": function ( oSettings, nPaging, fnCallbackDraw )
288			{
289				var nPrevious, nNext, nPreviousInner, nNextInner;
290				
291				/* Store the next and previous elements in the oSettings object as they can be very
292				 * usful for automation - particularly testing
293				 */
294				if ( !oSettings.bJUI )
295				{
296					nPrevious = document.createElement( 'div' );
297					nNext = document.createElement( 'div' );
298				}
299				else
300				{
301					nPrevious = document.createElement( 'a' );
302					nNext = document.createElement( 'a' );
303					
304					nNextInner = document.createElement('span');
305					nNextInner.className = oSettings.oClasses.sPageJUINext;
306					nNext.appendChild( nNextInner );
307					
308					nPreviousInner = document.createElement('span');
309					nPreviousInner.className = oSettings.oClasses.sPageJUIPrev;
310					nPrevious.appendChild( nPreviousInner );
311				}
312				
313				nPrevious.className = oSettings.oClasses.sPagePrevDisabled;
314				nNext.className = oSettings.oClasses.sPageNextDisabled;
315				
316				nPrevious.title = oSettings.oLanguage.oPaginate.sPrevious;
317				nNext.title = oSettings.oLanguage.oPaginate.sNext;
318				
319				nPaging.appendChild( nPrevious );
320				nPaging.appendChild( nNext );
321				
322				$(nPrevious).bind( 'click.DT', function() {
323					if ( oSettings.oApi._fnPageChange( oSettings, "previous" ) )
324					{
325						/* Only draw when the page has actually changed */
326						fnCallbackDraw( oSettings );
327					}
328				} );
329				
330				$(nNext).bind( 'click.DT', function() {
331					if ( oSettings.oApi._fnPageChange( oSettings, "next" ) )
332					{
333						fnCallbackDraw( oSettings );
334					}
335				} );
336				
337				/* Take the brutal approach to cancelling text selection */
338				$(nPrevious).bind( 'selectstart.DT', function () { return false; } );
339				$(nNext).bind( 'selectstart.DT', function () { return false; } );
340				
341				/* ID the first elements only */
342				if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.p == "undefined" )
343				{
344					nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' );
345					nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' );
346					nNext.setAttribute( 'id', oSettings.sTableId+'_next' );
347				}
348			},
349			
350			/*
351			 * Function: oPagination.two_button.fnUpdate
352			 * Purpose:  Update the two button pagination at the end of the draw
353			 * Returns:  -
354			 * Inputs:   object:oSettings - dataTables settings object
355			 *           function:fnCallbackDraw - draw function to call on page change
356			 */
357			"fnUpdate": function ( oSettings, fnCallbackDraw )
358			{
359				if ( !oSettings.aanFeatures.p )
360				{
361					return;
362				}
363				
364				/* Loop over each instance of the pager */
365				var an = oSettings.aanFeatures.p;
366				for ( var i=0, iLen=an.length ; i<iLen ; i++ )
367				{
368					if ( an[i].childNodes.length !== 0 )
369					{
370						an[i].childNodes[0].className = 
371							( oSettings._iDisplayStart === 0 ) ? 
372							oSettings.oClasses.sPagePrevDisabled : oSettings.oClasses.sPagePrevEnabled;
373						
374						an[i].childNodes[1].className = 
375							( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() ) ? 
376							oSettings.oClasses.sPageNextDisabled : oSettings.oClasses.sPageNextEnabled;
377					}
378				}
379			}
380		},
381		
382		
383		/*
384		 * Variable: iFullNumbersShowPages
385		 * Purpose:  Change the number of pages which can be seen
386		 * Scope:    jQuery.fn.dataTableExt.oPagination
387		 */
388		"iFullNumbersShowPages": 5,
389		
390		/*
391		 * Variable: full_numbers
392		 * Purpose:  Full numbers pagination
393		 * Scope:    jQuery.fn.dataTableExt.oPagination
394		 */
395		"full_numbers": {
396			/*
397			 * Function: oPagination.full_numbers.fnInit
398			 * Purpose:  Initalise dom elements required for pagination with a list of the pages
399			 * Returns:  -
400			 * Inputs:   object:oSettings - dataTables settings object
401			 *           node:nPaging - the DIV which contains this pagination control
402			 *           function:fnCallbackDraw - draw function which must be called on update
403			 */
404			"fnInit": function ( oSettings, nPaging, fnCallbackDraw )
405			{
406				var nFirst = document.createElement( 'span' );
407				var nPrevious = document.createElement( 'span' );
408				var nList = document.createElement( 'span' );
409				var nNext = document.createElement( 'span' );
410				var nLast = document.createElement( 'span' );
411				
412				nFirst.innerHTML = oSettings.oLanguage.oPaginate.sFirst;
413				nPrevious.innerHTML = oSettings.oLanguage.oPaginate.sPrevious;
414				nNext.innerHTML = oSettings.oLanguage.oPaginate.sNext;
415				nLast.innerHTML = oSettings.oLanguage.oPaginate.sLast;
416				
417				var oClasses = oSettings.oClasses;
418				nFirst.className = oClasses.sPageButton+" "+oClasses.sPageFirst;
419				nPrevious.className = oClasses.sPageButton+" "+oClasses.sPagePrevious;
420				nNext.className= oClasses.sPageButton+" "+oClasses.sPageNext;
421				nLast.className = oClasses.sPageButton+" "+oClasses.sPageLast;
422				
423				nPaging.appendChild( nFirst );
424				nPaging.appendChild( nPrevious );
425				nPaging.appendChild( nList );
426				nPaging.appendChild( nNext );
427				nPaging.appendChild( nLast );
428				
429				$(nFirst).bind( 'click.DT', function () {
430					if ( oSettings.oApi._fnPageChange( oSettings, "first" ) )
431					{
432						fnCallbackDraw( oSettings );
433					}
434				} );
435				
436				$(nPrevious).bind( 'click.DT', function() {
437					if ( oSettings.oApi._fnPageChange( oSettings, "previous" ) )
438					{
439						fnCallbackDraw( oSettings );
440					}
441				} );
442				
443				$(nNext).bind( 'click.DT', function() {
444					if ( oSettings.oApi._fnPageChange( oSettings, "next" ) )
445					{
446						fnCallbackDraw( oSettings );
447					}
448				} );
449				
450				$(nLast).bind( 'click.DT', function() {
451					if ( oSettings.oApi._fnPageChange( oSettings, "last" ) )
452					{
453						fnCallbackDraw( oSettings );
454					}
455				} );
456				
457				/* Take the brutal approach to cancelling text selection */
458				$('span', nPaging)
459					.bind( 'mousedown.DT', function () { return false; } )
460					.bind( 'selectstart.DT', function () { return false; } );
461				
462				/* ID the first elements only */
463				if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.p == "undefined" )
464				{
465					nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' );
466					nFirst.setAttribute( 'id', oSettings.sTableId+'_first' );
467					nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' );
468					nNext.setAttribute( 'id', oSettings.sTableId+'_next' );
469					nLast.setAttribute( 'id', oSettings.sTableId+'_last' );
470				}
471			},
472			
473			/*
474			 * Function: oPagination.full_numbers.fnUpdate
475			 * Purpose:  Update the list of page buttons shows
476			 * Returns:  -
477			 * Inputs:   object:oSettings - dataTables settings object
478			 *           function:fnCallbackDraw - draw function to call on page change
479			 */
480			"fnUpdate": function ( oSettings, fnCallbackDraw )
481			{
482				if ( !oSettings.aanFeatures.p )
483				{
484					return;
485				}
486				
487				var iPageCount = _oExt.oPagination.iFullNumbersShowPages;
488				var iPageCountHalf = Math.floor(iPageCount / 2);
489				var iPages = Math.ceil((oSettings.fnRecordsDisplay()) / oSettings._iDisplayLength);
490				var iCurrentPage = Math.ceil(oSettings._iDisplayStart / oSettings._iDisplayLength) + 1;
491				var sList = "";
492				var iStartButton, iEndButton, i, iLen;
493				var oClasses = oSettings.oClasses;
494				
495				/* Pages calculation */
496				if (iPages < iPageCount)
497				{
498					iStartButton = 1;
499					iEndButton = iPages;
500				}
501				else
502				{
503					if (iCurrentPage <= iPageCountHalf)
504					{
505						iStartButton = 1;
506						iEndButton = iPageCount;
507					}
508					else
509					{
510						if (iCurrentPage >= (iPages - iPageCountHalf))
511						{
512							iStartButton = iPages - iPageCount + 1;
513							iEndButton = iPages;
514						}
515						else
516						{
517							iStartButton = iCurrentPage - Math.ceil(iPageCount / 2) + 1;
518							iEndButton = iStartButton + iPageCount - 1;
519						}
520					}
521				}
522				
523				/* Build the dynamic list */
524				for ( i=iStartButton ; i<=iEndButton ; i++ )
525				{
526					if ( iCurrentPage != i )
527					{
528						sList += '<span class="'+oClasses.sPageButton+'">'+i+'</span>';
529					}
530					else
531					{
532						sList += '<span class="'+oClasses.sPageButtonActive+'">'+i+'</span>';
533					}
534				}
535				
536				/* Loop over each instance of the pager */
537				var an = oSettings.aanFeatures.p;
538				var anButtons, anStatic, nPaginateList;
539				var fnClick = function(e) {
540					/* Use the information in the element to jump to the required page */
541					var iTarget = (this.innerHTML * 1) - 1;
542					oSettings._iDisplayStart = iTarget * oSettings._iDisplayLength;
543					fnCallbackDraw( oSettings );
544					e.preventDefault();
545				};
546				var fnFalse = function () { return false; };
547				
548				for ( i=0, iLen=an.length ; i<iLen ; i++ )
549				{
550					if ( an[i].childNodes.length === 0 )
551					{
552						continue;
553					}
554					
555					/* Build up the dynamic list forst - html and listeners */
556					var qjPaginateList = $('span:eq(2)', an[i]);
557					qjPaginateList.html( sList );
558					$('span', qjPaginateList).bind( 'click.DT', fnClick ).bind( 'mousedown.DT', fnFalse )
559						.bind( 'selectstart.DT', fnFalse );
560					
561					/* Update the 'premanent botton's classes */
562					anButtons = an[i].getElementsByTagName('span');
563					anStatic = [
564						anButtons[0], anButtons[1], 
565						anButtons[anButtons.length-2], anButtons[anButtons.length-1]
566					];
567					$(anStatic).removeClass( oClasses.sPageButton+" "+oClasses.sPageButtonActive+" "+oClasses.sPageButtonStaticDisabled );
568					if ( iCurrentPage == 1 )
569					{
570						anStatic[0].className += " "+oClasses.sPageButtonStaticDisabled;
571						anStatic[1].className += " "+oClasses.sPageButtonStaticDisabled;
572					}
573					else
574					{
575						anStatic[0].className += " "+oClasses.sPageButton;
576						anStatic[1].className += " "+oClasses.sPageButton;
577					}
578					
579					if ( iPages === 0 || iCurrentPage == iPages || oSettings._iDisplayLength == -1 )
580					{
581						anStatic[2].className += " "+oClasses.sPageButtonStaticDisabled;
582						anStatic[3].className += " "+oClasses.sPageButtonStaticDisabled;
583					}
584					else
585					{
586						anStatic[2].className += " "+oClasses.sPageButton;
587						anStatic[3].className += " "+oClasses.sPageButton;
588					}
589				}
590			}
591		}
592	};
593	
594	/*
595	 * Variable: oSort
596	 * Purpose:  Wrapper for the sorting functions that can be used in DataTables
597	 * Scope:    jQuery.fn.dataTableExt
598	 * Notes:    The functions provided in this object are basically standard javascript sort
599	 *   functions - they expect two inputs which they then compare and then return a priority
600	 *   result. For each sort method added, two functions need to be defined, an ascending sort and
601	 *   a descending sort.
602	 */
603	_oExt.oSort = {
604		/*
605		 * text sorting
606		 */
607		"string-asc": function ( a, b )
608		{
609			if ( typeof a != 'string' ) { a = ''; }
610			if ( typeof b != 'string' ) { b = ''; }
611			var x = a.toLowerCase();
612			var y = b.toLowerCase();
613			return ((x < y) ? -1 : ((x > y) ? 1 : 0));
614		},
615		
616		"string-desc": function ( a, b )
617		{
618			if ( typeof a != 'string' ) { a = ''; }
619			if ( typeof b != 'string' ) { b = ''; }
620			var x = a.toLowerCase();
621			var y = b.toLowerCase();
622			return ((x < y) ? 1 : ((x > y) ? -1 : 0));
623		},
624		
625		
626		/*
627		 * html sorting (ignore html tags)
628		 */
629		"html-asc": function ( a, b )
630		{
631			var x = a.replace( /<.*?>/g, "" ).toLowerCase();
632			var y = b.replace( /<.*?>/g, "" ).toLowerCase();
633			return ((x < y) ? -1 : ((x > y) ? 1 : 0));
634		},
635		
636		"html-desc": function ( a, b )
637		{
638			var x = a.replace( /<.*?>/g, "" ).toLowerCase();
639			var y = b.replace( /<.*?>/g, "" ).toLowerCase();
640			return ((x < y) ? 1 : ((x > y) ? -1 : 0));
641		},
642		
643		
644		/*
645		 * date sorting
646		 */
647		"date-asc": function ( a, b )
648		{
649			var x = Date.parse( a );
650			var y = Date.parse( b );
651			
652			if ( isNaN(x) || x==="" )
653			{
654			x = Date.parse( "01/01/1970 00:00:00" );
655			}
656			if ( isNaN(y) || y==="" )
657			{
658				y =	Date.parse( "01/01/1970 00:00:00" );
659			}
660			
661			return x - y;
662		},
663		
664		"date-desc": function ( a, b )
665		{
666			var x = Date.parse( a );
667			var y = Date.parse( b );
668			
669			if ( isNaN(x) || x==="" )
670			{
671			x = Date.parse( "01/01/1970 00:00:00" );
672			}
673			if ( isNaN(y) || y==="" )
674			{
675				y =	Date.parse( "01/01/1970 00:00:00" );
676			}
677			
678			return y - x;
679		},
680		
681		
682		/*
683		 * numerical sorting
684		 */
685		"numeric-asc": function ( a, b )
686		{
687			var x = (a=="-" || a==="") ? 0 : a*1;
688			var y = (b=="-" || b==="") ? 0 : b*1;
689			return x - y;
690		},
691		
692		"numeric-desc": function ( a, b )
693		{
694			var x = (a=="-" || a==="") ? 0 : a*1;
695			var y = (b=="-" || b==="") ? 0 : b*1;
696			return y - x;
697		}
698	};
699	
700	
701	/*
702	 * Variable: aTypes
703	 * Purpose:  Container for the various type of type detection that dataTables supports
704	 * Scope:    jQuery.fn.dataTableExt
705	 * Notes:    The functions in this array are expected to parse a string to see if it is a data
706	 *   type that it recognises. If so then the function should return the name of the type (a
707	 *   corresponding sort function should be defined!), if the type is not recognised then the
708	 *   function should return null such that the parser and move on to check the next type.
709	 *   Note that ordering is important in this array - the functions are processed linearly,
710	 *   starting at index 0.
711	 *   Note that the input for these functions is always a string! It cannot be any other data
712	 *   type
713	 */
714	_oExt.aTypes = [
715		/*
716		 * Function: -
717		 * Purpose:  Check to see if a string is numeric
718		 * Returns:  string:'numeric' or null
719		 * Inputs:   mixed:sText - string to check
720		 */
721		function ( sData )
722		{
723			/* Allow zero length strings as a number */
724			if ( typeof sData == 'number' )
725			{
726				return 'numeric';
727			}
728			else if ( typeof sData != 'string' )
729			{
730				return null;
731			}
732			
733			var sValidFirstChars = "0123456789-";
734			var sValidChars = "0123456789.";
735			var Char;
736			var bDecimal = false;
737			
738			/* Check for a valid first char (no period and allow negatives) */
739			Char = sData.charAt(0); 
740			if (sValidFirstChars.indexOf(Char) == -1) 
741			{
742				return null;
743			}
744			
745			/* Check all the other characters are valid */
746			for ( var i=1 ; i<sData.length ; i++ ) 
747			{
748				Char = sData.charAt(i); 
749				if (sValidChars.indexOf(Char) == -1) 
750				{
751					return null;
752				}
753				
754				/* Only allowed one decimal place... */
755				if ( Char == "." )
756				{
757					if ( bDecimal )
758					{
759						return null;
760					}
761					bDecimal = true;
762				}
763			}
764			
765			return 'numeric';
766		},
767		
768		/*
769		 * Function: -
770		 * Purpose:  Check to see if a string is actually a formatted date
771		 * Returns:  string:'date' or null
772		 * Inputs:   string:sText - string to check
773		 */
774		function ( sData )
775		{
776			var iParse = Date.parse(sData);
777			if ( (iParse !== null && !isNaN(iParse)) || (typeof sData == 'string' && sData.length === 0) )
778			{
779				return 'date';
780			}
781			return null;
782		},
783		
784		/*
785		 * Function: -
786		 * Purpose:  Check to see if a string should be treated as an HTML string
787		 * Returns:  string:'html' or null
788		 * Inputs:   string:sText - string to check
789		 */
790		function ( sData )
791		{
792			if ( typeof sData == 'string' && sData.indexOf('<') != -1 && sData.indexOf('>') != -1 )
793			{
794				return 'html';
795			}
796			return null;
797		}
798	];
799	
800	/*
801	 * Function: fnVersionCheck
802	 * Purpose:  Check a version string against this version of DataTables. Useful for plug
802-ins
803	 * Returns:  bool:true -this version of DataTables is greater or equal to the required version
804	 *                false -this version of DataTales is not suitable
805	 * Inputs:   string:sVersion - the version to check against. May be in the following formats:
806	 *             "a", "a.b" or "a.b.c"
807	 * Notes:    This function will only check the first three parts of a version string. It is
808	 *   assumed that beta and dev versions will meet the requirements. This might change in future
809	 */
810	_oExt.fnVersionCheck = function( sVersion )
811	{
812		/* This is cheap, but very effective */
813		var fnZPad = function (Zpad, count)
814		{
815			while(Zpad.length < count) {
816				Zpad += '0';
817			}
818			return Zpad;
819		};
820		var aThis = _oExt.sVersion.split('.');
821		var aThat = sVersion.split('.');
822		var sThis = '', sThat = '';
823		
824		for ( var i=0, iLen=aThat.length ; i<iLen ; i++ )
825		{
826			sThis += fnZPad( aThis[i], 3 );
827			sThat += fnZPad( aThat[i], 3 );
828		}
829		
830		return parseInt(sThis, 10) >= parseInt(sThat, 10);
831	};
832	
833	/*
834	 * Variable: _oExternConfig
835	 * Purpose:  Store information for DataTables to access globally about other instances
836	 * Scope:    jQuery.fn.dataTableExt
837	 */
838	_oExt._oExternConfig = {
839		/* int:iNextUnique - next unique number for an instance */
840		"iNextUnique": 0
841	};
842	
843	
844	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
845	 * Section - DataTables prototype
846	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
847	
848	/*
849	 * Function: dataTable
850	 * Purpose:  DataTables information
851	 * Returns:  -
852	 * Inputs:   object:oInit - initalisation options for the table
853	 */
854	$.fn.dataTable = function( oInit )
855	{
856		/*
857		 * Function: classSettings
858		 * Purpose:  Settings container function for all 'class' properties which are required
859		 *   by dataTables
860		 * Returns:  -
861		 * Inputs:   -
862		 */
863		function classSettings ()
864		{
865			this.fnRecordsTotal = function ()
866			{
867				if ( this.oFeatures.bServerSide ) {
868					return parseInt(this._iRecordsTotal, 10);
869				} else {
870					return this.aiDisplayMaster.length;
871				}
872			};
873			
874			this.fnRecordsDisplay = function ()
875			{
876				if ( this.oFeatures.bServerSide ) {
877					return parseInt(this._iRecordsDisplay, 10);
878				} else {
879					return this.aiDisplay.length;
880				}
881			};
882			
883			this.fnDisplayEnd = function ()
884			{
885				if ( this.oFeatures.bServerSide ) {
886					if ( this.oFeatures.bPaginate === false || this._iDisplayLength == -1 ) {
887						return this._iDisplayStart+this.aiDisplay.length;
888					} else {
889						return Math.min( this._iDisplayStart+this._iDisplayLength, 
890							this._iRecordsDisplay );
891					}
892				} else {
893					return this._iDisplayEnd;
894				}
895			};
896			
897			/*
898			 * Variable: oInstance
899			 * Purpose:  The DataTables object for this table
900			 * Scope:    jQuery.dataTable.classSettings 
901			 */
902			this.oInstance = null;
903			
904			/*
905			 * Variable: sInstance
906			 * Purpose:  Unique idendifier for each instance of the DataTables object
907			 * Scope:    jQuery.dataTable.classSettings 
908			 */
909			this.sInstance = null;
910			
911			/*
912			 * Variable: oFeatures
913			 * Purpose:  Indicate the enablement of key dataTable features
914			 * Scope:    jQuery.dataTable.classSettings 
915			 */
916			this.oFeatures = {
917				"bPaginate": true,
918				"bLengthChange": true,
919				"bFilter": true,
920				"bSort": true,
921				"bInfo": true,
922				"bAutoWidth": true,
923				"bProcessing": false,
924				"bSortClasses": true,
925				"bStateSave": false,
926				"bServerSide": false,
927				"bDeferRender": false
928			};
929			
930			/*
931			 * Variable: oScroll
932			 * Purpose:  Container for scrolling options
933			 * Scope:    jQuery.dataTable.classSettings 
934			 */
935			this.oScroll = {
936				"sX": "",
937				"sXInner": "",
938				"sY": "",
939				"bCollapse": false,
940				"bInfinite": false,
941				"iLoadGap": 100,
942				"iBarWidth": 0,
943				"bAutoCss": true
944			};
945			
946			/*
947			 * Variable: aanFeatures
948			 * Purpose:  Array referencing the nodes which are used for the features
949			 * Scope:    jQuery.dataTable.classSettings 
950			 * Notes:    The parameters of this object match what is allowed by sDom - i.e.
951			 *   'l' - Length changing
952			 *   'f' - Filtering input
953			 *   't' - The table!
954			 *   'i' - Information
955			 *   'p' - Pagination
956			 *   'r' - pRocessing
957			 */
958			this.aanFeatures = [];
959			
960			/*
961			 * Variable: oLanguage
962			 * Purpose:  Store the language strings used by dataTables
963			 * Scope:    jQuery.dataTable.classSettings
964			 * Notes:    The words in the format _VAR_ are variables which are dynamically replaced
965			 *   by javascript
966			 */
967			this.oLanguage = {
968				"sProcessing": "Processing...",
969				"sLengthMenu": "Show _MENU_ entries",
970				"sZeroRecords": "No matching records found",
971				"sEmptyTable": "No data available in table",
972				"sLoadingRecords": "Loading...",
973				"sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
974				"sInfoEmpty": "Showing 0 to 0 of 0 entries",
975				"sInfoFiltered": "(filtered from _MAX_ total entries)",
976				"sInfoPostFix": "",
977				"sSearch": "",
978				"sUrl": "",
979				"oPaginate": {
980					"sFirst":    "First",
981					"sPrevious": "Previous",
982					"sNext":     "Next",
983					"sLast":     "Last"
984				},
985				"fnInfoCallback": null
986			};
987			
988			/*
989			 * Variable: aoData
990			 * Purpose:  Store data information
991			 * Scope:    jQuery.dataTable.classSettings 
992			 * Notes:    This is an array of objects with the following parameters:
993			 *   int: _iId - internal id for tracking
994			 *   array: _aData - internal data - used for sorting / filtering etc
995			 *   node: nTr - display node
996			 *   array node: _anHidden - hidden TD nodes
997			 *   string: _sRowStripe
998			 */
999			this.aoData = [];
1000			
1001			/*
1002			 * Variable: aiDisplay
1003			 * Purpose:  Array of indexes which are in the current display (after filtering etc)
1004			 * Scope:    jQuery.dataTable.classSettings
1005			 */
1006			this.aiDisplay = [];
1007			
1008			/*
1009			 * Variable: aiDisplayMaster
1010			 * Purpose:  Array of indexes for display - no filtering
1011			 * Scope:    jQuery.dataTable.classSettings
1012			 */
1013			this.aiDisplayMaster = [];
1014							
1015			/*
1016			 * Variable: aoColumns
1017			 * Purpose:  Store information about each column that is in use
1018			 * Scope:    jQuery.dataTable.classSettings 
1019			 */
1020			this.aoColumns = [];
1021							
1022			/*
1023			 * Variable: aoHeader
1024			 * Purpose:  Store information about the table's header
1025			 * Scope:    jQuery.dataTable.classSettings 
1026			 */
1027			this.aoHeader = [];
1028							
1029			/*
1030			 * Variable: aoFooter
1031			 * Purpose:  Store information about the table's footer
1032			 * Scope:    jQuery.dataTable.classSettings 
1033			 */
1034			this.aoFooter = [];
1035			
1036			/*
1037			 * Variable: iNextId
1038			 * Purpose:  Store the next unique id to be used for a new row
1039			 * Scope:    jQuery.dataTable.classSettings 
1040			 */
1041			this.iNextId = 0;
1042			
1043			/*
1044			 * Variable: asDataSearch
1045			 * Purpose:  Search data array for regular expression searching
1046			 * Scope:    jQuery.dataTable.classSettings
1047			 */
1048			this.asDataSearch = [];
1049			
1050			/*
1051			 * Variable: oPreviousSearch
1052			 * Purpose:  Store the previous search incase we want to force a re-search
1053			 *   or compare the old search to a new one
1054			 * Scope:    jQuery.dataTable.classSettings
1055			 */
1056			this.oPreviousSearch = {
1057				"sSearch": "",
1058				"bRegex": false,
1059				"bSmart": true
1060			};
1061			
1062			/*
1063			 * Variable: aoPreSearchCols
1064			 * Purpose:  Store the previous search for each column
1065			 * Scope:    jQuery.dataTable.classSettings
1066			 */
1067			this.aoPreSearchCols = [];
1068			
1069			/*
1070			 * Variable: aaSorting
1071			 * Purpose:  Sorting information
1072			 * Scope:    jQuery.dataTable.classSettings
1073			 * Notes:    Index 0 - column number
1074			 *           Index 1 - current sorting direction
1075			 *           Index 2 - index of asSorting for this column
1076			 */
1077			this.aaSorting = [ [0, 'asc', 0] ];
1078			
1079			/*
1080			 * Variable: aaSortingFixed
1081			 * Purpose:  Sorting information that is always applied
1082			 * Scope:    jQuery.dataTable.classSettings
1083			 */
1084			this.aaSortingFixed = null;
1085			
1086			/*
1087			 * Variable: asStripClasses
1088			 * Purpose:  Classes to use for the striping of a table
1089			 * Scope:    jQuery.dataTable.classSettings
1090			 */
1091			this.asStripClasses = [];
1092			
1093			/*
1094			 * Variable: asDestoryStrips
1095			 * Purpose:  If restoring a table - we should restore it's striping classes as well
1096			 * Scope:    jQuery.dataTable.classSettings
1097			 */
1098			this.asDestoryStrips = [];
1099			
1100			/*
1101			 * Variable: sDestroyWidth
1102			 * Purpose:  If restoring a table - we should restore it's width
1103			 * Scope:    jQuery.dataTable.classSettings
1104			 */
1105			this.sDestroyWidth = 0;
1106			
1107			/*
1108			 * Variable: fnRowCallback
1109			 * Purpose:  Call this function every time a row is inserted (draw)
1110			 * Scope:    jQuery.dataTable.classSettings
1111			 */
1112			this.fnRowCallback = null;
1113			
1114			/*
1115			 * Variable: fnHeaderCallback
1116			 * Purpose:  Callback function for the header on each draw
1117			 * Scope:    jQuery.dataTable.classSettings
1118			 */
1119			this.fnHeaderCallback = null;
1120			
1121			/*
1122			 * Variable: fnFooterCallback
1123			 * Purpose:  Callback function for the footer on each draw
1124			 * Scope:    jQuery.dataTable.classSettings
1125			 */
1126			this.fnFooterCallback = null;
1127			
1128			/*
1129			 * Variable: aoDrawCallback
1130			 * Purpose:  Array of callback functions for draw callback functions
1131			 * Scope:    jQuery.dataTable.classSettings
1132			 * Notes:    Each array element is an object with the following parameters:
1133			 *   function:fn - function to call
1134			 *   string:sName - name callback (feature). useful for arranging array
1135			 */
1136			this.aoDrawCallback = [];
1137			
1138			/*
1139			 * Variable: fnPreDrawCallback
1140			 * Purpose:  Callback function for just before the table is redrawn. A return of false
1141			 *           will be used to cancel the draw.
1142			 * Scope:    jQuery.dataTable.classSettings
1143			 */
1144			this.fnPreDrawCallback = null;
1145			
1146			/*
1147			 * Variable: fnInitComplete
1148			 * Purpose:  Callback function for when the table has been initalised
1149			 * Scope:    jQuery.dataTable.classSettings
1150			 */
1151			this.fnInitComplete = null;
1152			
1153			/*
1154			 * Variable: sTableId
1155			 * Purpose:  Cache the table ID for quick access
1156			 * Scope:    jQuery.dataTable.classSettings
1157			 */
1158			this.sTableId = "";
1159			
1160			/*
1161			 * Variable: nTable
1162			 * Purpose:  Cache the table node for quick access
1163			 * Scope:    jQuery.dataTable.classSettings
1164			 */
1165			this.nTable = null;
1166			
1167			/*
1168			 * Variable: nTHead
1169			 * Purpose:  Permanent ref to the thead element
1170			 * Scope:    jQuery.dataTable.classSettings
1171			 */
1172			this.nTHead = null;
1173			
1174			/*
1175			 * Variable: nTFoot
1176			 * Purpose:  Permanent ref to the tfoot element - if it exists
1177			 * Scope:    jQuery.dataTable.classSettings
1178			 */
1179			this.nTFoot = null;
1180			
1181			/*
1182			 * Variable: nTBody
1183			 * Purpose:  Permanent ref to the tbody element
1184			 * Scope:    jQuery.dataTable.classSettings
1185			 */
1186			this.nTBody = null;
1187			
1188			/*
1189			 * Variable: nTableWrapper
1190			 * Purpose:  Cache the wrapper node (contains all DataTables controlled elements)
1191			 * Scope:    jQuery.dataTable.classSettings
1192			 */
1193			this.nTableWrapper = null;
1194			
1195			/*
1196			 * Variable: bDeferLoading
1197			 * Purpose:  Indicate if when using server-side processing the loading of data 
1198			 *           should be deferred until the second draw
1199			 * Scope:    jQuery.dataTable.classSettings
1200			 */
1201			this.bDeferLoading = false;
1202			
1203			/*
1204			 * Variable: bInitialised
1205			 * Purpose:  Indicate if all required information has been read in
1206			 * Scope:    jQuery.dataTable.classSettings
1207			 */
1208			this.bInitialised = false;
1209			
1210			/*
1211			 * Variable: aoOpenRows
1212			 * Purpose:  Information about open rows
1213			 * Scope:    jQuery.dataTable.classSettings
1214			 * Notes:    Has the parameters 'nTr' and 'nParent'
1215			 */
1216			this.aoOpenRows = [];
1217			
1218			/*
1219			 * Variable: sDom
1220			 * Purpose:  Dictate the positioning that the created elements will take
1221			 * Scope:    jQuery.dataTable.classSettings
1222			 * Notes:    
1223			 *   The following options are allowed:
1224			 *     'l' - Length changing
1225			 *     'f' - Filtering input
1226			 *     't' - The table!
1227			 *     'i' - Information
1228			 *     'p' - Pagination
1229			 *     'r' - pRocessing
1230			 *   The following constants are allowed:
1231			 *     'H' - jQueryUI theme "header" classes
1232			 *     'F' - jQueryUI theme "footer" classes
1233			 *   The following syntax is expected:
1234			 *     '<' and '>' - div elements
1235			 *     '<"class" and '>' - div with a class
1236			 *   Examples:
1237			 *     '<"wrapper"flipt>', '<lf<t>ip>'
1238			 */
1239			this.sDom = 'lfrtip';
1240			
1241			/*
1242			 * Variable: sPaginationType
1243			 * Purpose:  Note which type of sorting should be used
1244			 * Scope:    jQuery.dataTable.classSettings
1245			 */
1246			this.sPaginationType = "two_button";
1247			
1248			/*
1249			 * Variable: iCookieDuration
1250			 * Purpose:  The cookie duration (for bStateSave) in seconds - default 2 hours
1251			 * Scope:    jQuery.dataTable.classSettings
1252			 */
1253			this.iCookieDuration = 60 * 60 * 2;
1254			
1255			/*
1256			 * Variable: sCookiePrefix
1257			 * Purpose:  The cookie name prefix
1258			 * Scope:    jQuery.dataTable.classSettings
1259			 */
1260			this.sCookiePrefix = "SpryMedia_DataTables_";
1261			
1262			/*
1263			 * Variable: fnCookieCallback
1264			 * Purpose:  Callback function for cookie creation
1265			 * Scope:    jQuery.dataTable.classSettings
1266			 */
1267			this.fnCookieCallback = null;
1268			
1269			/*
1270			 * Variable: aoStateSave
1271			 * Purpose:  Array of callback functions for state saving
1272			 * Scope:    jQuery.dataTable.classSettings
1273			 * Notes:    Each array element is an object with the following parameters:
1274			 *   function:fn - function to call. Takes two parameters, oSettings and the JSON string to
1275			 *     save that has been thus far created. Returns a JSON string to be inserted into a 
1276			 *     json object (i.e. '"param": [ 0, 1, 2]')
1277			 *   string:sName - name of callback
1278			 */
1279			this.aoStateSave = [];
1280			
1281			/*
1282			 * Variable: aoStateLoad
1283			 * Purpose:  Array of callback functions for state loading
1284			 * Scope:    jQuery.dataTable.classSettings
1285			 * Notes:    Each array element is an object with the following parameters:
1286			 *   function:fn - function to call. Takes two parameters, oSettings and the object stored.
1287			 *     May return false to cancel state loading.
1288			 *   string:sName - name of callback
1289			 */
1290			this.aoStateLoad = [];
1291			
1292			/*
1293			 * Variable: oLoadedState
1294			 * Purpose:  State that was loaded from the cookie. Useful for back reference
1295			 * Scope:    jQuery.dataTable.classSettings
1296			 */
1297			this.oLoadedState = null;
1298			
1299			/*
1300			 * Variable: sAjaxSource
1301			 * Purpose:  Source url for AJAX data for the table
1302			 * Scope:    jQuery.dataTable.classSettings
1303			 */
1304			this.sAjaxSource = null;
1305			
1306			/*
1307			 * Variable: sAjaxDataProp
1308			 * Purpose:  Property from a given object from which to read the table data from. This can
1309			 *           be an empty string (when not server-side processing), in which case it is 
1310			 *           assumed an an array is given directly.
1311			 * Scope:    jQuery.dataTable.classSettings
1312			 */
1313			this.sAjaxDataProp = 'aaData';
1314			
1315			/*
1316			 * Variable: bAjaxDataGet
1317			 * Purpose:  Note if draw should be blocked while getting data
1318			 * Scope:    jQuery.dataTable.classSettings
1319			 */
1320			this.bAjaxDataGet = true;
1321			
1322			/*
1323			 * Variable: jqXHR
1324			 * Purpose:  The last jQuery XHR object that was used for server-side data gathering. 
1325			 *           This can be used for working with the XHR information in one of the callbacks
1326			 * Scope:    jQuery.dataTable.classSettings
1327			 */
1328			this.jqXHR = null;
1329			
1330			/*
1331			 * Variable: fnServerData
1332			 * Purpose:  Function to get the server-side data - can be overruled by the developer
1333			 * Scope:    jQuery.dataTable.classSettings
1334			 */
1335			this.fnServerData = function ( url, data, callback, settings ) {
1336				settings.jqXHR = $.ajax( {
1337					"url": url,
1338					"data": data,
1339					"success": callback,
1340					"dataType": "json",
1341					"cache": false,
1342					"error": function (xhr, error, thrown) {
1343						if ( error == "parsererror" ) {
1344							alert( "DataTables warning: JSON data from server could not be parsed. "+
1345								"This is caused by a JSON formatting error." );
1346						}
1347					}
1348				} );
1349			};
1350			
1351			/*
1352			 * Variable: fnFormatNumber
1353			 * Purpose:  Format numbers for display
1354			 * Scope:    jQuery.dataTable.classSettings
1355			 */
1356			this.fnFormatNumber = function ( iIn )
1357			{
1358				if ( iIn < 1000 )
1359				{
1360					/* A small optimisation for what is likely to be the vast majority of use cases */
1361					return iIn;
1362				}
1363				else
1364				{
1365					var s=(iIn+""), a=s.split(""), out="", iLen=s.length;
1366					
1367					for ( var i=0 ; i<iLen ; i++ )
1368					{
1369						if ( i%3 === 0 && i !== 0 )
1370						{
1371							out = ','+out;
1372						}
1373						out = a[iLen-i-1]+out;
1374					}
1375				}
1376				return out;
1377			};
1378			
1379			/*
1380			 * Variable: aLengthMenu
1381			 * Purpose:  List of options that can be used for the user selectable length menu
1382			 * Scope:    jQuery.dataTable.classSettings
1383			 * Note:     This varaible can take for form of a 1D array, in which case the value and the 
1384			 *   displayed value in the menu are the same, or a 2D array in which case the value comes
1385			 *   from the first array, and the displayed value to the end user comes from the second
1386			 *   array. 2D example: [ [ 10, 25, 50, 100, -1 ], [ 10, 25, 50, 100, 'All' ] ];
1387			 */
1388			this.aLengthMenu = [ 10, 25, 50, 100 ];
1389			
1390			/*
1391			 * Variable: iDraw
1392			 * Purpose:  Counter for the draws that the table does. Also used as a tracker for
1393			 *   server-side processing
1394			 * Scope:    jQuery.dataTable.classSettings
1395			 */
1396			this.iDraw = 0;
1397			
1398			/*
1399			 * Variable: bDrawing
1400			 * Purpose:  Indicate if a redraw is being done - useful for Ajax
1401			 * Scope:    jQuery.dataTable.classSettings
1402			 */
1403			this.bDrawing = 0;
1404			
1405			/*
1406			 * Variable: iDrawError
1407			 * Purpose:  Last draw error
1408			 * Scope:    jQuery.dataTable.classSettings
1409			 */
1410			this.iDrawError = -1;
1411			
1412			/*
1413			 * Variable: _iDisplayLength, _iDisplayStart, _iDisplayEnd
1414			 * Purpose:  Display length variables
1415			 * Scope:    jQuery.dataTable.classSettings
1416			 * Notes:    These variable must NOT be used externally to get the data length. Rather, use
1417			 *   the fnRecordsTotal() (etc) functions.
1418			 */
1419			this._iDisplayLength = 10;
1420			this._iDisplayStart = 0;
1421			this._iDisplayEnd = 10;
1422			
1423			/*
1424			 * Variable: _iRecordsTotal, _iRecordsDisplay
1425			 * Purpose:  Display length variables used for server side processing
1426			 * Scope:    jQuery.dataTable.classSettings
1427			 * Notes:    These variable must NOT be used externally to get the data length. Rather, use
1428			 *   the fnRecordsTotal() (etc) functions.
1429			 */
1430			this._iRecordsTotal = 0;
1431			this._iRecordsDisplay = 0;
1432			
1433			/*
1434			 * Variable: bJUI
1435			 * Purpose:  Should we add the markup needed for jQuery UI theming?
1436			 * Scope:    jQuery.dataTable.classSettings
1437			 */
1438			this.bJUI = false;
1439			
1440			/*
1441			 * Variable: oClasses
1442			 * Purpose:  Should we add the markup needed for jQuery UI theming?
1443			 * Scope:    jQuery.dataTable.classSettings
1444			 */
1445			this.oClasses = _oExt.oStdClasses;
1446			
1447			/*
1448			 * Variable: bFiltered and bSorted
1449			 * Purpose:  Flags to allow callback functions to see what actions have been performed
1450			 * Scope:    jQuery.dataTable.classSettings
1451			 */
1452			this.bFiltered = false;
1453			this.bSorted = false;
1454			
1455			/*
1456			 * Variable: bSortCellsTop
1457			 * Purpose:  Indicate that if multiple rows are in the header and there is more than one
1458			 *           unique cell per column, if the top one (true) or bottom one (false) should
1459			 *           be used for sorting / title by DataTables
1460			 * Scope:    jQuery.dataTable.classSettings
1461			 */
1462			this.bSortCellsTop = false;
1463			
1464			/*
1465			 * Variable: oInit
1466			 * Purpose:  Initialisation object that is used for the table
1467			 * Scope:    jQuery.dataTable.classSettings
1468			 */
1469			this.oInit = null;
1470		}
1471		
1472		/*
1473		 * Variable: oApi
1474		 * Purpose:  Container for publicly exposed 'private' functions
1475		 * Scope:    jQuery.dataTable
1476		 */
1477		this.oApi = {};
1478		
1479		
1480		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1481		 * Section - API functions
1482		 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1483		
1484		/*
1485		 * Function: fnDraw
1486		 * Purpose:  Redraw the table
1487		 * Returns:  -
1488		 * Inputs:   bool:bComplete - Refilter and resort (if enabled) the table before the draw.
1489		 *             Optional: default - true
1490		 */
1491		this.fnDraw = function( bComplete )
1492		{
1493			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1494			if ( typeof bComplete != 'undefined' && bComplete === false )
1495			{
1496				_fnCalculateEnd( oSettings );
1497				_fnDraw( oSettings );
1498			}
1499			else
1500			{
1501				_fnReDraw( oSettings );
1502			}
1503		};
1504		
1505		/*
1506		 * Function: fnFilter
1507		 * Purpose:  Filter the input based on data
1508		 * Returns:  -
1509		 * Inputs:   string:sInput - string to filter the table on
1510		 *           int:iColumn - optional - column to limit filtering to
1511		 *           bool:bRegex - optional - treat as regular expression or not - default false
1512		 *           bool:bSmart - optional - perform smart filtering or not - default true
1513		 *           bool:bShowGlobal - optional - show the input global filter in it's input box(es)
1514		 *              - default true
1515		 */
1516		this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal )
1517		{
1518			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1519			
1520			if ( !oSettings.oFeatures.bFilter )
1521			{
1522				return;
1523			}
1524			
1525			if ( typeof bRegex == 'undefined' )
1526			{
1527				bRegex = false;
1528			}
1529			
1530			if ( typeof bSmart == 'undefined' )
1531			{
1532				bSmart = true;
1533			}
1534			
1535			if ( typeof bShowGlobal == 'undefined' )
1536			{
1537				bShowGlobal = true;
1538			}
1539			
1540			if ( typeof iColumn == "undefined" || iColumn === null )
1541			{
1542				/* Global filter */
1543				_fnFilterComplete( oSettings, {
1544					"sSearch":sInput,
1545					"bRegex": bRegex,
1546					"bSmart": bSmart
1547				}, 1 );
1548				
1549				if ( bShowGlobal && typeof oSettings.aanFeatures.f != 'undefined' )
1550				{
1551					var n = oSettings.aanFeatures.f;
1552					for ( var i=0, iLen=n.length ; i<iLen ; i++ )
1553					{
1554						$('input', n[i]).val( sInput );
1555					}
1556				}
1557			}
1558			else
1559			{
1560				/* Single column filter */
1561				oSettings.aoPreSearchCols[ iColumn ].sSearch = sInput;
1562				oSettings.aoPreSearchCols[ iColumn ].bRegex = bRegex;
1563				oSettings.aoPreSearchCols[ iColumn ].bSmart = bSmart;
1564				_fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
1565			}
1566		};
1567		
1568		/*
1569		 * Function: fnSettings
1570		 * Purpose:  Get the settings for a particular table for extern. manipulation
1571		 * Returns:  -
1572		 * Inputs:   -
1573		 */
1574		this.fnSettings = function( nNode  )
1575		{
1576			return _fnSettingsFromNode( this[_oExt.iApiIndex] );
1577		};
1578		
1579		/*
1580		 * Function: fnVersionCheck
1581		 * Notes:    The function is the same as the 'static' function provided in the ext variable
1582		 */
1583		this.fnVersionCheck = _oExt.fnVersionCheck;
1584		
1585		/*
1586		 * Function: fnSort
1587		 * Purpose:  Sort the table by a particular row
1588		 * Returns:  -
1589		 * Inputs:   int:iCol - the data index to sort on. Note that this will
1590		 *   not match the 'display index' if you have hidden data entries
1591		 */
1592		this.fnSort = function( aaSort )
1593		{
1594			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1595			oSettings.aaSorting = aaSort;
1596			_fnSort( oSettings );
1597		};
1598		
1599		/*
1600		 * Function: fnSortListener
1601		 * Purpose:  Attach a sort listener to an element for a given column
1602		 * Returns:  -
1603		 * Inputs:   node:nNode - the element to attach the sort listener to
1604		 *           int:iColumn - the column that a click on this node will sort on
1605		 *           function:fnCallback - callback function when sort is run - optional
1606		 */
1607		this.fnSortListener = function( nNode, iColumn, fnCallback )
1608		{
1609			_fnSortAttachListener( _fnSettingsFromNode( this[_oExt.iApiIndex] ), nNode, iColumn,
1610			 	fnCallback );
1611		};
1612		
1613		/*
1614		 * Function: fnAddData
1615		 * Purpose:  Add new row(s) into the table
1616		 * Returns:  array int: array of indexes (aoData) which have been added (zero length on error)
1617		 * Inputs:   array:mData - the data to be added. The length must match
1618		 *               the original data from the DOM
1619		 *             or
1620		 *             array array:mData - 2D array of data to be added
1621		 *           bool:bRedraw - redraw the table or not - default true
1622		 * Notes:    Warning - the refilter here will cause the table to redraw
1623		 *             starting at zero
1624		 * Notes:    Thanks to Yekimov Denis for contributing the basis for this function!
1625		 */
1626		this.fnAddData = function( mData, bRedraw )
1627		{
1628			if ( mData.length === 0 )
1629			{
1630				return [];
1631			}
1632			
1633			var aiReturn = [];
1634			var iTest;
1635			
1636			/* Find settings from table node */
1637			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1638			
1639			/* Check if we want to add multiple rows or not */
1640			if ( typeof mData[0] == "object" )
1641			{
1642				for ( var i=0 ; i<mData.length ; i++ )
1643				{
1644					iTest = _fnAddData( oSettings, mData[i] );
1645					if ( iTest == -1 )
1646					{
1647						return aiReturn;
1648					}
1649					aiReturn.push( iTest );
1650				}
1651			}
1652			else
1653			{
1654				iTest = _fnAddData( oSettings, mData );
1655				if ( iTest == -1 )
1656				{
1657					return aiReturn;
1658				}
1659				aiReturn.push( iTest );
1660			}
1661			
1662			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
1663			
1664			if ( typeof bRedraw == 'undefined' || bRedraw )
1665			{
1666				_fnReDraw( oSettings );
1667			}
1668			return aiReturn;
1669		};
1670		
1671		/*
1672		 * Function: fnDeleteRow
1673		 * Purpose:  Remove a row for the table
1674		 * Returns:  array:aReturn - the row that was deleted
1675		 * Inputs:   mixed:mTarget - 
1676		 *             int: - index of aoData to be deleted, or
1677		 *             node(TR): - TR element you want to delete
1678		 *           function:fnCallBack - callback function - default null
1679		 *           bool:bRedraw - redraw the table or not - default true
1680		 */
1681		this.fnDeleteRow = function( mTarget, fnCallBack, bRedraw )
1682		{
1683			/* Find settings from table node */
1684			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1685			var i, iAODataIndex;
1686			
1687			iAODataIndex = (typeof mTarget == 'object') ? 
1688				_fnNodeToDataIndex(oSettings, mTarget) : mTarget;
1689			
1690			/* Return the data array from this row */
1691			var oData = oSettings.aoData.splice( iAODataIndex, 1 );
1692			
1693			/* Remove the target row from the search array */
1694			var iDisplayIndex = $.inArray( iAODataIndex, oSettings.aiDisplay );
1695			oSettings.asDataSearch.splice( iDisplayIndex, 1 );
1696			
1697			/* Delete from the display arrays */
1698			_fnDeleteIndex( oSettings.aiDisplayMaster, iAODataIndex );
1699			_fnDeleteIndex( oSettings.aiDisplay, iAODataIndex );
1700			
1701			/* If there is a user callback function - call it */
1702			if ( typeof fnCallBack == "function" )
1703			{
1704				fnCallBack.call( this, oSettings, oData );
1705			}
1706			
1707			/* Check for an 'overflow' they case for dislaying the table */
1708			if ( oSettings._iDisplayStart >= oSettings.aiDisplay.length )
1709			{
1710				oSettings._iDisplayStart -= oSettings._iDisplayLength;
1711				if ( oSettings._iDisplayStart < 0 )
1712				{
1713					oSettings._iDisplayStart = 0;
1714				}
1715			}
1716			
1717			if ( typeof bRedraw == 'undefined' || bRedraw )
1718			{
1719				_fnCalculateEnd( oSettings );
1720				_fnDraw( oSettings );
1721			}
1722			
1723			return oData;
1724		};
1725		
1726		/*
1727		 * Function: fnClearTable
1728		 * Purpose:  Quickly and simply clear a table
1729		 * Returns:  -
1730		 * Inputs:   bool:bRedraw - redraw the table or not - default true
1731		 * Notes:    Thanks to Yekimov Denis for contributing the basis for this function!
1732		 */
1733		this.fnClearTable = function( bRedraw )
1734		{
1735			/* Find settings from table node */
1736			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1737			_fnClearTable( oSettings );
1738			
1739			if ( typeof bRedraw == 'undefined' || bRedraw )
1740			{
1741				_fnDraw( oSettings );
1742			}
1743		};
1744		
1745		/*
1746		 * Function: fnOpen
1747		 * Purpose:  Open a display row (append a row after the row in question)
1748		 * Returns:  node:nNewRow - the row opened
1749		 * Inputs:   node:nTr - the table row to 'open'
1750		 *           string|node|jQuery:mHtml - the HTML to put into the row
1751		 *           string:sClass - class to give the new TD cell
1752		 */
1753		this.fnOpen = function( nTr, mHtml, sClass )
1754		{
1755			/* Find settings from table node */
1756			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1757			
1758			/* the old open one if there is one */
1759			this.fnClose( nTr );
1760			
1761			var nNewRow = document.createElement("tr");
1762			var nNewCell = document.createElement("td");
1763			nNewRow.appendChild( nNewCell );
1764			nNewCell.className = sClass;
1765			nNewCell.colSpan = _fnVisbleColumns( oSettings );
1766
1767			if( typeof mHtml.jquery != 'undefined' || typeof mHtml == "object" )
1768			{
1769				nNewCell.appendChild( mHtml );
1770			}
1771			else
1772			{
1773				nNewCell.innerHTML = mHtml;
1774			}
1775
1776			/* If the nTr isn't on the page at the moment - then we don't insert at the moment */
1777			var nTrs = $('tr', oSettings.nTBody);
1778			if ( $.inArray(nTr, nTrs) != -1 )
1779			{
1780				$(nNewRow).insertAfter(nTr);
1781			}
1782			
1783			oSettings.aoOpenRows.push( {
1784				"nTr": nNewRow,
1785				"nParent": nTr
1786			} );
1787			
1788			return nNewRow;
1789		};
1790		
1791		/*
1792		 * Function: fnClose
1793		 * Purpose:  Close a display row
1794		 * Returns:  int: 0 (success) or 1 (failed)
1795		 * Inputs:   node:nTr - the table row to 'close'
1796		 */
1797		this.fnClose = function( nTr )
1798		{
1799			/* Find settings from table node */
1800			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1801			
1802			for ( var i=0 ; i<oSettings.aoOpenRows.length ; i++ )
1803			{
1804				if ( oSettings.aoOpenRows[i].nParent == nTr )
1805				{
1806					var nTrParent = oSettings.aoOpenRows[i].nTr.parentNode;
1807					if ( nTrParent )
1808					{
1809						/* Remove it if it is currently on display */
1810						nTrParent.removeChild( oSettings.aoOpenRows[i].nTr );
1811					}
1812					oSettings.aoOpenRows.splice( i, 1 );
1813					return 0;
1814				}
1815			}
1816			return 1;
1817		};
1818		
1819		/*
1820		 * Function: fnGetData
1821		 * Purpose:  Return an array with the data which is used to make up the table
1822		 * Returns:  array array string: 2d data array ([row][column]) or array string: 1d data array
1823		 *           or string if both row and column are given
1824		 * Inputs:   mixed:mRow - optional - if not present, then the full 2D array for the table 
1825		 *             if given then:
1826		 *               int: - return data object for aoData entry of this index
1827		 *               node(TR): - return data object for this TR element
1828		 *           int:iCol - optional - the column that you want the data of. This will take into
1829		 *               account mDataProp and return the value DataTables uses for this column
1830		 */
1831		this.fnGetData = function( mRow, iCol )
1832		{
1833			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1834			
1835			if ( typeof mRow != 'undefined' )
1836			{
1837				var iRow = (typeof mRow == 'object') ? 
1838					_fnNodeToDataIndex(oSettings, mRow) : mRow;
1839				
1840				if ( typeof iCol != 'undefined' )
1841				{
1842					return _fnGetCellData( oSettings, iRow, iCol, '' );
1843				}
1844				return (typeof oSettings.aoData[iRow] != 'undefined') ? 
1845					oSettings.aoData[iRow]._aData : null;
1846			}
1847			return _fnGetDataMaster( oSettings );
1848		};
1849		
1850		/*
1851		 * Function: fnGetNodes
1852		 * Purpose:  Return an array with the TR nodes used for drawing the table
1853		 * Returns:  array node: TR elements
1854		 *           or
1855		 *           node (if iRow specified)
1856		 * Inputs:   int:iRow - optional - if present then the array returned will be the node for
1857		 *             the row with the index 'iRow'
1858		 */
1859		this.fnGetNodes = function( iRow )
1860		{
1861			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1862			
1863			if ( typeof iRow != 'undefined' )
1864			{
1865				return (typeof oSettings.aoData[iRow] != 'undefined') ? oSettings.aoData[iRow].nTr : null;
1866			}
1867			return _fnGetTrNodes( oSettings );
1868		};
1869		
1870		/*
1871		 * Function: fnGetPosition
1872		 * Purpose:  Get the array indexes of a particular cell from it's DOM element
1873		 * Returns:  int: - row index, or array[ int, int, int ]: - row index, column index (visible)
1874		 *             and column index including hidden columns
1875		 * Inputs:   node:nNode - this can either be a TR, TD or TH in the table's body, the return is
1876		 *             dependent on this input
1877		 */
1878		this.fnGetPosition = function( nNode )
1879		{
1880			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1881			var sNodeName = nNode.nodeName.toUpperCase();
1882			
1883			if ( sNodeName == "TR" )
1884			{
1885				return _fnNodeToDataIndex(oSettings, nNode);
1886			}
1887			else if ( sNodeName == "TD" || sNodeName == "TH" )
1888			{
1889				var iDataIndex = _fnNodeToDataIndex(oSettings, nNode.parentNode);
1890				var anCells = _fnGetTdNodes( oSettings, iDataIndex );
1891
1892				for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
1893				{
1894					if ( anCells[i] == nNode )
1895					{
1896						return [ iDataIndex, _fnColumnIndexToVisible(oSettings, i ), i ];
1897					}
1898				}
1899			}
1900			return null;
1901		};
1902		
1903		/*
1904		 * Function: fnUpdate
1905		 * Purpose:  Update a table cell or row - this method will accept either a single value to
1906		 *             update the cell with, an array of values with one element for each column or
1907		 *             an object in the same format as the original data source. The function is
1908		 *             self-referencing in order to make the multi column updates easier.
1909		 * Returns:  int: 0 okay, 1 error
1910		 * Inputs:   object | array string | string:mData - data to update the cell/row with
1911		 *           mixed:mRow - 
1912		 *             int: - index of aoData to be updated, or
1913		 *             node(TR): - TR element you want to update
1914		 *           int:iColumn - the column to update - optional (not used of mData is an array or object)
1915		 *           bool:bRedraw - redraw the table or not - default true
1916		 *           bool:bAction - perform predraw actions or not (you will want this as 'true' if
1917		 *             you have bRedraw as true) - default true
1918		 */
1919		this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction )
1920		{
1921			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1922			var iVisibleColumn, i, iLen, sDisplay;
1923			var iRow = (typeof mRow == 'object') ? 
1924				_fnNodeToDataIndex(oSettings, mRow) : mRow;
1925			
1926			if ( $.isArray(mData) && typeof mData == 'object' )
1927			{
1928				/* Array update - update the whole row */
1929				if ( mData.length != oSettings.aoColumns.length )
1930				{
1931					_fnLog( oSettings, 0, 'An array passed to fnUpdate must have the same number of '+
1932						'columns as the table in question - in this case '+oSettings.aoColumns.length );
1933					return 1;
1934				}
1935
1936				oSettings.aoData[iRow]._aData = mData.slice();
1937
1938				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
1939				{
1940					this.fnUpdate( _fnGetCellData( oSettings, iRow, i ), iRow, i, false, false );
1941				}
1942			}
1943			else if ( typeof mData == 'object' )
1944			{
1945				/* Object update - update the whole row - assume the developer gets the object right */
1946				oSettings.aoData[iRow]._aData = $.extend( true, {}, mData );
1947
1948				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
1949				{
1950					this.fnUpdate( _fnGetCellData( oSettings, iRow, i ), iRow, i, false, false );
1951				}
1952			}
1953			else
1954			{
1955				/* Individual cell update */
1956				sDisplay = mData;
1957				_fnSetCellData( oSettings, iRow, iColumn, sDisplay );
1958				
1959				if ( oSettings.aoColumns[iColumn].fnRender !== null )
1960				{
1961					sDisplay = oSettings.aoColumns[iColumn].fnRender( {
1962						"iDataRow": iRow,
1963						"iDataColumn": iColumn,
1964						"aData": oSettings.aoData[iRow]._aData,
1965						"oSettings": oSettings
1966					} );
1967					
1968					if ( oSettings.aoColumns[iColumn].bUseRendered )
1969					{
1970						_fnSetCellData( oSettings, iRow, iColumn, sDisplay );
1971					}
1972				}
1973				
1974				if ( oSettings.aoData[iRow].nTr !== null )
1975				{
1976					/* Do the actual HTML update */
1977					_fnGetTdNodes( oSettings, iRow )[iColumn].innerHTML = sDisplay;
1978				}
1979			}
1980			
1981			/* Modify the search index for this row (strictly this is likely not needed, since fnReDraw
1982			 * will rebuild the search array - however, the redraw might be disabled by the user)
1983			 */
1984			var iDisplayIndex = $.inArray( iRow, oSettings.aiDisplay );
1985			oSettings.asDataSearch[iDisplayIndex] = _fnBuildSearchRow( oSettings, 
1986				_fnGetRowData( oSettings, iRow, 'filter' ) );
1987			
1988			/* Perform pre-draw actions */
1989			if ( typeof bAction == 'undefined' || bAction )
1990			{
1991				_fnAjustColumnSizing( oSettings );
1992			}
1993			
1994			/* Redraw the table */
1995			if ( typeof bRedraw == 'undefined' || bRedraw )
1996			{
1997				_fnReDraw( oSettings );
1998			}
1999			return 0;
2000		};
2001		
2002		
2003		/*
2004		 * Function: fnShowColoumn
2005		 * Purpose:  Show a particular column
2006		 * Returns:  -
2007		 * Inputs:   int:iCol - the column whose display should be changed
2008		 *           bool:bShow - show (true) or hide (false) the column
2009		 *           bool:bRedraw - redraw the table or not - default true
2010		 */
2011		this.fnSetColumnVis = function ( iCol, bShow, bRedraw )
2012		{
2013			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
2014			var i, iLen;
2015			var iColumns = oSettings.aoColumns.length;
2016			var nTd, nCell, anTrs, jqChildren, bAppend, iBefore;
2017			
2018			/* No point in doing anything if we are requesting what is already true */
2019			if ( oSettings.aoColumns[iCol].bVisible == bShow )
2020			{
2021				return;
2022			}
2023			
2024			/* Show the column */
2025			if ( bShow )
2026			{
2027				var iInsert = 0;
2028				for ( i=0 ; i<iCol ; i++ )
2029				{
2030					if ( oSettings.aoColumns[i].bVisible )
2031					{
2032						iInsert++;
2033					}
2034				}
2035				
2036				/* Need to decide if we should use appendChild or insertBefore */
2037				bAppend = (iInsert >= _fnVisbleColumns( oSettings ));
2038
2039				/* Which coloumn should we be inserting before? */
2040				if ( !bAppend )
2041				{
2042					for ( i=iCol ; i<iColumns ; i++ )
2043					{
2044						if ( oSettings.aoColumns[i].bVisible )
2045						{
2046							iBefore = i;
2047							break;
2048						}
2049					}
2050				}
2051
2052				for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
2053				{
2054					if ( oSettings.aoData[i].nTr !== null )
2055					{
2056						if ( bAppend )
2057						{
2058							oSettings.aoData[i].nTr.appendChild( 
2059								oSettings.aoData[i]._anHidden[iCol]
2060							);
2061						}
2062						else
2063						{
2064							oSettings.aoData[i].nTr.insertBefore(
2065								oSettings.aoData[i]._anHidden[iCol], 
2066								_fnGetTdNodes( oSettings, i )[iBefore] );
2067						}
2068					}
2069				}
2070			}
2071			else
2072			{
2073				/* Remove a column from display */
2074				for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
2075				{
2076					if ( oSettings.aoData[i].nTr !== null )
2077					{
2078						nTd = _fnGetTdNodes( oSettings, i )[iCol];
2079						oSettings.aoData[i]._anHidden[iCol] = nTd;
2080						nTd.parentNode.removeChild( nTd );
2081					}
2082				}
2083			}
2084
2085			/* Clear to set the visible flag */
2086			oSettings.aoColumns[iCol].bVisible = bShow;
2087
2088			/* Redraw the header and footer based on the new column visibility */
2089			_fnDrawHead( oSettings, oSettings.aoHeader );
2090			if ( oSettings.nTFoot )
2091			{
2092				_fnDrawHead( oSettings, oSettings.aoFooter );
2093			}
2094			
2095			/* If there are any 'open' rows, then we need to alter the colspan for this col change */
2096			for ( i=0, iLen=oSettings.aoOpenRows.length ; i<iLen ; i++ )
2097			{
2098				oSettings.aoOpenRows[i].nTr.colSpan = _fnVisbleColumns( oSettings );
2099			}
2100			
2101			/* Do a redraw incase anything depending on the table columns needs it 
2102			 * (built-in: scrolling) 
2103			 */
2104			if ( typeof bRedraw == 'undefined' || bRedraw )
2105			{
2106				_fnAjustColumnSizing( oSettings );
2107				_fnDraw( oSettings );
2108			}
2109			
2110			_fnSaveState( oSettings );
2111		};
2112		
2113		/*
2114		 * Function: fnPageChange
2115		 * Purpose:  Change the pagination
2116		 * Returns:  -
2117		 * Inputs:   string:sAction - paging action to take: "first", "previous", "next" or "last"
2118		 *           bool:bRedraw - redraw the table or not - optional - default true
2119		 */
2120		this.fnPageChange = function ( sAction, bRedraw )
2121		{
2122			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
2123			_fnPageChange( oSettings, sAction );
2124			_fnCalculateEnd( oSettings );
2125			
2126			if ( typeof bRedraw == 'undefined' || bRedraw )
2127			{
2128				_fnDraw( oSettings );
2129			}
2130		};
2131		
2132		/*
2133		 * Function: fnDestroy
2134		 * Purpose:  Destructor for the DataTable
2135		 * Returns:  -
2136		 * Inputs:   -
2137		 */
2138		this.fnDestroy = function ( )
2139		{
2140			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
2141			var nOrig = oSettings.nTableWrapper.parentNode;
2142			var nBody = oSettings.nTBody;
2143			var i, iLen;
2144			
2145			/* Flag to note that the table is currently being destoryed - no action should be taken */
2146			oSettings.bDestroying = true;
2147			
2148			/* Blitz all DT events */
2149			$(oSettings.nTableWrapper).find('*').andSelf().unbind('.DT');
2150			
2151			/* Restore hidden columns */
2152			for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2153			{
2154				if ( oSettings.aoColumns[i].bVisible === false )
2155				{
2156					this.fnSetColumnVis( i, true );
2157				}
2158			}
2159			
2160			/* If there is an 'empty' indicator row, remove it */
2161			$('tbody>tr>td.'+oSettings.oClasses.sRowEmpty, oSettings.nTable).parent().remove();
2162			
2163			/* When scrolling we had to break the table up - restore it */
2164			if ( oSettings.nTable != oSettings.nTHead.parentNode )
2165			{
2166				$('>thead', oSettings.nTable).remove();
2167				oSettings.nTable.appendChild( oSettings.nTHead );
2168			}
2169			
2170			if ( oSettings.nTFoot && oSettings.nTable != oSettings.nTFoot.parentNode )
2171			{
2172				$('>tfoot', oSettings.nTable).remove();
2173				oSettings.nTable.appendChild( oSettings.nTFoot );
2174			}
2175			
2176			/* Remove the DataTables generated nodes, events and classes */
2177			oSettings.nTable.parentNode.removeChild( oSettings.nTable );
2178			$(oSettings.nTableWrapper).remove();
2179			
2180			oSettings.aaSorting = [];
2181			oSettings.aaSortingFixed = [];
2182			_fnSortingClasses( oSettings );
2183			
2184			$(_fnGetTrNodes( oSettings )).removeClass( oSettings.asStripClasses.join(' ') );
2185			
2186			if ( !oSettings.bJUI )
2187			{
2188				$('th', oSettings.nTHead).removeClass( [ _oExt.oStdClasses.sSortable,
2189					_oExt.oStdClasses.sSortableAsc,
2190					_oExt.oStdClasses.sSortableDesc,
2191					_oExt.oStdClasses.sSortableNone ].join(' ')
2192				);
2193			}
2194			else
2195			{
2196				$('th', oSettings.nTHead).removeClass( [ _oExt.oStdClasses.sSortable,
2197					_oExt.oJUIClasses.sSortableAsc,
2198					_oExt.oJUIClasses.sSortableDesc,
2199					_oExt.oJUIClasses.sSortableNone ].join(' ')
2200				);
2201				$('th span.'+_oExt.oJUIClasses.sSortIcon, oSettings.nTHead).remove();
2202
2203				$('th', oSettings.nTHead).each( function () {
2204					var jqWrapper = $('div.'+_oExt.oJUIClasses.sSortJUIWrapper, this);
2205					var kids = jqWrapper.contents();
2206					$(this).append( kids );
2207					jqWrapper.remove();
2208				} );
2209			}
2210			
2211			/* Add the TR elements back into the table in their original order */
2212			if ( oSettings.nTableReinsertBefore )
2213			{
2214				nOrig.insertBefore( oSettings.nTable, oSettings.nTableReinsertBefore );
2215			}
2216			else
2217			{
2218				nOrig.appendChild( oSettings.nTable );
2219			}
2220
2221			for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
2222			{
2223				if ( oSettings.aoData[i].nTr !== null )
2224				{
2225					nBody.appendChild( oSettings.aoData[i].nTr );
2226				}
2227			}
2228			
2229			/* Restore the width of the original table */
2230			oSettings.nTable.style.width = _fnStringToCss(oSettings.sDestroyWidth);
2231			
2232			/* If the were originally odd/even type classes - then we add them back here. Note
2233			 * this is not fool proof (for example if not all rows as odd/even classes - but 
2234			 * it's a good effort without getting carried away
2235			 */
2236			$('>tr:even', nBody).addClass( oSettings.asDestoryStrips[0] );
2237			$('>tr:odd', nBody).addClass( oSettings.asDestoryStrips[1] );
2238			
2239			/* Remove the settings object from the settings array */
2240			for ( i=0, iLen=_aoSettings.length ; i<iLen ; i++ )
2241			{
2242				if ( _aoSettings[i] == oSettings )
2243				{
2244					_aoSettings.splice( i, 1 );
2245				}
2246			}
2247			
2248			/* End it all */
2249			oSettings = null;
2250		};
2251		
2252		/*
2253		 * Function: fnAjustColumnSizing
2254		 * Purpose:  Update tale sizing based on content. This would most likely be used for scrolling
2255		 *   and will typically need a redraw after it.
2256		 * Returns:  -
2257		 * Inputs:   bool:bRedraw - redraw the table or not, you will typically want to - default true
2258		 */
2259		this.fnAdjustColumnSizing = function ( bRedraw )
2260		{
2261			var oSettings = _fnSettingsFromNode(this[_oExt.iApiIndex]);
2262			_fnAjustColumnSizing( oSettings );
2263			
2264			if ( typeof bRedraw == 'undefined' || bRedraw )
2265			{
2266				this.fnDraw( false );
2267			}
2268			else if ( oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "" )
2269			{
2270				/* If not redrawing, but scrolling, we want to apply the new column sizes anyway */
2271				this.oApi._fnScrollDraw(oSettings);
2272			}
2273		};
2274		
2275		/*
2276		 * Plugin API functions
2277		 * 
2278		 * This call will add the functions which are defined in _oExt.oApi to the
2279		 * DataTables object, providing a rather nice way to allow plug-in API functions. Note that
2280		 * this is done here, so that API function can actually override the built in API functions if
2281		 * required for a particular purpose.
2282		 */
2283		
2284		/*
2285		 * Function: _fnExternApiFunc
2286		 * Purpose:  Create a wrapper function for exporting an internal func to an external API func
2287		 * Returns:  function: - wrapped function
2288		 * Inputs:   string:sFunc - API function name
2289		 */
2290		function _fnExternApiFunc (sFunc)
2291		{
2292			return function() {
2293					var aArgs = [_fnSettingsFromNode(this[_oExt.iApiIndex])].concat( 
2294						Array.prototype.slice.call(arguments) );
2295					return _oExt.oApi[sFunc].apply( this, aArgs );
2296				};
2297		}
2298		
2299		for ( var sFunc in _oExt.oApi )
2300		{
2301			if ( sFunc )
2302			{
2303				/*
2304				 * Function: anon
2305				 * Purpose:  Wrap the plug-in API functions in order to provide the settings as 1st arg 
2306				 *   and execute in this scope
2307				 * Returns:  -
2308				 * Inputs:   -
2309				 */
2310				this[sFunc] = _fnExternApiFunc(sFunc);
2311			}
2312		}
2313		
2314		
2315		
2316		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2317		 * Section - Local functions
2318		 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2319		
2320		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2321		 * Section - Initalisation
2322		 */
2323		
2324		/*
2325		 * Function: _fnInitalise
2326		 * Purpose:  Draw the table for the first time, adding all required features
2327		 * Returns:  -
2328		 * Inputs:   object:oSettings - dataTables settings object
2329		 */
2330		function _fnInitalise ( oSettings )
2331		{
2332			var i, iLen, iAjaxStart=oSettings.iInitDisplayStart;
2333			
2334			/* Ensure that the table data is fully initialised */
2335			if ( oSettings.bInitialised === false )
2336			{
2337				setTimeout( function(){ _fnInitalise( oSettings ); }, 200 );
2338				return;
2339			}
2340			
2341			/* Show the display HTML options */
2342			_fnAddOptionsHtml( oSettings );
2343			
2344			/* Build and draw the header / footer for the table */
2345			_fnBuildHead( oSettings );
2346			_fnDrawHead( oSettings, oSettings.aoHeader );
2347			if ( oSettings.nTFoot )
2348			{
2349				_fnDrawHead( oSettings, oSettings.aoFooter );
2350			}
2351
2352			/* Okay to show that something is going on now */
2353			_fnProcessingDisplay( oSettings, true );
2354			
2355			/* Calculate sizes for columns */
2356			if ( oSettings.oFeatures.bAutoWidth )
2357			{
2358				_fnCalculateColumnWidths( oSettings );
2359			}
2360			
2361			for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2362			{
2363				if ( oSettings.aoColumns[i].sWidth !== null )
2364				{
2365					oSettings.aoColumns[i].nTh.style.width = _fnStringToCss( oSettings.aoColumns[i].sWidth );
2366				}
2367			}
2368			
2369			/* If there is default sorting required - let's do it. The sort function will do the
2370			 * drawing for us. Otherwise we draw the table regardless of the Ajax source - this allows
2371			 * the table to look initialised for Ajax sourcing data (show 'loading' message possibly)
2372			 */
2373			if ( oSettings.oFeatures.bSort )
2374			{
2375				_fnSort( oSettings );
2376			}
2377			else if ( oSettings.oFeatures.bFilter )
2378			{
2379				_fnFilterComplete( oSettings, oSettings.oPreviousSearch );
2380			}
2381			else
2382			{
2383				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
2384				_fnCalculateEnd( oSettings );
2385				_fnDraw( oSettings );
2386			}
2387			
2388			/* if there is an ajax source load the data */
2389			if ( oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide )
2390			{
2391				oSettings.fnServerData.call( oSettings.oInstance, oSettings.sAjaxSource, [], function(json) {
2392					var aData = json;
2393					if ( oSettings.sAjaxDataProp !== "" )
2394					{
2395						var fnDataSrc = _fnGetObjectDataFn( oSettings.sAjaxDataProp );
2396						aData = fnDataSrc( json );
2397					}
2398
2399					/* Got the data - add it to the table */
2400					for ( i=0 ; i<aData.length ; i++ )
2401					{
2402						_fnAddData( oSettings, aData[i] );
2403					}
2404					
2405					/* Reset the init display for cookie saving. We've already done a filter, and
2406					 * therefore cleared it before. So we need to make it appear 'fresh'
2407					 */
2408					oSettings.iInitDisplayStart = iAjaxStart;
2409					
2410					if ( oSettings.oFeatures.bSort )
2411					{
2412						_fnSort( oSettings );
2413					}
2414					else
2415					{
2416						oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
2417						_fnCalculateEnd( oSettings );
2418						_fnDraw( oSettings );
2419					}
2420					
2421					_fnProcessingDisplay( oSettings, false );
2422					_fnInitComplete( oSettings, json );
2423				}, oSettings );
2424				return;
2425			}
2426			
2427			/* Server-side processing initialisation complete is done at the end of _fnDraw */
2428			if ( !oSettings.oFeatures.bServerSide )
2429			{
2430				_fnProcessingDisplay( oSettings, false );
2431				_fnInitComplete( oSettings );
2432			}
2433		}
2434		
2435		/*
2436		 * Function: _fnInitalise
2437		 * Purpose:  Draw the table for the first time, adding all required features
2438		 * Returns:  -
2439		 * Inputs:   object:oSettings - dataTables settings object
2440		 */
2441		function _fnInitComplete ( oSettings, json )
2442		{
2443			oSettings._bInitComplete = true;
2444			if ( typeof oSettings.fnInitComplete == 'function' )
2445			{
2446				if ( typeof json != 'undefined' )
2447				{
2448					oSettings.fnInitComplete.call( oSettings.oInstance, oSettings, json );
2449				}
2450				else
2451				{
2452					oSettings.fnInitComplete.call( oSettings.oInstance, oSettings );
2453				}
2454			}
2455		}
2456		
2457		/*
2458		 * Function: _fnLanguageProcess
2459		 * Purpose:  Copy language variables from remote object to a local one
2460		 * Returns:  -
2461		 * Inputs:   object:oSettings - dataTables settings object
2462		 *           object:oLanguage - Language information
2463		 *           bool:bInit - init once complete
2464		 */
2465		function _fnLanguageProcess( oSettings, oLanguage, bInit )
2466		{
2467			_fnMap( oSettings.oLanguage, oLanguage, 'sProcessing' );
2468			_fnMap( oSettings.oLanguage, oLanguage, 'sLengthMenu' );
2469			_fnMap( oSettings.oLanguage, oLanguage, 'sEmptyTable' );
2470			_fnMap( oSettings.oLanguage, oLanguage, 'sLoadingRecords' );
2471			_fnMap( oSettings.oLanguage, oLanguage, 'sZeroRecords' );
2472			_fnMap( oSettings.oLanguage, oLanguage, 'sInfo' );
2473			_fnMap( oSettings.oLanguage, oLanguage, 'sInfoEmpty' );
2474			_fnMap( oSettings.oLanguage, oLanguage, 'sInfoFiltered' );
2475			_fnMap( oSettings.oLanguage, oLanguage, 'sInfoPostFix' );
2476			_fnMap( oSettings.oLanguage, oLanguage, 'sSearch' );
2477			
2478			if ( typeof oLanguage.oPaginate != 'undefined' )
2479			{
2480				_fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sFirst' );
2481				_fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sPrevious' );
2482				_fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sNext' );
2483				_fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sLast' );
2484			}
2485			
2486			/* Backwards compatibility - if there is no sEmptyTable given, then use the same as
2487			 * sZeroRecords - assuming that is given.
2488			 */
2489			if ( typeof oLanguage.sEmptyTable == 'undefined' && 
2490			     typeof oLanguage.sZeroRecords != 'undefined' )
2491			{
2492				_fnMap( oSettings.oLanguage, oLanguage, 'sZeroRecords', 'sEmptyTable' );
2493			}
2494
2495			/* Likewise with loading records */
2496			if ( typeof oLanguage.sLoadingRecords == 'undefined' && 
2497			     typeof oLanguage.sZeroRecords != 'undefined' )
2498			{
2499				_fnMap( oSettings.oLanguage, oLanguage, 'sZeroRecords', 'sLoadingRecords' );
2500			}
2501			
2502			if ( bInit )
2503			{
2504				_fnInitalise( oSettings );
2505			}
2506		}
2507		
2508		/*
2509		 * Function: _fnAddColumn
2510		 * Purpose:  Add a column to the list used for the table with default values
2511		 * Returns:  -
2512		 * Inputs:   object:oSettings - dataTables settings object
2513		 *           node:nTh - the th element for this column
2514		 */
2515		function _fnAddColumn( oSettings, nTh )
2516		{
2517			var iCol = oSettings.aoColumns.length;
2518			var oCol = {
2519				"sType": null,
2520				"_bAutoType": true,
2521				"bVisible": true,
2522				"bSearchable": true,
2523				"bSortable": true,
2524				"asSorting": [ 'asc', 'desc' ],
2525				"sSortingClass": oSettings.oClasses.sSortable,
2526				"sSortingClassJUI": oSettings.oClasses.sSortJUI,
2527				"sTitle": nTh ? nTh.innerHTML : '',
2528				"sName": '',
2529				"sWidth": null,
2530				"sWidthOrig": null,
2531				"sClass": null,
2532				"fnRender": null,
2533				"bUseRendered": true,
2534				"iDataSort": iCol,
2535				"mDataProp": iCol,
2536				"fnGetData": null,
2537				"fnSetData": null,
2538				"sSortDataType": 'std',
2539				"sDefaultContent": null,
2540				"sContentPadding": "",
2541				"nTh": nTh ? nTh : document.createElement('th'),
2542				"nTf": null
2543			};
2544			oSettings.aoColumns.push( oCol );
2545			
2546			/* Add a column specific filter */
2547			if ( typeof oSettings.aoPreSearchCols[ iCol ] == 'undefined' ||
2548			     oSettings.aoPreSearchCols[ iCol ] === null )
2549			{
2550				oSettings.aoPreSearchCols[ iCol ] = {
2551					"sSearch": "",
2552					"bRegex": false,
2553					"bSmart": true
2554				};
2555			}
2556			else
2557			{
2558				/* Don't require that the user must specify bRegex and / or bSmart */
2559				if ( typeof oSettings.aoPreSearchCols[ iCol ].bRegex == 'undefined' )
2560				{
2561					oSettings.aoPreSearchCols[ iCol ].bRegex = true;
2562				}
2563				
2564				if ( typeof oSettings.aoPreSearchCols[ iCol ].bSmart == 'undefined' )
2565				{
2566					oSettings.aoPreSearchCols[ iCol ].bSmart = true;
2567				}
2568			}
2569			
2570			/* Use the column options function to initialise classes etc */
2571			_fnColumnOptions( oSettings, iCol, null );
2572		}
2573		
2574		/*
2575		 * Function: _fnColumnOptions
2576		 * Purpose:  Apply options for a column
2577		 * Returns:  -
2578		 * Inputs:   object:oSettings - dataTables settings object
2579		 *           int:iCol - column index to consider
2580		 *           object:oOptions - object with sType, bVisible and bSearchable
2581		 */
2582		function _fnColumnOptions( oSettings, iCol, oOptions )
2583		{
2584			var oCol = oSettings.aoColumns[ iCol ];
2585			
2586			/* User specified column options */
2587			if ( typeof oOptions != 'undefined' && oOptions !== null )
2588			{
2589				if ( typeof oOptions.sType != 'undefined' )
2590				{
2591					oCol.sType = oOptions.sType;
2592					oCol._bAutoType = false;
2593				}
2594				
2595				_fnMap( oCol, oOptions, "bVisible" );
2596				_fnMap( oCol, oOptions, "bSearchable" );
2597				_fnMap( oCol, oOptions, "bSortable" );
2598				_fnMap( oCol, oOptions, "sTitle" );
2599				_fnMap( oCol, oOptions, "sName" );
2600				_fnMap( oCol, oOptions, "sWidth" );
2601				_fnMap( oCol, oOptions, "sWidth", "sWidthOrig" );
2602				_fnMap( oCol, oOptions, "sClass" );
2603				_fnMap( oCol, oOptions, "fnRender" );
2604				_fnMap( oCol, oOptions, "bUseRendered" );
2605				_fnMap( oCol, oOptions, "iDataSort" );
2606				_fnMap( oCol, oOptions, "mDataProp" );
2607				_fnMap( oCol, oOptions, "asSorting" );
2608				_fnMap( oCol, oOptions, "sSortDataType" );
2609				_fnMap( oCol, oOptions, "sDefaultContent" );
2610				_fnMap( oCol, oOptions, "sContentPadding" );
2611			}
2612
2613			/* Cache the data get and set functions for speed */
2614			oCol.fnGetData = _fnGetObjectDataFn( oCol.mDataProp );
2615			oCol.fnSetData = _fnSetObjectDataFn( oCol.mDataProp );
2616			
2617			/* Feature sorting overrides column specific when off */
2618			if ( !oSettings.oFeatures.bSort )
2619			{
2620				oCol.bSortable = false;
2621			}
2622			
2623			/* Check that the class assignment is correct for sorting */
2624			if ( !oCol.bSortable ||
2625					 ($.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) == -1) )
2626			{
2627				oCol.sSortingClass = oSettings.oClasses.sSortableNone;
2628				oCol.sSortingClassJUI = "";
2629			}
2630			else if ( $.inArray('asc', oCol.asSorting) != -1 && $.inArray('desc', oCol.asSorting) == -1 )
2631			{
2632				oCol.sSortingClass = oSettings.oClasses.sSortableAsc;
2633				oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIAscAllowed;
2634			}
2635			else if ( $.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) != -1 )
2636			{
2637				oCol.sSortingClass = oSettings.oClasses.sSortableDesc;
2638				oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIDescAllowed;
2639			}
2640		}
2641		
2642		/*
2643		 * Function: _fnAddData
2644		 * Purpose:  Add a data array to the table, creating DOM node etc
2645		 * Returns:  int: - >=0 if successful (index of new aoData entry), -1 if failed
2646		 * Inputs:   object:oSettings - dataTables settings object
2647		 *           array:aData - data array to be added
2648		 * Notes:    There are two basic methods for DataTables to get data to display - a JS array
2649		 *   (which is dealt with by this function), and the DOM, which has it's own optimised
2650		 *   function (_fnGatherData). Be careful to make the same changes here as there and vice-versa
2651		 */
2652		function _fnAddData ( oSettings, aDataSupplied )
2653		{
2654			var oCol;
2655			
2656			/* Take an independent copy of the data source so we can bash it about as we wish */
2657			var aDataIn = (typeof aDataSupplied.length == 'number') ?
2658				aDataSupplied.slice() :
2659				$.extend( true, {}, aDataSupplied );
2660			
2661			/* Create the object for storing information about this new row */
2662			var iRow = oSettings.aoData.length;
2663			var oData = {
2664				"nTr": null,
2665				"_iId": oSettings.iNextId++,
2666				"_aData": aDataIn,
2667				"_anHidden": [],
2668				"_sRowStripe": ""
2669			};
2670			oSettings.aoData.push( oData );
2671
2672			/* Create the cells */
2673			var nTd, sThisType;
2674			for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2675			{
2676				oCol = oSettings.aoColumns[i];
2677
2678				/* Use rendered data for filtering/sorting */
2679				if ( typeof oCol.fnRender == 'function' && oCol.bUseRendered && oCol.mDataProp !== null )
2680				{
2681					_fnSetCellData( oSettings, iRow, i, oCol.fnRender( {
2682						"iDataRow": iRow,
2683						"iDataColumn": i,
2684						"aData": oData._aData,
2685						"oSettings": oSettings
2686					} ) );
2687				}
2688				
2689				/* See if we should auto-detect the column type */
2690				if ( oCol._bAutoType && oCol.sType != 'string' )
2691				{
2692					/* Attempt to auto detect the type - same as _fnGatherData() */
2693					var sVarType = _fnGetCellData( oSettings, iRow, i, 'type' );
2694					if ( sVarType !== null && sVarType !== '' )
2695					{
2696						sThisType = _fnDetectType( sVarType );
2697						if ( oCol.sType === null )
2698						{
2699							oCol.sType = sThisType;
2700						}
2701						else if ( oCol.sType != sThisType )
2702						{
2703							/* String is always the 'fallback' option */
2704							oCol.sType = 'string';
2705						}
2706					}
2707				}
2708			}
2709			
2710			/* Add to the display array */
2711			oSettings.aiDisplayMaster.push( iRow );
2712
2713			/* Create the DOM imformation */
2714			if ( !oSettings.oFeatures.bDeferRender )
2715			{
2716				_fnCreateTr( oSettings, iRow );
2717			}
2718
2719			return iRow;
2720		}
2721		
2722		/*
2723		 * Function: _fnCreateTr
2724		 * Purpose:  Create a new TR element (and it's TD children) for a row
2725		 * Returns:  void
2726		 * Inputs:   object:oSettings - dataTables settings object
2727		 *           int:iRow - Row to consider
2728		 */
2729		function _fnCreateTr ( oSettings, iRow )
2730		{
2731			var oData = oSettings.aoData[iRow];
2732			var nTd;
2733
2734			if ( oData.nTr === null )
2735			{
2736				oData.nTr = document.createElement('tr');
2737
2738				/* Special parameters can be given by the data source to be used on the row */
2739				if ( typeof oData._aData.DT_RowId != 'undefined' )
2740				{
2741					oData.nTr.setAttribute( 'id', oData._aData.DT_RowId );
2742				}
2743
2744				if ( typeof oData._aData.DT_RowClass != 'undefined' )
2745				{
2746					$(oData.nTr).addClass( oData._aData.DT_RowClass );
2747				}
2748
2749				/* Process each column */
2750				for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2751				{
2752					var oCol = oSettings.aoColumns[i];
2753					nTd = document.createElement('td');
2754
2755					/* Render if needed - if bUseRendered is true then we already have the rendered
2756					 * value in the data source - so can just use that
2757					 */
2758					if ( typeof oCol.fnRender == 'function' && (!oCol.bUseRendered || oCol.mDataProp === null) )
2759					{
2760						nTd.innerHTML = oCol.fnRender( {
2761							"iDataRow": iRow,
2762							"iDataColumn": i,
2763							"aData": oData._aData,
2764							"oSettings": oSettings
2765						} );
2766					}
2767					else
2768					{
2769						nTd.innerHTML = _fnGetCellData( oSettings, iRow, i, 'display' );
2770					}
2771				
2772					/* Add user defined class */
2773					if ( oCol.sClass !== null )
2774					{
2775						nTd.className = oCol.sClass;
2776					}
2777					
2778					if ( oCol.bVisible )
2779					{
2780						oData.nTr.appendChild( nTd );
2781						oData._anHidden[i] = null;
2782					}
2783					else
2784					{
2785						oData._anHidden[i] = nTd;
2786					}
2787				}
2788			}
2789		}
2790		
2791		/*
2792		 * Function: _fnGatherData
2793		 * Purpose:  Read in the data from the target table from the DOM
2794		 * Returns:  -
2795		 * Inputs:   object:oSettings - dataTables settings object
2796		 * Notes:    This is a optimised version of _fnAddData (more or less) for reading information
2797		 *   from the DOM. The basic actions must be identical in the two functions.
2798		 */
2799		function _fnGatherData( oSettings )
2800		{
2801			var iLoop, i, iLen, j, jLen, jInner,
2802			 	nTds, nTrs, nTd, aLocalData, iThisIndex,
2803				iRow, iRows, iColumn, iColumns, sNodeName;
2804			
2805			/*
2806			 * Process by row first
2807			 * Add the data object for the whole table - storing the tr node. Note - no point in getting
2808			 * DOM based data if we are going to go and replace it with Ajax source data.
2809			 */
2810			if ( oSettings.bDeferLoading || oSettings.sAjaxSource === null )
2811			{
2812				nTrs = oSettings.nTBody.childNodes;
2813				for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
2814				{
2815					if ( nTrs[i].nodeName.toUpperCase() == "TR" )
2816					{
2817						iThisIndex = oSettings.aoData.length;
2818						oSettings.aoData.push( {
2819							"nTr": nTrs[i],
2820							"_iId": oSettings.iNextId++,
2821							"_aData": [],
2822							"_anHidden": [],
2823							"_sRowStripe": ''
2824						} );
2825						
2826						oSettings.aiDisplayMaster.push( iThisIndex );
2827						nTds = nTrs[i].childNodes;
2828						jInner = 0;
2829						
2830						for ( j=0, jLen=nTds.length ; j<jLen ; j++ )
2831						{
2832							sNodeName = nTds[j].nodeName.toUpperCase();
2833							if ( sNodeName == "TD" || sNodeName == "TH" )
2834							{
2835								_fnSetCellData( oSettings, iThisIndex, jInner, $.trim(nTds[j].innerHTML) );
2836								jInner++;
2837							}
2838						}
2839					}
2840				}
2841			}
2842			
2843			/* Gather in the TD elements of the Table - note that this is basically the same as
2844			 * fnGetTdNodes, but that function takes account of hidden columns, which we haven't yet
2845			 * setup!
2846			 */
2847			nTrs = _fnGetTrNodes( oSettings );
2848			nTds = [];
2849			for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
2850			{
2851				for ( j=0, jLen=nTrs[i].childNodes.length ; j<jLen ; j++ )
2852				{
2853					nTd = nTrs[i].childNodes[j];
2854					sNodeName = nTd.nodeName.toUpperCase();
2855					if ( sNodeName == "TD" || sNodeName == "TH" )
2856					{
2857						nTds.push( nTd );
2858					}
2859				}
2860			}
2861			
2862			/* Sanity check */
2863			if ( nTds.length != nTrs.length * oSettings.aoColumns.length )
2864			{
2865				_fnLog( oSettings, 1, "Unexpected number of TD elements. Expected "+
2866					(nTrs.length * oSettings.aoColumns.length)+" and got "+nTds.length+". DataTables does "+
2867					"not support rowspan / colspan in the table body, and there must be one cell for each "+
2868					"row/column combination." );
2869			}
2870			
2871			/* Now process by column */
2872			for ( iColumn=0, iColumns=oSettings.aoColumns.length ; iColumn<iColumns ; iColumn++ )
2873			{
2874				/* Get the title of the column - unless there is a user set one */
2875				if ( oSettings.aoColumns[iColumn].sTitle === null )
2876				{
2877					oSettings.aoColumns[iColumn].sTitle = oSettings.aoColumns[iColumn].nTh.innerHTML;
2878				}
2879				
2880				var
2881					bAutoType = oSettings.aoColumns[iColumn]._bAutoType,
2882					bRender = typeof oSettings.aoColumns[iColumn].fnRender == 'function',
2883					bClass = oSettings.aoColumns[iColumn].sClass !== null,
2884					bVisible = oSettings.aoColumns[iColumn].bVisible,
2885					nCell, sThisType, sRendered, sValType;
2886				
2887				/* A single loop to rule them all (and be more efficient) */
2888				if ( bAutoType || bRender || bClass || !bVisible )
2889				{
2890					for ( iRow=0, iRows=oSettings.aoData.length ; iRow<iRows ; iRow++ )
2891					{
2892						nCell = nTds[ (iRow*iColumns) + iColumn ];
2893						
2894						/* Type detection */
2895						if ( bAutoType && oSettings.aoColumns[iColumn].sType != 'string' )
2896						{
2897							sValType = _fnGetCellData( oSettings, iRow, iColumn, 'type' );
2898							if ( sValType !== '' )
2899							{
2900								sThisType = _fnDetectType( sValType );
2901								if ( oSettings.aoColumns[iColumn].sType === null )
2902								{
2903									oSettings.aoColumns[iColumn].sType = sThisType;
2904								}
2905								else if ( oSettings.aoColumns[iColumn].sType != sThisType )
2906								{
2907									/* String is always the 'fallback' option */
2908									oSettings.aoColumns[iColumn].sType = 'string';
2909								}
2910							}
2911						}
2912						
2913						/* Rendering */
2914						if ( bRender )
2915						{
2916							sRendered = oSettings.aoColumns[iColumn].fnRender( {
2917									"iDataRow": iRow,
2918									"iDataColumn": iColumn,
2919									"aData": oSettings.aoData[iRow]._aData,
2920									"oSettings": oSettings
2921								} );
2922							nCell.innerHTML = sRendered;
2923							if ( oSettings.aoColumns[iColumn].bUseRendered )
2924							{
2925								/* Use the rendered data for filtering/sorting */
2926								_fnSetCellData( oSettings, iRow, iColumn, sRendered );
2927							}
2928						}
2929						
2930						/* Classes */
2931						if ( bClass )
2932						{
2933							nCell.className += ' '+oSettings.aoColumns[iColumn].sClass;
2934						}
2935						
2936						/* Column visability */
2937						if ( !bVisible )
2938						{
2939							oSettings.aoData[iRow]._anHidden[iColumn] = nCell;
2940							nCell.parentNode.removeChild( nCell );
2941						}
2942						else
2943						{
2944							oSettings.aoData[iRow]._anHidden[iColumn] = null;
2945						}
2946					}
2947				}
2948			}
2949		}
2950		
2951		/*
2952		 * Function: _fnBuildHead
2953		 * Purpose:  Create the HTML header for the table
2954		 * Returns:  -
2955		 * Inputs:   object:oSettings - dataTables settings object
2956		 */
2957		function _fnBuildHead( oSettings )
2958		{
2959			var i, nTh, iLen, j, jLen;
2960			var anTr = oSettings.nTHead.getElementsByTagName('tr');
2961			var iThs = oSettings.nTHead.getElementsByTagName('th').length;
2962			var iCorrector = 0;
2963			var jqChildren;
2964			
2965			/* If there is a header in place - then use it - otherwise it's going to get nuked... */
2966			if ( iThs !== 0 )
2967			{
2968				/* We've got a thead from the DOM, so remove hidden columns and apply width to vis cols */
2969				for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2970				{
2971					nTh = oSettings.aoColumns[i].nTh;
2972					
2973					if ( oSettings.aoColumns[i].sClass !== null )
2974					{
2975						$(nTh).addClass( oSettings.aoColumns[i].sClass );
2976					}
2977					
2978					/* Set the title of the column if it is user defined (not what was auto detected) */
2979					if ( oSettings.aoColumns[i].sTitle != nTh.innerHTML )
2980					{
2981						nTh.innerHTML = oSettings.aoColumns[i].sTitle;
2982					}
2983				}
2984			}
2985			else
2986			{
2987				/* We don't have a header in the DOM - so we are going to have to create one */
2988				var nTr = document.createElement( "tr" );
2989				
2990				for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2991				{
2992					nTh = oSettings.aoColumns[i].nTh;
2993					nTh.innerHTML = oSettings.aoColumns[i].sTitle;
2994					
2995					if ( oSettings.aoColumns[i].sClass !== null )
2996					{
2997						$(nTh).addClass( oSettings.aoColumns[i].sClass );
2998					}
2999					
3000					nTr.appendChild( nTh );
3001				}
3002				$(oSettings.nTHead).html( '' )[0].appendChild( nTr );
3003				_fnDetectHeader( oSettings.aoHeader, oSettings.nTHead );
3004			}
3005			
3006			/* Add the extra markup needed by jQuery UI's themes */
3007			if ( oSettings.bJUI )
3008			{
3009				for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
3010				{
3011					nTh = oSettings.aoColumns[i].nTh;
3012					
3013					var nDiv = document.createElement('div');
3014					nDiv.className = oSettings.oClasses.sSortJUIWrapper;
3015					$(nTh).contents().appendTo(nDiv);
3016					
3017					var nSpan = document.createElement('span');
3018					nSpan.className = oSettings.oClasses.sSortIcon;
3019					nDiv.appendChild( nSpan );
3020					nTh.appendChild( nDiv );
3021				}
3022			}
3023			
3024			/* Add sort listener */
3025			var fnNoSelect = function (e) {
3026				this.onselectstart = function() { return false; };
3027				return false;
3028			};
3029			
3030			if ( oSettings.oFeatures.bSort )
3031			{
3032				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
3033				{
3034					if ( oSettings.aoColumns[i].bSortable !== false )
3035					{
3036						_fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i );
3037						
3038						/* Take the brutal approach to cancelling text selection in header */
3039						$(oSettings.aoColumns[i].nTh).bind( 'mousedown.DT', fnNoSelect );
3040					}
3041					else
3042					{
3043						$(oSettings.aoColumns[i].nTh).addClass( oSettings.oClasses.sSortableNone );
3044					}
3045				}
3046			}
3047			
3048			/* Deal with the footer - add classes if required */
3049			if ( oSettings.oClasses.sFooterTH !== "" )
3050			{
3051				$('>tr>th', oSettings.nTFoot).addClass( oSettings.oClasses.sFooterTH );
3052			}
3053			
3054			/* Cache the footer elements */
3055			if ( oSettings.nTFoot !== null )
3056			{
3057				var anCells = _fnGetUniqueThs( oSettings, null, oSettings.aoFooter );
3058				for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
3059				{
3060					if ( typeof anCells[i] != 'undefined' )
3061					{
3062						oSettings.aoColumns[i].nTf = anCells[i];
3063					}
3064				}
3065			}
3066		}
3067
3068		
3069		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
3070		 * Section - Drawing functions
3071		 */
3072		
3073		/*
3074		 * Function: _fnDrawHead
3075		 * Purpose:  Draw the header (or footer) element based on the column visibility states. The
3076		 *           methodology here is to use the layout array from _fnDetectHeader, modified for
3077		 *           the instantaneous column visibility, to construct the new layout. The grid is
3078		 *           traversed over cell at a time in a rows x columns grid fashion, although each 
3079		 *           cell insert can cover multiple elements in the grid - which is tracks using the
3080		 *           aApplied array. Cell inserts in the grid will only occur where there isn't
3081		 *           already a cell in that position.
3082		 * Returns:  -
3083		 * Inputs:   object:oSettings - dataTables settings object
3084		 *           array objects:aoSource - Layout array from _fnDetectHeader
3085		 *           boolean:bIncludeHidden - If true then include the hidden columns in the calc, 
3086		 *             - optional: default false
3087		 */
3088		function _fnDrawHead( oSettings, aoSource, bIncludeHidden )
3089		{
3090			var i, iLen, j, jLen, k, kLen;
3091			var aoLocal = [];
3092			var aApplied = [];
3093			var iColumns = oSettings.aoColumns.length;
3094			var iRowspan, iColspan;
3095
3096			if ( typeof bIncludeHidden == 'undefined' )
3097			{
3098				bIncludeHidden = false;
3099			}
3100
3101			/* Make a copy of the master layout array, but without the visible columns in it */
3102			for ( i=0, iLen=aoSource.length ; i<iLen ; i++ )
3103			{
3104				aoLocal[i] = aoSource[i].slice();
3105				aoLocal[i].nTr = aoSource[i].nTr;
3106
3107				/* Remove any columns which are currently hidden */
3108				for ( j=iColumns-1 ; j>=0 ; j-- )
3109				{
3110					if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden )
3111					{
3112						aoLocal[i].splice( j, 1 );
3113					}
3114				}
3115
3116				/* Prep the applied array - it needs an element for each row */
3117				aApplied.push( [] );
3118			}
3119
3120			for ( i=0, iLen=aoLocal.length ; i<iLen ; i++ )
3121			{
3122				/* All cells are going to be replaced, so empty out the row */
3123				if ( aoLocal[i].nTr )
3124				{
3125					for ( k=0, kLen=aoLocal[i].nTr.childNodes.length ; k<kLen ; k++ )
3126					{
3127						aoLocal[i].nTr.removeChild( aoLocal[i].nTr.childNodes[0] );
3128					}
3129				}
3130
3131				for ( j=0, jLen=aoLocal[i].length ; j<jLen ; j++ )
3132				{
3133					iRowspan = 1;
3134					iColspan = 1;
3135
3136					/* Check to see if there is already a cell (row/colspan) covering our target
3137					 * insert point. If there is, then there is nothing to do.
3138					 */
3139					if ( typeof aApplied[i][j] == 'undefined' )
3140					{
3141						aoLocal[i].nTr.appendChild( aoLocal[i][j].cell );
3142						aApplied[i][j] = 1;
3143
3144						/* Expand the cell to cover as many rows as needed */
3145						while ( typeof aoLocal[i+iRowspan] != 'undefined' &&
3146						        aoLocal[i][j].cell == aoLocal[i+iRowspan][j].cell )
3147						{
3148							aApplied[i+iRowspan][j] = 1;
3149							iRowspan++;
3150						}
3151
3152						/* Expand the cell to cover as many columns as needed */
3153						while ( typeof aoLocal[i][j+iColspan] != 'undefined' &&
3154						        aoLocal[i][j].cell == aoLocal[i][j+iColspan].cell )
3155						{
3156							/* Must update the applied array over the rows for the columns */
3157							for ( k=0 ; k<iRowspan ; k++ )
3158							{
3159								aApplied[i+k][j+iColspan] = 1;
3160							}
3161							iColspan++;
3162						}
3163
3164						/* Do the actual expansion in the DOM */
3165						aoLocal[i][j].cell.setAttribute('rowspan', iRowspan);
3166						aoLocal[i][j].cell.setAttribute('colspan', iColspan);
3167					}
3168				}
3169			}
3170		}
3171		
3172		/*
3173		 * Function: _fnDraw
3174		 * Purpose:  Insert the required TR nodes into the table for display
3175		 * Returns:  -
3176		 * Inputs:   object:oSettings - dataTables settings object
3177		 */
3178		function _fnDraw( oSettings )
3179		{
3180			var i, iLen;
3181			var anRows = [];
3182			var iRowCount = 0;
3183			var bRowError = false;
3184			var iStrips = oSettings.asStripClasses.length;
3185			var iOpenRows = oSettings.aoOpenRows.length;
3186			
3187			/* Provide a pre-callback function which can be used to cancel the draw is false is returned */
3188			if ( oSettings.fnPreDrawCallback !== null &&
3189			     oSettings.fnPreDrawCallback.call( oSettings.oInstance, oSettings ) === false )
3190			{
3191			     return;
3192			}
3193			
3194			oSettings.bDrawing = true;
3195			
3196			/* Check and see if we have an initial draw position from state saving */
3197			if ( typeof oSettings.iInitDisplayStart != 'undefined' && oSettings.iInitDisplayStart != -1 )
3198			{
3199				if ( oSettings.oFeatures.bServerSide )
3200				{
3201					oSettings._iDisplayStart = oSettings.iInitDisplayStart;
3202				}
3203				else
3204				{
3205					oSettings._iDisplayStart = (oSettings.iInitDisplayStart >= oSettings.fnRecordsDisplay()) ?
3206						0 : oSettings.iInitDisplayStart;
3207				}
3208				oSettings.iInitDisplayStart = -1;
3209				_fnCalculateEnd( oSettings );
3210			}
3211			
3212			/* Server-side processing draw intercept */
3213			if ( oSettings.bDeferLoading )
3214			{
3215				oSettings.bDeferLoading = false;
3216				oSettings.iDraw++;
3217			}
3218			else if ( !oSettings.oFeatures.bServerSide )
3219			{
3220				oSettings.iDraw++;
3221			}
3222			else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) )
3223			{
3224				return;
3225			}
3226			
3227			if ( oSettings.aiDisplay.length !== 0 )
3228			{
3229				var iStart = oSettings._iDisplayStart;
3230				var iEnd = oSettings._iDisplayEnd;
3231				
3232				if ( oSettings.oFeatures.bServerSide )
3233				{
3234					iStart = 0;
3235					iEnd = oSettings.aoData.length;
3236				}
3237				
3238				for ( var j=iStart ; j<iEnd ; j++ )
3239				{
3240					var aoData = oSettings.aoData[ oSettings.aiDisplay[j] ];
3241					if ( aoData.nTr === null )
3242					{
3243						_fnCreateTr( oSettings, oSettings.aiDisplay[j] );
3244					}
3245
3246					var nRow = aoData.nTr;
3247					
3248					/* Remove the old stripping classes and then add the new one */
3249					if ( iStrips !== 0 )
3250					{
3251						var sStrip = oSettings.asStripClasses[ iRowCount % iStrips ];
3252						if ( aoData._sRowStripe != sStrip )
3253						{
3254							$(nRow).removeClass( aoData._sRowStripe ).addClass( sStrip );
3255							aoData._sRowStripe = sStrip;
3256						}
3257					}
3258					
3259					/* Custom row callback function - might want to manipule the row */
3260					if ( typeof oSettings.fnRowCallback == "function" )
3261					{
3262						nRow = oSettings.fnRowCallback.call( oSettings.oInstance, nRow, 
3263							oSettings.aoData[ oSettings.aiDisplay[j] ]._aData, iRowCount, j );
3264						if ( !nRow && !bRowError )
3265						{
3266							_fnLog( oSettings, 0, "A node was not returned by fnRowCallback" );
3267							bRowError = true;
3268						}
3269					}
3270					
3271					anRows.push( nRow );
3272					iRowCount++;
3273					
3274					/* If there is an open row - and it is attached to this parent - attach it on redraw */
3275					if ( iOpenRows !== 0 )
3276					{
3277						for ( var k=0 ; k<iOpenRows ; k++ )
3278						{
3279							if ( nRow == oSettings.aoOpenRows[k].nParent )
3280							{
3281								anRows.push( oSettings.aoOpenRows[k].nTr );
3282							}
3283						}
3284					}
3285				}
3286			}
3287			else
3288			{
3289				/* Table is empty - create a row with an empty message in it */
3290				anRows[ 0 ] = document.createElement( 'tr' );
3291				
3292				if ( typeof oSettings.asStripClasses[0] != 'undefined' )
3293				{
3294					anRows[ 0 ].className = oSettings.asStripClasses[0];
3295				}
3296
3297				var sZero = oSettings.oLanguage.sZeroRecords.replace(
3298					'_MAX_', oSettings.fnFormatNumber(oSettings.fnRecordsTotal()) );
3299				if ( oSettings.iDraw == 1 && oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide )
3300				{
3301					sZero = oSettings.oLanguage.sLoadingRecords;
3302				}
3303				else if ( typeof oSettings.oLanguage.sEmptyTable != 'undefined' &&
3304				     oSettings.fnRecordsTotal() === 0 )
3305				{
3306					sZero = oSettings.oLanguage.sEmptyTable;
3307				}
3308
3309				var nTd = document.createElement( 'td' );
3310				nTd.setAttribute( 'valign', "top" );
3311				nTd.colSpan = _fnVisbleColumns( oSettings );
3312				nTd.className = oSettings.oClasses.sRowEmpty;
3313				nTd.innerHTML = sZero;
3314				
3315				anRows[ iRowCount ].appendChild( nTd );
3316			}
3317			
3318			/* Callback the header and footer custom funcation if there is one */
3319			if ( typeof oSettings.fnHeaderCallback == 'function' )
3320			{
3321				oSettings.fnHeaderCallback.call( oSettings.oInstance, $('>tr', oSettings.nTHead)[0], 
3322					_fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(),
3323					oSettings.aiDisplay );
3324			}
3325			
3326			if ( typeof oSettings.fnFooterCallback == 'function' )
3327			{
3328				oSettings.fnFooterCallback.call( oSettings.oInstance, $('>tr', oSettings.nTFoot)[0], 
3329					_fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(),
3330					oSettings.aiDisplay );
3331			}
3332			
3333			/* 
3334			 * Need to remove any old row from the display - note we can't just empty the tbody using
3335			 * $().html('') since this will unbind the jQuery event handlers (even although the node 
3336			 * still exists!) - equally we can't use innerHTML, since IE throws an exception.
3337			 */
3338			var
3339				nAddFrag = document.createDocumentFragment(),
3340				nRemoveFrag = document.createDocumentFragment(),
3341				nBodyPar, nTrs;
3342			
3343			if ( oSettings.nTBody )
3344			{
3345				nBodyPar = oSettings.nTBody.parentNode;
3346				nRemoveFrag.appendChild( oSettings.nTBody );
3347				
3348				/* When doing infinite scrolling, only remove child rows when sorting, filtering or start
3349				 * up. When not infinite scroll, always do it.
3350				 */
3351				if ( !oSettings.oScroll.bInfinite || !oSettings._bInitComplete ||
3352				 	oSettings.bSorted || oSettings.bFiltered )
3353				{
3354					nTrs = oSettings.nTBody.childNodes;
3355					for ( i=nTrs.length-1 ; i>=0 ; i-- )
3356					{
3357						nTrs[i].parentNode.removeChild( nTrs[i] );
3358					}
3359				}
3360				
3361				/* Put the draw table into the dom */
3362				for ( i=0, iLen=anRows.length ; i<iLen ; i++ )
3363				{
3364					nAddFrag.appendChild( anRows[i] );
3365				}
3366				
3367				oSettings.nTBody.appendChild( nAddFrag );
3368				if ( nBodyPar !== null )
3369				{
3370					nBodyPar.appendChild( oSettings.nTBody );
3371				}
3372			}
3373			
3374			/* Call all required callback functions for the end of a draw */
3375			for ( i=oSettings.aoDrawCallback.length-1 ; i>=0 ; i-- )
3376			{
3377				oSettings.aoDrawCallback[i].fn.call( oSettings.oInstance, oSettings );
3378			}
3379			
3380			/* Draw is complete, sorting and filtering must be as well */
3381			oSettings.bSorted = false;
3382			oSettings.bFiltered = false;
3383			oSettings.bDrawing = false;
3384			
3385			if ( oSettings.oFeatures.bServerSide )
3386			{
3387				_fnProcessingDisplay( oSettings, false );
3388				if ( typeof oSettings._bInitComplete == 'undefined' )
3389				{
3390					_fnInitComplete( oSettings );
3391				}
3392			}
3393		}
3394		
3395		/*
3396		 * Function: _fnReDraw
3397		 * Purpose:  Redraw the table - taking account of the various features which are enabled
3398		 * Returns:  -
3399		 * Inputs:   object:oSettings - dataTables settings object
3400		 */
3401		function _fnReDraw( oSettings )
3402		{
3403			if ( oSettings.oFeatures.bSort )
3404			{
3405				/* Sorting will refilter and draw for us */
3406				_fnSort( oSettings, oSettings.oPreviousSearch );
3407			}
3408			else if ( oSettings.oFeatures.bFilter )
3409			{
3410				/* Filtering will redraw for us */
3411				_fnFilterComplete( oSettings, oSettings.oPreviousSearch );
3412			}
3413			else
3414			{
3415				_fnCalculateEnd( oSettings );
3416				_fnDraw( oSettings );
3417			}
3418		}
3419		
3420		/*
3421		 * Function: _fnAjaxUpdate
3422		 * Purpose:  Update the table using an Ajax call
3423		 * Returns:  bool: block the table drawing or not
3424		 * Inputs:   object:oSettings - dataTables settings object
3425		 */
3426		function _fnAjaxUpdate( oSettings )
3427		{
3428			if ( oSettings.bAjaxDataGet )
3429			{
3430				_fnProcessingDisplay( oSettings, true );
3431				var iColumns = oSettings.aoColumns.length;
3432				var aoData = [];
3433				var i;
3434				
3435				/* Paging and general */
3436				oSettings.iDraw++;
3437				aoData.push( { "name": "sEcho",          "value": oSettings.iDraw } );
3438				aoData.push( { "name": "iColumns",       "value": iColumns } );
3439				aoData.push( { "name": "sColumns",       "value": _fnColumnOrdering(oSettings) } );
3440				aoData.push( { "name": "iDisplayStart",  "value": oSettings._iDisplayStart } );
3441				aoData.push( { "name": "iDisplayLength", "value": oSettings.oFeatures.bPaginate !== false ?
3442					oSettings._iDisplayLength : -1 } );
3443				
3444				/* Filtering */
3445				if ( oSettings.oFeatures.bFilter !== false )
3446				{
3447					aoData.push( { "name": "sSearch", "value": oSettings.oPreviousSearch.sSearch } );
3448					aoData.push( { "name": "bRegex",  "value": oSettings.oPreviousSearch.bRegex } );
3449					for ( i=0 ; i<iColumns ; i++ )
3450					{
3451						aoData.push( { "name": "sSearch_"+i,     "value": oSettings.aoPreSearchCols[i].sSearch } );
3452						aoData.push( { "name": "bRegex_"+i,      "value": oSettings.aoPreSearchCols[i].bRegex } );
3453						aoData.push( { "name": "bSearchable_"+i, "value": oSettings.aoColumns[i].bSearchable } );
3454					}
3455				}
3456				
3457				/* Sorting */
3458				if ( oSettings.oFeatures.bSort !== false )
3459				{
3460					var iFixed = oSettings.aaSortingFixed !== null ? oSettings.aaSortingFixed.length : 0;
3461					var iUser = oSettings.aaSorting.length;
3462					aoData.push( { "name": "iSortingCols",   "value": iFixed+iUser } );
3463					for ( i=0 ; i<iFixed ; i++ )
3464					{
3465						aoData.push( { "name": "iSortCol_"+i,  "value": oSettings.aaSortingFixed[i][0] } );
3466						aoData.push( { "name": "sSortDir_"+i,  "value": oSettings.aaSortingFixed[i][1] } );
3467					}
3468					
3469					for ( i=0 ; i<iUser ; i++ )
3470					{
3471						aoData.push( { "name": "iSortCol_"+(i+iFixed),  "value": oSettings.aaSorting[i][0] } );
3472						aoData.push( { "name": "sSortDir_"+(i+iFixed),  "value": oSettings.aaSorting[i][1] } );
3473					}
3474					
3475					for ( i=0 ; i<iColumns ; i++ )
3476					{
3477						aoData.push( { "name": "bSortable_"+i,  "value": oSettings.aoColumns[i].bSortable } );
3478					}
3479				}
3480				
3481				oSettings.fnServerData.call( oSettings.oInstance, oSettings.sAjaxSource, aoData,
3482					function(json) {
3483						_fnAjaxUpdateDraw( oSettings, json );
3484					}, oSettings );
3485				return false;
3486			}
3487			else
3488			{
3489				return true;
3490			}
3491		}
3492		
3493		/*
3494		 * Function: _fnAjaxUpdateDraw
3495		 * Purpose:  Data the data from the server (nuking the old) and redraw the table
3496		 * Returns:  -
3497		 * Inputs:   object:oSettings - dataTables settings object
3498		 *           object:json - json data return from the server.
3499		 *             The following must be defined:
3500		 *               iTotalRecords, iTotalDisplayRecords, aaData
3501		 *             The following may be defined:
3502		 *               sColumns
3503		 */
3504		function _fnAjaxUpdateDraw ( oSettings, json )
3505		{
3506			if ( typeof json.sEcho != 'undefined' )
3507			{
3508				/* Protect against old returns over-writing a new one. Possible when you get
3509				 * very fast interaction, and later queires are completed much faster
3510				 */
3511				if ( json.sEcho*1 < oSettings.iDraw )
3512				{
3513					return;
3514				}
3515				else
3516				{
3517					oSettings.iDraw = json.sEcho * 1;
3518				}
3519			}
3520			
3521			if ( !oSettings.oScroll.bInfinite ||
3522				   (oSettings.oScroll.bInfinite && (oSettings.bSorted || oSettings.bFiltered)) )
3523			{
3524				_fnClearTable( oSettings );
3525			}
3526			oSettings._iRecordsTotal = json.iTotalRecords;
3527			oSettings._iRecordsDisplay = json.iTotalDisplayRecords;
3528			
3529			/* Determine if reordering is required */
3530			var sOrdering = _fnColumnOrdering(oSettings);
3531			var bReOrder = (typeof json.sColumns != 'undefined' && sOrdering !== "" && json.sColumns != sOrdering );
3532			if ( bReOrder )
3533			{
3534				var aiIndex = _fnReOrderIndex( oSettings, json.sColumns );
3535			}
3536
3537			var fnDataSrc = _fnGetObjectDataFn( oSettings.sAjaxDataProp );
3538			var aData = fnDataSrc( json );
3539			
3540			for ( var i=0, iLen=aData.length ; i<iLen ; i++ )
3541			{
3542				if ( bReOrder )
3543				{
3544					/* If we need to re-order, then create a new array with the correct order and add it */
3545					var aDataSorted = [];
3546					for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ )
3547					{
3548						aDataSorted.push( aData[i][ aiIndex[j] ] );
3549					}
3550					_fnAddData( oSettings, aDataSorted );
3551				}
3552				else
3553				{
3554					/* No re-order required, sever got it "right" - just straight add */
3555					_fnAddData( oSettings, aData[i] );
3556				}
3557			}
3558			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
3559			
3560			oSettings.bAjaxDataGet = false;
3561			_fnDraw( oSettings );
3562			oSettings.bAjaxDataGet = true;
3563			_fnProcessingDisplay( oSettings, false );
3564		}
3565		
3566		
3567		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
3568		 * Section - Options (features) HTML
3569		 */
3570		
3571		/*
3572		 * Function: _fnAddOptionsHtml
3573		 * Purpose:  Add the options to the page HTML for the table
3574		 * Returns:  -
3575		 * Inputs:   object:oSettings - dataTables settings object
3576		 */
3577		function _fnAddOptionsHtml ( oSettings )
3578		{
3579			/*
3580			 * Create a temporary, empty, div which we can later on replace with what we have generated
3581			 * we do it this way to rendering the 'options' html offline - speed :-)
3582			 */
3583			var nHolding = document.createElement( 'div' );
3584			oSettings.nTable.parentNode.insertBefore( nHolding, oSettings.nTable );
3585			
3586			/* 
3587			 * All DataTables are wrapped in a div
3588			 */
3589			oSettings.nTableWrapper = document.createElement( 'div' );
3590			oSettings.nTableWrapper.className = oSettings.oClasses.sWrapper;
3591			if ( oSettings.sTableId !== '' )
3592			{
3593				oSettings.nTableWrapper.setAttribute( 'id', oSettings.sTableId+'_wrapper' );
3594			}
3595
3596			oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling;
3597
3598			/* Track where we want to insert the option */
3599			var nInsertNode = oSettings.nTableWrapper;
3600			
3601			/* Loop over the user set positioning and place the elements as needed */
3602			var aDom = oSettings.sDom.split('');
3603			var nTmp, iPushFeature, cOption, nNewNode, cNext, sAttr, j;
3604			for ( var i=0 ; i<aDom.length ; i++ )
3605			{
3606				iPushFeature = 0;
3607				cOption = aDom[i];
3608				
3609				if ( cOption == '<' )
3610				{
3611					/* New container div */
3612					nNewNode = document.createElement( 'div' );
3613					
3614					/* Check to see if we should append an id and/or a class name to the container */
3615					cNext = aDom[i+1];
3616					if ( cNext == "'" || cNext == '"' )
3617					{
3618						sAttr = "";
3619						j = 2;
3620						while ( aDom[i+j] != cNext )
3621						{
3622							sAttr += aDom[i+j];
3623							j++;
3624						}
3625						
3626						/* Replace jQuery UI constants */
3627						if ( sAttr == "H" )
3628						{
3629							sAttr = "fg-toolbar ui-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix";
3630						}
3631						else if ( sAttr == "F" )
3632						{
3633							sAttr = "fg-toolbar ui-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix";
3634						}
3635						
3636						/* The attribute can be in the format of "#id.class", "#id" or "class" This logic
3637						 * breaks the string into parts and applies them as needed
3638						 */
3639						if ( sAttr.indexOf('.') != -1 )
3640						{
3641							var aSplit = sAttr.split('.');
3642							nNewNode.setAttribute('id', aSplit[0].substr(1, aSplit[0].length-1) );
3643							nNewNode.className = aSplit[1];
3644						}
3645						else if ( sAttr.charAt(0) == "#" )
3646						{
3647							nNewNode.setAttribute('id', sAttr.substr(1, sAttr.length-1) );
3648						}
3649						else
3650						{
3651							nNewNode.className = sAttr;
3652						}
3653						
3654						i += j; /* Move along the position array */
3655					}
3656					
3657					nInsertNode.appendChild( nNewNode );
3658					nInsertNode = nNewNode;
3659				}
3660				else if ( cOption == '>' )
3661				{
3662					/* End container div */
3663					nInsertNode = nInsertNode.parentNode;
3664				}
3665				else if ( cOption == 'l' && oSettings.oFeatures.bPaginate && oSettings.oFeatures.bLengthChange )
3666				{
3667					/* Length */
3668					nTmp = _fnFeatureHtmlLength( oSettings );
3669					iPushFeature = 1;
3670				}
3671				else if ( cOption == 'f' && oSettings.oFeatures.bFilter )
3672				{
3673					/* Filter */
3674					nTmp = _fnFeatureHtmlFilter( oSettings );
3675					iPushFeature = 1;
3676				}
3677				else if ( cOption == 'r' && oSettings.oFeatures.bProcessing )
3678				{
3679					/* pRocessing */
3680					nTmp = _fnFeatureHtmlProcessing( oSettings );
3681					iPushFeature = 1;
3682				}
3683				else if ( cOption == 't' )
3684				{
3685					/* Table */
3686					nTmp = _fnFeatureHtmlTable( oSettings );
3687					iPushFeature = 1;
3688				}
3689				else if ( cOption ==  'i' && oSettings.oFeatures.bInfo )
3690				{
3691					/* Info */
3692					nTmp = _fnFeatureHtmlInfo( oSettings );
3693					iPushFeature = 1;
3694				}
3695				else if ( cOption == 'p' && oSettings.oFeatures.bPaginate )
3696				{
3697					/* Pagination */
3698					nTmp = _fnFeatureHtmlPaginate( oSettings );
3699					iPushFeature = 1;
3700				}
3701				else if ( _oExt.aoFeatures.length !== 0 )
3702				{
3703					/* Plug-in features */
3704					var aoFeatures = _oExt.aoFeatures;
3705					for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ )
3706					{
3707						if ( cOption == aoFeatures[k].cFeature )
3708						{
3709							nTmp = aoFeatures[k].fnInit( oSettings );
3710							if ( nTmp )
3711							{
3712								iPushFeature = 1;
3713							}
3714							break;
3715						}
3716					}
3717				}
3718				
3719				/* Add to the 2D features array */
3720				if ( iPushFeature == 1 && nTmp !== null )
3721				{
3722					if ( typeof oSettings.aanFeatures[cOption] != 'object' )
3723					{
3724						oSettings.aanFeatures[cOption] = [];
3725					}
3726					oSettings.aanFeatures[cOption].push( nTmp );
3727					nInsertNode.appendChild( nTmp );
3728				}
3729			}
3730			
3731			/* Built our DOM structure - replace the holding div with what we want */
3732			nHolding.parentNode.replaceChild( oSettings.nTableWrapper, nHolding );
3733		}
3734		
3735		
3736		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
3737		 * Section - Feature: Filtering
3738		 */
3739		
3740		/*
3741		 * Function: _fnFeatureHtmlTable
3742		 * Purpose:  Add any control elements for the table - specifically scrolling
3743		 * Returns:  node: - Node to add to the DOM
3744		 * Inputs:   object:oSettings - dataTables settings object
3745		 */
3746		function _fnFeatureHtmlTable ( oSettings )
3747		{
3748			/* Chack if scrolling is enabled or not - if not then leave the DOM unaltered */
3749			if ( oSettings.oScroll.sX === "" && oSettings.oScroll.sY === "" )
3750			{
3751				return oSettings.nTable;
3752			}
3753			
3754			/*
3755			 * The HTML structure that we want to generate in this function is:
3756			 *  div - nScroller
3757			 *    div - nScrollHead
3758			 *      div - nScrollHeadInner
3759			 *        table - nScrollHeadTable
3760			 *          thead - nThead
3761			 *    div - nScrollBody
3762			 *      table - oSettings.nTable
3763			 *        thead - nTheadSize
3764			 *        tbody - nTbody
3765			 *    div - nScrollFoot
3766			 *      div - nScrollFootInner
3767			 *        table - nScrollFootTable
3768			 *          tfoot - nTfoot
3769			 */
3770			var
3771			 	nScroller = document.createElement('div'),
3772			 	nScrollHead = document.createElement('div'),
3773			 	nScrollHeadInner = document.createElement('div'),
3774			 	nScrollBody = document.createElement('div'),
3775			 	nScrollFoot = document.createElement('div'),
3776			 	nScrollFootInner = document.createElement('div'),
3777			 	nScrollHeadTable = oSettings.nTable.cloneNode(false),
3778			 	nScrollFootTable = oSettings.nTable.cloneNode(false),
3779				nThead = oSettings.nTable.getElementsByTagName('thead')[0],
3780			 	nTfoot = oSettings.nTable.getElementsByTagName('tfoot').length === 0 ? null : 
3781					oSettings.nTable.getElementsByTagName('tfoot')[0],
3782				oClasses = (typeof oInit.bJQueryUI != 'undefined' && oInit.bJQueryUI) ?
3783					_oExt.oJUIClasses : _oExt.oStdClasses;
3784			
3785			nScrollHead.appendChild( nScrollHeadInner );
3786			nScrollFoot.appendChild( nScrollFootInner );
3787			nScrollBody.appendChild( oSettings.nTable );
3788			nScroller.appendChild( nScrollHead );
3789			nScroller.appendChild( nScrollBody );
3790			nScrollHeadInner.appendChild( nScrollHeadTable );
3791			nScrollHeadTable.appendChild( nThead );
3792			if ( nTfoot !== null )
3793			{
3794				nScroller.appendChild( nScrollFoot );
3795				nScrollFootInner.appendChild( nScrollFootTable );
3796				nScrollFootTable.appendChild( nTfoot );
3797			}
3798			
3799			nScroller.className = oClasses.sScrollWrapper;
3800			nScrollHead.className = oClasses.sScrollHead;
3801			nScrollHeadInner.className = oClasses.sScrollHeadInner;
3802			nScrollBody.className = oClasses.sScrollBody;
3803			nScrollFoot.className = oClasses.sScrollFoot;
3804			nScrollFootInner.className = oClasses.sScrollFootInner;
3805			
3806			if ( oSettings.oScroll.bAutoCss )
3807			{
3808				nScrollHead.style.overflow = "hidden";
3809				nScrollHead.style.position = "relative";
3810				nScrollFoot.style.overflow = "hidden";
3811				nScrollBody.style.overflow = "auto";
3812			}
3813			
3814			nScrollHead.style.border = "0";
3815			nScrollHead.style.width = "100%";
3816			nScrollFoot.style.border = "0";
3817			nScrollHeadInner.style.width = "150%"; /* will be overwritten */
3818			
3819			/* Modify attributes to respect the clones */
3820			nScrollHeadTable.removeAttribute('id');
3821			nScrollHeadTable.style.marginLeft = "0";
3822			oSettings.nTable.style.marginLeft = "0";
3823			if ( nTfoot !== null )
3824			{
3825				nScrollFootTable.removeAttribute('id');
3826				nScrollFootTable.style.marginLeft = "0";
3827			}
3828			
3829			/* Move any caption elements from the body to the header */
3830			var nCaptions = $('>caption', oSettings.nTable);
3831			for ( var i=0, iLen=nCaptions.length ; i<iLen ; i++ )
3832			{
3833				nScrollHeadTable.appendChild( nCaptions[i] );
3834			}
3835			
3836			/*
3837			 * Sizing
3838			 */
3839			/* When xscrolling add the width and a scroller to move the header with the body */
3840			if ( oSettings.oScroll.sX !== "" )
3841			{
3842				nScrollHead.style.width = _fnStringToCss( oSettings.oScroll.sX );
3843				nScrollBody.style.width = _fnStringToCss( oSettings.oScroll.sX );
3844				
3845				if ( nTfoot !== null )
3846				{
3847					nScrollFoot.style.width = _fnStringToCss( oSettings.oScroll.sX );	
3848				}
3849				
3850				/* When the body is scrolled, then we also want to scroll the headers */
3851				$(nScrollBody).scroll( function (e) {
3852					nScrollHead.scrollLeft = this.scrollLeft;
3853					
3854					if ( nTfoot !== null )
3855					{
3856						nScrollFoot.scrollLeft = this.scrollLeft;
3857					}
3858				} );
3859			}
3860			
3861			/* When yscrolling, add the height */
3862			if ( oSettings.oScroll.sY !== "" )
3863			{
3864				nScrollBody.style.height = _fnStringToCss( oSettings.oScroll.sY );
3865			}
3866			
3867			/* Redraw - align columns across the tables */
3868			oSettings.aoDrawCallback.push( {
3869				"fn": _fnScrollDraw,
3870				"sName": "scrolling"
3871			} );
3872			
3873			/* Infinite scrolling event handlers */
3874			if ( oSettings.oScroll.bInfinite )
3875			{
3876				$(nScrollBody).scroll( function() {
3877					/* Use a blocker to stop scrolling from loading more data while other data is still loading */
3878					if ( !oSettings.bDrawing )
3879					{
3880						/* Check if we should load the next data set */
3881						if ( $(this).scrollTop() + $(this).height() > 
3882							$(oSettings.nTable).height() - oSettings.oScroll.iLoadGap )
3883						{
3884							/* Only do the redraw if we have to - we might be at the end of the data */
3885							if ( oSettings.fnDisplayEnd() < oSettings.fnRecordsDisplay() )
3886							{
3887								_fnPageChange( oSettings, 'next' );
3888								_fnCalculateEnd( oSettings );
3889								_fnDraw( oSettings );
3890							}
3891						}
3892					}
3893				} );
3894			}
3895			
3896			oSettings.nScrollHead = nScrollHead;
3897			oSettings.nScrollFoot = nScrollFoot;
3898			
3899			return nScroller;
3900		}
3901		
3902		/*
3903		 * Function: _fnScrollDraw
3904		 * Purpose:  Update the various tables for resizing
3905		 * Returns:  node: - Node to add to the DOM
3906		 * Inputs:   object:o - dataTables settings object
3907		 * Notes:    It's a bit of a pig this function, but basically the idea to:
3908		 *   1. Re-create the table inside the scrolling div
3909		 *   2. Take live measurements from the DOM
3910		 *   3. Apply the measurements
3911		 *   4. Clean up
3912		 */
3913		function _fnScrollDraw ( o )
3914		{
3915			var
3916				nScrollHeadInner = o.nScrollHead.getElementsByTagName('div')[0],
3917				nScrollHeadTable = nScrollHeadInner.getElementsByTagName('table')[0],
3918				nScrollBody = o.nTable.parentNode,
3919				i, iLen, j, jLen, anHeadToSize, anHeadSizers, anFootSizers, anFootToSize, oStyle, iVis,
3920				iWidth, aApplied=[], iSanityWidth;
3921			
3922			/*
3923			 * 1. Re-create the table inside the scrolling div
3924			 */
3925			
3926			/* Remove the old minimised thead and tfoot elements in the inner table */
3927			var nTheadSize = o.nTable.getElementsByTagName('thead');
3928			if ( nTheadSize.length > 0 )
3929			{
3930				o.nTable.removeChild( nTheadSize[0] );
3931			}
3932			
3933			if ( o.nTFoot !== null )
3934			{
3935				/* Remove the old minimised footer element in the cloned header */
3936				var nTfootSize = o.nTable.getElementsByTagName('tfoot');
3937				if ( nTfootSize.length > 0 )
3938				{
3939					o.nTable.removeChild( nTfootSize[0] );
3940				}
3941			}
3942			
3943			/* Clone the current header and footer elements and then place it into the inner table */
3944			nTheadSize = o.nTHead.cloneNode(true);
3945			o.nTable.insertBefore( nTheadSize, o.nTable.childNodes[0] );
3946			
3947			if ( o.nTFoot !== null )
3948			{
3949				nTfootSize = o.nTFoot.cloneNode(true);
3950				o.nTable.insertBefore( nTfootSize, o.nTable.childNodes[1] );
3951			}
3952			
3953			/*
3954			 * 2. Take live measurements from the DOM - do not alter the DOM itself!
3955			 */
3956			
3957			/* Remove old sizing and apply the calculated column widths
3958			 * Get the unique column headers in the newly created (cloned) header. We want to apply the
3959			 * calclated sizes to this header
3960			 */
3961			var nThs = _fnGetUniqueThs( o, nTheadSize );
3962			for ( i=0, iLen=nThs.length ; i<iLen ; i++ )
3963			{
3964				iVis = _fnVisibleToColumnIndex( o, i );
3965				nThs[i].style.width = o.aoColumns[iVis].sWidth;
3966			}
3967			
3968			if ( o.nTFoot !== null )
3969			{
3970				_fnApplyToChildren( function(n) {
3971					n.style.width = "";
3972				}, nTfootSize.getElementsByTagName('tr') );
3973			}
3974			
3975			/* Size the table as a whole */
3976			iSanityWidth = $(o.nTable).outerWidth();
3977			if ( o.oScroll.sX === "" )
3978			{
3979				/* No x scrolling */
3980				o.nTable.style.width = "100%";
3981				
3982				/* I know this is rubbish - but IE7 will make the width of the table when 100% include
3983				 * the scrollbar - which is shouldn't. This needs feature detection in future - to do
3984				 */
3985				if ( $.browser.msie && $.browser.version <= 7 )
3986				{
3987					o.nTable.style.width = _fnStringToCss( $(o.nTable).outerWidth()-o.oScroll.iBarWidth );
3988				}
3989			}
3990			else
3991			{
3992				if ( o.oScroll.sXInner !== "" )
3993				{
3994					/* x scroll inner has been given - use it */
3995					o.nTable.style.width = _fnStringToCss(o.oScroll.sXInner);
3996				}
3997				else if ( iSanityWidth == $(nScrollBody).width() &&
3998				   $(nScrollBody).height() < $(o.nTable).height() )
3999				{
4000					/* There is y-scrolling - try to take account of the y scroll bar */
4001					o.nTable.style.width = _fnStringToCss( iSanityWidth-o.oScroll.iBarWidth );
4002					if ( $(o.nTable).outerWidth() > iSanityWidth-o.oScroll.iBarWidth )
4003					{
4004						/* Not possible to take account of it */
4005						o.nTable.style.width = _fnStringToCss( iSanityWidth );
4006					}
4007				}
4008				else
4009				{
4010					/* All else fails */
4011					o.nTable.style.width = _fnStringToCss( iSanityWidth );
4012				}
4013			}
4014			
4015			/* Recalculate the sanity width - now that we've applied the required width, before it was
4016			 * a temporary variable. This is required because the column width calculation is done
4017			 * before this table DOM is created.
4018			 */
4019			iSanityWidth = $(o.nTable).outerWidth();
4020			
4021			/* We want the hidden header to have zero height, so remove padding and borders. Then
4022			 * set the width based on the real headers
4023			 */
4024			anHeadToSize = o.nTHead.getElementsByTagName('tr');
4025			anHeadSizers = nTheadSize.getElementsByTagName('tr');
4026			
4027			_fnApplyToChildren( function(nSizer, nToSize) {
4028				oStyle = nSizer.style;
4029				oStyle.paddingTop = "0";
4030				oStyle.paddingBottom = "0";
4031				oStyle.borderTopWidth = "0";
4032				oStyle.borderBottomWidth = "0";
4033				oStyle.height = 0;
4034				
4035				iWidth = $(nSizer).width();
4036				nToSize.style.width = _fnStringToCss( iWidth );
4037				aApplied.push( iWidth );
4038			}, anHeadSizers, anHeadToSize );
4039			$(anHeadSizers).height(0);
4040			
4041			if ( o.nTFoot !== null )
4042			{
4043				/* Clone the current footer and then place it into the body table as a "hidden header" */
4044				anFootSizers = nTfootSize.getElementsByTagName('tr');
4045				anFootToSize = o.nTFoot.getElementsByTagName('tr');
4046				
4047				_fnApplyToChildren( function(nSizer, nToSize) {
4048					oStyle = nSizer.style;
4049					oStyle.paddingTop = "0";
4050					oStyle.paddingBottom = "0";
4051					oStyle.borderTopWidth = "0";
4052					oStyle.borderBottomWidth = "0";
4053					oStyle.height = 0;
4054					
4055					iWidth = $(nSizer).width();
4056					nToSize.style.width = _fnStringToCss( iWidth );
4057					aApplied.push( iWidth );
4058				}, anFootSizers, anFootToSize );
4059				$(anFootSizers).height(0);
4060			}
4061			
4062			/*
4063			 * 3. Apply the measurements
4064			 */
4065			
4066			/* "Hide" the header and footer that we used for the sizing. We want to also fix their width
4067			 * to what they currently are
4068			 */
4069			_fnApplyToChildren( function(nSizer) {
4070				nSizer.innerHTML = "";
4071				nSizer.style.width = _fnStringToCss( aApplied.shift() );
4072			}, anHeadSizers );
4073			
4074			if ( o.nTFoot !== null )
4075			{
4076				_fnApplyToChildren( function(nSizer) {
4077					nSizer.innerHTML = "";
4078					nSizer.style.width = _fnStringToCss( aApplied.shift() );
4079				}, anFootSizers );
4080			}
4081			
4082			/* Sanity check that the table is of a sensible width. If not then we are going to get
4083			 * misalignment
4084			 */
4085			if ( $(o.nTable).outerWidth() < iSanityWidth )
4086			{
4087				if ( o.oScroll.sX === "" )
4088				{
4089					_fnLog( o, 1, "The table cannot fit into the current element which will cause column"+
4090						" misalignment. It is suggested that you enable x-scrolling or increase the width"+
4091						" the table has in which to be drawn" );
4092				}
4093				else if ( o.oScroll.sXInner !== "" )
4094				{
4095					_fnLog( o, 1, "The table cannot fit into the current element which will cause column"+
4096						" misalignment. It is suggested that you increase the sScrollXInner property to"+
4097						" allow it to draw in a larger area, or simply remove that parameter to allow"+
4098						" automatic calculation" );
4099				}
4100			}
4101			
4102			
4103			/*
4104			 * 4. Clean up
4105			 */
4106			
4107			if ( o.oScroll.sY === "" )
4108			{
4109				/* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting
4110				 * the scrollbar height from the visible display, rather than adding it on. We need to
4111				 * set the height in order to sort this. Don't want to do it in any other browsers.
4112				 */
4113				if ( $.browser.msie && $.browser.version <= 7 )
4114				{
4115					nScrollBody.style.height = _fnStringToCss( o.nTable.offsetHeight+o.oScroll.iBarWidth );
4116				}
4117			}
4118			
4119			if ( o.oScroll.sY !== "" && o.oScroll.bCollapse )
4120			{
4121				nScrollBody.style.height = _fnStringToCss( o.oScroll.sY );
4122				
4123				var iExtra = (o.oScroll.sX !== "" && o.nTable.offsetWidth > nScrollBody.offsetWidth) ?
4124				 	o.oScroll.iBarWidth : 0;
4125				if ( o.nTable.offsetHeight < nScrollBody.offsetHeight )
4126				{
4127					nScrollBody.style.height = _fnStringToCss( $(o.nTable).height()+iExtra );
4128				}
4129			}
4130			
4131			/* Finally set the width's of the header and footer tables */
4132			var iOuterWidth = $(o.nTable).outerWidth();
4133			nScrollHeadTable.style.width = _fnStringToCss( iOuterWidth );
4134			nScrollHeadInner.style.width = _fnStringToCss( iOuterWidth+o.oScroll.iBarWidth );
4135			
4136			if ( o.nTFoot !== null )
4137			{
4138				var
4139					nScrollFootInner = o.nScrollFoot.getElementsByTagName('div')[0],
4140					nScrollFootTable = nScrollFootInner.getElementsByTagName('table')[0];
4141				
4142				nScrollFootInner.style.width = _fnStringToCss( o.nTable.offsetWidth+o.oScroll.iBarWidth );
4143				nScrollFootTable.style.width = _fnStringToCss( o.nTable.offsetWidth );
4144			}
4145			
4146			/* If sorting or filtering has occured, jump the scrolling back to the top */
4147			if ( o.bSorted || o.bFiltered )
4148			{
4149				nScrollBody.scrollTop = 0;
4150			}
4151		}
4152		
4153		/*
4154		 * Function: _fnAjustColumnSizing
4155		 * Purpose:  Ajust the table column widths for new data
4156		 * Returns:  -
4157		 * Inputs:   object:oSettings - dataTables settings object
4158		 * Notes:    You would probably want to do a redraw after calling this function!
4159		 */
4160		function _fnAjustColumnSizing ( oSettings )
4161		{
4162			/* Not interested in doing column width calculation if autowidth is disabled */
4163			if ( oSettings.oFeatures.bAutoWidth === false )
4164			{
4165				return false;
4166			}
4167			
4168			_fnCalculateColumnWidths( oSettings );
4169			for ( var i=0 , iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
4170			{
4171				oSettings.aoColumns[i].nTh.style.width = oSettings.aoColumns[i].sWidth;
4172			}
4173		}
4174		
4175		
4176		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
4177		 * Section - Feature: Filtering
4178		 */
4179		
4180		/*
4181		 * Function: _fnFeatureHtmlFilter
4182		 * Purpose:  Generate the node required for filtering text
4183		 * Returns:  node
4184		 * Inputs:   object:oSettings - dataTables settings object
4185		 */
4186		function _fnFeatureHtmlFilter ( oSettings )
4187		{
4188			var nFilter = document.createElement( 'div' );
4189			if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.f == "undefined" )
4190			{
4191				nFilter.setAttribute( 'id', oSettings.sTableId+'_filter' );
4192			}
4193			nFilter.className = oSettings.oClasses.sFilter;
4194			var sSpace = oSettings.oLanguage.sSearch==="" ? "" : " ";
4195			nFilter.innerHTML = oSettings.oLanguage.sSearch+sSpace+'<input type="text" />';
4196			
4197			var jqFilter = $("input", nFilter);
4198			jqFilter.val( oSettings.oPreviousSearch.sSearch.replace('"','&quot;') );
4199			jqFilter.bind( 'keyup.DT', function(e) {
4200				/* Update all other filter input elements for the new display */
4201				var n = oSettings.aanFeatures.f;
4202				for ( var i=0, iLen=n.length ; i<iLen ; i++ )
4203				{
4204					if ( n[i] != this.parentNode )
4205					{
4206						$('input', n[i]).val( this.value );
4207					}
4208				}
4209				
4210				/* Now do the filter */
4211				if ( this.value != oSettings.oPreviousSearch.sSearch )
4212				{
4213					_fnFilterComplete( oSettings, { 
4214						"sSearch": this.value, 
4215						"bRegex":  oSettings.oPreviousSearch.bRegex,
4216						"bSmart":  oSettings.oPreviousSearch.bSmart 
4217					} );
4218				}
4219			} );
4220			
4221			jqFilter.bind( 'keypress.DT', function(e) {
4222				/* Prevent default */
4223				if ( e.keyCode == 13 )
4224				{
4225					return false;
4226				}
4227			} );
4228			
4229			return nFilter;
4230		}
4231		
4232		/*
4233		 * Function: _fnFilterComplete
4234		 * Purpose:  Filter the table using both the global filter and column based filtering
4235		 * Returns:  -
4236		 * Inputs:   object:oSettings - dataTables settings object
4237		 *           object:oSearch: search information
4238		 *           int:iForce - optional - force a research of the master array (1) or not (undefined or 0)
4239		 */
4240		function _fnFilterComplete ( oSettings, oInput, iForce )
4241		{
4242			/* Filter on everything */
4243			_fnFilter( oSettings, oInput.sSearch, iForce, oInput.bRegex, oInput.bSmart );
4244			
4245			/* Now do the individual column filter */
4246			for ( var i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
4247			{
4248				_fnFilterColumn( oSettings, oSettings.aoPreSearchCols[i].sSearch, i, 
4249					oSettings.aoPreSearchCols[i].bRegex, oSettings.aoPreSearchCols[i].bSmart );
4250			}
4251			
4252			/* Custom filtering */
4253			if ( _oExt.afnFiltering.length !== 0 )
4254			{
4255				_fnFilterCustom( oSettings );
4256			}
4257			
4258			/* Tell the draw function we have been filtering */
4259			oSettings.bFiltered = true;
4260			
4261			/* Redraw the table */
4262			oSettings._iDisplayStart = 0;
4263			_fnCalculateEnd( oSettings );
4264			_fnDraw( oSettings );
4265			
4266			/* Rebuild search array 'offline' */
4267			_fnBuildSearchArray( oSettings, 0 );
4268		}
4269		
4270		/*
4271		 * Function: _fnFilterCustom
4272		 * Purpose:  Apply custom filtering functions
4273		 * Returns:  -
4274		 * Inputs:   object:oSettings - dataTables settings object
4275		 */
4276		function _fnFilterCustom( oSettings )
4277		{
4278			var afnFilters = _oExt.afnFiltering;
4279			for ( var i=0, iLen=afnFilters.length ; i<iLen ; i++ )
4280			{
4281				var iCorrector = 0;
4282				for ( var j=0, jLen=oSettings.aiDisplay.length ; j<jLen ; j++ )
4283				{
4284					var iDisIndex = oSettings.aiDisplay[j-iCorrector];
4285					
4286					/* Check if we should use this row based on the filtering function */
4287					if ( !afnFilters[i]( oSettings, _fnGetRowData( oSettings, iDisIndex, 'filter' ), iDisIndex ) )
4288					{
4289						oSettings.aiDisplay.splice( j-iCorrector, 1 );
4290						iCorrector++;
4291					}
4292				}
4293			}
4294		}
4295		
4296		/*
4297		 * Function: _fnFilterColumn
4298		 * Purpose:  Filter the table on a per-column basis
4299		 * Returns:  -
4300		 * Inputs:   object:oSettings - dataTables settings object
4301		 *           string:sInput - string to filter on
4302		 *           int:iColumn - column to filter
4303		 *           bool:bRegex - treat search string as a regular expression or not
4304		 *           bool:bSmart - use smart filtering or not
4305		 */
4306		function _fnFilterColumn ( oSettings, sInput, iColumn, bRegex, bSmart )
4307		{
4308			if ( sInput === "" )
4309			{
4310				return;
4311			}
4312			
4313			var iIndexCorrector = 0;
4314			var rpSearch = _fnFilterCreateSearch( sInput, bRegex, bSmart );
4315			
4316			for ( var i=oSettings.aiDisplay.length-1 ; i>=0 ; i-- )
4317			{
4318				var sData = _fnDataToSearch( _fnGetCellData( oSettings, oSettings.aiDisplay[i], iColumn, 'filter' ),
4319					oSettings.aoColumns[iColumn].sType );
4320				if ( ! rpSearch.test( sData ) )
4321				{
4322					oSettings.aiDisplay.splice( i, 1 );
4323					iIndexCorrector++;
4324				}
4325			}
4326		}
4327		
4328		/*
4329		 * Function: _fnFilter
4330		 * Purpose:  Filter the data table based on user input and draw the table
4331		 * Returns:  -
4332		 * Inputs:   object:oSettings - dataTables settings object
4333		 *           string:sInput - string to filter on
4334		 *           int:iForce - optional - force a research of the master array (1) or not (undefined or 0)
4335		 *           bool:bRegex - treat as a regular expression or not
4336		 *           bool:bSmart - perform smart filtering or not
4337		 */
4338		function _fnFilter( oSettings, sInput, iForce, bRegex, bSmart )
4339		{
4340			var i;
4341			var rpSearch = _fnFilterCreateSearch( sInput, bRegex, bSmart );
4342			
4343			/* Check if we are forcing or not - optional parameter */
4344			if ( typeof iForce == 'undefined' || iForce === null )
4345			{
4346				iForce = 0;
4347			}
4348			
4349			/* Need to take account of custom filtering functions - always filter */
4350			if ( _oExt.afnFiltering.length !== 0 )
4351			{
4352				iForce = 1;
4353			}
4354			
4355			/*
4356			 * If the input is blank - we want the full data set
4357			 */
4358			if ( sInput.length <= 0 )
4359			{
4360				oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
4361				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
4362			}
4363			else
4364			{
4365				/*
4366				 * We are starting a new search or the new search string is smaller 
4367				 * then the old one (i.e. delete). Search from the master array
4368			 	 */
4369				if ( oSettings.aiDisplay.length == oSettings.aiDisplayMaster.length ||
4370					   oSettings.oPreviousSearch.sSearch.length > sInput.length || iForce == 1 ||
4371					   sInput.indexOf(oSettings.oPreviousSearch.sSearch) !== 0 )
4372				{
4373					/* Nuke the old display array - we are going to rebuild it */
4374					oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
4375					
4376					/* Force a rebuild of the search array */
4377					_fnBuildSearchArray( oSettings, 1 );
4378					
4379					/* Search through all records to populate the search array
4380					 * The the oSettings.aiDisplayMaster and asDataSearch arrays have 1 to 1 
4381					 * mapping
4382					 */
4383					for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ )
4384					{
4385						if ( rpSearch.test(oSettings.asDataSearch[i]) )
4386						{
4387							oSettings.aiDisplay.push( oSettings.aiDisplayMaster[i] );
4388						}
4389					}
4390			  }
4391			  else
4392				{
4393			  	/* Using old search array - refine it - do it this way for speed
4394			  	 * Don't have to search the whole master array again
4395					 */
4396			  	var iIndexCorrector = 0;
4397			  	
4398			  	/* Search the current results */
4399			  	for ( i=0 ; i<oSettings.asDataSearch.length ; i++ )
4400					{
4401			  		if ( ! rpSearch.test(oSettings.asDataSearch[i]) )
4402						{
4403			  			oSettings.aiDisplay.splice( i-iIndexCorrector, 1 );
4404			  			iIndexCorrector++;
4405			  		}
4406			  	}
4407			  }
4408			}
4409			oSettings.oPreviousSearch.sSearch = sInput;
4410			oSettings.oPreviousSearch.bRegex = bRegex;
4411			oSettings.oPreviousSearch.bSmart = bSmart;
4412		}
4413		
4414		/*
4415		 * Function: _fnBuildSearchArray
4416		 * Purpose:  Create an array which can be quickly search through
4417		 * Returns:  -
4418		 * Inputs:   object:oSettings - dataTables settings object
4419		 *           int:iMaster - use the master data array - optional
4420		 */
4421		function _fnBuildSearchArray ( oSettings, iMaster )
4422		{
4423			/* Clear out the old data */
4424			oSettings.asDataSearch.splice( 0, oSettings.asDataSearch.length );
4425			
4426			var aArray = (typeof iMaster != 'undefined' && iMaster == 1) ?
4427			 	oSettings.aiDisplayMaster : oSettings.aiDisplay;
4428			
4429			for ( var i=0, iLen=aArray.length ; i<iLen ; i++ )
4430			{
4431				oSettings.asDataSearch[i] = _fnBuildSearchRow( oSettings,
4432					_fnGetRowData( oSettings, aArray[i], 'filter' ) );
4433			}
4434		}
4435		
4436		/*
4437		 * Function: _fnBuildSearchRow
4438		 * Purpose:  Create a searchable string from a single data row
4439		 * Returns:  -
4440		 * Inputs:   object:oSettings - dataTables settings object
4441		 *           array:aData - Row data array to use for the data to search
4442		 */
4443		function _fnBuildSearchRow( oSettings, aData )
4444		{
4445			var sSearch = '';
4446			if ( typeof oSettings.__nTmpFilter == 'undefined' ) {
4447				oSettings.__nTmpFilter = document.createElement('div');
4448			}
4449			var nTmp = oSettings.__nTmpFilter;
4450			
4451			for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ )
4452			{
4453				if ( oSettings.aoColumns[j].bSearchable )
4454				{
4455					var sData = aData[j];
4456					sSearch += _fnDataToSearch( sData, oSettings.aoColumns[j].sType )+'  ';
4457				}
4458			}
4459			
4460			/* If it looks like there is an HTML entity in the string, attempt to decode it */
4461			if ( sSearch.indexOf('&') !== -1 )
4462			{
4463				nTmp.innerHTML = sSearch;
4464				sSearch = nTmp.textContent ? nTmp.textContent : nTmp.innerText;
4465				
4466				/* IE and Opera appear to put an newline where there is a <br> tag - remove it */
4467				sSearch = sSearch.replace(/\n/g," ").replace(/\r/g,"");
4468			}
4469			
4470			return sSearch;
4471		}
4472		
4473		/*
4474		 * Function: _fnFilterCreateSearch
4475		 * Purpose:  Build a regular expression object suitable for searching a table
4476		 * Returns:  RegExp: - constructed object
4477		 * Inputs:   string:sSearch - string to search for
4478		 *           bool:bRegex - treat as a regular expression or not
4479		 *           bool:bSmart - perform smart filtering or not
4480		 */
4481		function _fnFilterCreateSearch( sSearch, bRegex, bSmart )
4482		{
4483			var asSearch, sRegExpString;
4484			
4485			if ( bSmart )
4486			{
4487				/* Generate the regular expression to use. Something along the lines of:
4488				 * ^(?=.*?\bone\b)(?=.*?\btwo\b)(?=.*?\bthree\b).*$
4489				 */
4490				asSearch = bRegex ? sSearch.split( ' ' ) : _fnEscapeRegex( sSearch ).split( ' ' );
4491				sRegExpString = '^(?=.*?'+asSearch.join( ')(?=.*?' )+').*$';
4492				return new RegExp( sRegExpString, "i" );
4493			}
4494			else
4495			{
4496				sSearch = bRegex ? sSearch : _fnEscapeRegex( sSearch );
4497				return new RegExp( sSearch, "i" );
4498			}
4499		}
4500		
4501		/*
4502		 * Function: _fnDataToSearch
4503		 * Purpose:  Convert raw data into something that the user can search on
4504		 * Returns:  string: - search string
4505		 * Inputs:   string:sData - data to be modified
4506		 *           string:sType - data type
4507		 */
4508		function _fnDataToSearch ( sData, sType )
4509		{
4510			if ( typeof _oExt.ofnSearch[sType] == "function" )
4511			{
4512				return _oExt.ofnSearch[sType]( sData );
4513			}
4514			else if ( sType == "html" )
4515			{
4516				return sData.replace(/\n/g," ").replace( /<.*?>/g, "" );
4517			}
4518			else if ( typeof sData == "string" )
4519			{
4520				return sData.replace(/\n/g," ");
4521			}
4522			else if ( sData === null )
4523			{
4524				return '';
4525			}
4526			return sData;
4527		}
4528		
4529		
4530		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
4531		 * Section - Feature: Sorting
4532		 */
4533		
4534		/*
4535	 	 * Function: _fnSort
4536		 * Purpose:  Change the order of the table
4537		 * Returns:  -
4538		 * Inputs:   object:oSettings - dataTables settings object
4539		 *           bool:bApplyClasses - optional - should we apply classes or not
4540		 * Notes:    We always sort the master array and then apply a filter again
4541		 *   if it is needed. This probably isn't optimal - but atm I can't think
4542		 *   of any other way which is (each has disadvantages). we want to sort aiDisplayMaster - 
4543		 *   but according to aoData[]._aData
4544		 */
4545		function _fnSort ( oSettings, bApplyClasses )
4546		{
4547			var
4548				iDataSort, iDataType,
4549				i, iLen, j, jLen,
4550				aaSort = [],
4551			 	aiOrig = [],
4552				oSort = _oExt.oSort,
4553				aoData = oSettings.aoData,
4554				aoColumns = oSettings.aoColumns;
4555			
4556			/* No sorting required if server-side or no sorting array */
4557			if ( !oSettings.oFeatures.bServerSide && 
4558				(oSettings.aaSorting.length !== 0 || oSettings.aaSortingFixed !== null) )
4559			{
4560				if ( oSettings.aaSortingFixed !== null )
4561				{
4562					aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
4563				}
4564				else
4565				{
4566					aaSort = oSettings.aaSorting.slice();
4567				}
4568				
4569				/* If there is a sorting data type, and a fuction belonging to it, then we need to
4570				 * get the data from the developer's function and apply it for this column
4571				 */
4572				for ( i=0 ; i<aaSort.length ; i++ )
4573				{
4574					var iColumn = aaSort[i][0];
4575					var iVisColumn = _fnColumnIndexToVisible( oSettings, iColumn );
4576					var sDataType = oSettings.aoColumns[ iColumn ].sSortDataType;
4577					if ( typeof _oExt.afnSortData[sDataType] != 'undefined' )
4578					{
4579						var aData = _oExt.afnSortData[sDataType]( oSettings, iColumn, iVisColumn );
4580						for ( j=0, jLen=aoData.length ; j<jLen ; j++ )
4581						{
4582							_fnSetCellData( oSettings, j, iColumn, aData[j] );
4583						}
4584					}
4585				}
4586				
4587				/* Create a value - key array of the current row positions such that we can use their
4588				 * current position during the sort, if values match, in order to perform stable sorting
4589				 */
4590				for ( i=0, iLen=oSettings.aiDisplayMaster.length ; i<iLen ; i++ )
4591				{
4592					aiOrig[ oSettings.aiDisplayMaster[i] ] = i;
4593				}
4594				
4595				/* Do the sort - here we want multi-column sorting based on a given data source (column)
4596				 * and sorting function (from oSort) in a certain direction. It's reasonably complex to
4597				 * follow on it's own, but this is what we want (example two column sorting):
4598				 *  fnLocalSorting = function(a,b){
4599				 *  	var iTest;
4600				 *  	iTest = oSort['string-asc']('data11', 'data12');
4601				 *  	if (iTest !== 0)
4602				 *  		return iTest;
4603				 *    iTest = oSort['numeric-desc']('data21', 'data22');
4604				 *    if (iTest !== 0)
4605				 *  		return iTest;
4606				 *  	return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
4607				 *  }
4608				 * Basically we have a test for each sorting column, if the data in that column is equal,
4609				 * test the next column. If all columns match, then we use a numeric sort on the row 
4610				 * positions in the original data array to provide a stable sort.
4611				 */
4612				var iSortLen = aaSort.length;
4613				oSettings.aiDisplayMaster.sort( function ( a, b ) {
4614					var iTest, iDataSort, sDataType;
4615					for ( i=0 ; i<iSortLen ; i++ )
4616					{
4617						iDataSort = aoColumns[ aaSort[i][0] ].iDataSort;
4618						sDataType = aoColumns[ iDataSort ].sType;
4619						iTest = oSort[ (sDataType?sDataType:'string')+"-"+aaSort[i][1] ](
4620							_fnGetCellData( oSettings, a, iDataSort, 'sort' ),
4621							_fnGetCellData( oSettings, b, iDataSort, 'sort' )
4622						);
4623						
4624						if ( iTest !== 0 )
4625						{
4626							return iTest;
4627						}
4628					}
4629					
4630					return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
4631				} );
4632			}
4633			
4634			/* Alter the sorting classes to take account of the changes */
4635			if ( (typeof bApplyClasses == 'undefined' || bApplyClasses) && !oSettings.oFeatures.bDeferRender )
4636			{
4637				_fnSortingClasses( oSettings );
4638			}
4639			
4640			/* Tell the draw function that we have sorted the data */
4641			oSettings.bSorted = true;
4642			
4643			/* Copy the master data into the draw array and re-draw */
4644			if ( oSettings.oFeatures.bFilter )
4645			{
4646				/* _fnFilter() will redraw the table for us */
4647				_fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
4648			}
4649			else
4650			{
4651				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
4652				oSettings._iDisplayStart = 0; /* reset display back to page 0 */
4653				_fnCalculateEnd( oSettings );
4654				_fnDraw( oSettings );
4655			}
4656		}
4657		
4658		/*
4659		 * Function: _fnSortAttachListener
4660		 * Purpose:  Attach a sort handler (click) to a node
4661		 * Returns:  -
4662		 * Inputs:   object:oSettings - dataTables settings object
4663		 *           node:nNode - node to attach the handler to
4664		 *           int:iDataIndex - column sorting index
4665		 *           function:fnCallback - callback function - optional
4666		 */
4667		function _fnSortAttachListener ( oSettings, nNode, iDataIndex, fnCallback )
4668		{
4669			$(nNode).bind( 'click.DT', function (e) {
4670				/* If the column is not sortable - don't to anything */
4671				if ( oSettings.aoColumns[iDataIndex].bSortable === false )
4672				{
4673					return;
4674				}
4675				
4676				/*
4677				 * This is a little bit odd I admit... I declare a temporary function inside the scope of
4678				 * _fnBuildHead and the click handler in order that the code presented here can be used 
4679				 * twice - once for when bProcessing is enabled, and another time for when it is 
4680				 * disabled, as we need to perform slightly different actions.
4681				 *   Basically the issue here is that the Javascript engine in modern browsers don't 
4682				 * appear to allow the rendering engine to update the display while it is still excuting
4683				 * it's thread (well - it does but only after long intervals). This means that the 
4684				 * 'processing' display doesn't appear for a table sort. To break the js thread up a bit
4685				 * I force an execution break by using setTimeout - but this breaks the expected 
4686				 * thread continuation for the end-developer's point of view (their code would execute
4687				 * too early), so we on;y do it when we absolutely have to.
4688				 */
4689				var fnInnerSorting = function () {
4690					var iColumn, iNextSort;
4691					
4692					/* If the shift key is pressed then we are multipe column sorting */
4693					if ( e.shiftKey )
4694					{
4695						/* Are we already doing some kind of sort on this column? */
4696						var bFound = false;
4697						for ( var i=0 ; i<oSettings.aaSorting.length ; i++ )
4698						{
4699							if ( oSettings.aaSorting[i][0] == iDataIndex )
4700							{
4701								bFound = true;
4702								iColumn = oSettings.aaSorting[i][0];
4703								iNextSort = oSettings.aaSorting[i][2]+1;
4704								
4705								if ( typeof oSettings.aoColumns[iColumn].asSorting[iNextSort] == 'undefined' )
4706								{
4707									/* Reached the end of the sorting options, remove from multi-col sort */
4708									oSettings.aaSorting.splice( i, 1 );
4709								}
4710								else
4711								{
4712									/* Move onto next sorting direction */
4713									oSettings.aaSorting[i][1] = oSettings.aoColumns[iColumn].asSorting[iNextSort];
4714									oSettings.aaSorting[i][2] = iNextSort;
4715								}
4716								break;
4717							}
4718						}
4719						
4720						/* No sort yet - add it in */
4721						if ( bFound === false )
4722						{
4723							oSettings.aaSorting.push( [ iDataIndex, 
4724								oSettings.aoColumns[iDataIndex].asSorting[0], 0 ] );
4725						}
4726					}
4727					else
4728					{
4729						/* If no shift key then single column sort */
4730						if ( oSettings.aaSorting.length == 1 && oSettings.aaSorting[0][0] == iDataIndex )
4731						{
4732							iColumn = oSettings.aaSorting[0][0];
4733							iNextSort = oSettings.aaSorting[0][2]+1;
4734							if ( typeof oSettings.aoColumns[iColumn].asSorting[iNextSort] == 'undefined' )
4735							{
4736								iNextSort = 0;
4737							}
4738							oSettings.aaSorting[0][1] = oSettings.aoColumns[iColumn].asSorting[iNextSort];
4739							oSettings.aaSorting[0][2] = iNextSort;
4740						}
4741						else
4742						{
4743							oSettings.aaSorting.splice( 0, oSettings.aaSorting.length );
4744							oSettings.aaSorting.push( [ iDataIndex, 
4745								oSettings.aoColumns[iDataIndex].asSorting[0], 0 ] );
4746						}
4747					}
4748					
4749					/* Run the sort */
4750					_fnSort( oSettings );
4751				}; /* /fnInnerSorting */
4752				
4753				if ( !oSettings.oFeatures.bProcessing )
4754				{
4755					fnInnerSorting();
4756				}
4757				else
4758				{
4759					_fnProcessingDisplay( oSettings, true );
4760					setTimeout( function() {
4761						fnInnerSorting();
4762						if ( !oSettings.oFeatures.bServerSide )
4763						{
4764							_fnProcessingDisplay( oSettings, false );
4765						}
4766					}, 0 );
4767				}
4768				
4769				/* Call the user specified callback function - used for async user interaction */
4770				if ( typeof fnCallback == 'function' )
4771				{
4772					fnCallback( oSettings );
4773				}
4774			} );
4775		}
4776		
4777		/*
4778		 * Function: _fnSortingClasses
4779		 * Purpose:  Set the sortting classes on the header
4780		 * Returns:  -
4781		 * Inputs:   object:oSettings - dataTables settings object
4782		 * Notes:    It is safe to call this function when bSort and bSortClasses are false
4783		 */
4784		function _fnSortingClasses( oSettings )
4785		{
4786			var i, iLen, j, jLen, iFound;
4787			var aaSort, sClass;
4788			var iColumns = oSettings.aoColumns.length;
4789			var oClasses = oSettings.oClasses;
4790			
4791			for ( i=0 ; i<iColumns ; i++ )
4792			{
4793				if ( oSettings.aoColumns[i].bSortable )
4794				{
4795					$(oSettings.aoColumns[i].nTh).removeClass( oClasses.sSortAsc +" "+ oClasses.sSortDesc +
4796						" "+ oSettings.aoColumns[i].sSortingClass );
4797				}
4798			}
4799			
4800			if ( oSettings.aaSortingFixed !== null )
4801			{
4802				aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
4803			}
4804			else
4805			{
4806				aaSort = oSettings.aaSorting.slice();
4807			}
4808			
4809			/* Apply the required classes to the header */
4810			for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
4811			{
4812				if ( oSettings.aoColumns[i].bSortable )
4813				{
4814					sClass = oSettings.aoColumns[i].sSortingClass;
4815					iFound = -1;
4816					for ( j=0 ; j<aaSort.length ; j++ )
4817					{
4818						if ( aaSort[j][0] == i )
4819						{
4820							sClass = ( aaSort[j][1] == "asc" ) ?
4821								oClasses.sSortAsc : oClasses.sSortDesc;
4822							iFound = j;
4823							break;
4824						}
4825					}
4826					$(oSettings.aoColumns[i].nTh).addClass( sClass );
4827					
4828					if ( oSettings.bJUI )
4829					{
4830						/* jQuery UI uses extra markup */
4831						var jqSpan = $("span", oSettings.aoColumns[i].nTh);
4832						jqSpan.removeClass(oClasses.sSortJUIAsc +" "+ oClasses.sSortJUIDesc +" "+ 
4833							oClasses.sSortJUI +" "+ oClasses.sSortJUIAscAllowed +" "+ oClasses.sSortJUIDescAllowed );
4834						
4835						var sSpanClass;
4836						if ( iFound == -1 )
4837						{
4838						 	sSpanClass = oSettings.aoColumns[i].sSortingClassJUI;
4839						}
4840						else if ( aaSort[iFound][1] == "asc" )
4841						{
4842							sSpanClass = oClasses.sSortJUIAsc;
4843						}
4844						else
4845						{
4846							sSpanClass = oClasses.sSortJUIDesc;
4847						}
4848						
4849						jqSpan.addClass( sSpanClass );
4850					}
4851				}
4852				else
4853				{
4854					/* No sorting on this column, so add the base class. This will have been assigned by
4855					 * _fnAddColumn
4856					 */
4857					$(oSettings.aoColumns[i].nTh).addClass( oSettings.aoColumns[i].sSortingClass );
4858				}
4859			}
4860			
4861			/* 
4862			 * Apply the required classes to the table body
4863			 * Note that this is given as a feature switch since it can significantly slow down a sort
4864			 * on large data sets (adding and removing of classes is always slow at the best of times..)
4865			 * Further to this, note that this code is admitadly fairly ugly. It could be made a lot 
4866			 * simpiler using jQuery selectors and add/removeClass, but that is significantly slower
4867			 * (on the order of 5 times slower) - hence the direct DOM manipulation here.
4868			 * Note that for defered drawing we do use jQuery - the reason being that taking the first
4869			 * row found to see if the whole column needs processed can miss classes since the first
4870			 * column might be new.
4871			 */
4872			sClass = oClasses.sSortColumn;
4873			
4874			if ( oSettings.oFeatures.bSort && oSettings.oFeatures.bSortClasses )
4875			{
4876				var nTds = _fnGetTdNodes( oSettings );
4877
4878				/* Remove the old classes */
4879				if ( oSettings.oFeatures.bDeferRender )
4880				{
4881					$(nTds).removeClass(sClass+'1 '+sClass+'2 '+sClass+'3');
4882				}
4883				else if ( nTds.length >= iColumns )
4884				{
4885					for ( i=0 ; i<iColumns ; i++ )
4886					{
4887						if ( nTds[i].className.indexOf(sClass+"1") != -1 )
4888						{
4889							for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ )
4890							{
4891								nTds[(iColumns*j)+i].className = 
4892									$.trim( nTds[(iColumns*j)+i].className.replace( sClass+"1", "" ) );
4893							}
4894						}
4895						else if ( nTds[i].className.indexOf(sClass+"2") != -1 )
4896						{
4897							for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ )
4898							{
4899								nTds[(iColumns*j)+i].className = 
4900									$.trim( nTds[(iColumns*j)+i].className.replace( sClass+"2", "" ) );
4901							}
4902						}
4903						else if ( nTds[i].className.indexOf(sClass+"3") != -1 )
4904						{
4905							for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ )
4906							{
4907								nTds[(iColumns*j)+i].className = 
4908									$.trim( nTds[(iColumns*j)+i].className.replace( " "+sClass+"3", "" ) );
4909							}
4910						}
4911					}
4912				}
4913				
4914				/* Add the new classes to the table */
4915				var iClass = 1, iTargetCol;
4916				for ( i=0 ; i<aaSort.length ; i++ )
4917				{
4918					iTargetCol = parseInt( aaSort[i][0], 10 );
4919					for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ )
4920					{
4921						nTds[(iColumns*j)+iTargetCol].className += " "+sClass+iClass;
4922					}
4923					
4924					if ( iClass < 3 )
4925					{
4926						iClass++;
4927					}
4928				}
4929			}
4930		}
4931		
4932		
4933		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
4934		 * Section - Feature: Pagination. Note that most of the paging logic is done in 
4935		 * _oExt.oPagination
4936		 */
4937		
4938		/*
4939		 * Function: _fnFeatureHtmlPaginate
4940		 * Purpose:  Generate the node required for default pagination
4941		 * Returns:  node
4942		 * Inputs:   object:oSettings - dataTables settings object
4943		 */
4944		function _fnFeatureHtmlPaginate ( oSettings )
4945		{
4946			if ( oSettings.oScroll.bInfinite )
4947			{
4948				return null;
4949			}
4950			
4951			var nPaginate = document.createElement( 'div' );
4952			nPaginate.className = oSettings.oClasses.sPaging+oSettings.sPaginationType;
4953			
4954			_oExt.oPagination[ oSettings.sPaginationType ].fnInit( oSettings, nPaginate, 
4955				function( oSettings ) {
4956					_fnCalculateEnd( oSettings );
4957					_fnDraw( oSettings );
4958				}
4959			);
4960			
4961			/* Add a draw callback for the pagination on first instance, to update the paging display */
4962			if ( typeof oSettings.aanFeatures.p == "undefined" )
4963			{
4964				oSettings.aoDrawCallback.push( {
4965					"fn": function( oSettings ) {
4966						_oExt.oPagination[ oSettings.sPaginationType ].fnUpdate( oSettings, function( oSettings ) {
4967							_fnCalculateEnd( oSettings );
4968							_fnDraw( oSettings );
4969						} );
4970					},
4971					"sName": "pagination"
4972				} );
4973			}
4974			return nPaginate;
4975		}
4976		
4977		/*
4978		 * Function: _fnPageChange
4979		 * Purpose:  Alter the display settings to change the page
4980		 * Returns:  bool:true - page has changed, false - no change (no effect) eg 'first' on page 1
4981		 * Inputs:   object:oSettings - dataTables settings object
4982		 *           string:sAction - paging action to take: "first", "previous", "next" or "last"
4983		 */
4984		function _fnPageChange ( oSettings, sAction )
4985		{
4986			var iOldStart = oSettings._iDisplayStart;
4987			
4988			if ( sAction == "first" )
4989			{
4990				oSettings._iDisplayStart = 0;
4991			}
4992			else if ( sAction == "previous" )
4993			{
4994				oSettings._iDisplayStart = oSettings._iDisplayLength>=0 ?
4995					oSettings._iDisplayStart - oSettings._iDisplayLength :
4996					0;
4997				
4998				/* Correct for underrun */
4999				if ( oSettings._iDisplayStart < 0 )
5000				{
5001				  oSettings._iDisplayStart = 0;
5002				}
5003			}
5004			else if ( sAction == "next" )
5005			{
5006				if ( oSettings._iDisplayLength >= 0 )
5007				{
5008					/* Make sure we are not over running the display array */
5009					if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
5010					{
5011						oSettings._iDisplayStart += oSettings._iDisplayLength;
5012					}
5013				}
5014				else
5015				{
5016					oSettings._iDisplayStart = 0;
5017				}
5018			}
5019			else if ( sAction == "last" )
5020			{
5021				if ( oSettings._iDisplayLength >= 0 )
5022				{
5023					var iPages = parseInt( (oSettings.fnRecordsDisplay()-1) / oSettings._iDisplayLength, 10 ) + 1;
5024					oSettings._iDisplayStart = (iPages-1) * oSettings._iDisplayLength;
5025				}
5026				else
5027				{
5028					oSettings._iDisplayStart = 0;
5029				}
5030			}
5031			else
5032			{
5033				_fnLog( oSettings, 0, "Unknown paging action: "+sAction );
5034			}
5035			
5036			return iOldStart != oSettings._iDisplayStart;
5037		}
5038		
5039		
5040		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
5041		 * Section - Feature: HTML info
5042		 */
5043		
5044		/*
5045		 * Function: _fnFeatureHtmlInfo
5046		 * Purpose:  Generate the node required for the info display
5047		 * Returns:  node
5048		 * Inputs:   object:oSettings - dataTables settings object
5049		 */
5050		function _fnFeatureHtmlInfo ( oSettings )
5051		{
5052			var nInfo = document.createElement( 'div' );
5053			nInfo.className = oSettings.oClasses.sInfo;
5054			
5055			/* Actions that are to be taken once only for this feature */
5056			if ( typeof oSettings.aanFeatures.i == "undefined" )
5057			{
5058				/* Add draw callback */
5059				oSettings.aoDrawCallback.push( {
5060					"fn": _fnUpdateInfo,
5061					"sName": "information"
5062				} );
5063				
5064				/* Add id */
5065				if ( oSettings.sTableId !== '' )
5066				{
5067					nInfo.setAttribute( 'id', oSettings.sTableId+'_info' );
5068				}
5069			}
5070			
5071			return nInfo;
5072		}
5073		
5074		/*
5075		 * Function: _fnUpdateInfo
5076		 * Purpose:  Update the information elements in the display
5077		 * Returns:  -
5078		 * Inputs:   object:oSettings - dataTables settings object
5079		 */
5080		function _fnUpdateInfo ( oSettings )
5081		{
5082			/* Show information about the table */
5083			if ( !oSettings.oFeatures.bInfo || oSettings.aanFeatures.i.length === 0 )
5084			{
5085				return;
5086			}
5087			
5088			var
5089				iStart = oSettings._iDisplayStart+1, iEnd = oSettings.fnDisplayEnd(),
5090				iMax = oSettings.fnRecordsTotal(), iTotal = oSettings.fnRecordsDisplay(),
5091				sStart = oSettings.fnFormatNumber( iStart ), sEnd = oSettings.fnFormatNumber( iEnd ),
5092				sMax = oSettings.fnFormatNumber( iMax ), sTotal = oSettings.fnFormatNumber( iTotal ),
5093				sOut;
5094			
5095			/* When infinite scrolling, we are always starting at 1. _iDisplayStart is used only
5096			 * internally
5097			 */
5098			if ( oSettings.oScroll.bInfinite )
5099			{
5100				sStart = oSettings.fnFormatNumber( 1 );
5101			}
5102			
5103			if ( oSettings.fnRecordsDisplay() === 0 && 
5104				   oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() )
5105			{
5106				/* Empty record set */
5107				sOut = oSettings.oLanguage.sInfoEmpty+ oSettings.oLanguage.sInfoPostFix;
5108			}
5109			else if ( oSettings.fnRecordsDisplay() === 0 )
5110			{
5111				/* Rmpty record set after filtering */
5112				sOut = oSettings.oLanguage.sInfoEmpty +' '+ 
5113					oSettings.oLanguage.sInfoFiltered.replace('_MAX_', sMax)+
5114						oSettings.oLanguage.sInfoPostFix;
5115			}
5116			else if ( oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() )
5117			{
5118				/* Normal record set */
5119				sOut = oSettings.oLanguage.sInfo.
5120						replace('_START_', sStart).
5121						replace('_END_',   sEnd).
5122						replace('_TOTAL_', sTotal)+ 
5123					oSettings.oLanguage.sInfoPostFix;
5124			}
5125			else
5126			{
5127				/* Record set after filtering */
5128				sOut = oSettings.oLanguage.sInfo.
5129						replace('_START_', sStart).
5130						replace('_END_',   sEnd).
5131						replace('_TOTAL_', sTotal) +' '+ 
5132					oSettings.oLanguage.sInfoFiltered.replace('_MAX_', 
5133						oSettings.fnFormatNumber(oSettings.fnRecordsTotal()))+ 
5134					oSettings.oLanguage.sInfoPostFix;
5135			}
5136			
5137			if ( oSettings.oLanguage.fnInfoCallback !== null )
5138			{
5139				sOut = oSettings.oLanguage.fnInfoCallback( oSettings, iStart, iEnd, iMax, iTotal, sOut );
5140			}
5141			
5142			var n = oSettings.aanFeatures.i;
5143			for ( var i=0, iLen=n.length ; i<iLen ; i++ )
5144			{
5145				$(n[i]).html( sOut );
5146			}
5147		}
5148		
5149		
5150		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
5151		 * Section - Feature: Length change
5152		 */
5153		
5154		/*
5155		 * Function: _fnFeatureHtmlLength
5156		 * Purpose:  Generate the node required for user display length changing
5157		 * Returns:  node
5158		 * Inputs:   object:oSettings - dataTables settings object
5159		 */
5160		function _fnFeatureHtmlLength ( oSettings )
5161		{
5162			if ( oSettings.oScroll.bInfinite )
5163			{
5164				return null;
5165			}
5166			
5167			/* This can be overruled by not using the _MENU_ var/macro in the language variable */
5168			var sName = (oSettings.sTableId === "") ? "" : 'name="'+oSettings.sTableId+'_length"';
5169			var sStdMenu = '<select size="1" '+sName+'>';
5170			var i, iLen;
5171			
5172			if ( oSettings.aLengthMenu.length == 2 && typeof oSettings.aLengthMenu[0] == 'object' && 
5173					typeof oSettings.aLengthMenu[1] == 'object' )
5174			{
5175				for ( i=0, iLen=oSettings.aLengthMenu[0].length ; i<iLen ; i++ )
5176				{
5177					sStdMenu += '<option value="'+oSettings.aLengthMenu[0][i]+'">'+
5178						oSettings.aLengthMenu[1][i]+'</option>';
5179				}
5180			}
5181			else
5182			{
5183				for ( i=0, iLen=oSettings.aLengthMenu.length ; i<iLen ; i++ )
5184				{
5185					sStdMenu += '<option value="'+oSettings.aLengthMenu[i]+'">'+
5186						oSettings.aLengthMenu[i]+'</option>';
5187				}
5188			}
5189			sStdMenu += '</select>';
5190			
5191			var nLength = document.createElement( 'div' );
5192			if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.l == "undefined" )
5193			{
5194				nLength.setAttribute( 'id', oSettings.sTableId+'_length' );
5195			}
5196			nLength.className = oSettings.oClasses.sLength;
5197			nLength.innerHTML = oSettings.oLanguage.sLengthMenu.replace( '_MENU_', sStdMenu );
5198			
5199			/*
5200			 * Set the length to the current display length - thanks to Andrea Pavlovic for this fix,
5201			 * and Stefan Skopnik for fixing the fix!
5202			 */
5203			$('select option[value="'+oSettings._iDisplayLength+'"]',nLength).attr("selected",true);
5204			
5205			$('select', nLength).bind( 'change.DT', function(e) {
5206				var iVal = $(this).val();
5207				
5208				/* Update all other length options for the new display */
5209				var n = oSettings.aanFeatures.l;
5210				for ( i=0, iLen=n.length ; i<iLen ; i++ )
5211				{
5212					if ( n[i] != this.parentNode )
5213					{
5214						$('select', n[i]).val( iVal );
5215					}
5216				}
5217				
5218				/* Redraw the table */
5219				oSettings._iDisplayLength = parseInt(iVal, 10);
5220				_fnCalculateEnd( oSettings );
5221				
5222				/* If we have space to show extra rows (backing up from the end point - then do so */
5223				if ( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() )
5224				{
5225					oSettings._iDisplayStart = oSettings.fnDisplayEnd() - oSettings._iDisplayLength;
5226					if ( oSettings._iDisplayStart < 0 )
5227					{
5228						oSettings._iDisplayStart = 0;
5229					}
5230				}
5231				
5232				if ( oSettings._iDisplayLength == -1 )
5233				{
5234					oSettings._iDisplayStart = 0;
5235				}
5236				
5237				_fnDraw( oSettings );
5238			} );
5239			
5240			return nLength;
5241		}
5242		
5243		
5244		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
5245		 * Section - Feature: Processing incidator
5246		 */
5247		
5248		/*
5249		 * Function: _fnFeatureHtmlProcessing
5250		 * Purpose:  Generate the node required for the processing node
5251		 * Returns:  node
5252		 * Inputs:   object:oSettings - dataTables settings object
5253		 */
5254		function _fnFeatureHtmlProcessing ( oSettings )
5255		{
5256			var nProcessing = document.createElement( 'div' );
5257			
5258			if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.r == "undefined" )
5259			{
5260				nProcessing.setAttribute( 'id', oSettings.sTableId+'_processing' );
5261			}
5262			nProcessing.innerHTML = oSettings.oLanguage.sProcessing;
5263			nProcessing.className = oSettings.oClasses.sProcessing;
5264			oSettings.nTable.parentNode.insertBefore( nProcessing, oSettings.nTable );
5265			
5266			return nProcessing;
5267		}
5268		
5269		/*
5270		 * Function: _fnProcessingDisplay
5271		 * Purpose:  Display or hide the processing indicator
5272		 * Returns:  -
5273		 * Inputs:   object:oSettings - dataTables settings object
5274		 *           bool:
5275		 *   true - show the processing indicator
5276		 *   false - don't show
5277		 */
5278		function _fnProcessingDisplay ( oSettings, bShow )
5279		{
5280			if ( oSettings.oFeatures.bProcessing )
5281			{
5282				var an = oSettings.aanFeatures.r;
5283				for ( var i=0, iLen=an.length ; i<iLen ; i++ )
5284				{
5285					an[i].style.visibility = bShow ? "visible" : "hidden";
5286				}
5287			}
5288		}
5289		
5290		
5291		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
5292		 * Section - Support functions
5293		 */
5294		
5295		/*
5296		 * Function: _fnVisibleToColumnIndex
5297		 * Purpose:  Covert the index of a visible column to the index in the data array (take account
5298		 *   of hidden columns)
5299		 * Returns:  int:i - the data index
5300		 * Inputs:   object:oSettings - dataTables settings object
5301		 */
5302		function _fnVisibleToColumnIndex( oSettings, iMatch )
5303		{
5304			var iColumn = -1;
5305			
5306			for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
5307			{
5308				if ( oSettings.aoColumns[i].bVisible === true )
5309				{
5310					iColumn++;
5311				}
5312				
5313				if ( iColumn == iMatch )
5314				{
5315					return i;
5316				}
5317			}
5318			
5319			return null;
5320		}
5321		
5322		/*
5323		 * Function: _fnColumnIndexToVisible
5324		 * Purpose:  Covert the index of an index in the data array and convert it to the visible
5325		 *   column index (take account of hidden columns)
5326		 * Returns:  int:i - the data index
5327		 * Inputs:   object:oSettings - dataTables settings object
5328		 */
5329		function _fnColumnIndexToVisible( oSettings, iMatch )
5330		{
5331			var iVisible = -1;
5332			for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
5333			{
5334				if ( oSettings.aoColumns[i].bVisible === true )
5335				{
5336					iVisible++;
5337				}
5338				
5339				if ( i == iMatch )
5340				{
5341					return oSettings.aoColumns[i].bVisible === true ? iVisible : null;
5342				}
5343			}
5344			
5345			return null;
5346		}
5347		
5348		
5349		/*
5350		 * Function: _fnNodeToDataIndex
5351		 * Purpose:  Take a TR element and convert it to an index in aoData
5352		 * Returns:  int:i - index if found, null if not
5353		 * Inputs:   object:s - dataTables settings object
5354		 *           node:n - the TR element to find
5355		 */
5356		function _fnNodeToDataIndex( s, n )
5357		{
5358			var i, iLen;
5359			
5360			/* Optimisation - see if the nodes which are currently visible match, since that is
5361			 * the most likely node to be asked for (a selector or event for example)
5362			 */
5363			for ( i=s._iDisplayStart, iLen=s._iDisplayEnd ; i<iLen ; i++ )
5364			{
5365				if ( s.aoData[ s.aiDisplay[i] ].nTr == n )
5366				{
5367					return s.aiDisplay[i];
5368				}
5369			}
5370			
5371			/* Otherwise we are in for a slog through the whole data cache */
5372			for ( i=0, iLen=s.aoData.length ; i<iLen ; i++ )
5373			{
5374				if ( s.aoData[i].nTr == n )
5375				{
5376					return i;
5377				}
5378			}
5379			return null;
5380		}
5381		
5382		/*
5383		 * Function: _fnVisbleColumns
5384		 * Purpose:  Get the number of visible columns
5385		 * Returns:  int:i - the number of visible columns
5386		 * Inputs:   object:oS - dataTables settings object
5387		 */
5388		function _fnVisbleColumns( oS )
5389		{
5390			var iVis = 0;
5391			for ( var i=0 ; i<oS.aoColumns.length ; i++ )
5392			{
5393				if ( oS.aoColumns[i].bVisible === true )
5394				{
5395					iVis++;
5396				}
5397			}
5398			return iVis;
5399		}
5400		
5401		/*
5402		 * Function: _fnCalculateEnd
5403		 * Purpose:  Rcalculate the end point based on the start point
5404		 * Returns:  -
5405		 * Inputs:   object:oSettings - dataTables settings object
5406		 */
5407		function _fnCalculateEnd( oSettings )
5408		{
5409			if ( oSettings.oFeatures.bPaginate === false )
5410			{
5411				oSettings._iDisplayEnd = oSettings.aiDisplay.length;
5412			}
5413			else
5414			{
5415				/* Set the end point of the display - based on how many elements there are
5416				 * still to display
5417				 */
5418				if ( oSettings._iDisplayStart + oSettings._iDisplayLength > oSettings.aiDisplay.length ||
5419					   oSettings._iDisplayLength == -1 )
5420				{
5421					oSettings._iDisplayEnd = oSettings.aiDisplay.length;
5422				}
5423				else
5424				{
5425					oSettings._iDisplayEnd = oSettings._iDisplayStart + oSettings._iDisplayLength;
5426				}
5427			}
5428		}
5429		
5430		/*
5431		 * Function: _fnConvertToWidth
5432		 * Purpose:  Convert a CSS unit width to pixels (e.g. 2em)
5433		 * Returns:  int:iWidth - width in pixels
5434		 * Inputs:   string:sWidth - width to be converted
5435		 *           node:nParent - parent to get the with for (required for
5436		 *             relative widths) - optional
5437		 */
5438		function _fnConvertToWidth ( sWidth, nParent )
5439		{
5440			if ( !sWidth || sWidth === null || sWidth === '' )
5441			{
5442				return 0;
5443			}
5444			
5445			if ( typeof nParent == "undefined" )
5446			{
5447				nParent = document.getElementsByTagName('body')[0];
5448			}
5449			
5450			var iWidth;
5451			var nTmp = document.createElement( "div" );
5452			nTmp.style.width = _fnStringToCss( sWidth );
5453			
5454			nParent.appendChild( nTmp );
5455			iWidth = nTmp.offsetWidth;
5456			nParent.removeChild( nTmp );
5457			
5458			return ( iWidth );
5459		}
5460		
5461		/*
5462		 * Function: _fnCalculateColumnWidths
5463		 * Purpose:  Calculate the width of columns for the table
5464		 * Returns:  -
5465		 * Inputs:   object:oSettings - dataTables settings object
5466		 */
5467		function _fnCalculateColumnWidths ( oSettings )
5468		{
5469			var iTableWidth = oSettings.nTable.offsetWidth;
5470			var iUserInputs = 0;
5471			var iTmpWidth;
5472			var iVisibleColumns = 0;
5473			var iColums = oSettings.aoColumns.length;
5474			var i, iIndex, iCorrector, iWidth;
5475			var oHeaders = $('th', oSettings.nTHead);
5476			
5477			/* Convert any user input sizes into pixel sizes */
5478			for ( i=0 ; i<iColums ; i++ )
5479			{
5480				if ( oSettings.aoColumns[i].bVisible )
5481				{
5482					iVisibleColumns++;
5483					
5484					if ( oSettings.aoColumns[i].sWidth !== null )
5485					{
5486						iTmpWidth = _fnConvertToWidth( oSettings.aoColumns[i].sWidthOrig, 
5487							oSettings.nTable.parentNode );
5488						if ( iTmpWidth !== null )
5489						{
5490							oSettings.aoColumns[i].sWidth = _fnStringToCss( iTmpWidth );
5491						}
5492							
5493						iUserInputs++;
5494					}
5495				}
5496			}
5497			
5498			/* If the number of columns in the DOM equals the number that we have to process in 
5499			 * DataTables, then we can use the offsets that are created by the web-browser. No custom 
5500			 * sizes can be set in order for this to happen, nor scrolling used
5501			 */
5502			if ( iColums == oHeaders.length && iUserInputs === 0 && iVisibleColumns == iColums &&
5503				oSettings.oScroll.sX === "" && oSettings.oScroll.sY === "" )
5504			{
5505				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
5506				{
5507					iTmpWidth = $(oHeaders[i]).width();
5508					if ( iTmpWidth !== null )
5509					{
5510						oSettings.aoColumns[i].sWidth = _fnStringToCss( iTmpWidth );
5511					}
5512				}
5513			}
5514			else
5515			{
5516				/* Otherwise we are going to have to do some calculations to get the width of each column.
5517				 * Construct a 1 row table with the widest node in the data, and any user defined widths,
5518				 * then insert it into the DOM and allow the browser to do all the hard work of
5519				 * calculating table widths.
5520				 */
5521				var
5522					nCalcTmp = oSettings.nTable.cloneNode( false ),
5523					nTheadClone = oSettings.nTHead.cloneNode(true),
5524					nBody = document.createElement( 'tbody' ),
5525					nTr = document.createElement( 'tr' ),
5526					nDivSizing;
5527				
5528				nCalcTmp.removeAttribute( "id" );
5529				nCalcTmp.appendChild( nTheadClone );
5530				if ( oSettings.nTFoot !== null )
5531				{
5532					nCalcTmp.appendChild( oSettings.nTFoot.cloneNode(true) );
5533					_fnApplyToChildren( function(n) {
5534						n.style.width = "";
5535					}, nCalcTmp.getElementsByTagName('tr') );
5536				}
5537				
5538				nCalcTmp.appendChild( nBody );
5539				nBody.appendChild( nTr );
5540				
5541				/* Remove any sizing that was previously applied by the styles */
5542				var jqColSizing = $('thead th', nCalcTmp);
5543				if ( jqColSizing.length === 0 )
5544				{
5545					jqColSizing = $('tbody tr:eq(0)>td', nCalcTmp);
5546				}
5547
5548				/* Apply custom sizing to the cloned header */
5549				var nThs = _fnGetUniqueThs( oSettings, nTheadClone );
5550				iCorrector = 0;
5551				for ( i=0 ; i<iColums ; i++ )
5552				{
5553					var oColumn = oSettings.aoColumns[i];
5554					if ( oColumn.bVisible && oColumn.sWidthOrig !== null && oColumn.sWidthOrig !== "" )
5555					{
5556						nThs[i-iCorrector].style.width = _fnStringToCss( oColumn.sWidthOrig );
5557					}
5558					else if ( oColumn.bVisible )
5559					{
5560						nThs[i-iCorrector].style.width = "";
5561					}
5562					else
5563					{
5564						iCorrector++;
5565					}
5566				}
5567
5568				/* Find the biggest td for each column and put it into the table */
5569				for ( i=0 ; i<iColums ; i++ )
5570				{
5571					if ( oSettings.aoColumns[i].bVisible )
5572					{
5573						var nTd = _fnGetWidestNode( oSettings, i );
5574						if ( nTd !== null )
5575						{
5576							nTd = nTd.cloneNode(true);
5577							if ( oSettings.aoColumns[i].sContentPadding !== "" )
5578							{
5579								nTd.innerHTML += oSettings.aoColumns[i].sContentPadding;
5580							}
5581							nTr.appendChild( nTd );
5582						}
5583					}
5584				}
5585				
5586				/* Build the table and 'display' it */
5587				var nWrapper = oSettings.nTable.parentNode;
5588				nWrapper.appendChild( nCalcTmp );
5589				
5590				/* When scrolling (X or Y) we want to set the width of the table as appropriate. However,
5591				 * when not scrolling leave the table width as it is. This results in slightly different,
5592				 * but I think correct behaviour
5593				 */
5594				if ( oSettings.oScroll.sX !== "" && oSettings.oScroll.sXInner !== "" )
5595				{
5596					nCalcTmp.style.width = _fnStringToCss(oSettings.oScroll.sXInner);
5597				}
5598				else if ( oSettings.oScroll.sX !== "" )
5599				{
5600					nCalcTmp.style.width = "";
5601					if ( $(nCalcTmp).width() < nWrapper.offsetWidth )
5602					{
5603						nCalcTmp.style.width = _fnStringToCss( nWrapper.offsetWidth );
5604					}
5605				}
5606				else if ( oSettings.oScroll.sY !== "" )
5607				{
5608					nCalcTmp.style.width = _fnStringToCss( nWrapper.offsetWidth );
5609				}
5610				nCalcTmp.style.visibility = "hidden";
5611				
5612				/* Scrolling considerations */
5613				_fnScrollingWidthAdjust( oSettings, nCalcTmp );
5614				
5615				/* Read the width's calculated by the browser and store them for use by the caller. We
5616				 * first of all try to use the elements in the body, but it is possible that there are
5617				 * no elements there, under which circumstances we use the header elements
5618				 */
5619				var oNodes = $("tbody tr:eq(0)", nCalcTmp).children();
5620				if ( oNodes.length === 0 )
5621				{
5622					oNodes = _fnGetUniqueThs( oSettings, $('thead', nCalcTmp)[0] );
5623				}
5624
5625				/* Browsers need a bit of a hand when a width is assigned to any columns when 
5626				 * x-scrolling as they tend to collapse the table to the min-width, even if
5627				 * we sent the column widths. So we need to keep track of what the table width
5628				 * should be by summing the user given values, and the automatic values
5629				 */
5630				if ( oSettings.oScroll.sX !== "" )
5631				{
5632					var iTotal = 0;
5633					iCorrector = 0;
5634					for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
5635					{
5636						if ( oSettings.aoColumns[i].bVisible )
5637						{
5638							if ( oSettings.aoColumns[i].sWidthOrig === null )
5639							{
5640								iTotal += $(oNodes[iCorrector]).outerWidth();
5641							}
5642							else
5643							{
5644								iTotal += parseInt(oSettings.aoColumns[i].sWidth.replace('px',''), 10) +
5645									($(oNodes[iCorrector]).outerWidth() - $(oNodes[iCorrector]).width());
5646							}
5647							iCorrector++;
5648						}
5649					}
5650				
5651					nCalcTmp.style.width = _fnStringToCss( iTotal );
5652					oSettings.nTable.style.width = _fnStringToCss( iTotal );
5653				}
5654
5655				iCorrector = 0;
5656				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
5657				{
5658					if ( oSettings.aoColumns[i].bVisible )
5659					{
5660						iWidth = $(oNodes[iCorrector]).width();
5661						if ( iWidth !== null && iWidth > 0 )
5662						{
5663							oSettings.aoColumns[i].sWidth = _fnStringToCss( iWidth );
5664						}
5665						iCorrector++;
5666					}
5667				}
5668				
5669				oSettings.nTable.style.width = _fnStringToCss( $(nCalcTmp).outerWidth() );
5670				nCalcTmp.parentNode.removeChild( nCalcTmp );
5671			}
5672		}
5673		
5674		/*
5675		 * Function: _fnScrollingWidthAdjust
5676		 * Purpose:  Adjust a table's width to take account of scrolling
5677		 * Returns:  -
5678		 * Inputs:   object:oSettings - dataTables settings object
5679		 *           node:n - table node
5680		 */
5681		function _fnScrollingWidthAdjust ( oSettings, n )
5682		{
5683			if ( oSettings.oScroll.sX === "" && oSettings.oScroll.sY !== "" )
5684			{
5685				/* When y-scrolling only, we want to remove the width of the scroll bar so the table
5686				 * + scroll bar will fit into the area avaialble.
5687				 */
5688				var iOrigWidth = $(n).width();
5689				n.style.width = _fnStringToCss( $(n).outerWidth()-oSettings.oScroll.iBarWidth );
5690			}
5691			else if ( oSettings.oScroll.sX !== "" )
5692			{
5693				/* When x-scrolling both ways, fix the table at it's current size, without adjusting */
5694				n.style.width = _fnStringToCss( $(n).outerWidth() );
5695			}
5696		}
5697		
5698		/*
5699		 * Function: _fnGetWidestNode
5700		 * Purpose:  Get the widest node
5701		 * Returns:  string: - max strlens for each column
5702		 * Inputs:   object:oSettings - dataTables settings object
5703		 *           int:iCol - column of interest
5704		 */
5705		function _fnGetWidestNode( oSettings, iCol )
5706		{
5707			var iMaxIndex = _fnGetMaxLenString( oSettings, iCol );
5708			if ( iMaxIndex < 0 )
5709			{
5710				return null;
5711			}
5712
5713			if ( oSettings.aoData[iMaxIndex].nTr === null )
5714			{
5715				var n = document.createElement('td');
5716				n.innerHTML = _fnGetCellData( oSettings, iMaxIndex, iCol, '' );
5717				return n;
5718			}
5719			return _fnGetTdNodes(oSettings, iMaxIndex)[iCol];
5720		}
5721		
5722		/*
5723		 * Function: _fnGetMaxLenString
5724		 * Purpose:  Get the maximum strlen for each data column
5725		 * Returns:  string: - max strlens for each column
5726		 * Inputs:   object:oSettings - dataTables settings object
5727		 *           int:iCol - column of interest
5728		 */
5729		function _fnGetMaxLenString( oSettings, iCol )
5730		{
5731			var iMax = -1;
5732			var iMaxIndex = -1;
5733			
5734			for ( var i=0 ; i<oSettings.aoData.length ; i++ )
5735			{
5736				var s = _fnGetCellData( oSettings, i, iCol, 'display' )+"";
5737				s = s.replace( /<.*?>/g, "" );
5738				if ( s.length > iMax )
5739				{
5740					iMax = s.length;
5741					iMaxIndex = i;
5742				}
5743			}
5744			
5745			return iMaxIndex;
5746		}
5747		
5748		/*
5749		 * Function: _fnStringToCss
5750		 * Purpose:  Append a CSS unit (only if required) to a string
5751		 * Returns:  0 if match, 1 if length is different, 2 if no match
5752		 * Inputs:   array:aArray1 - first array
5753		 *           array:aArray2 - second array
5754		 */
5755		function _fnStringToCss( s )
5756		{
5757			if ( s === null )
5758			{
5759				return "0px";
5760			}
5761			
5762			if ( typeof s == 'number' )
5763			{
5764				if ( s < 0 )
5765				{
5766					return "0px";
5767				}
5768				return s+"px";
5769			}
5770			
5771			/* Check if the last character is not 0-9 */
5772			var c = s.charCodeAt( s.length-1 );
5773			if (c < 0x30 || c > 0x39)
5774			{
5775				return s;
5776			}
5777			return s+"px";
5778		}
5779		
5780		/*
5781		 * Function: _fnArrayCmp
5782		 * Purpose:  Compare two arrays
5783		 * Returns:  0 if match, 1 if length is different, 2 if no match
5784		 * Inputs:   array:aArray1 - first array
5785		 *           array:aArray2 - second array
5786		 */
5787		function _fnArrayCmp( aArray1, aArray2 )
5788		{
5789			if ( aArray1.length != aArray2.length )
5790			{
5791				return 1;
5792			}
5793			
5794			for ( var i=0 ; i<aArray1.length ; i++ )
5795			{
5796				if ( aArray1[i] != aArray2[i] )
5797				{
5798					return 2;
5799				}
5800			}
5801			
5802			return 0;
5803		}
5804		
5805		/*
5806		 * Function: _fnDetectType
5807		 * Purpose:  Get the sort type based on an input string
5808		 * Returns:  string: - type (defaults to 'string' if no type can be detected)
5809		 * Inputs:   string:sData - data we wish to know the type of
5810		 * Notes:    This function makes use of the DataTables plugin objct _oExt 
5811		 *   (.aTypes) such that new types can easily be added.
5812		 */
5813		function _fnDetectType( sData )
5814		{
5815			var aTypes = _oExt.aTypes;
5816			var iLen = aTypes.length;
5817			
5818			for ( var i=0 ; i<iLen ; i++ )
5819			{
5820				var sType = aTypes[i]( sData );
5821				if ( sType !== null )
5822				{
5823					return sType;
5824				}
5825			}
5826			
5827			return 'string';
5828		}
5829		
5830		/*
5831		 * Function: _fnSettingsFromNode
5832		 * Purpose:  Return the settings object for a particular table
5833		 * Returns:  object: Settings object - or null if not found
5834		 * Inputs:   node:nTable - table we are using as a dataTable
5835		 */
5836		function _fnSettingsFromNode ( nTable )
5837		{
5838			for ( var i=0 ; i<_aoSettings.length ; i++ )
5839			{
5840				if ( _aoSettings[i].nTable == nTable )
5841				{
5842					return _aoSettings[i];
5843				}
5844			}
5845			
5846			return null;
5847		}
5848		
5849		/*
5850		 * Function: _fnGetDataMaster
5851		 * Purpose:  Return an array with the full table data
5852		 * Returns:  array array:aData - Master data array
5853		 * Inputs:   object:oSettings - dataTables settings object
5854		 */
5855		function _fnGetDataMaster ( oSettings )
5856		{
5857			var aData = [];
5858			var iLen = oSettings.aoData.length;
5859			for ( var i=0 ; i<iLen; i++ )
5860			{
5861				aData.push( oSettings.aoData[i]._aData );
5862			}
5863			return aData;
5864		}
5865		
5866		/*
5867		 * Function: _fnGetTrNodes
5868		 * Purpose:  Return an array with the TR nodes for the table
5869		 * Returns:  array: - TR array
5870		 * Inputs:   object:oSettings - dataTables settings object
5871		 */
5872		function _fnGetTrNodes ( oSettings )
5873		{
5874			var aNodes = [];
5875			for ( var i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
5876			{
5877				if ( oSettings.aoData[i].nTr !== null )
5878				{
5879					aNodes.push( oSettings.aoData[i].nTr );
5880				}
5881			}
5882			return aNodes;
5883		}
5884		
5885		/*
5886		 * Function: _fnGetTdNodes
5887		 * Purpose:  Return an flat array with all TD nodes for the table, or row
5888		 * Returns:  array: - TD array
5889		 * Inputs:   object:oSettings - dataTables settings object
5890		 *           int:iIndividualRow - aoData index to get the nodes for - optional if not
5891		 *             given then the return array will contain all nodes for the table
5892		 */
5893		function _fnGetTdNodes ( oSettings, iIndividualRow )
5894		{
5895			var anReturn = [];
5896			var iCorrector;
5897			var anTds;
5898			var iRow, iRows=oSettings.aoData.length,
5899				iColumn, iColumns, oData, sNodeName, iStart=0, iEnd=iRows;
5900			
5901			/* Allow the collection to be limited to just one row */
5902			if ( typeof iIndividualRow != 'undefined' )
5903			{
5904				iStart = iIndividualRow;
5905				iEnd = iIndividualRow+1;
5906			}
5907
5908			for ( iRow=iStart ; iRow<iEnd ; iRow++ )
5909			{
5910				oData = oSettings.aoData[iRow];
5911				if ( oData.nTr !== null )
5912				{
5913					/* get the TD child nodes - taking into account text etc nodes */
5914					anTds = [];
5915					for ( iColumn=0, iColumns=oData.nTr.childNodes.length ; iColumn<iColumns ; iColumn++ )
5916					{
5917						sNodeName = oData.nTr.childNodes[iColumn].nodeName.toLowerCase();
5918						if ( sNodeName == 'td' || sNodeName == 'th' )
5919						{
5920							anTds.push( oData.nTr.childNodes[iColumn] );
5921						}
5922					}
5923
5924					iCorrector = 0;
5925					for ( iColumn=0, iColumns=oSettings.aoColumns.length ; iColumn<iColumns ; iColumn++ )
5926					{
5927						if ( oSettings.aoColumns[iColumn].bVisible )
5928						{
5929							anReturn.push( anTds[iColumn-iCorrector] );
5930						}
5931						else
5932						{
5933							anReturn.push( oData._anHidden[iColumn] );
5934							iCorrector++;
5935						}
5936					}
5937				}
5938			}
5939
5940			return anReturn;
5941		}
5942		
5943		/*
5944		 * Function: _fnEscapeRegex
5945		 * Purpose:  scape a string stuch that it can be used in a regular expression
5946		 * Returns:  string: - escaped string
5947		 * Inputs:   string:sVal - string to escape
5948		 */
5949		function _fnEscapeRegex ( sVal )
5950		{
5951			var acEscape = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^' ];
5952		  var reReplace = new RegExp( '(\\' + acEscape.join('|\\') + ')', 'g' );
5953		  return sVal.replace(reReplace, '\\$1');
5954		}
5955		
5956		/*
5957		 * Function: _fnDeleteIndex
5958		 * Purpose:  Take an array of integers (index array) and remove a target integer (value - not 
5959		 *             the key!)
5960		 * Returns:  -
5961		 * Inputs:   a:array int - Index array to target
5962		 *           int:iTarget - value to find
5963		 */
5964		function _fnDeleteIndex( a, iTarget )
5965		{
5966			var iTargetIndex = -1;
5967			
5968			for ( var i=0, iLen=a.length ; i<iLen ; i++ )
5969			{
5970				if ( a[i] == iTarget )
5971				{
5972					iTargetIndex = i;
5973				}
5974				else if ( a[i] > iTarget )
5975				{
5976					a[i]--;
5977				}
5978			}
5979			
5980			if ( iTargetIndex != -1 )
5981			{
5982				a.splice( iTargetIndex, 1 );
5983			}
5984		}
5985		
5986		/*
5987		 * Function: _fnReOrderIndex
5988		 * Purpose:  Figure out how to reorder a display list
5989		 * Returns:  array int:aiReturn - index list for reordering
5990		 * Inputs:   object:oSettings - dataTables settings object
5991		 */
5992		function _fnReOrderIndex ( oSettings, sColumns )
5993		{
5994			var aColumns = sColumns.split(',');
5995			var aiReturn = [];
5996			
5997			for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
5998			{
5999				for ( var j=0 ; j<iLen ; j++ )
6000				{
6001					if ( oSettings.aoColumns[i].sName == aColumns[j] )
6002					{
6003						aiReturn.push( j );
6004						break;
6005					}
6006				}
6007			}
6008			
6009			return aiReturn;
6010		}
6011		
6012		/*
6013		 * Function: _fnColumnOrdering
6014		 * Purpose:  Get the column ordering that DataTables expects
6015		 * Returns:  string: - comma separated list of names
6016		 * Inputs:   object:oSettings - dataTables settings object
6017		 */
6018		function _fnColumnOrdering ( oSettings )
6019		{
6020			var sNames = '';
6021			for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
6022			{
6023				sNames += oSettings.aoColumns[i].sName+',';
6024			}
6025			if ( sNames.length == iLen )
6026			{
6027				return "";
6028			}
6029			return sNames.slice(0, -1);
6030		}
6031		
6032		/*
6033		 * Function: _fnLog
6034		 * Purpose:  Log an error message
6035		 * Returns:  -
6036		 * Inputs:   int:iLevel - log error messages, or display them to the user
6037		 *           string:sMesg - error message
6038		 */
6039		function _fnLog( oSettings, iLevel, sMesg )
6040		{
6041			var sAlert = oSettings.sTableId === "" ?
6042			 	"DataTables warning: " +sMesg :
6043			 	"DataTables warning (table id = '"+oSettings.sTableId+"'): " +sMesg;
6044			
6045			if ( iLevel === 0 )
6046			{
6047				if ( _oExt.sErrMode == 'alert' )
6048				{
6049					alert( sAlert );
6050				}
6051				else
6052				{
6053					throw sAlert;
6054				}
6055				return;
6056			}
6057			else if ( typeof console != 'undefined' && typeof console.log != 'undefined' )
6058			{
6059				console.log( sAlert );
6060			}
6061		}
6062		
6063		/*
6064		 * Function: _fnClearTable
6065		 * Purpose:  Nuke the table
6066		 * Returns:  -
6067		 * Inputs:   object:oSettings - dataTables settings object
6068		 */
6069		function _fnClearTable( oSettings )
6070		{
6071			oSettings.aoData.splice( 0, oSettings.aoData.length );
6072			oSettings.aiDisplayMaster.splice( 0, oSettings.aiDisplayMaster.length );
6073			oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length );
6074			_fnCalculateEnd( oSettings );
6075		}
6076		
6077		/*
6078		 * Function: _fnSaveState
6079		 * Purpose:  Save the state of a table in a cookie such that the page can be reloaded
6080		 * Returns:  -
6081		 * Inputs:   object:oSettings - dataTables settings object
6082		 */
6083		function _fnSaveState ( oSettings )
6084		{
6085			if ( !oSettings.oFeatures.bStateSave || typeof oSettings.bDestroying != 'undefined' )
6086			{
6087				return;
6088			}
6089			
6090			/* Store the interesting variables */
6091			var i, iLen, sTmp;
6092			var sValue = "{";
6093			sValue += '"iCreate":'+ new Date().getTime()+',';
6094			sValue += '"iStart":'+ (oSettings.oScroll.bInfinite ? 0 : oSettings._iDisplayStart)+',';
6095			sValue += '"iEnd":'+ (oSettings.oScroll.bInfinite ? oSettings._iDisplayLength : oSettings._iDisplayEnd)+',';
6096			sValue += '"iLength":'+ oSettings._iDisplayLength+',';
6097			sValue += '"sFilter":"'+ encodeURIComponent(oSettings.oPreviousSearch.sSearch)+'",';
6098			sValue += '"sFilterEsc":'+ !oSettings.oPreviousSearch.bRegex+',';
6099			
6100			sValue += '"aaSorting":[ ';
6101			for ( i=0 ; i<oSettings.aaSorting.length ; i++ )
6102			{
6103				sValue += '['+oSettings.aaSorting[i][0]+',"'+oSettings.aaSorting[i][1]+'"],';
6104			}
6105			sValue = sValue.substring(0, sValue.length-1);
6106			sValue += "],";
6107			
6108			sValue += '"aaSearchCols":[ ';
6109			for ( i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
6110			{
6111				sValue += '["'+encodeURIComponent(oSettings.aoPreSearchCols[i].sSearch)+
6112					'",'+!oSettings.aoPreSearchCols[i].bRegex+'],';
6113			}
6114			sValue = sValue.substring(0, sValue.length-1);
6115			sValue += "],";
6116			
6117			sValue += '"abVisCols":[ ';
6118			for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
6119			{
6120				sValue += oSettings.aoColumns[i].bVisible+",";
6121			}
6122			sValue = sValue.substring(0, sValue.length-1);
6123			sValue += "]";
6124			
6125			/* Save state from any plug-ins */
6126			for ( i=0, iLen=oSettings.aoStateSave.length ; i<iLen ; i++ )
6127			{
6128				sTmp = oSettings.aoStateSave[i].fn( oSettings, sValue );
6129				if ( sTmp !== "" )
6130				{
6131					sValue = sTmp;
6132				}
6133			}
6134			
6135			sValue += "}";
6136			
6137			_fnCreateCookie( oSettings.sCookiePrefix+oSettings.sInstance, sValue, 
6138				oSettings.iCookieDuration, oSettings.sCookiePrefix, oSettings.fnCookieCallback );
6139		}
6140		
6141		/*
6142		 * Function: _fnLoadState
6143		 * Purpose:  Attempt to load a saved table state from a cookie
6144		 * Returns:  -
6145		 * Inputs:   object:oSettings - dataTables settings object
6146		 *           object:oInit - DataTables init object so we can override settings
6147		 */
6148		function _fnLoadState ( oSettings, oInit )
6149		{
6150			if ( !oSettings.oFeatures.bStateSave )
6151			{
6152				return;
6153			}
6154			
6155			var oData, i, iLen;
6156			var sData = _fnReadCookie( oSettings.sCookiePrefix+oSettings.sInstance );
6157			if ( sData !== null && sData !== '' )
6158			{
6159				/* Try/catch the JSON eval - if it is bad then we ignore it - note that 1.7.0 and before
6160				 * incorrectly used single quotes for some strings - hence the replace below
6161				 */
6162				try
6163				{
6164					oData = (typeof $.parseJSON == 'function') ? 
6165						$.parseJSON( sData.replace(/'/g, '"') ) : eval( '('+sData+')' );
6166				}
6167				catch( e )
6168				{
6169					return;
6170				}
6171				
6172				/* Allow custom and plug-in manipulation functions to alter the data set which was
6173				 * saved, and also reject any saved state by returning false
6174				 */
6175				for ( i=0, iLen=oSettings.aoStateLoad.length ; i<iLen ; i++ )
6176				{
6177					if ( !oSettings.aoStateLoad[i].fn( oSettings, oData ) )
6178					{
6179						return;
6180					}
6181				}
6182				
6183				/* Store the saved state so it might be accessed at any time (particualrly a plug-in */
6184				oSettings.oLoadedState = $.extend( true, {}, oData );
6185				
6186				/* Restore key features */
6187				oSettings._iDisplayStart = oData.iStart;
6188				oSettings.iInitDisplayStart = oData.iStart;
6189				oSettings._iDisplayEnd = oData.iEnd;
6190				oSettings._iDisplayLength = oData.iLength;
6191				oSettings.oPreviousSearch.sSearch = decodeURIComponent(oData.sFilter);
6192				oSettings.aaSorting = oData.aaSorting.slice();
6193				oSettings.saved_aaSorting = oData.aaSorting.slice();
6194				
6195				/*
6196				 * Search filtering - global reference added in 1.4.1
6197				 * Note that we use a 'not' for the value of the regular expression indicator to maintain
6198				 * compatibility with pre 1.7 versions, where this was basically inverted. Added in 1.7.0
6199				 */
6200				if ( typeof oData.sFilterEsc != 'undefined' )
6201				{
6202					oSettings.oPreviousSearch.bRegex = !oData.sFilterEsc;
6203				}
6204				
6205				/* Column filtering - added in 1.5.0 beta 6 */
6206				if ( typeof oData.aaSearchCols != 'undefined' )
6207				{
6208					for ( i=0 ; i<oData.aaSearchCols.length ; i++ )
6209					{
6210						oSettings.aoPreSearchCols[i] = {
6211							"sSearch": decodeURIComponent(oData.aaSearchCols[i][0]),
6212							"bRegex": !oData.aaSearchCols[i][1]
6213						};
6214					}
6215				}
6216				
6217				/* Column visibility state - added in 1.5.0 beta 10 */
6218				if ( typeof oData.abVisCols != 'undefined' )
6219				{
6220					/* Pass back visibiliy settings to the init handler, but to do not here override
6221					 * the init object that the user might have passed in
6222					 */
6223					oInit.saved_aoColumns = [];
6224					for ( i=0 ; i<oData.abVisCols.length ; i++ )
6225					{
6226						oInit.saved_aoColumns[i] = {};
6227						oInit.saved_aoColumns[i].bVisible = oData.abVisCols[i];
6228					}
6229				}
6230			}
6231		}
6232		
6233		/*
6234		 * Function: _fnCreateCookie
6235		 * Purpose:  Create a new cookie with a value to store the state of a table
6236		 * Returns:  -
6237		 * Inputs:   string:sName - name of the cookie to create
6238		 *           string:sValue - the value the cookie should take
6239		 *           int:iSecs - duration of the cookie
6240		 *           string:sBaseName - sName is made up of the base + file name - this is the base
6241		 *           function:fnCallback - User definable function to modify the cookie
6242		 */
6243		function _fnCreateCookie ( sName, sValue, iSecs, sBaseName, fnCallback )
6244		{
6245			var date = new Date();
6246			date.setTime( date.getTime()+(iSecs*1000) );
6247			
6248			/* 
6249			 * Shocking but true - it would appear IE has major issues with having the path not having
6250			 * a trailing slash on it. We need the cookie to be available based on the path, so we
6251			 * have to append the file name to the cookie name. Appalling. Thanks to vex for adding the
6252			 * patch to use at least some of the path
6253			 */
6254			var aParts = window.location.pathname.split('/');
6255			var sNameFile = sName + '_' + aParts.pop().replace(/[\/:]/g,"").toLowerCase();
6256			var sFullCookie, oData;
6257			
6258			if ( fnCallback !== null )
6259			{
6260				oData = (typeof $.parseJSON == 'function') ? 
6261					$.parseJSON( sValue ) : eval( '('+sValue+')' );
6262				sFullCookie = fnCallback( sNameFile, oData, date.toGMTString(),
6263					aParts.join('/')+"/" );
6264			}
6265			else
6266			{
6267				sFullCookie = sNameFile + "=" + encodeURIComponent(sValue) +
6268					"; expires=" + date.toGMTString() +"; path=" + aParts.join('/')+"/";
6269			}
6270			
6271			/* Are we going to go over the cookie limit of 4KiB? If so, try to delete a cookies
6272			 * belonging to DataTables. This is FAR from bullet proof
6273			 */
6274			var sOldName="", iOldTime=9999999999999;
6275			var iLength = _fnReadCookie( sNameFile )!==null ? document.cookie.length : 
6276				sFullCookie.length + document.cookie.length;
6277			
6278			if ( iLength+10 > 4096 ) /* Magic 10 for padding */
6279			{
6280				var aCookies =document.cookie.split(';');
6281				for ( var i=0, iLen=aCookies.length ; i<iLen ; i++ )
6282				{
6283					if ( aCookies[i].indexOf( sBaseName ) != -1 )
6284					{
6285						/* It's a DataTables cookie, so eval it and check the time stamp */
6286						var aSplitCookie = aCookies[i].split('=');
6287						try { oData = eval( '('+decodeURIComponent(aSplitCookie[1])+')' ); }
6288						catch( e ) { continue; }
6289						
6290						if ( typeof oData.iCreate != 'undefined' && oData.iCreate < iOldTime )
6291						{
6292							sOldName = aSplitCookie[0];
6293							iOldTime = oData.iCreate;
6294						}
6295					}
6296				}
6297				
6298				if ( sOldName !== "" )
6299				{
6300					document.cookie = sOldName+"=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path="+
6301						aParts.join('/') + "/";
6302				}
6303			}
6304			
6305			document.cookie = sFullCookie;
6306		}
6307		
6308		/*
6309		 * Function: _fnReadCookie
6310		 * Purpose:  Read an old cookie to get a cookie with an old table state
6311		 * Returns:  string: - contents of the cookie - or null if no cookie with that name found
6312		 * Inputs:   string:sName - name of the cookie to read
6313		 */
6314		function _fnReadCookie ( sName )
6315		{
6316			var
6317				aParts = window.location.pathname.split('/'),
6318				sNameEQ = sName + '_' + aParts[aParts.length-1].replace(/[\/:]/g,"").toLowerCase() + '=',
6319			 	sCookieContents = document.cookie.split(';');
6320			
6321			for( var i=0 ; i<sCookieContents.length ; i++ )
6322			{
6323				var c = sCookieContents[i];
6324				
6325				while (c.charAt(0)==' ')
6326				{
6327					c = c.substring(1,c.length);
6328				}
6329				
6330				if (c.indexOf(sNameEQ) === 0)
6331				{
6332					return decodeURIComponent( c.substring(sNameEQ.length,c.length) );
6333				}
6334			}
6335			return null;
6336		}
6337		
6338		/*
6339		 * Function: _fnDetectHeader
6340		 * Purpose:  Use the DOM source to create up an array of header cells. The idea here is to
6341		 *           create a layout grid (array) of rows x columns, which contains a reference
6342		 *           to the cell that that point in the grid (regardless of col/rowspan), such that
6343		 *           any column / row could be removed and the new grid constructed
6344		 * Returns:  void
6345		 * Outputs:  array object:aLayout - Array to store the calculated layout in
6346		 * Inputs:   node:nThead - The header/footer element for the table
6347		 */
6348		function _fnDetectHeader ( aLayout, nThead )
6349		{
6350			var nTrs = nThead.getElementsByTagName('tr');
6351			var nCell;
6352			var i, j, k, l, iLen, jLen, iColShifted;
6353			var fnShiftCol = function ( a, i, j ) {
6354				while ( typeof a[i][j] != 'undefined' ) {
6355					j++;
6356				}
6357				return j;
6358			};
6359
6360			aLayout.splice( 0, aLayout.length );
6361			
6362			/* We know how many rows there are in the layout - so prep it */
6363			for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
6364			{
6365				aLayout.push( [] );
6366			}
6367			
6368			/* Calculate a layout array */
6369			for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
6370			{
6371				var iColumn = 0;
6372				
6373				/* For every cell in the row... */
6374				for ( j=0, jLen=nTrs[i].childNodes.length ; j<jLen ; j++ )
6375				{
6376					nCell = nTrs[i].childNodes[j];
6377
6378					if ( nCell.nodeName.toUpperCase() == "TD" ||
6379					     nCell.nodeName.toUpperCase() == "TH" )
6380					{
6381						/* Get the col and rowspan attributes from the DOM and sanitise them */
6382						var iColspan = nCell.getAttribute('colspan') * 1;
6383						var iRowspan = nCell.getAttribute('rowspan') * 1;
6384						iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan;
6385						iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan;
6386
6387						/* There might be colspan cells already in this row, so shift our target 
6388						 * accordingly
6389						 */
6390						iColShifted = fnShiftCol( aLayout, i, iColumn );
6391						
6392						/* If there is col / rowspan, copy the information into the layout grid */
6393						for ( l=0 ; l<iColspan ; l++ )
6394						{
6395							for ( k=0 ; k<iRowspan ; k++ )
6396							{
6397								aLayout[i+k][iColShifted+l] = {
6398									"cell": nCell,
6399									"unique": iColspan == 1 ? true : false
6400								};
6401								aLayout[i+k].nTr = nTrs[i];
6402							}
6403						}
6404					}
6405				}
6406			}
6407		}
6408		
6409		/*
6410		 * Function: _fnGetUniqueThs
6411		 * Purpose:  Get an array of unique th elements, one for each column
6412		 * Returns:  array node:aReturn - list of unique ths
6413		 * Inputs:   object:oSettings - dataTables settings object
6414		 *           node:nHeader - automatically detect the layout from this node - optional
6415		 *           array object:aLayout - thead/tfoot layout from _fnDetectHeader - optional
6416		 */
6417		function _fnGetUniqueThs ( oSettings, nHeader, aLayout )
6418		{
6419			var aReturn = [];
6420			if ( typeof aLayout == 'undefined' )
6421			{
6422				aLayout = oSettings.aoHeader;
6423				if ( typeof nHeader != 'undefined' )
6424				{
6425					aLayout = [];
6426					_fnDetectHeader( aLayout, nHeader );
6427				}
6428			}
6429
6430			for ( var i=0, iLen=aLayout.length ; i<iLen ; i++ )
6431			{
6432				for ( var j=0, jLen=aLayout[i].length ; j<jLen ; j++ )
6433				{
6434					if ( aLayout[i][j].unique && 
6435						 (typeof aReturn[j] == 'undefined' || !oSettings.bSortCellsTop) )
6436					{
6437						aReturn[j] = aLayout[i][j].cell;
6438					}
6439				}
6440			}
6441			
6442			return aReturn;
6443		}
6444		
6445		/*
6446		 * Function: _fnScrollBarWidth
6447		 * Purpose:  Get the width of a scroll bar in this browser being used
6448		 * Returns:  int: - width in pixels
6449		 * Inputs:   -
6450		 * Notes:    All credit for this function belongs to Alexandre Gomes. Thanks for sharing!
6451		 *   http://www.alexandre-gomes.com/?p=115
6452		 */
6453		function _fnScrollBarWidth ()
6454		{  
6455			var inner = document.createElement('p');  
6456			var style = inner.style;
6457			style.width = "100%";  
6458			style.height = "200px";  
6459			
6460			var outer = document.createElement('div');  
6461			style = outer.style;
6462			style.position = "absolute";  
6463			style.top = "0px";  
6464			style.left = "0px";  
6465			style.visibility = "hidden";  
6466			style.width = "200px";  
6467			style.height = "150px";  
6468			style.overflow = "hidden";  
6469			outer.appendChild(inner);  
6470			
6471			document.body.appendChild(outer);  
6472			var w1 = inner.offsetWidth;  
6473			outer.style.overflow = 'scroll';  
6474			var w2 = inner.offsetWidth;  
6475			if ( w1 == w2 )
6476			{
6477				w2 = outer.clientWidth;  
6478			}
6479			
6480			document.body.removeChild(outer); 
6481			return (w1 - w2);  
6482		}
6483		
6484		/*
6485		 * Function: _fnApplyToChildren
6486		 * Purpose:  Apply a given function to the display child nodes of an element array (typically
6487		 *   TD children of TR rows
6488		 * Returns:  - (done by reference)
6489		 * Inputs:   function:fn - Method to apply to the objects
6490		 *           array nodes:an1 - List of elements to look through for display children
6491		 *           array nodes:an2 - Another list (identical structure to the first) - optional
6492		 */
6493		function _fnApplyToChildren( fn, an1, an2 )
6494		{
6495			for ( var i=0, iLen=an1.length ; i<iLen ; i++ )
6496			{
6497				for ( var j=0, jLen=an1[i].childNodes.length ; j<jLen ; j++ )
6498				{
6499					if ( an1[i].childNodes[j].nodeType == 1 )
6500					{
6501						if ( typeof an2 != 'undefined' )
6502						{
6503							fn( an1[i].childNodes[j], an2[i].childNodes[j] );
6504						}
6505						else
6506						{
6507							fn( an1[i].childNodes[j] );
6508						}
6509					}
6510				}
6511			}
6512		}
6513		
6514		/*
6515		 * Function: _fnMap
6516		 * Purpose:  See if a property is defined on one object, if so assign it to the other object
6517		 * Returns:  - (done by reference)
6518		 * Inputs:   object:oRet - target object
6519		 *           object:oSrc - source object
6520		 *           string:sName - property
6521		 *           string:sMappedName - name to map too - optional, sName used if not given
6522		 */
6523		function _fnMap( oRet, oSrc, sName, sMappedName )
6524		{
6525			if ( typeof sMappedName == 'undefined' )
6526			{
6527				sMappedName = sName;
6528			}
6529			if ( typeof oSrc[sName] != 'undefined' )
6530			{
6531				oRet[sMappedName] = oSrc[sName];
6532			}
6533		}
6534		
6535		/*
6536		 * Function: _fnGetRowData
6537		 * Purpose:  Get an array of data for a given row from the internal data cache
6538		 * Returns:  array: - Data array
6539		 * Inputs:   object:oSettings - dataTables settings object
6540		 *           int:iRow - aoData row id
6541		 *           string:sSpecific - data get type ('type' 'filter' 'sort')
6542		 */
6543		function _fnGetRowData( oSettings, iRow, sSpecific )
6544		{
6545			var out = [];
6546			for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
6547			{
6548				out.push( _fnGetCellData( oSettings, iRow, i, sSpecific ) );
6549			}
6550			return out;
6551		}
6552		
6553		/*
6554		 * Function: _fnGetCellData
6555		 * Purpose:  Get the data for a given cell from the internal cache, taking into account data mapping
6556		 * Returns:  *: - Cell data
6557		 * Inputs:   object:oSettings - dataTables settings object
6558		 *           int:iRow - aoData row id
6559		 *           int:iCol - Column index
6560		 *           string:sSpecific - data get type ('display', 'type' 'filter' 'sort')
6561		 */
6562		function _fnGetCellData( oSettings, iRow, iCol, sSpecific )
6563		{
6564			var sData;
6565			var oCol = oSettings.aoColumns[iCol];
6566			var oData = oSettings.aoData[iRow]._aData;
6567
6568			if ( (sData=oCol.fnGetData( oData )) === undefined )
6569			{
6570				if ( oSettings.iDrawError != oSettings.iDraw && oCol.sDefaultContent === null )
6571				{
6572					_fnLog( oSettings, 0, "Requested unknown parameter '"+oCol.mDataProp+
6573						"' from the data source for row "+iRow );
6574					oSettings.iDrawError = oSettings.iDraw;
6575				}
6576				return oCol.sDefaultContent;
6577			}
6578
6579			/* When the data source is null, we can use default column data */
6580			if ( sData === null && oCol.sDefaultContent !== null )
6581			{
6582				sData = oCol.sDefaultContent;
6583			}
6584
6585			if ( sSpecific == 'display' && sData === null )
6586			{
6587				return '';
6588			}
6589			return sData;
6590		}
6591		
6592		/*
6593		 * Function: _fnSetCellData
6594		 * Purpose:  Set the value for a specific cell, into the internal data cache
6595		 * Returns:  *: - Cell data
6596		 * Inputs:   object:oSettings - dataTables settings object
6597		 *           int:iRow - aoData row id
6598		 *           int:iCol - Column index
6599		 *           *:val - Value to set
6600		 */
6601		function _fnSetCellData( oSettings, iRow, iCol, val )
6602		{
6603			var oCol = oSettings.aoColumns[iCol];
6604			var oData = oSettings.aoData[iRow]._aData;
6605
6606			oCol.fnSetData( oData, val );
6607		}
6608		
6609		/*
6610		 * Function: _fnGetObjectDataFn
6611		 * Purpose:  Return a function that can be used to get data from a source object, taking
6612		 *           into account the ability to use nested objects as a source
6613		 * Returns:  function: - Data get function
6614		 * Inputs:   string|int|function:mSource - The data source for the object
6615		 */
6616		function _fnGetObjectDataFn( mSource )
6617		{
6618			if ( mSource === null )
6619			{
6620				/* Give an empty string for rendering / sorting etc */
6621				return function (data) {
6622					return null;
6623				};
6624			}
6625			else if ( typeof mSource == 'function' )
6626			{
6627			    return function (data) {
6628			        return mSource( data );
6629			    };
6630			}
6631			else if ( typeof mSource == 'string' && mSource.indexOf('.') != -1 )
6632			{
6633				/* If there is a . in the source string then the data source is in a nested object
6634				 * we provide two 'quick' functions for the look up to speed up the most common
6635				 * operation, and a generalised one for when it is needed
6636				 */
6637				var a = mSource.split('.');
6638				if ( a.length == 2 )
6639				{
6640					return function (data) {
6641						return data[ a[0] ][ a[1] ];
6642					};
6643				}
6644				else if ( a.length == 3 )
6645				{
6646					return function (data) {
6647						return data[ a[0] ][ a[1] ][ a[2] ];
6648					};
6649				}
6650				else
6651				{
6652					return function (data) {
6653						for ( var i=0, iLen=a.length ; i<iLen ; i++ )
6654						{
6655							data = data[ a[i] ];
6656						}
6657						return data;
6658					};
6659				}
6660			}
6661			else
6662			{
6663				/* Array or flat object mapping */
6664				return function (data) {
6665					return data[mSource];	
6666				};
6667			}
6668		}
6669		
6670		/*
6671		 * Function: _fnSetObjectDataFn
6672		 * Purpose:  Return a function that can be used to set data from a source object, taking
6673		 *           into account the ability to use nested objects as a source
6674		 * Returns:  function: - Data set function
6675		 * Inputs:   string|int|function:mSource - The data source for the object
6676		 */
6677		function _fnSetObjectDataFn( mSource )
6678		{
6679			if ( mSource === null )
6680			{
6681				/* Nothing to do when the data source is null */
6682				return function (data, val) {};
6683			}
6684			else if ( typeof mSource == 'function' )
6685			{
6686			    return function (data, val) {
6687			        return mSource( data, val );
6688			    };
6689			}
6690			else if ( typeof mSource == 'string' && mSource.indexOf('.') != -1 )
6691			{
6692				/* Like the get, we need to get data from a nested object. Again two fast lookup
6693				 * functions are provided, and a generalised one.
6694				 */
6695				var a = mSource.split('.');
6696				if ( a.length == 2 )
6697				{
6698					return function (data, val) {
6699						data[ a[0] ][ a[1] ] = val;
6700					};
6701				}
6702				else if ( a.length == 3 )
6703				{
6704					return function (data, val) {
6705						data[ a[0] ][ a[1] ][ a[2] ] = val;
6706					};
6707				}
6708				else
6709				{
6710					return function (data, val) {
6711						for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ )
6712						{
6713							data = data[ a[i] ];
6714						}
6715						data[ a[a.length-1] ] = val;
6716					};
6717				}
6718			}
6719			else
6720			{
6721				/* Array or flat object mapping */
6722				return function (data, val) {
6723					data[mSource] = val;	
6724				};
6725			}
6726		}
6727
6728		
6729		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6730		 * Section - API
6731		 * I'm not happy with this solution... - To be fixed in 2.0
6732		 */
6733		this.oApi._fnExternApiFunc = _fnExternApiFunc;
6734		this.oApi._fnInitalise = _fnInitalise;
6735		this.oApi._fnInitComplete = _fnInitComplete;
6736		this.oApi._fnLanguageProcess = _fnLanguageProcess;
6737		this.oApi._fnAddColumn = _fnAddColumn;
6738		this.oApi._fnColumnOptions = _fnColumnOptions;
6739		this.oApi._fnAddData = _fnAddData;
6740		this.oApi._fnCreateTr = _fnCreateTr;
6741		this.oApi._fnGatherData = _fnGatherData;
6742		this.oApi._fnBuildHead = _fnBuildHead;
6743		this.oApi._fnDrawHead = _fnDrawHead;
6744		this.oApi._fnDraw = _fnDraw;
6745		this.oApi._fnReDraw = _fnReDraw;
6746		this.oApi._fnAjaxUpdate = _fnAjaxUpdate;
6747		this.oApi._fnAjaxUpdateDraw = _fnAjaxUpdateDraw;
6748		this.oApi._fnAddOptionsHtml = _fnAddOptionsHtml;
6749		this.oApi._fnFeatureHtmlTable = _fnFeatureHtmlTable;
6750		this.oApi._fnScrollDraw = _fnScrollDraw;
6751		this.oApi._fnAjustColumnSizing = _fnAjustColumnSizing;
6752		this.oApi._fnFeatureHtmlFilter = _fnFeatureHtmlFilter;
6753		this.oApi._fnFilterComplete = _fnFilterComplete;
6754		this.oApi._fnFilterCustom = _fnFilterCustom;
6755		this.oApi._fnFilterColumn = _fnFilterColumn;
6756		this.oApi._fnFilter = _fnFilter;
6757		this.oApi._fnBuildSearchArray = _fnBuildSearchArray;
6758		this.oApi._fnBuildSearchRow = _fnBuildSearchRow;
6759		this.oApi._fnFilterCreateSearch = _fnFilterCreateSearch;
6760		this.oApi._fnDataToSearch = _fnDataToSearch;
6761		this.oApi._fnSort = _fnSort;
6762		this.oApi._fnSortAttachListener = _fnSortAttachListener;
6763		this.oApi._fnSortingClasses = _fnSortingClasses;
6764		this.oApi._fnFeatureHtmlPaginate = _fnFeatureHtmlPaginate;
6765		this.oApi._fnPageChange = _fnPageChange;
6766		this.oApi._fnFeatureHtmlInfo = _fnFeatureHtmlInfo;
6767		this.oApi._fnUpdateInfo = _fnUpdateInfo;
6768		this.oApi._fnFeatureHtmlLength = _fnFeatureHtmlLength;
6769		this.oApi._fnFeatureHtmlProcessing = _fnFeatureHtmlProcessing;
6770		this.oApi._fnProcessingDisplay = _fnProcessingDisplay;
6771		this.oApi._fnVisibleToColumnIndex = _fnVisibleToColumnIndex;
6772		this.oApi._fnColumnIndexToVisible = _fnColumnIndexToVisible;
6773		this.oApi._fnNodeToDataIndex = _fnNodeToDataIndex;
6774		this.oApi._fnVisbleColumns = _fnVisbleColumns;
6775		this.oApi._fnCalculateEnd = _fnCalculateEnd;
6776		this.oApi._fnConvertToWidth = _fnConvertToWidth;
6777		this.oApi._fnCalculateColumnWidths = _fnCalculateColumnWidths;
6778		this.oApi._fnScrollingWidthAdjust = _fnScrollingWidthAdjust;
6779		this.oApi._fnGetWidestNode = _fnGetWidestNode;
6780		this.oApi._fnGetMaxLenString = _fnGetMaxLenString;
6781		this.oApi._fnStringToCss = _fnStringToCss;
6782		this.oApi._fnArrayCmp = _fnArrayCmp;
6783		this.oApi._fnDetectType = _fnDetectType;
6784		this.oApi._fnSettingsFromNode = _fnSettingsFromNode;
6785		this.oApi._fnGetDataMaster = _fnGetDataMaster;
6786		this.oApi._fnGetTrNodes = _fnGetTrNodes;
6787		this.oApi._fnGetTdNodes = _fnGetTdNodes;
6788		this.oApi._fnEscapeRegex = _fnEscapeRegex;
6789		this.oApi._fnDeleteIndex = _fnDeleteIndex;
6790		this.oApi._fnReOrderIndex = _fnReOrderIndex;
6791		this.oApi._fnColumnOrdering = _fnColumnOrdering;
6792		this.oApi._fnLog = _fnLog;
6793		this.oApi._fnClearTable = _fnClearTable;
6794		this.oApi._fnSaveState = _fnSaveState;
6795		this.oApi._fnLoadState = _fnLoadState;
6796		this.oApi._fnCreateCookie = _fnCreateCookie;
6797		this.oApi._fnReadCookie = _fnReadCookie;
6798		this.oApi._fnDetectHeader = _fnDetectHeader;
6799		this.oApi._fnGetUniqueThs = _fnGetUniqueThs;
6800		this.oApi._fnScrollBarWidth = _fnScrollBarWidth;
6801		this.oApi._fnApplyToChildren = _fnApplyToChildren;
6802		this.oApi._fnMap = _fnMap;
6803		this.oApi._fnGetRowData = _fnGetRowData;
6804		this.oApi._fnGetCellData = _fnGetCellData;
6805		this.oApi._fnSetCellData = _fnSetCellData;
6806		this.oApi._fnGetObjectDataFn = _fnGetObjectDataFn;
6807		this.oApi._fnSetObjectDataFn = _fnSetObjectDataFn;
6808		
6809		
6810		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6811		 * Section - Constructor
6812		 */
6813		
6814		/* Want to be able to reference "this" inside the this.each function */
6815		var _that = this;
6816		return this.each(function()
6817		{
6818			var i=0, iLen, j, jLen, k, kLen;
6819			
6820			/* Check to see if we are re-initalising a table */
6821			for ( i=0, iLen=_aoSettings.length ; i<iLen ; i++ )
6822			{
6823				/* Base check on table node */
6824				if ( _aoSettings[i].nTable == this )
6825				{
6826					if ( typeof oInit == 'undefined' || 
6827					   ( typeof oInit.bRetrieve != 'undefined' && oInit.bRetrieve === true ) )
6828					{
6829						return _aoSettings[i].oInstance;
6830					}
6831					else if ( typeof oInit.bDestroy != 'undefined' && oInit.bDestroy === true )
6832					{
6833						_aoSettings[i].oInstance.fnDestroy();
6834						break;
6835					}
6836					else
6837					{
6838						_fnLog( _aoSettings[i], 0, "Cannot reinitialise DataTable.\n\n"+
6839							"To retrieve the DataTables object for this table, please pass either no arguments "+
6840							"to the dataTable() function, or set bRetrieve to true. Alternatively, to destory "+
6841							"the old table and create a new one, set bDestroy to true (note that a lot of "+
6842							"changes to the configuration can be made through the API which is usually much "+
6843							"faster)." );
6844						return;
6845					}
6846				}
6847				
6848				/* If the element we are initialising has the same ID as a table which was previously
6849				 * initialised, but the table nodes don't match (from before) then we destory the old
6850				 * instance by simply deleting it. This is under the assumption that the table has been
6851				 * destroyed by other methods. Anyone using non-id selectors will need to do this manually
6852				 */
6853				if ( _aoSettings[i].sTableId !== "" && _aoSettings[i].sTableId == this.getAttribute('id') )
6854				{
6855					_aoSettings.splice( i, 1 );
6856					break;
6857				}
6858			}
6859			
6860			/* Make a complete and independent copy of the settings object */
6861			var oSettings = new classSettings();
6862			_aoSettings.push( oSettings );
6863			
6864			var bInitHandedOff = false;
6865			var bUsePassedData = false;
6866			
6867			/* Set the id */
6868			var sId = this.getAttribute( 'id' );
6869			if ( sId !== null )
6870			{
6871				oSettings.sTableId = sId;
6872				oSettings.sInstance = sId;
6873			}
6874			else
6875			{
6876				oSettings.sInstance = _oExt._oExternConfig.iNextUnique ++;
6877			}
6878			
6879			/* Sanity check */
6880			if ( this.nodeName.toLowerCase() != 'table' )
6881			{
6882				_fnLog( oSettings, 0, "Attempted to initialise DataTables on a node which is not a "+
6883					"table: "+this.nodeName );
6884				return;
6885			}
6886			
6887			/* Set the table node */
6888			oSettings.nTable = this;
6889			
6890			/* Keep a reference to the 'this' instance for the table. Note that if this table is being
6891			 * created with others, we retrieve a unique instance to ease API access.
6892			 */
6893			oSettings.oInstance = _that.length == 1 ? _that : $(this).dataTable();
6894			
6895			/* Bind the API functions to the settings, so we can perform actions whenever oSettings is
6896			 * available
6897			 */
6898			oSettings.oApi = _that.oApi;
6899			
6900			/* State the table's width for if a destroy is called at a later time */
6901			oSettings.sDestroyWidth = $(this).width();
6902			
6903			/* Store the features that we have available */
6904			if ( typeof oInit != 'undefined' && oInit !== null )
6905			{
6906				oSettings.oInit = oInit;
6907				_fnMap( oSettings.oFeatures, oInit, "bPaginate" );
6908				_fnMap( oSettings.oFeatures, oInit, "bLengthChange" );
6909				_fnMap( oSettings.oFeatures, oInit, "bFilter" );
6910				_fnMap( oSettings.oFeatures, oInit, "bSort" );
6911				_fnMap( oSettings.oFeatures, oInit, "bInfo" );
6912				_fnMap( oSettings.oFeatures, oInit, "bProcessing" );
6913				_fnMap( oSettings.oFeatures, oInit, "bAutoWidth" );
6914				_fnMap( oSettings.oFeatures, oInit, "bSortClasses" );
6915				_fnMap( oSettings.oFeatures, oInit, "bServerSide" );
6916				_fnMap( oSettings.oFeatures, oInit, "bDeferRender" );
6917				_fnMap( oSettings.oScroll, oInit, "sScrollX", "sX" );
6918				_fnMap( oSettings.oScroll, oInit, "sScrollXInner", "sXInner" );
6919				_fnMap( oSettings.oScroll, oInit, "sScrollY", "sY" );
6920				_fnMap( oSettings.oScroll, oInit, "bScrollCollapse", "bCollapse" );
6921				_fnMap( oSettings.oScroll, oInit, "bScrollInfinite", "bInfinite" );
6922				_fnMap( oSettings.oScroll, oInit, "iScrollLoadGap", "iLoadGap" );
6923				_fnMap( oSettings.oScroll, oInit, "bScrollAutoCss", "bAutoCss" );
6924				_fnMap( oSettings, oInit, "asStripClasses" );
6925				_fnMap( oSettings, oInit, "fnPreDrawCallback" );
6926				_fnMap( oSettings, oInit, "fnRowCallback" );
6927				_fnMap( oSettings, oInit, "fnHeaderCallback" );
6928				_fnMap( oSettings, oInit, "fnFooterCallback" );
6929				_fnMap( oSettings, oInit, "fnCookieCallback" );
6930				_fnMap( oSettings, oInit, "fnInitComplete" );
6931				_fnMap( oSettings, oInit, "fnServerData" );
6932				_fnMap( oSettings, oInit, "fnFormatNumber" );
6933				_fnMap( oSettings, oInit, "aaSorting" );
6934				_fnMap( oSettings, oInit, "aaSortingFixed" );
6935				_fnMap( oSettings, oInit, "aLengthMenu" );
6936				_fnMap( oSettings, oInit, "sPaginationType" );
6937				_fnMap( oSettings, oInit, "sAjaxSource" );
6938				_fnMap( oSettings, oInit, "sAjaxDataProp" );
6939				_fnMap( oSettings, oInit, "iCookieDuration" );
6940				_fnMap( oSettings, oInit, "sCookiePrefix" );
6941				_fnMap( oSettings, oInit, "sDom" );
6942				_fnMap( oSettings, oInit, "bSortCellsTop" );
6943				_fnMap( oSettings, oInit, "oSearch", "oPreviousSearch" );
6944				_fnMap( oSettings, oInit, "aoSearchCols", "aoPreSearchCols" );
6945				_fnMap( oSettings, oInit, "iDisplayLength", "_iDisplayLength" );
6946				_fnMap( oSettings, oInit, "bJQueryUI", "bJUI" );
6947				_fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
6948				
6949				/* Callback functions which are array driven */
6950				if ( typeof oInit.fnDrawCallback == 'function' )
6951				{
6952					oSettings.aoDrawCallback.push( {
6953						"fn": oInit.fnDrawCallback,
6954						"sName": "user"
6955					} );
6956				}
6957				
6958				if ( typeof oInit.fnStateSaveCallback == 'function' )
6959				{
6960					oSettings.aoStateSave.push( {
6961						"fn": oInit.fnStateSaveCallback,
6962						"sName": "user"
6963					} );
6964				}
6965				
6966				if ( typeof oInit.fnStateLoadCallback == 'function' )
6967				{
6968					oSettings.aoStateLoad.push( {
6969						"fn": oInit.fnStateLoadCallback,
6970						"sName": "user"
6971					} );
6972				}
6973				
6974				if ( oSettings.oFeatures.bServerSide && oSettings.oFeatures.bSort &&
6975					   oSettings.oFeatures.bSortClasses )
6976				{
6977					/* Enable sort classes for server-side processing. Safe to do it here, since server-side
6978					 * processing must be enabled by the developer
6979					 */
6980					oSettings.aoDrawCallback.push( {
6981						"fn": _fnSortingClasses,
6982						"sName": "server_side_sort_classes"
6983					} );
6984				}
6985				else if ( oSettings.oFeatures.bDeferRender )
6986				{
6987					oSettings.aoDrawCallback.push( {
6988						"fn": _fnSortingClasses,
6989						"sName": "defer_sort_classes"
6990					} );
6991				}
6992				
6993				if ( typeof oInit.bJQueryUI != 'undefined' && oInit.bJQueryUI )
6994				{
6995					/* Use the JUI classes object for display. You could clone the oStdClasses object if 
6996					 * you want to have multiple tables with multiple independent classes 
6997					 */
6998					oSettings.oClasses = _oExt.oJUIClasses;
6999					
7000					if ( typeof oInit.sDom == 'undefined' )
7001					{
7002						/* Set the DOM to use a layout suitable for jQuery UI's theming */
7003						oSettings.sDom = '<"H"lfr>t<"F"ip>';
7004					}
7005				}
7006				
7007				/* Calculate the scroll bar width and cache it for use later on */
7008				if ( oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "" )
7009				{
7010					oSettings.oScroll.iBarWidth = _fnScrollBarWidth();
7011				}
7012				
7013				if ( typeof oInit.iDisplayStart != 'undefined' && 
7014				     typeof oSettings.iInitDisplayStart == 'undefined' )
7015				{
7016					/* Display start point, taking into account the save saving */
7017					oSettings.iInitDisplayStart = oInit.iDisplayStart;
7018					oSettings._iDisplayStart = oInit.iDisplayStart;
7019				}
7020				
7021				/* Must be done after everything which can be overridden by a cookie! */
7022				if ( typeof oInit.bStateSave != 'undefined' )
7023				{
7024					oSettings.oFeatures.bStateSave = oInit.bStateSave;
7025					_fnLoadState( oSettings, oInit );
7026					oSettings.aoDrawCallback.push( {
7027						"fn": _fnSaveState,
7028						"sName": "state_save"
7029					} );
7030				}
7031
7032				if ( typeof oInit.iDeferLoading != 'undefined' )
7033				{
7034					oSettings.bDeferLoading = true;
7035					oSettings._iRecordsTotal = oInit.iDeferLoading;
7036					oSettings._iRecordsDisplay = oInit.iDeferLoading;
7037				}
7038				
7039				if ( typeof oInit.aaData != 'undefined' )
7040				{
7041					bUsePassedData = true;
7042				}
7043				
7044				/* Backwards compatability */
7045				/* aoColumns / aoData - remove at some point... */
7046				if ( typeof oInit != 'undefined' && typeof oInit.aoData != 'undefined' )
7047				{
7048					oInit.aoColumns = oInit.aoData;
7049				}
7050				
7051				/* Language definitions */
7052				if ( typeof oInit.oLanguage != 'undefined' )
7053				{
7054					if ( typeof oInit.oLanguage.sUrl != 'undefined' && oInit.oLanguage.sUrl !== "" )
7055					{
7056						/* Get the language definitions from a file */
7057						oSettings.oLanguage.sUrl = oInit.oLanguage.sUrl;
7058						$.getJSON( oSettings.oLanguage.sUrl, null, function( json ) { 
7059							_fnLanguageProcess( oSettings, json, true ); } );
7060						bInitHandedOff = true;
7061					}
7062					else
7063					{
7064						_fnLanguageProcess( oSettings, oInit.oLanguage, false );
7065					}
7066				}
7067				/* Warning: The _fnLanguageProcess function is async to the remainder of this function due
7068				 * to the XHR. We use _bInitialised in _fnLanguageProcess() to check this the processing 
7069				 * below is complete. The reason for spliting it like this is optimisation - we can fire
7070				 * off the XHR (if needed) and then continue processing the data.
7071				 */
7072			}
7073			else
7074			{
7075				/* Create a dummy object for quick manipulation later on. */
7076				oInit = {};
7077			}
7078			
7079			/*
7080			 * Stripes
7081			 * Add the strip classes now that we know which classes to apply - unless overruled
7082			 */
7083			if ( typeof oInit.asStripClasses == 'undefined' )
7084			{
7085				oSettings.asStripClasses.push( oSettings.oClasses.sStripOdd );
7086				oSettings.asStripClasses.push( oSettings.oClasses.sStripEven );
7087			}
7088			
7089			/* Remove row stripe classes if they are already on the table row */
7090			var bStripeRemove = false;
7091			var anRows = $('>tbody>tr', this);
7092			for ( i=0, iLen=oSettings.asStripClasses.length ; i<iLen ; i++ )
7093			{
7094				if ( anRows.filter(":lt(2)").hasClass( oSettings.asStripClasses[i]) )
7095				{
7096					bStripeRemove = true;
7097					break;
7098				}
7099			}
7100					
7101			if ( bStripeRemove )
7102			{
7103				/* Store the classes which we are about to remove so they can be readded on destory */
7104				oSettings.asDestoryStrips = [ '', '' ];
7105				if ( $(anRows[0]).hasClass(oSettings.oClasses.sStripOdd) )
7106				{
7107					oSettings.asDestoryStrips[0] += oSettings.oClasses.sStripOdd+" ";
7108				}
7109				if ( $(anRows[0]).hasClass(oSettings.oClasses.sStripEven) )
7110				{
7111					oSettings.asDestoryStrips[0] += oSettings.oClasses.sStripEven;
7112				}
7113				if ( $(anRows[1]).hasClass(oSettings.oClasses.sStripOdd) )
7114				{
7115					oSettings.asDestoryStrips[1] += oSettings.oClasses.sStripOdd+" ";
7116				}
7117				if ( $(anRows[1]).hasClass(oSettings.oClasses.sStripEven) )
7118				{
7119					oSettings.asDestoryStrips[1] += oSettings.oClasses.sStripEven;
7120				}
7121				
7122				anRows.removeClass( oSettings.asStripClasses.join(' ') );
7123			}
7124			
7125			/*
7126			 * Columns
7127			 * See if we should load columns automatically or use defined ones
7128			 */
7129			var anThs = [];
7130			var aoColumnsInit;
7131			var nThead = this.getElementsByTagName('thead');
7132			if ( nThead.length !== 0 )
7133			{
7134				_fnDetectHeader( oSettings.aoHeader, nThead[0] );
7135				anThs = _fnGetUniqueThs( oSettings );
7136			}
7137			
7138			/* If not given a column array, generate one with nulls */
7139			if ( typeof oInit.aoColumns == 'undefined' )
7140			{
7141				aoColumnsInit = [];
7142				for ( i=0, iLen=anThs.length ; i<iLen ; i++ )
7143				{
7144					aoColumnsInit.push( null );
7145				}
7146			}
7147			else
7148			{
7149				aoColumnsInit = oInit.aoColumns;
7150			}
7151			
7152			/* Add the columns */
7153			for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ )
7154			{
7155				/* Check if we have column visibilty state to restore */
7156				if ( typeof oInit.saved_aoColumns != 'undefined' && oInit.saved_aoColumns.length == iLen )
7157				{
7158					if ( aoColumnsInit[i] === null )
7159					{
7160						aoColumnsInit[i] = {};
7161					}
7162					aoColumnsInit[i].bVisible = oInit.saved_aoColumns[i].bVisible;
7163				}
7164				
7165				_fnAddColumn( oSettings, anThs ? anThs[i] : null );
7166			}
7167			
7168			/* Add options from column definations */
7169			if ( typeof oInit.aoColumnDefs != 'undefined' )
7170			{
7171				/* Loop over the column defs array - loop in reverse so first instace has priority */
7172				for ( i=oInit.aoColumnDefs.length-1 ; i>=0 ; i-- )
7173				{
7174					/* Each column def can target multiple columns, as it is an array */
7175					var aTargets = oInit.aoColumnDefs[i].aTargets;
7176					if ( !$.isArray( aTargets ) )
7177					{
7178						_fnLog( oSettings, 1, 'aTargets must be an array of targets, not a '+(typeof aTargets) );
7179					}
7180					for ( j=0, jLen=aTargets.length ; j<jLen ; j++ )
7181					{
7182						if ( typeof aTargets[j] == 'number' && aTargets[j] >= 0 )
7183						{
7184							/* 0+ integer, left to right column counting. We add columns which are unknown
7185							 * automatically. Is this the right behaviour for this? We should at least
7186							 * log it in future. We cannot do this for the negative or class targets, only here.
7187							 */
7188							while( oSettings.aoColumns.length <= aTargets[j] )
7189							{
7190								_fnAddColumn( oSettings );
7191							}
7192							_fnColumnOptions( oSettings, aTargets[j], oInit.aoColumnDefs[i] );
7193						}
7194						else if ( typeof aTargets[j] == 'number' && aTargets[j] < 0 )
7195						{
7196							/* Negative integer, right to left column counting */
7197							_fnColumnOptions( oSettings, oSettings.aoColumns.length+aTargets[j], 
7198								oInit.aoColumnDefs[i] );
7199						}
7200						else if ( typeof aTargets[j] == 'string' )
7201						{
7202							/* Class name matching on TH element */
7203							for ( k=0, kLen=oSettings.aoColumns.length ; k<kLen ; k++ )
7204							{
7205								if ( aTargets[j] == "_all" ||
7206								     $(oSettings.aoColumns[k].nTh).hasClass( aTargets[j] ) )
7207								{
7208									_fnColumnOptions( oSettings, k, oInit.aoColumnDefs[i] );
7209								}
7210							}
7211						}
7212					}
7213				}
7214			}
7215			
7216			/* Add options from column array - after the defs array so this has priority */
7217			if ( typeof aoColumnsInit != 'undefined' )
7218			{
7219				for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ )
7220				{
7221					_fnColumnOptions( oSettings, i, aoColumnsInit[i] );
7222				}
7223			}
7224			
7225			/*
7226			 * Sorting
7227			 * Check the aaSorting array
7228			 */
7229			for ( i=0, iLen=oSettings.aaSorting.length ; i<iLen ; i++ )
7230			{
7231				if ( oSettings.aaSorting[i][0] >= oSettings.aoColumns.length )
7232				{
7233					oSettings.aaSorting[i][0] = 0;
7234				}
7235				var oColumn = oSettings.aoColumns[ oSettings.aaSorting[i][0] ];
7236				
7237				/* Add a default sorting index */
7238				if ( typeof oSettings.aaSorting[i][2] == 'undefined' )
7239				{
7240					oSettings.aaSorting[i][2] = 0;
7241				}
7242				
7243				/* If aaSorting is not defined, then we use the first indicator in asSorting */
7244				if ( typeof oInit.aaSorting == "undefined" && 
7245						 typeof oSettings.saved_aaSorting == "undefined" )
7246				{
7247					oSettings.aaSorting[i][1] = oColumn.asSorting[0];
7248				}
7249				
7250				/* Set the current sorting index based on aoColumns.asSorting */
7251				for ( j=0, jLen=oColumn.asSorting.length ; j<jLen ; j++ )
7252				{
7253					if ( oSettings.aaSorting[i][1] == oColumn.asSorting[j] )
7254					{
7255						oSettings.aaSorting[i][2] = j;
7256						break;
7257					}
7258				}
7259			}
7260				
7261			/* Do a first pass on the sorting classes (allows any size changes to be taken into
7262			 * account, and also will apply sorting disabled classes if disabled
7263			 */
7264			_fnSortingClasses( oSettings );
7265			
7266			/*
7267			 * Final init
7268			 * Cache the header, body and footer as required, creating them if needed
7269			 */
7270			var thead = $('>thead', this);
7271			if ( thead.length === 0 )
7272			{
7273				thead = [ document.createElement( 'thead' ) ];
7274				this.appendChild( thead[0] );
7275			}
7276			oSettings.nTHead = thead[0];
7277			
7278			var tbody = $('>tbody', this);
7279			if ( tbody.length === 0 )
7280			{
7281				tbody = [ document.createElement( 'tbody' ) ];
7282				this.appendChild( tbody[0] );
7283			}
7284			oSettings.nTBody = tbody[0];
7285			
7286			var tfoot = $('>tfoot', this);
7287			if ( tfoot.length > 0 )
7288			{
7289				oSettings.nTFoot = tfoot[0];
7290				_fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot );
7291			}
7292			
7293			/* Check if there is data passing into the constructor */
7294			if ( bUsePassedData )
7295			{
7296				for ( i=0 ; i<oInit.aaData.length ; i++ )
7297				{
7298					_fnAddData( oSettings, oInit.aaData[ i ] );
7299				}
7300			}
7301			else
7302			{
7303				/* Grab the data from the page */
7304				_fnGatherData( oSettings );
7305			}
7306			
7307			/* Copy the data index array */
7308			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
7309			
7310			/* Initialisation complete - table can be drawn */
7311			oSettings.bInitialised = true;
7312			
7313			/* Check if we need to initialise the table (it might not have been handed off to the
7314			 * language processor)
7315			 */
7316			if ( bInitHandedOff === false )
7317			{
7318				_fnInitalise( oSettings );
7319			}
7320		});
7321	};
7322})(jQuery, window, document);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.