comparison examples/browser/web/js/jquery.dataTables.js @ 640:901803e1305f

First instance of audioDB browser code.
author mas01mj
date Thu, 08 Oct 2009 11:19:11 +0000
parents
children
comparison
equal deleted inserted replaced
639:2eaea1afd6b3 640:901803e1305f
1 /*
2 * File: jquery.dataTables.js
3 * Version: 1.5.2
4 * CVS: $Id$
5 * Description: Paginate, search and sort HTML tables
6 * Author: Allan Jardine (www.sprymedia.co.uk)
7 * Created: 28/3/2008
8 * Modified: $Date$ by $Author$
9 * Language: Javascript
10 * License: GPL v2 or BSD 3 point style
11 * Project: Mtaala
12 * Contact: allan.jardine@sprymedia.co.uk
13 *
14 * Copyright 2008-2009 Allan Jardine, all rights reserved.
15 *
16 * This source file is free software, under either the GPL v2 license or a
17 * BSD style license, as supplied with this software.
18 *
19 * This source file is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
22 *
23 * For details pleease refer to: http://www.datatables.net
24 */
25
26 /*
27 * When considering jsLint, we need to allow eval() as it it is used for reading cookies and
28 * building the dynamic multi-column sort functions.
29 */
30 /*jslint evil: true, undef: true, browser: true */
31 /*globals $, jQuery,_fnReadCookie,_fnProcessingDisplay,_fnDraw,_fnSort,_fnReDraw,_fnDetectType,_fnSortingClasses,_fnSettingsFromNode,_fnBuildSearchArray,_fnCalculateEnd,_fnFeatureHtmlProcessing,_fnFeatureHtmlPaginate,_fnFeatureHtmlInfo,_fnFeatureHtmlFilter,_fnFilter,_fnSaveState,_fnFilterColumn,_fnEscapeRegex,_fnFilterComplete,_fnFeatureHtmlLength,_fnGetDataMaster,_fnVisibleToColumnIndex,_fnDrawHead,_fnAddData,_fnGetTrNodes,_fnColumnIndexToVisible,_fnCreateCookie,_fnAddOptionsHtml,_fnMap,_fnClearTable,_fnDataToSearch,_fnReOrderIndex,_fnFilterCustom,_fnVisbleColumns,_fnAjaxUpdate,_fnAjaxUpdateDraw,_fnColumnOrdering,fnGetMaxLenString*/
32
33 (function($) {
34 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
35 * DataTables variables
36 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
37
38 /*
39 * Variable: dataTableSettings
40 * Purpose: Store the settings for each dataTables instance
41 * Scope: jQuery.fn
42 */
43 $.fn.dataTableSettings = [];
44
45 /*
46 * Variable: dataTableExt
47 * Purpose: Container for customisable parts of DataTables
48 * Scope: jQuery.fn
49 */
50 $.fn.dataTableExt = {};
51 var _oExt = $.fn.dataTableExt; /* Short reference for fast internal lookup */
52
53
54 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
55 * DataTables extensible objects
56 *
57 * The _oExt object is used to provide an area where user dfined plugins can be
58 * added to DataTables. The following properties of the object are used:
59 * oApi - Plug-in API functions
60 * aTypes - Auto-detection of types
61 * oSort - Sorting functions used by DataTables (based on the type)
62 * oPagination - Pagination functions for different input styles
63 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
64
65 /*
66 * Variable: sVersion
67 * Purpose: Version string for plug-ins to check compatibility
68 * Scope: jQuery.fn.dataTableExt
69 */
70 _oExt.sVersion = "1.5.2";
71
72 /*
73 * Variable: iApiIndex
74 * Purpose: Index for what 'this' index API functions should use
75 * Scope: jQuery.fn.dataTableExt
76 */
77 _oExt.iApiIndex = 0;
78
79 /*
80 * Variable: oApi
81 * Purpose: Container for plugin API functions
82 * Scope: jQuery.fn.dataTableExt
83 */
84 _oExt.oApi = { };
85
86 /*
87 * Variable: aFiltering
88 * Purpose: Container for plugin filtering functions
89 * Scope: jQuery.fn.dataTableExt
90 */
91 _oExt.afnFiltering = [ ];
92
93 /*
94 * Variable: aoFeatures
95 * Purpose: Container for plugin function functions
96 * Scope: jQuery.fn.dataTableExt
97 * Notes: Array of objects with the following parameters:
98 * fnInit: Function for initialisation of Feature. Takes oSettings and returns node
99 * cFeature: Character that will be matched in sDom - case sensitive
100 * sFeature: Feature name - just for completeness :-)
101 */
102 _oExt.aoFeatures = [ ];
103
104 /*
105 * Variable: ofnSearch
106 * Purpose: Container for custom filtering functions
107 * Scope: jQuery.fn.dataTableExt
108 * Notes: This is an object (the name should match the type) for custom filtering function,
109 * which can be used for live DOM checking or formatted text filtering
110 */
111 _oExt.ofnSearch = { };
112
113 /*
114 * Variable: oStdClasses
115 * Purpose: Storage for the various classes that DataTables uses
116 * Scope: jQuery.fn.dataTableExt
117 */
118 _oExt.oStdClasses = {
119 /* Two buttons buttons */
120 "sPagePrevEnabled": "paginate_enabled_previous",
121 "sPagePrevDisabled": "paginate_disabled_previous",
122 "sPageNextEnabled": "paginate_enabled_next",
123 "sPageNextDisabled": "paginate_disabled_next",
124 "sPageJUINext": "",
125 "sPageJUIPrev": "",
126
127 /* Full numbers paging buttons */
128 "sPageButton": "paginate_button",
129 "sPageButtonActive": "paginate_active",
130 "sPageButtonStaticActive": "paginate_button",
131 "sPageFirst": "first",
132 "sPagePrevious": "previous",
133 "sPageNext": "next",
134 "sPageLast": "last",
135
136 /* Stripping classes */
137 "sStripOdd": "odd",
138 "sStripEven": "even",
139
140 /* Empty row */
141 "sRowEmpty": "dataTables_empty",
142
143 /* Features */
144 "sWrapper": "dataTables_wrapper",
145 "sFilter": "dataTables_filter",
146 "sInfo": "dataTables_info",
147 "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */
148 "sLength": "dataTables_length",
149 "sProcessing": "dataTables_processing",
150
151 /* Sorting */
152 "sSortAsc": "sorting_asc",
153 "sSortDesc": "sorting_desc",
154 "sSortable": "sorting",
155 "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
156 "sSortJUIAsc": "",
157 "sSortJUIDesc": "",
158 "sSortJUI": ""
159 };
160
161 /*
162 * Variable: oJUIClasses
163 * Purpose: Storage for the various classes that DataTables uses - jQuery UI suitable
164 * Scope: jQuery.fn.dataTableExt
165 */
166 _oExt.oJUIClasses = {
167 /* Two buttons buttons */
168 "sPagePrevEnabled": "fg-button ui-state-default ui-corner-left",
169 "sPagePrevDisabled": "fg-button ui-state-default ui-corner-left ui-state-disabled",
170 "sPageNextEnabled": "fg-button ui-state-default ui-corner-right",
171 "sPageNextDisabled": "fg-button ui-state-default ui-corner-right ui-state-disabled",
172 "sPageJUINext": "ui-icon ui-icon-circle-arrow-e",
173 "sPageJUIPrev": "ui-icon ui-icon-circle-arrow-w",
174
175 /* Full numbers paging buttons */
176 "sPageButton": "fg-button ui-state-default",
177 "sPageButtonActive": "fg-button ui-state-default ui-state-disabled",
178 "sPageButtonStaticActive": "fg-button ui-state-default ui-state-disabled",
179 "sPageFirst": "first ui-corner-tl ui-corner-bl",
180 "sPagePrevious": "previous",
181 "sPageNext": "next",
182 "sPageLast": "last ui-corner-tr ui-corner-br",
183
184 /* Stripping classes */
185 "sStripOdd": "odd",
186 "sStripEven": "even",
187
188 /* Empty row */
189 "sRowEmpty": "dataTables_empty",
190
191 /* Features */
192 "sWrapper": "dataTables_wrapper",
193 "sFilter": "dataTables_filter",
194 "sInfo": "dataTables_info",
195 "sPaging": "dataTables_paginate fg-buttonset fg-buttonset-multi paging_", /* Note that the type is postfixed */
196 "sLength": "dataTables_length",
197 "sProcessing": "dataTables_processing",
198
199 /* Sorting */
200 "sSortAsc": "ui-state-default",
201 "sSortDesc": "ui-state-default",
202 "sSortable": "ui-state-default",
203 "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
204 "sSortJUIAsc": "css_right ui-icon ui-icon-triangle-1-n",
205 "sSortJUIDesc": "css_right ui-icon ui-icon-triangle-1-s",
206 "sSortJUI": "css_right ui-icon ui-icon-triangle-2-n-s"
207 };
208
209 /*
210 * Variable: oPagination
211 * Purpose: Container for the various type of pagination that dataTables supports
212 * Scope: jQuery.fn.dataTableExt
213 */
214 _oExt.oPagination = {
215 /*
216 * Variable: two_button
217 * Purpose: Standard two button (forward/back) pagination
218 * Scope: jQuery.fn.dataTableExt.oPagination
219 */
220 "two_button": {
221 /*
222 * Function: oPagination.two_button.fnInit
223 * Purpose: Initalise dom elements required for pagination with forward/back buttons only
224 * Returns: -
225 * Inputs: object:oSettings - dataTables settings object
226 * function:fnCallbackDraw - draw function which must be called on update
227 */
228 "fnInit": function ( oSettings, fnCallbackDraw )
229 {
230 var nPaging = oSettings.anFeatures.p;
231
232 /* Store the next and previous elements in the oSettings object as they can be very
233 * usful for automation - particularly testing
234 */
235 if ( !oSettings.bJUI )
236 {
237 oSettings.nPrevious = document.createElement( 'div' );
238 oSettings.nNext = document.createElement( 'div' );
239 }
240 else
241 {
242 oSettings.nPrevious = document.createElement( 'a' );
243 oSettings.nNext = document.createElement( 'a' );
244
245 var nNextInner = document.createElement('span');
246 nNextInner.className = oSettings.oClasses.sPageJUINext;
247 oSettings.nNext.appendChild( nNextInner );
248
249 var nPreviousInner = document.createElement('span');
250 nPreviousInner.className = oSettings.oClasses.sPageJUIPrev;
251 oSettings.nPrevious.appendChild( nPreviousInner );
252 }
253
254 if ( oSettings.sTableId !== '' )
255 {
256 nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' );
257 oSettings.nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' );
258 oSettings.nNext.setAttribute( 'id', oSettings.sTableId+'_next' );
259 }
260
261 oSettings.nPrevious.className = oSettings.oClasses.sPagePrevDisabled;
262 oSettings.nNext.className = oSettings.oClasses.sPageNextDisabled;
263
264 oSettings.nPrevious.title = oSettings.oLanguage.oPaginate.sPrevious;
265 oSettings.nNext.title = oSettings.oLanguage.oPaginate.sNext;
266
267 nPaging.appendChild( oSettings.nPrevious );
268 nPaging.appendChild( oSettings.nNext );
269 $(nPaging).insertAfter( oSettings.nTable );
270
271 $(oSettings.nPrevious).click( function() {
272 oSettings._iDisplayStart -= oSettings._iDisplayLength;
273
274 /* Correct for underrun */
275 if ( oSettings._iDisplayStart < 0 )
276 {
277 oSettings._iDisplayStart = 0;
278 }
279
280 fnCallbackDraw( oSettings );
281 } );
282
283 $(oSettings.nNext).click( function() {
284 /* Make sure we are not over running the display array */
285 if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
286 {
287 oSettings._iDisplayStart += oSettings._iDisplayLength;
288 }
289
290 fnCallbackDraw( oSettings );
291 } );
292
293 /* Take the brutal approach to cancelling text selection */
294 $(oSettings.nPrevious).bind( 'selectstart', function () { return false; } );
295 $(oSettings.nNext).bind( 'selectstart', function () { return false; } );
296 },
297
298 /*
299 * Function: oPagination.two_button.fnUpdate
300 * Purpose: Update the two button pagination at the end of the draw
301 * Returns: -
302 * Inputs: object:oSettings - dataTables settings object
303 * function:fnCallbackDraw - draw function which must be called on update
304 */
305 "fnUpdate": function ( oSettings, fnCallbackDraw )
306 {
307 if ( !oSettings.anFeatures.p )
308 {
309 return;
310 }
311
312 oSettings.nPrevious.className =
313 ( oSettings._iDisplayStart === 0 ) ?
314 oSettings.oClasses.sPagePrevDisabled : oSettings.oClasses.sPagePrevEnabled;
315
316 oSettings.nNext.className =
317 ( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() ) ?
318 oSettings.oClasses.sPageNextDisabled : oSettings.oClasses.sPageNextEnabled;
319 }
320 },
321
322
323 /*
324 * Variable: iFullNumbersShowPages
325 * Purpose: Change the number of pages which can be seen
326 * Scope: jQuery.fn.dataTableExt.oPagination
327 */
328 "iFullNumbersShowPages": 5,
329
330 /*
331 * Variable: full_numbers
332 * Purpose: Full numbers pagination
333 * Scope: jQuery.fn.dataTableExt.oPagination
334 */
335 "full_numbers": {
336 /*
337 * Function: oPagination.full_numbers.fnInit
338 * Purpose: Initalise dom elements required for pagination with a list of the pages
339 * Returns: -
340 * Inputs: object:oSettings - dataTables settings object
341 * function:fnCallbackDraw - draw function which must be called on update
342 */
343 "fnInit": function ( oSettings, fnCallbackDraw )
344 {
345 var nPaging = oSettings.anFeatures.p;
346 var nFirst = document.createElement( 'span' );
347 var nPrevious = document.createElement( 'span' );
348 var nList = document.createElement( 'span' );
349 var nNext = document.createElement( 'span' );
350 var nLast = document.createElement( 'span' );
351
352 nFirst.innerHTML = oSettings.oLanguage.oPaginate.sFirst;
353 nPrevious.innerHTML = oSettings.oLanguage.oPaginate.sPrevious;
354 nNext.innerHTML = oSettings.oLanguage.oPaginate.sNext;
355 nLast.innerHTML = oSettings.oLanguage.oPaginate.sLast;
356
357 var oClasses = oSettings.oClasses;
358 nFirst.className = oClasses.sPageButton+" "+oClasses.sPageFirst;
359 nPrevious.className = oClasses.sPageButton+" "+oClasses.sPagePrevious;
360 nNext.className= oClasses.sPageButton+" "+oClasses.sPageNext;
361 nLast.className = oClasses.sPageButton+" "+oClasses.sPageLast;
362
363 if ( oSettings.sTableId !== '' )
364 {
365 nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' );
366 nFirst.setAttribute( 'id', oSettings.sTableId+'_first' );
367 nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' );
368 nNext.setAttribute( 'id', oSettings.sTableId+'_next' );
369 nLast.setAttribute( 'id', oSettings.sTableId+'_last' );
370 }
371
372 nPaging.appendChild( nFirst );
373 nPaging.appendChild( nPrevious );
374 nPaging.appendChild( nList );
375 nPaging.appendChild( nNext );
376 nPaging.appendChild( nLast );
377
378 $(nFirst).click( function () {
379 oSettings._iDisplayStart = 0;
380 fnCallbackDraw( oSettings );
381 } );
382
383 $(nPrevious).click( function() {
384 oSettings._iDisplayStart -= oSettings._iDisplayLength;
385
386 /* Correct for underrun */
387 if ( oSettings._iDisplayStart < 0 )
388 {
389 oSettings._iDisplayStart = 0;
390 }
391
392 fnCallbackDraw( oSettings );
393 } );
394
395 $(nNext).click( function() {
396 /* Make sure we are not over running the display array */
397 if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
398 {
399 oSettings._iDisplayStart += oSettings._iDisplayLength;
400 }
401
402 fnCallbackDraw( oSettings );
403 } );
404
405 $(nLast).click( function() {
406 var iPages = parseInt( (oSettings.fnRecordsDisplay()-1) / oSettings._iDisplayLength, 10 ) + 1;
407 oSettings._iDisplayStart = (iPages-1) * oSettings._iDisplayLength;
408
409 fnCallbackDraw( oSettings );
410 } );
411
412 /* Take the brutal approach to cancelling text selection */
413 $('span', nPaging).bind( 'mousedown', function () { return false; } );
414 $('span', nPaging).bind( 'selectstart', function () { return false; } );
415
416 oSettings.nPaginateList = nList;
417 },
418
419 /*
420 * Function: oPagination.full_numbers.fnUpdate
421 * Purpose: Update the list of page buttons shows
422 * Returns: -
423 * Inputs: object:oSettings - dataTables settings object
424 * function:fnCallbackDraw - draw function which must be called on update
425 */
426 "fnUpdate": function ( oSettings, fnCallbackDraw )
427 {
428 if ( !oSettings.anFeatures.p )
429 {
430 return;
431 }
432
433 var iPageCount = jQuery.fn.dataTableExt.oPagination.iFullNumbersShowPages;
434 var iPageCountHalf = Math.floor(iPageCount / 2);
435 var iPages = Math.ceil((oSettings.fnRecordsDisplay()) / oSettings._iDisplayLength);
436 var iCurrentPage = Math.ceil(oSettings._iDisplayStart / oSettings._iDisplayLength) + 1;
437 var sList = "";
438 var iStartButton;
439 var iEndButton;
440 var oClasses = oSettings.oClasses;
441
442 if (iPages < iPageCount)
443 {
444 iStartButton = 1;
445 iEndButton = iPages;
446 }
447 else
448 {
449 if (iCurrentPage <= iPageCountHalf)
450 {
451 iStartButton = 1;
452 iEndButton = iPageCount;
453 }
454 else
455 {
456 if (iCurrentPage >= (iPages - iPageCountHalf))
457 {
458 iStartButton = iPages - iPageCount + 1;
459 iEndButton = iPages;
460 }
461 else
462 {
463 iStartButton = iCurrentPage - Math.ceil(iPageCount / 2) + 1;
464 iEndButton = iStartButton + iPageCount - 1;
465 }
466 }
467 }
468
469 for ( var i=iStartButton ; i<=iEndButton ; i++ )
470 {
471 if ( iCurrentPage != i )
472 {
473 sList += '<span class="'+oClasses.sPageButton+'">'+i+'</span>';
474 }
475 else
476 {
477 sList += '<span class="'+oClasses.sPageButtonActive+'">'+i+'</span>';
478 }
479 }
480
481 oSettings.nPaginateList.innerHTML = sList;
482
483 /* Take the brutal approach to cancelling text selection */
484 $('span', oSettings.nPaginateList).bind( 'mousedown', function () { return false; } );
485 $('span', oSettings.nPaginateList).bind( 'selectstart', function () { return false; } );
486
487 $('span', oSettings.nPaginateList).click( function() {
488 var iTarget = (this.innerHTML * 1) - 1;
489 oSettings._iDisplayStart = iTarget * oSettings._iDisplayLength;
490
491 fnCallbackDraw( oSettings );
492 return false;
493 } );
494
495 /* Update the 'premanent botton's classes */
496 var nButtons = $('span', oSettings.anFeatures.p);
497 var nStatic = [ nButtons[0], nButtons[1], nButtons[nButtons.length-2], nButtons[nButtons.length-1] ];
498 $(nStatic).removeClass( oClasses.sPageButton+" "+oClasses.sPageButtonActive );
499 if ( iCurrentPage == 1 )
500 {
501 nStatic[0].className += " "+oClasses.sPageButtonStaticActive;
502 nStatic[1].className += " "+oClasses.sPageButtonStaticActive;
503 }
504 else
505 {
506 nStatic[0].className += " "+oClasses.sPageButton;
507 nStatic[1].className += " "+oClasses.sPageButton;
508 }
509
510 if ( iCurrentPage == iPages )
511 {
512 nStatic[2].className += " "+oClasses.sPageButtonStaticActive;
513 nStatic[3].className += " "+oClasses.sPageButtonStaticActive;
514 }
515 else
516 {
517 nStatic[2].className += " "+oClasses.sPageButton;
518 nStatic[3].className += " "+oClasses.sPageButton;
519 }
520 }
521 }
522 };
523
524 /*
525 * Variable: oSort
526 * Purpose: Wrapper for the sorting functions that can be used in DataTables
527 * Scope: jQuery.fn.dataTableExt
528 * Notes: The functions provided in this object are basically standard javascript sort
529 * functions - they expect two inputs which they then compare and then return a priority
530 * result. For each sort method added, two functions need to be defined, an ascending sort and
531 * a descending sort.
532 */
533 _oExt.oSort = {
534 /*
535 * text sorting
536 */
537 "string-asc": function ( a, b )
538 {
539 var x = a.toLowerCase();
540 var y = b.toLowerCase();
541 return ((x < y) ? -1 : ((x > y) ? 1 : 0));
542 },
543
544 "string-desc": function ( a, b )
545 {
546 var x = a.toLowerCase();
547 var y = b.toLowerCase();
548 return ((x < y) ? 1 : ((x > y) ? -1 : 0));
549 },
550
551
552 /*
553 * html sorting (ignore html tags)
554 */
555 "html-asc": function ( a, b )
556 {
557 var x = a.replace( /<.*?>/g, "" ).toLowerCase();
558 var y = b.replace( /<.*?>/g, "" ).toLowerCase();
559 return ((x < y) ? -1 : ((x > y) ? 1 : 0));
560 },
561
562 "html-desc": function ( a, b )
563 {
564 var x = a.replace( /<.*?>/g, "" ).toLowerCase();
565 var y = b.replace( /<.*?>/g, "" ).toLowerCase();
566 return ((x < y) ? 1 : ((x > y) ? -1 : 0));
567 },
568
569
570 /*
571 * date sorting
572 */
573 "date-asc": function ( a, b )
574 {
575 var x = Date.parse( a );
576 var y = Date.parse( b );
577
578 if ( isNaN( x ) )
579 {
580 x = Date.parse( "01/01/1970 00:00:00" );
581 }
582 if ( isNaN( y ) )
583 {
584 y = Date.parse( "01/01/1970 00:00:00" );
585 }
586
587 return x - y;
588 },
589
590 "date-desc": function ( a, b )
591 {
592 var x = Date.parse( a );
593 var y = Date.parse( b );
594
595 if ( isNaN( x ) )
596 {
597 x = Date.parse( "01/01/1970 00:00:00" );
598 }
599 if ( isNaN( y ) )
600 {
601 y = Date.parse( "01/01/1970 00:00:00" );
602 }
603
604 return y - x;
605 },
606
607
608 /*
609 * numerical sorting
610 */
611 "numeric-asc": function ( a, b )
612 {
613 var x = a == "-" ? 0 : a;
614 var y = b == "-" ? 0 : b;
615 return x - y;
616 },
617
618 "numeric-desc": function ( a, b )
619 {
620 var x = a == "-" ? 0 : a;
621 var y = b == "-" ? 0 : b;
622 return y - x;
623 }
624 };
625
626
627 /*
628 * Variable: aTypes
629 * Purpose: Container for the various type of type detection that dataTables supports
630 * Scope: jQuery.fn.dataTableExt
631 * Notes: The functions in this array are expected to parse a string to see if it is a data
632 * type that it recognises. If so then the function should return the name of the type (a
633 * corresponding sort function should be defined!), if the type is not recognised then the
634 * function should return null such that the parser and move on to check the next type.
635 * Note that ordering is important in this array - the functions are processed linearly,
636 * starting at index 0.
637 */
638 _oExt.aTypes = [
639 /*
640 * Function: -
641 * Purpose: Check to see if a string is numeric
642 * Returns: string:'numeric' or null
643 * Inputs: string:sText - string to check
644 */
645 function ( sData )
646 {
647 /* Snaity check that we are dealing with a string or quick return for a number */
648 if ( typeof sData == 'number' )
649 {
650 return 'numeric';
651 }
652 else if ( typeof sData.charAt != 'function' )
653 {
654 return null;
655 }
656
657 var sValidFirstChars = "0123456789-";
658 var sValidChars = "0123456789.";
659 var Char;
660 var bDecimal = false;
661
662 /* Check for a valid first char (no period and allow negatives) */
663 Char = sData.charAt(0);
664 if (sValidFirstChars.indexOf(Char) == -1)
665 {
666 return null;
667 }
668
669 /* Check all the other characters are valid */
670 for ( var i=1 ; i<sData.length ; i++ )
671 {
672 Char = sData.charAt(i);
673 if (sValidChars.indexOf(Char) == -1)
674 {
675 return null;
676 }
677
678 /* Only allowed one decimal place... */
679 if ( Char == "." )
680 {
681 if ( bDecimal )
682 {
683 return null;
684 }
685 bDecimal = true;
686 }
687 }
688
689 return 'numeric';
690 },
691
692 /*
693 * Function: -
694 * Purpose: Check to see if a string is actually a formatted date
695 * Returns: string:'date' or null
696 * Inputs: string:sText - string to check
697 */
698 function ( sData )
699 {
700 var iParse = Date.parse(sData);
701 if ( iParse !== null && !isNaN(iParse) )
702 {
703 return 'date';
704 }
705 return null;
706 }
707 ];
708
709
710 /*
711 * Variable: _oExternConfig
712 * Purpose: Store information for DataTables to access globally about other instances
713 * Scope: jQuery.fn.dataTableExt
714 */
715 _oExt._oExternConfig = {
716 /* int:iNextUnique - next unique number for an instance */
717 "iNextUnique": 0
718 };
719
720
721 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
722 * DataTables prototype
723 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
724
725 /*
726 * Function: dataTable
727 * Purpose: DataTables information
728 * Returns: -
729 * Inputs: object:oInit - initalisation options for the table
730 */
731 $.fn.dataTable = function( oInit )
732 {
733 /*
734 * Variable: _aoSettings
735 * Purpose: Easy reference to data table settings
736 * Scope: jQuery.dataTable
737 */
738 var _aoSettings = $.fn.dataTableSettings;
739
740 /*
741 * Function: classSettings
742 * Purpose: Settings container function for all 'class' properties which are required
743 * by dataTables
744 * Returns: -
745 * Inputs: -
746 */
747 function classSettings ()
748 {
749 this.fnRecordsTotal = function ()
750 {
751 if ( this.oFeatures.bServerSide ) {
752 return this._iRecordsTotal;
753 } else {
754 return this.aiDisplayMaster.length;
755 }
756 };
757
758 this.fnRecordsDisplay = function ()
759 {
760 if ( this.oFeatures.bServerSide ) {
761 return this._iRecordsDisplay;
762 } else {
763 return this.aiDisplay.length;
764 }
765 };
766
767 this.fnDisplayEnd = function ()
768 {
769 if ( this.oFeatures.bServerSide ) {
770 return this._iDisplayStart + this.aiDisplay.length;
771 } else {
772 return this._iDisplayEnd;
773 }
774 };
775
776 /*
777 * Variable: sInstance
778 * Purpose: Unique idendifier for each instance of the DataTables object
779 * Scope: jQuery.dataTable.classSettings
780 */
781 this.sInstance = null;
782
783 /*
784 * Variable: oFeatures
785 * Purpose: Indicate the enablement of key dataTable features
786 * Scope: jQuery.dataTable.classSettings
787 */
788 this.oFeatures = {
789 "bPaginate": true,
790 "bLengthChange": true,
791 "bFilter": true,
792 "bSort": true,
793 "bInfo": true,
794 "bAutoWidth": true,
795 "bProcessing": false,
796 "bSortClasses": true,
797 "bStateSave": false,
798 "bServerSide": false
799 };
800
801 /*
802 * Variable: anFeatures
803 * Purpose: Array referencing the nodes which are used for the features
804 * Scope: jQuery.dataTable.classSettings
805 * Notes: The parameters of this object match what is allowed by sDom - i.e.
806 * 'l' - Length changing
807 * 'f' - Filtering input
808 * 't' - The table!
809 * 'i' - Information
810 * 'p' - Pagination
811 * 'r' - pRocessing
812 */
813 this.anFeatures = [];
814
815 /*
816 * Variable: oLanguage
817 * Purpose: Store the language strings used by dataTables
818 * Scope: jQuery.dataTable.classSettings
819 * Notes: The words in the format _VAR_ are variables which are dynamically replaced
820 * by javascript
821 */
822 this.oLanguage = {
823 "sProcessing": "Processing...",
824 "sLengthMenu": "Show _MENU_ entries",
825 "sZeroRecords": "No matching records found",
826 "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
827 "sInfoEmpty": "Showing 0 to 0 of 0 entries",
828 "sInfoFiltered": "(filtered from _MAX_ total entries)",
829 "sInfoPostFix": "",
830 "sSearch": "Search:",
831 "sUrl": "",
832 "oPaginate": {
833 "sFirst": "First",
834 "sPrevious": "Previous",
835 "sNext": "Next",
836 "sLast": "Last"
837 }
838 };
839
840 /*
841 * Variable: aoData
842 * Purpose: Store data information
843 * Scope: jQuery.dataTable.classSettings
844 * Notes: This is an array of objects with the following parameters:
845 * int: _iId - internal id for tracking
846 * array: _aData - internal data - used for sorting / filtering etc
847 * node: nTr - display node
848 * array node: _anHidden - hidden TD nodes
849 */
850 this.aoData = [];
851
852 /*
853 * Variable: aiDisplay
854 * Purpose: Array of indexes which are in the current display (after filtering etc)
855 * Scope: jQuery.dataTable.classSettings
856 */
857 this.aiDisplay = [];
858
859 /*
860 * Variable: aiDisplayMaster
861 * Purpose: Array of indexes for display - no filtering
862 * Scope: jQuery.dataTable.classSettings
863 */
864 this.aiDisplayMaster = [];
865
866 /*
867 * Variable: aoColumns
868 * Purpose: Store information about each column that is in use
869 * Scope: jQuery.dataTable.classSettings
870 */
871 this.aoColumns = [];
872
873 /*
874 * Variable: iNextId
875 * Purpose: Store the next unique id to be used for a new row
876 * Scope: jQuery.dataTable.classSettings
877 */
878 this.iNextId = 0;
879
880 /*
881 * Variable: asDataSearch
882 * Purpose: Search data array for regular expression searching
883 * Scope: jQuery.dataTable.classSettings
884 */
885 this.asDataSearch = [];
886
887 /*
888 * Variable: oPreviousSearch
889 * Purpose: Store the previous search incase we want to force a re-search
890 * or compare the old search to a new one
891 * Scope: jQuery.dataTable.classSettings
892 */
893 this.oPreviousSearch = {
894 "sSearch": "",
895 "bEscapeRegex": true
896 };
897
898 /*
899 * Variable: aoPreSearchCols
900 * Purpose: Store the previous search for each column
901 * Scope: jQuery.dataTable.classSettings
902 */
903 this.aoPreSearchCols = [];
904
905 /*
906 * Variable: aaSorting
907 * Purpose: Sorting information
908 * Scope: jQuery.dataTable.classSettings
909 */
910 this.aaSorting = [ [0, 'asc'] ];
911
912 /*
913 * Variable: aaSortingFixed
914 * Purpose: Sorting information that is always applied
915 * Scope: jQuery.dataTable.classSettings
916 */
917 this.aaSortingFixed = null;
918
919 /*
920 * Variable: asStripClasses
921 * Purpose: Classes to use for the striping of a table
922 * Scope: jQuery.dataTable.classSettings
923 */
924 this.asStripClasses = [];
925
926 /*
927 * Variable: fnRowCallback
928 * Purpose: Call this function every time a row is inserted (draw)
929 * Scope: jQuery.dataTable.classSettings
930 */
931 this.fnRowCallback = null;
932
933 /*
934 * Variable: fnHeaderCallback
935 * Purpose: Callback function for the header on each draw
936 * Scope: jQuery.dataTable.classSettings
937 */
938 this.fnHeaderCallback = null;
939
940 /*
941 * Variable: fnFooterCallback
942 * Purpose: Callback function for the footer on each draw
943 * Scope: jQuery.dataTable.classSettings
944 */
945 this.fnFooterCallback = null;
946
947 /*
948 * Variable: fnDrawCallback
949 * Purpose: Callback function for the whole table on each draw
950 * Scope: jQuery.dataTable.classSettings
951 */
952 this.fnDrawCallback = null;
953
954 /*
955 * Variable: fnInitComplete
956 * Purpose: Callback function for when the table has been initalised
957 * Scope: jQuery.dataTable.classSettings
958 */
959 this.fnInitComplete = null;
960
961 /*
962 * Variable: sTableId
963 * Purpose: Cache the table ID for quick access
964 * Scope: jQuery.dataTable.classSettings
965 */
966 this.sTableId = "";
967
968 /*
969 * Variable: nTable
970 * Purpose: Cache the table node for quick access
971 * Scope: jQuery.dataTable.classSettings
972 */
973 this.nTable = null;
974
975 /*
976 * Variable: iDefaultSortIndex
977 * Purpose: Sorting index which will be used by default
978 * Scope: jQuery.dataTable.classSettings
979 */
980 this.iDefaultSortIndex = 0;
981
982 /*
983 * Variable: bInitialised
984 * Purpose: Indicate if all required information has been read in
985 * Scope: jQuery.dataTable.classSettings
986 */
987 this.bInitialised = false;
988
989 /*
990 * Variable: aoOpenRows
991 * Purpose: Information about open rows
992 * Scope: jQuery.dataTable.classSettings
993 * Notes: Has the parameters 'nTr' and 'nParent'
994 */
995 this.aoOpenRows = [];
996
997 /*
998 * Variable: sDomPositioning
999 * Purpose: Dictate the positioning that the created elements will take
1000 * Scope: jQuery.dataTable.classSettings
1001 * Notes: The following syntax is expected:
1002 * 'l' - Length changing
1003 * 'f' - Filtering input
1004 * 't' - The table!
1005 * 'i' - Information
1006 * 'p' - Pagination
1007 * 'r' - pRocessing
1008 * '<' and '>' - div elements
1009 * '<"class" and '>' - div with a class
1010 * Examples: '<"wrapper"flipt>', '<lf<t>ip>'
1011 */
1012 this.sDomPositioning = 'lfrtip';
1013
1014 /*
1015 * Variable: sPaginationType
1016 * Purpose: Note which type of sorting should be used
1017 * Scope: jQuery.dataTable.classSettings
1018 */
1019 this.sPaginationType = "two_button";
1020
1021 /*
1022 * Variable: iCookieDuration
1023 * Purpose: The cookie duration (for bStateSave) in seconds - default 2 hours
1024 * Scope: jQuery.dataTable.classSettings
1025 */
1026 this.iCookieDuration = 60 * 60 * 2;
1027
1028 /*
1029 * Variable: sAjaxSource
1030 * Purpose: Source url for AJAX data for the table
1031 * Scope: jQuery.dataTable.classSettings
1032 */
1033 this.sAjaxSource = null;
1034
1035 /*
1036 * Variable: bAjaxDataGet
1037 * Purpose: Note if draw should be blocked while getting data
1038 * Scope: jQuery.dataTable.classSettings
1039 */
1040 this.bAjaxDataGet = true;
1041
1042 /*
1043 * Variable: fnServerData
1044 * Purpose: Function to get the server-side data - can be overruled by the developer
1045 * Scope: jQuery.dataTable.classSettings
1046 */
1047 this.fnServerData = $.getJSON;
1048
1049 /*
1050 * Variable: iServerDraw
1051 * Purpose: Counter and tracker for server-side processing draws
1052 * Scope: jQuery.dataTable.classSettings
1053 */
1054 this.iServerDraw = 0;
1055
1056 /*
1057 * Variable: _iDisplayLength, _iDisplayStart, _iDisplayEnd
1058 * Purpose: Display length variables
1059 * Scope: jQuery.dataTable.classSettings
1060 * Notes: These variable must NOT be used externally to get the data length. Rather, use
1061 * the fnRecordsTotal() (etc) functions.
1062 */
1063 this._iDisplayLength = 10;
1064 this._iDisplayStart = 0;
1065 this._iDisplayEnd = 10;
1066
1067 /*
1068 * Variable: _iRecordsTotal, _iRecordsDisplay
1069 * Purpose: Display length variables used for server side processing
1070 * Scope: jQuery.dataTable.classSettings
1071 * Notes: These variable must NOT be used externally to get the data length. Rather, use
1072 * the fnRecordsTotal() (etc) functions.
1073 */
1074 this._iRecordsTotal = 0;
1075 this._iRecordsDisplay = 0;
1076
1077 /*
1078 * Variable: bJUI
1079 * Purpose: Should we add the markup needed for jQuery UI theming?
1080 * Scope: jQuery.dataTable.classSettings
1081 */
1082 this.bJUI = false;
1083
1084 /*
1085 * Variable: bJUI
1086 * Purpose: Should we add the markup needed for jQuery UI theming?
1087 * Scope: jQuery.dataTable.classSettings
1088 */
1089 this.oClasses = _oExt.oStdClasses;
1090 }
1091
1092 /*
1093 * Variable: oApi
1094 * Purpose: Container for publicly exposed 'private' functions
1095 * Scope: jQuery.dataTable
1096 */
1097 this.oApi = {};
1098
1099
1100 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1101 * API functions
1102 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1103
1104 /*
1105 * Function: fnDraw
1106 * Purpose: Redraw the table
1107 * Returns: -
1108 * Inputs: -
1109 */
1110 this.fnDraw = function()
1111 {
1112 _fnReDraw( _fnSettingsFromNode( this[_oExt.iApiIndex] ) );
1113 };
1114
1115 /*
1116 * Function: fnFilter
1117 * Purpose: Filter the input based on data
1118 * Returns: -
1119 * Inputs: string:sInput - string to filter the table on
1120 * int:iColumn - optional - column to limit filtering to
1121 * bool:bEscapeRegex - optional - escape regex characters or not - default true
1122 */
1123 this.fnFilter = function( sInput, iColumn, bEscapeRegex )
1124 {
1125 var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1126
1127 if ( typeof bEscapeRegex == 'undefined' )
1128 {
1129 bEscapeRegex = true;
1130 }
1131
1132 if ( typeof iColumn == "undefined" || iColumn === null )
1133 {
1134 /* Global filter */
1135 _fnFilterComplete( oSettings, {"sSearch":sInput, "bEscapeRegex": bEscapeRegex}, 1 );
1136 }
1137 else
1138 {
1139 /* Single column filter */
1140 oSettings.aoPreSearchCols[ iColumn ].sSearch = sInput;
1141 oSettings.aoPreSearchCols[ iColumn ].bEscapeRegex = bEscapeRegex;
1142 _fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
1143 }
1144 };
1145
1146 /*
1147 * Function: fnSettings
1148 * Purpose: Get the settings for a particular table for extern. manipulation
1149 * Returns: -
1150 * Inputs: -
1151 */
1152 this.fnSettings = function( nNode )
1153 {
1154 return _fnSettingsFromNode( this[_oExt.iApiIndex] );
1155 };
1156
1157 /*
1158 * Function: fnSort
1159 * Purpose: Sort the table by a particular row
1160 * Returns: -
1161 * Inputs: int:iCol - the data index to sort on. Note that this will
1162 * not match the 'display index' if you have hidden data entries
1163 */
1164 this.fnSort = function( aaSort )
1165 {
1166 var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1167 oSettings.aaSorting = aaSort;
1168 _fnSort( oSettings );
1169 };
1170
1171 /*
1172 * Function: fnAddData
1173 * Purpose: Add new row(s) into the table
1174 * Returns: array int: array of indexes (aoData) which have been added (zero length on error)
1175 * Inputs: array:mData - the data to be added. The length must match
1176 * the original data from the DOM
1177 * or
1178 * array array:mData - 2D array of data to be added
1179 * bool:bRedraw - redraw the table or not - default true
1180 * Notes: Warning - the refilter here will cause the table to redraw
1181 * starting at zero
1182 * Notes: Thanks to Yekimov Denis for contributing the basis for this function!
1183 */
1184 this.fnAddData = function( mData, bRedraw )
1185 {
1186 var aiReturn = [];
1187 var iTest;
1188 if ( typeof bRedraw == 'undefined' )
1189 {
1190 bRedraw = true;
1191 }
1192
1193 /* Find settings from table node */
1194 var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1195
1196 /* Check if we want to add multiple rows or not */
1197 if ( typeof mData[0] == "object" )
1198 {
1199 for ( var i=0 ; i<mData.length ; i++ )
1200 {
1201 iTest = _fnAddData( oSettings, mData[i] );
1202 if ( iTest == -1 )
1203 {
1204 return aiReturn;
1205 }
1206 aiReturn.push( iTest );
1207 }
1208 }
1209 else
1210 {
1211 iTest = _fnAddData( oSettings, mData );
1212 if ( iTest == -1 )
1213 {
1214 return aiReturn;
1215 }
1216 aiReturn.push( iTest );
1217 }
1218
1219 oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
1220
1221 /* Rebuild the search */
1222 _fnBuildSearchArray( oSettings, 1 );
1223
1224 if ( bRedraw )
1225 {
1226 _fnReDraw( oSettings );
1227 }
1228 return aiReturn;
1229 };
1230
1231 /*
1232 * Function: fnDeleteRow
1233 * Purpose: Remove a row for the table
1234 * Returns: array:aReturn - the row that was deleted
1235 * Inputs: int:iIndex - index of aoData to be deleted
1236 * function:fnCallBack - callback function - default null
1237 * bool:bNullRow - remove the row information from aoData by setting the value to
1238 * null - default false
1239 * Notes: This function requires a little explanation - we don't actually delete the data
1240 * from aoData - rather we remove it's references from aiDisplayMastr and aiDisplay. This
1241 * in effect prevnts DataTables from drawing it (hence deleting it) - it could be restored
1242 * if you really wanted. The reason for this is that actually removing the aoData object
1243 * would mess up all the subsequent indexes in the display arrays (they could be ajusted -
1244 * but this appears to do what is required).
1245 */
1246 this.fnDeleteRow = function( iAODataIndex, fnCallBack, bNullRow )
1247 {
1248 /* Find settings from table node */
1249 var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1250 var i;
1251
1252 /* Delete from the display master */
1253 for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ )
1254 {
1255 if ( oSettings.aiDisplayMaster[i] == iAODataIndex )
1256 {
1257 oSettings.aiDisplayMaster.splice( i, 1 );
1258 break;
1259 }
1260 }
1261
1262 /* Delete from the current display index */
1263 for ( i=0 ; i<oSettings.aiDisplay.length ; i++ )
1264 {
1265 if ( oSettings.aiDisplay[i] == iAODataIndex )
1266 {
1267 oSettings.aiDisplay.splice( i, 1 );
1268 break;
1269 }
1270 }
1271
1272 /* Rebuild the search */
1273 _fnBuildSearchArray( oSettings, 1 );
1274
1275 /* If there is a user callback function - call it */
1276 if ( typeof fnCallBack == "function" )
1277 {
1278 fnCallBack.call( this );
1279 }
1280
1281 /* Check for an 'overflow' they case for dislaying the table */
1282 if ( oSettings._iDisplayStart >= oSettings.aiDisplay.length )
1283 {
1284 oSettings._iDisplayStart -= oSettings._iDisplayLength;
1285 if ( oSettings._iDisplayStart < 0 )
1286 {
1287 oSettings._iDisplayStart = 0;
1288 }
1289 }
1290
1291 _fnCalculateEnd( oSettings );
1292 _fnDraw( oSettings );
1293
1294 /* Return the data array from this row */
1295 var aData = oSettings.aoData[iAODataIndex]._aData.slice();
1296
1297 if ( typeof bNullRow != "undefined" && bNullRow === true )
1298 {
1299 oSettings.aoData[iAODataIndex] = null;
1300 }
1301
1302 return aData;
1303 };
1304
1305 /*
1306 * Function: fnClearTable
1307 * Purpose: Quickly and simply clear a table
1308 * Returns: -
1309 * Inputs: bool:bRedraw - redraw the table or not - default true
1310 * Notes: Thanks to Yekimov Denis for contributing the basis for this function!
1311 */
1312 this.fnClearTable = function( bRedraw )
1313 {
1314 /* Find settings from table node */
1315 var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1316 _fnClearTable( oSettings );
1317
1318 if ( typeof bRedraw == 'undefined' || bRedraw )
1319 {
1320 _fnDraw( oSettings );
1321 }
1322 };
1323
1324 /*
1325 * Function: fnOpen
1326 * Purpose: Open a display row (append a row after the row in question)
1327 * Returns: -
1328 * Inputs: node:nTr - the table row to 'open'
1329 * string:sHtml - the HTML to put into the row
1330 * string:sClass - class to give the new cell
1331 */
1332 this.fnOpen = function( nTr, sHtml, sClass )
1333 {
1334 /* Find settings from table node */
1335 var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1336
1337 /* the old open one if there is one */
1338 this.fnClose( nTr );
1339
1340
1341 var nNewRow = document.createElement("tr");
1342 var nNewCell = document.createElement("td");
1343 nNewRow.appendChild( nNewCell );
1344 nNewCell.className = sClass;
1345 nNewCell.colSpan = _fnVisbleColumns( oSettings );
1346 nNewCell.innerHTML = sHtml;
1347
1348 $(nNewRow).insertAfter(nTr);
1349
1350 /* No point in storing the row if using server-side processing since the nParent will be
1351 * nuked on a re-draw anyway
1352 */
1353 if ( !oSettings.oFeatures.bServerSide )
1354 {
1355 oSettings.aoOpenRows.push( {
1356 "nTr": nNewRow,
1357 "nParent": nTr
1358 } );
1359 }
1360 };
1361
1362 /*
1363 * Function: fnClose
1364 * Purpose: Close a display row
1365 * Returns: int: 0 (success) or 1 (failed)
1366 * Inputs: node:nTr - the table row to 'close'
1367 */
1368 this.fnClose = function( nTr )
1369 {
1370 /* Find settings from table node */
1371 var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1372
1373 for ( var i=0 ; i<oSettings.aoOpenRows.length ; i++ )
1374 {
1375 if ( oSettings.aoOpenRows[i].nParent == nTr )
1376 {
1377 var nTrParent = oSettings.aoOpenRows[i].nTr.parentNode;
1378 if ( nTrParent )
1379 {
1380 /* Remove it if it is currently on display */
1381 nTrParent.removeChild( oSettings.aoOpenRows[i].nTr );
1382 }
1383 oSettings.aoOpenRows.splice( i, 1 );
1384 return 0;
1385 }
1386 }
1387 return 1;
1388 };
1389
1390 /*
1391 * Function: fnGetData
1392 * Purpose: Return an array with the data which is used to make up the table
1393 * Returns: array array string: 2d data array ([row][column]) or array string: 1d data array
1394 * or
1395 * array string (if iRow specified)
1396 * Inputs: int:iRow - optional - if present then the array returned will be the data for
1397 * the row with the index 'iRow'
1398 */
1399 this.fnGetData = function( iRow )
1400 {
1401 var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1402
1403 if ( typeof iRow != 'undefined' )
1404 {
1405 return oSettings.aoData[iRow]._aData;
1406 }
1407 return _fnGetDataMaster( oSettings );
1408 };
1409
1410 /*
1411 * Function: fnGetNodes
1412 * Purpose: Return an array with the TR nodes used for drawing the table
1413 * Returns: array node: TR elements
1414 * or
1415 * node (if iRow specified)
1416 * Inputs: int:iRow - optional - if present then the array returned will be the node for
1417 * the row with the index 'iRow'
1418 */
1419 this.fnGetNodes = function( iRow )
1420 {
1421 var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1422
1423 if ( typeof iRow != 'undefined' )
1424 {
1425 return oSettings.aoData[iRow].nTr;
1426 }
1427 return _fnGetTrNodes( oSettings );
1428 };
1429
1430 /*
1431 * Function: fnGetPosition
1432 * Purpose: Get the array indexes of a particular cell from it's DOM element
1433 * Returns: int: - row index, or array[ int, int ]: - row index and column index
1434 * Inputs: node:nNode - this can either be a TR or a TD in the table, the return is
1435 * dependent on this input
1436 */
1437 this.fnGetPosition = function( nNode )
1438 {
1439 var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1440 var i;
1441
1442 if ( nNode.nodeName == "TR" )
1443 {
1444 for ( i=0 ; i<oSettings.aoData.length ; i++ )
1445 {
1446 if ( oSettings.aoData[i] !== null && oSettings.aoData[i].nTr == nNode )
1447 {
1448 return i;
1449 }
1450 }
1451 }
1452 else if ( nNode.nodeName == "TD" )
1453 {
1454 for ( i=0 ; i<oSettings.aoData.length ; i++ )
1455 {
1456 var iCorrector = 0;
1457 for ( var j=0 ; j<oSettings.aoColumns.length ; j++ )
1458 {
1459 if ( oSettings.aoColumns[j].bVisible )
1460 {
1461 //$('>td', oSettings.aoData[i].nTr)[j-iCorrector] == nNode )
1462 if ( oSettings.aoData[i] !== null &&
1463 oSettings.aoData[i].nTr.getElementsByTagName('td')[j-iCorrector] == nNode )
1464 {
1465 return [ i, j-iCorrector, j ];
1466 }
1467 }
1468 else
1469 {
1470 iCorrector++;
1471 }
1472 }
1473 }
1474 }
1475 return null;
1476 };
1477
1478 /*
1479 * Function: fnUpdate
1480 * Purpose: Update a table cell or row
1481 * Returns: int: 0 okay, 1 error
1482 * Inputs: array string 'or' string:mData - data to update the cell/row with
1483 * int:iRow - the row (from aoData) to update
1484 * int:iColumn - the column to update
1485 * bool:bRedraw - redraw the table or not - default true
1486 */
1487 this.fnUpdate = function( mData, iRow, iColumn, bRedraw )
1488 {
1489 var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1490 var iVisibleColumn;
1491 var sDisplay;
1492 if ( typeof bRedraw == 'undefined' )
1493 {
1494 bRedraw = true;
1495 }
1496
1497 if ( typeof mData != 'object' )
1498 {
1499 sDisplay = mData;
1500 oSettings.aoData[iRow]._aData[iColumn] = sDisplay;
1501
1502 if ( oSettings.aoColumns[iColumn].fnRender !== null )
1503 {
1504 sDisplay = oSettings.aoColumns[iColumn].fnRender( {
1505 "iDataRow": iRow,
1506 "iDataColumn": iColumn,
1507 "aData": oSettings.aoData[iRow]._aData
1508 } );
1509
1510 if ( oSettings.aoColumns[iColumn].bUseRendered )
1511 {
1512 oSettings.aoData[iRow]._aData[iColumn] = sDisplay;
1513 }
1514 }
1515
1516 iVisibleColumn = _fnColumnIndexToVisible( oSettings, iColumn );
1517 if ( iVisibleColumn !== null )
1518 {
1519 oSettings.aoData[iRow].nTr.getElementsByTagName('td')[iVisibleColumn].innerHTML =
1520 sDisplay;
1521 }
1522 }
1523 else
1524 {
1525 if ( mData.length != oSettings.aoColumns.length )
1526 {
1527 alert( 'Warning: An array passed to fnUpdate must have the same number of columns as '+
1528 'the table in question - in this case '+oSettings.aoColumns.length );
1529 return 1;
1530 }
1531
1532 for ( var i=0 ; i<mData.length ; i++ )
1533 {
1534 sDisplay = mData[i];
1535 oSettings.aoData[iRow]._aData[i] = sDisplay;
1536
1537 if ( oSettings.aoColumns[i].fnRender !== null )
1538 {
1539 sDisplay = oSettings.aoColumns[i].fnRender( {
1540 "iDataRow": iRow,
1541 "iDataColumn": i,
1542 "aData": oSettings.aoData[iRow]._aData
1543 } );
1544
1545 if ( oSettings.aoColumns[i].bUseRendered )
1546 {
1547 oSettings.aoData[iRow]._aData[i] = sDisplay;
1548 }
1549 }
1550
1551 iVisibleColumn = _fnColumnIndexToVisible( oSettings, i );
1552 if ( iVisibleColumn !== null )
1553 {
1554 oSettings.aoData[iRow].nTr.getElementsByTagName('td')[iVisibleColumn].innerHTML =
1555 sDisplay;
1556 }
1557 }
1558 }
1559
1560 /* Update the search array */
1561 _fnBuildSearchArray( oSettings, 1 );
1562
1563 /* Redraw the table */
1564 if ( bRedraw )
1565 {
1566 _fnReDraw( oSettings );
1567 }
1568 return 0;
1569 };
1570
1571
1572 /*
1573 * Function: fnShowColoumn
1574 * Purpose: Show a particular column
1575 * Returns: -
1576 * Inputs: int:iCol - the column whose display should be changed
1577 * bool:bShow - show (true) or hide (false) the column
1578 */
1579 this.fnSetColumnVis = function ( iCol, bShow )
1580 {
1581 var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1582 var i, iLen;
1583 var iColumns = oSettings.aoColumns.length;
1584 var nTd;
1585
1586 /* No point in doing anything if we are requesting what is already true */
1587 if ( oSettings.aoColumns[iCol].bVisible == bShow )
1588 {
1589 return;
1590 }
1591
1592 var nTrHead = $('thead tr', oSettings.nTable)[0];
1593 var nTrFoot = $('tfoot tr', oSettings.nTable)[0];
1594 var anTheadTh = [];
1595 var anTfootTh = [];
1596 for ( i=0 ; i<iColumns ; i++ )
1597 {
1598 anTheadTh.push( oSettings.aoColumns[i].nTh );
1599 anTfootTh.push( oSettings.aoColumns[i].nTf );
1600 }
1601
1602 /* Show the column */
1603 if ( bShow )
1604 {
1605 var iInsert = 0;
1606 for ( i=0 ; i<iCol ; i++ )
1607 {
1608 if ( oSettings.aoColumns[i].bVisible )
1609 {
1610 iInsert++;
1611 }
1612 }
1613
1614 /* Need to decide if we should use appendChild or insertBefore */
1615 if ( iInsert >= _fnVisbleColumns( oSettings ) )
1616 {
1617 nTrHead.appendChild( anTheadTh[iCol] );
1618 if ( nTrFoot )
1619 {
1620 nTrFoot.appendChild( anTfootTh[iCol] );
1621 }
1622
1623 for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
1624 {
1625 nTd = oSettings.aoData[i]._anHidden[iCol];
1626 oSettings.aoData[i].nTr.appendChild( nTd );
1627 }
1628 }
1629 else
1630 {
1631 /* Which coloumn should we be inserting before? */
1632 var iBefore;
1633 for ( i=iCol ; i<iColumns ; i++ )
1634 {
1635 iBefore = _fnColumnIndexToVisible( oSettings, i );
1636 if ( iBefore !== null )
1637 {
1638 break;
1639 }
1640 }
1641
1642 nTrHead.insertBefore( anTheadTh[iCol], nTrHead.getElementsByTagName('th')[iBefore] );
1643 if ( nTrFoot )
1644 {
1645 nTrFoot.insertBefore( anTfootTh[iCol], nTrFoot.getElementsByTagName('th')[iBefore] );
1646 }
1647
1648 for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
1649 {
1650 nTd = oSettings.aoData[i]._anHidden[iCol];
1651 oSettings.aoData[i].nTr.insertBefore( nTd, oSettings.aoData[i].nTr.getElementsByTagName('td')[iBefore] );
1652 }
1653 }
1654
1655 oSettings.aoColumns[iCol].bVisible = true;
1656 }
1657 else
1658 {
1659 /* Remove a column from display */
1660 nTrHead.removeChild( anTheadTh[iCol] );
1661 if ( nTrFoot )
1662 {
1663 nTrFoot.removeChild( anTfootTh[iCol] );
1664 }
1665
1666 var iVisCol = _fnColumnIndexToVisible(oSettings, iCol);
1667 for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
1668 {
1669 nTd = oSettings.aoData[i].nTr.getElementsByTagName('td')[ iVisCol ];
1670 oSettings.aoData[i]._anHidden[iCol] = nTd;
1671 nTd.parentNode.removeChild( nTd );
1672 }
1673
1674 oSettings.aoColumns[iCol].bVisible = false;
1675 }
1676
1677 /* If there are any 'open' rows, then we need to alter the colspan for this col change */
1678 for ( i=0, iLen=oSettings.aoOpenRows.length ; i<iLen ; i++ )
1679 {
1680 oSettings.aoOpenRows[i].nTr.colSpan = _fnVisbleColumns( oSettings );
1681 }
1682
1683 /* Since there is no redraw done here, we need to save the state manually */
1684 _fnSaveState( oSettings );
1685 };
1686
1687
1688 /*
1689 * Plugin API functions
1690 *
1691 * This call will add the functions which are defined in _oExt.oApi to the
1692 * DataTables object, providing a rather nice way to allow plug-in API functions. Note that
1693 * this is done here, so that API function can actually override the built in API functions if
1694 * required for a particular purpose.
1695 */
1696
1697 /*
1698 * Function: _fnExternApiFunc
1699 * Purpose: Create a wrapper function for exporting an internal func to an external API func
1700 * Returns: function: - wrapped function
1701 * Inputs: string:sFunc - API function name
1702 */
1703 function _fnExternApiFunc (sFunc)
1704 {
1705 return function() {
1706 var aArgs = [_fnSettingsFromNode(this[_oExt.iApiIndex])].concat(
1707 Array.prototype.slice.call(arguments) );
1708 return _oExt.oApi[sFunc].apply( this, aArgs );
1709 };
1710 }
1711
1712 for ( var sFunc in _oExt.oApi )
1713 {
1714 if ( sFunc )
1715 {
1716 /*
1717 * Function: anon
1718 * Purpose: Wrap the plug-in API functions in order to provide the settings as 1st arg
1719 * and execute in this scope
1720 * Returns: -
1721 * Inputs: -
1722 */
1723 this[sFunc] = _fnExternApiFunc(sFunc);
1724 }
1725 }
1726
1727
1728
1729 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1730 * Local functions
1731 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1732
1733 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1734 * Initalisation
1735 */
1736
1737 /*
1738 * Function: _fnInitalise
1739 * Purpose: Draw the table for the first time, adding all required features
1740 * Returns: -
1741 * Inputs: object:oSettings - dataTables settings object
1742 */
1743 function _fnInitalise ( oSettings )
1744 {
1745 /* Ensure that the table data is fully initialised */
1746 if ( oSettings.bInitialised === false )
1747 {
1748 setTimeout( function(){ _fnInitalise( oSettings ); }, 200 );
1749 return;
1750 }
1751
1752 /* Show the display HTML options */
1753 _fnAddOptionsHtml( oSettings );
1754
1755 /* Draw the headers for the table */
1756 _fnDrawHead( oSettings );
1757
1758 /* If there is default sorting required - let's do it. The sort function
1759 * will do the drawing for us. Otherwise we draw the table
1760 */
1761 if ( oSettings.oFeatures.bSort )
1762 {
1763 _fnSort( oSettings, false );
1764 /*
1765 * Add the sorting classes to the header and the body (if needed).
1766 * Reason for doing it here after the first draw is to stop classes being applied to the
1767 * 'static' table.
1768 */
1769 _fnSortingClasses( oSettings );
1770 }
1771 else
1772 {
1773 oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
1774 _fnCalculateEnd( oSettings );
1775 _fnDraw( oSettings );
1776 }
1777
1778 /* if there is an ajax source */
1779 if ( oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide )
1780 {
1781 _fnProcessingDisplay( oSettings, true );
1782
1783 $.getJSON( oSettings.sAjaxSource, null, function(json) {
1784
1785 /* Got the data - add it to the table */
1786 for ( var i=0 ; i<json.aaData.length ; i++ )
1787 {
1788 _fnAddData( oSettings, json.aaData[i] );
1789 }
1790
1791 /* Reset the init display for cookie saving. We've already done a filter, and
1792 * therefore cleared it before. So we need to make it appear 'fresh'
1793 */
1794 oSettings.iInitDisplayStart = oSettings._iDisplayStart;
1795
1796 if ( oSettings.oFeatures.bSort )
1797 {
1798 _fnSort( oSettings );
1799 }
1800 else
1801 {
1802 oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
1803 _fnCalculateEnd( oSettings );
1804 _fnDraw( oSettings );
1805 }
1806 _fnProcessingDisplay( oSettings, false );
1807
1808 /* Run the init callback if there is one */
1809 if ( typeof oSettings.fnInitComplete == 'function' )
1810 {
1811 oSettings.fnInitComplete( oSettings, json );
1812 }
1813 } );
1814 return;
1815 }
1816
1817 /* Run the init callback if there is one */
1818 if ( typeof oSettings.fnInitComplete == 'function' )
1819 {
1820 oSettings.fnInitComplete( oSettings );
1821 }
1822 _fnProcessingDisplay( oSettings, false );
1823 }
1824
1825 /*
1826 * Function: _fnLanguageProcess
1827 * Purpose: Copy language variables from remote object to a local one
1828 * Returns: -
1829 * Inputs: object:oSettings - dataTables settings object
1830 * object:oLanguage - Language information
1831 * bool:bInit - init once complete
1832 */
1833 function _fnLanguageProcess( oSettings, oLanguage, bInit )
1834 {
1835 _fnMap( oSettings.oLanguage, oLanguage, 'sProcessing' );
1836 _fnMap( oSettings.oLanguage, oLanguage, 'sLengthMenu' );
1837 _fnMap( oSettings.oLanguage, oLanguage, 'sZeroRecords' );
1838 _fnMap( oSettings.oLanguage, oLanguage, 'sInfo' );
1839 _fnMap( oSettings.oLanguage, oLanguage, 'sInfoEmpty' );
1840 _fnMap( oSettings.oLanguage, oLanguage, 'sInfoFiltered' );
1841 _fnMap( oSettings.oLanguage, oLanguage, 'sInfoPostFix' );
1842 _fnMap( oSettings.oLanguage, oLanguage, 'sSearch' );
1843
1844 if ( typeof oLanguage.oPaginate != 'undefined' )
1845 {
1846 _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sFirst' );
1847 _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sPrevious' );
1848 _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sNext' );
1849 _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sLast' );
1850 }
1851
1852 if ( bInit )
1853 {
1854 _fnInitalise( oSettings );
1855 }
1856 }
1857
1858 /*
1859 * Function: _fnAddColumn
1860 * Purpose: Add a column to the list used for the table
1861 * Returns: -
1862 * Inputs: object:oSettings - dataTables settings object
1863 * object:oOptions - object with sType, bVisible and bSearchable
1864 * node:nTh - the th element for this column
1865 * Notes: All options in enter column can be over-ridden by the user
1866 * initialisation of dataTables
1867 */
1868 function _fnAddColumn( oSettings, oOptions, nTh )
1869 {
1870 oSettings.aoColumns[ oSettings.aoColumns.length++ ] = {
1871 "sType": null,
1872 "_bAutoType": true,
1873 "bVisible": true,
1874 "bSearchable": true,
1875 "bSortable": true,
1876 "sTitle": nTh ? nTh.innerHTML : '',
1877 "sName": '',
1878 "sWidth": null,
1879 "sClass": null,
1880 "fnRender": null,
1881 "bUseRendered": true,
1882 "iDataSort": oSettings.aoColumns.length-1,
1883 "nTh": nTh ? nTh : document.createElement('th'),
1884 "nTf": null
1885 };
1886
1887 /* User specified column options */
1888 var iLength = oSettings.aoColumns.length-1;
1889 if ( typeof oOptions != 'undefined' && oOptions !== null )
1890 {
1891 var oCol = oSettings.aoColumns[ iLength ];
1892
1893 if ( typeof oOptions.sType != 'undefined' )
1894 {
1895 oCol.sType = oOptions.sType;
1896 oCol._bAutoType = false;
1897 }
1898
1899 _fnMap( oCol, oOptions, "bVisible" );
1900 _fnMap( oCol, oOptions, "bSearchable" );
1901 _fnMap( oCol, oOptions, "bSortable" );
1902 _fnMap( oCol, oOptions, "sTitle" );
1903 _fnMap( oCol, oOptions, "sName" );
1904 _fnMap( oCol, oOptions, "sWidth" );
1905 _fnMap( oCol, oOptions, "sClass" );
1906 _fnMap( oCol, oOptions, "fnRender" );
1907 _fnMap( oCol, oOptions, "bUseRendered" );
1908 _fnMap( oCol, oOptions, "iDataSort" );
1909 }
1910
1911 /* Add a column specific filter */
1912 if ( typeof oSettings.aoPreSearchCols[ iLength ] == 'undefined' ||
1913 oSettings.aoPreSearchCols[ iLength ] === null )
1914 {
1915 oSettings.aoPreSearchCols[ iLength ] = {
1916 "sSearch": "",
1917 "bEscapeRegex": true
1918 };
1919 }
1920 else if ( typeof oSettings.aoPreSearchCols[ iLength ].bEscapeRegex == 'undefined' )
1921 {
1922 /* Don't require that the user must specify bEscapeRegex */
1923 oSettings.aoPreSearchCols[ iLength ].bEscapeRegex = true;
1924 }
1925 }
1926
1927 /*
1928 * Function: _fnAddData
1929 * Purpose: Add a data array to the table, creating DOM node etc
1930 * Returns: int: - >=0 if successful (index of new aoData entry), -1 if failed
1931 * Inputs: object:oSettings - dataTables settings object
1932 * array:aData - data array to be added
1933 */
1934 function _fnAddData ( oSettings, aData )
1935 {
1936 /* Sanity check the length of the new array */
1937 if ( aData.length != oSettings.aoColumns.length )
1938 {
1939 alert( "Warning - added data does not match known number of columns" );
1940 return -1;
1941 }
1942
1943 /* Create the object for storing information about this new row */
1944 var iThisIndex = oSettings.aoData.length;
1945 oSettings.aoData.push( {
1946 "_iId": oSettings.iNextId++,
1947 "_aData": aData.slice(),
1948 "nTr": document.createElement('tr'),
1949 "_anHidden": []
1950 } );
1951
1952 /* Create the cells */
1953 var nTd;
1954 for ( var i=0 ; i<aData.length ; i++ )
1955 {
1956 nTd = document.createElement('td');
1957
1958 if ( typeof oSettings.aoColumns[i].fnRender == 'function' )
1959 {
1960 var sRendered = oSettings.aoColumns[i].fnRender( {
1961 "iDataRow": iThisIndex,
1962 "iDataColumn": i,
1963 "aData": aData
1964 } );
1965 nTd.innerHTML = sRendered;
1966 if ( oSettings.aoColumns[i].bUseRendered )
1967 {
1968 /* Use the rendered data for filtering/sorting */
1969 oSettings.aoData[iThisIndex]._aData[i] = sRendered;
1970 }
1971 }
1972 else
1973 {
1974 nTd.innerHTML = aData[i];
1975 }
1976
1977 if ( oSettings.aoColumns[i].sClass !== null )
1978 {
1979 nTd.className = oSettings.aoColumns[i].sClass;
1980 }
1981
1982 /* See if we should auto-detect the column type */
1983 if ( oSettings.aoColumns[i]._bAutoType && oSettings.aoColumns[i].sType != 'string' )
1984 {
1985 /* Attempt to auto detect the type - same as _fnGatherData() */
1986 if ( oSettings.aoColumns[i].sType === null )
1987 {
1988 oSettings.aoColumns[i].sType = _fnDetectType( aData[i] );
1989 }
1990 else if ( oSettings.aoColumns[i].sType == "date" ||
1991 oSettings.aoColumns[i].sType == "numeric" )
1992 {
1993 oSettings.aoColumns[i].sType = _fnDetectType( aData[i] );
1994 }
1995 }
1996
1997 if ( oSettings.aoColumns[i].bVisible )
1998 {
1999 oSettings.aoData[iThisIndex].nTr.appendChild( nTd );
2000 }
2001 else
2002 {
2003 oSettings.aoData[iThisIndex]._anHidden[i] = nTd;
2004 }
2005 }
2006
2007 /* Add to the display array */
2008 oSettings.aiDisplayMaster.push( iThisIndex );
2009 return iThisIndex;
2010 }
2011
2012 /*
2013 * Function: _fnGatherData
2014 * Purpose: Read in the data from the target table
2015 * Returns: -
2016 * Inputs: object:oSettings - dataTables settings object
2017 */
2018 function _fnGatherData( oSettings )
2019 {
2020 var iLoop;
2021 var i, j;
2022
2023 /*
2024 * Process by row first
2025 * Add the data object for the whole table - storing the tr node. Note - no point in getting
2026 * DOM based data if we are going to go and replace it with Ajax source data.
2027 */
2028 if ( oSettings.sAjaxSource === null )
2029 {
2030 $('tbody:eq(0)>tr', oSettings.nTable).each( function() {
2031 var iThisIndex = oSettings.aoData.length;
2032 oSettings.aoData.push( {
2033 "_iId": oSettings.iNextId++,
2034 "_aData": [],
2035 "nTr": this,
2036 "_anHidden": []
2037 } );
2038
2039 oSettings.aiDisplayMaster.push( iThisIndex );
2040
2041 /* Add the data for this column */
2042 var aLocalData = oSettings.aoData[iThisIndex]._aData;
2043 $('td', this).each( function( i ) {
2044 aLocalData[i] = this.innerHTML;
2045 } );
2046 } );
2047 }
2048
2049 /*
2050 * Now process by column
2051 */
2052 var iCorrector = 0;
2053 for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
2054 {
2055 /* Get the title of the column - unless there is a user set one */
2056 if ( oSettings.aoColumns[i].sTitle === null )
2057 {
2058 oSettings.aoColumns[i].sTitle = oSettings.aoColumns[i].nTh.innerHTML;
2059 }
2060
2061 var bAutoType = oSettings.aoColumns[i]._bAutoType;
2062 var bRender = typeof oSettings.aoColumns[i].fnRender == 'function';
2063 var bClass = oSettings.aoColumns[i].sClass !== null;
2064 var bVisible = oSettings.aoColumns[i].bVisible;
2065
2066 /* A single loop to rule them all (and be more efficient) */
2067 if ( bAutoType || bRender || bClass || !bVisible )
2068 {
2069 iLoop = oSettings.aoData.length;
2070 for ( j=0 ; j<iLoop ; j++ )
2071 {
2072 var nCellNode = oSettings.aoData[j].nTr.getElementsByTagName('td')[ i-iCorrector ];
2073
2074 if ( bAutoType )
2075 {
2076 if ( oSettings.aoColumns[i].sType === null )
2077 {
2078 oSettings.aoColumns[i].sType = _fnDetectType( oSettings.aoData[j]._aData[i] );
2079 }
2080 else if ( oSettings.aoColumns[i].sType == "date" ||
2081 oSettings.aoColumns[i].sType == "numeric" )
2082 {
2083 /* If type is date or numeric - ensure that all collected data
2084 * in the column is of the same type
2085 */
2086 oSettings.aoColumns[i].sType = _fnDetectType( oSettings.aoData[j]._aData[i] );
2087 }
2088 /* The else would be 'type = string' we don't want to do anything
2089 * if that is the case
2090 */
2091 }
2092
2093 if ( bRender )
2094 {
2095 var sRendered = oSettings.aoColumns[i].fnRender( {
2096 "iDataRow": j,
2097 "iDataColumn": i,
2098 "aData": oSettings.aoData[j]._aData
2099 } );
2100 nCellNode.innerHTML = sRendered;
2101 if ( oSettings.aoColumns[i].bUseRendered )
2102 {
2103 /* Use the rendered data for filtering/sorting */
2104 oSettings.aoData[j]._aData[i] = sRendered;
2105 }
2106 }
2107
2108 if ( bClass )
2109 {
2110 nCellNode.className += ' '+oSettings.aoColumns[i].sClass;
2111 }
2112
2113 if ( !bVisible )
2114 {
2115 oSettings.aoData[j]._anHidden[i] = nCellNode;
2116 nCellNode.parentNode.removeChild( nCellNode );
2117 }
2118 }
2119
2120 /* Keep an index corrector for the next loop */
2121 if ( !bVisible )
2122 {
2123 iCorrector++;
2124 }
2125 }
2126 }
2127 }
2128
2129
2130
2131 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2132 * Drawing functions
2133 */
2134
2135 /*
2136 * Function: _fnDrawHead
2137 * Purpose: Create the HTML header for the table
2138 * Returns: -
2139 * Inputs: object:oSettings - dataTables settings object
2140 */
2141 function _fnDrawHead( oSettings )
2142 {
2143 var i, nTh, iLen;
2144 var iThs = oSettings.nTable.getElementsByTagName('thead')[0].getElementsByTagName('th').length;
2145 var iCorrector = 0;
2146
2147 /* If there is a header in place - then use it - otherwise it's going to get nuked... */
2148 if ( iThs !== 0 )
2149 {
2150 /* We've got a thead from the DOM, so remove hidden columns and apply width to vis cols */
2151 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2152 {
2153 //oSettings.aoColumns[i].nTh = nThs[i];
2154 nTh = oSettings.aoColumns[i].nTh;
2155
2156 if ( oSettings.aoColumns[i].bVisible )
2157 {
2158 /* Set width */
2159 if ( oSettings.aoColumns[i].sWidth !== null )
2160 {
2161 nTh.style.width = oSettings.aoColumns[i].sWidth;
2162 }
2163
2164 /* Set the title of the column if it is user defined (not what was auto detected) */
2165 if ( oSettings.aoColumns[i].sTitle != nTh.innerHTML )
2166 {
2167 nTh.innerHTML = oSettings.aoColumns[i].sTitle;
2168 }
2169 }
2170 else
2171 {
2172 nTh.parentNode.removeChild( nTh );
2173 iCorrector++;
2174 }
2175 }
2176 }
2177 else
2178 {
2179 /* We don't have a header in the DOM - so we are going to have to create one */
2180 var nTr = document.createElement( "tr" );
2181
2182 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2183 {
2184 if ( oSettings.aoColumns[i].bVisible )
2185 {
2186 nTh = oSettings.aoColumns[i].nTh;
2187
2188 if ( oSettings.aoColumns[i].sClass !== null )
2189 {
2190 nTh.className = oSettings.aoColumns[i].sClass;
2191 }
2192
2193 if ( oSettings.aoColumns[i].sWidth !== null )
2194 {
2195 nTh.style.width = oSettings.aoColumns[i].sWidth;
2196 }
2197
2198 nTh.innerHTML = oSettings.aoColumns[i].sTitle;
2199 nTr.appendChild( nTh );
2200 }
2201 }
2202 $('thead', oSettings.nTable).html( '' )[0].appendChild( nTr );
2203 }
2204
2205 /* Add the extra markup needed by jQuery UI's themes */
2206 if ( oSettings.bJUI )
2207 {
2208 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2209 {
2210 var nSpan = document.createElement('span');
2211 oSettings.aoColumns[i].nTh.appendChild( nSpan );
2212 }
2213 }
2214
2215 /* Add sort listener */
2216 if ( oSettings.oFeatures.bSort )
2217 {
2218 for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
2219 {
2220 if ( oSettings.aoColumns[i].bSortable === false )
2221 {
2222 continue;
2223 }
2224
2225 $(oSettings.aoColumns[i].nTh).click( function (e) {
2226 var iDataIndex;
2227 /* Find which column we are sorting on - can't use index() due to colspan etc */
2228 for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
2229 {
2230 if ( oSettings.aoColumns[i].nTh == this )
2231 {
2232 iDataIndex = i;
2233 break;
2234 }
2235 }
2236
2237 /* If the column is not sortable - don't to anything */
2238 if ( oSettings.aoColumns[iDataIndex].bSortable === false )
2239 {
2240 return;
2241 }
2242
2243 /*
2244 * This is a little bit odd I admit... I declare a temporary function inside the scope of
2245 * _fnDrawHead and the click handler in order that the code presented here can be used
2246 * twice - once for when bProcessing is enabled, and another time for when it is
2247 * disabled, as we need to perform slightly different actions.
2248 * Basically the issue here is that the Javascript engine in modern browsers don't
2249 * appear to allow the rendering engine to update the display while it is still excuting
2250 * it's thread (well - it does but only after long intervals). This means that the
2251 * 'processing' display doesn't appear for a table sort. To break the js thread up a bit
2252 * I force an execution break by using setTimeout - but this breaks the expected
2253 * thread continuation for the end-developer's point of view (their code would execute
2254 * too early), so we on;y do it when we absolutely have to.
2255 */
2256 var fnInnerSorting = function () {
2257 if ( e.shiftKey )
2258 {
2259 /* If the shift key is pressed then we are multipe column sorting */
2260 var bFound = false;
2261 for ( var i=0 ; i<oSettings.aaSorting.length ; i++ )
2262 {
2263 if ( oSettings.aaSorting[i][0] == iDataIndex )
2264 {
2265 if ( oSettings.aaSorting[i][1] == "asc" )
2266 {
2267 oSettings.aaSorting[i][1] = "desc";
2268 }
2269 else
2270 {
2271 oSettings.aaSorting.splice( i, 1 );
2272 }
2273 bFound = true;
2274 break;
2275 }
2276 }
2277
2278 if ( bFound === false )
2279 {
2280 oSettings.aaSorting.push( [ iDataIndex, "asc" ] );
2281 }
2282 }
2283 else
2284 {
2285 /* If no shift key then single column sort */
2286 if ( oSettings.aaSorting.length == 1 && oSettings.aaSorting[0][0] == iDataIndex )
2287 {
2288 oSettings.aaSorting[0][1] = oSettings.aaSorting[0][1]=="asc" ? "desc" : "asc";
2289 }
2290 else
2291 {
2292 oSettings.aaSorting.splice( 0, oSettings.aaSorting.length );
2293 oSettings.aaSorting.push( [ iDataIndex, "asc" ] );
2294 }
2295 }
2296
2297 /* Run the sort */
2298 _fnSort( oSettings );
2299 }; /* /fnInnerSorting */
2300
2301 if ( !oSettings.oFeatures.bProcessing )
2302 {
2303 fnInnerSorting();
2304 }
2305 else
2306 {
2307 _fnProcessingDisplay( oSettings, true );
2308 setTimeout( function() {
2309 fnInnerSorting();
2310 if ( !oSettings.oFeatures.bServerSide )
2311 {
2312 _fnProcessingDisplay( oSettings, false );
2313 }
2314 }, 0 );
2315 }
2316 } ); /* /click */
2317 } /* For each column */
2318
2319 /* Take the brutal approach to cancelling text selection due to the shift key */
2320 $('thead th', oSettings.nTable).mousedown( function (e) {
2321 if ( e.shiftKey )
2322 {
2323 this.onselectstart = function() { return false; };
2324 return false;
2325 }
2326 } );
2327 } /* /if feature sort */
2328
2329 /* Set an absolute width for the table such that pagination doesn't
2330 * cause the table to resize
2331 */
2332 if ( oSettings.oFeatures.bAutoWidth )
2333 {
2334 oSettings.nTable.style.width = oSettings.nTable.offsetWidth+"px";
2335 }
2336
2337 /* Cache the footer elements */
2338 var nTfoot = oSettings.nTable.getElementsByTagName('tfoot');
2339 if ( nTfoot.length !== 0 )
2340 {
2341 iCorrector = 0;
2342 var nTfs = nTfoot[0].getElementsByTagName('th');
2343 for ( i=0, iLen=nTfs.length ; i<iLen ; i++ )
2344 {
2345 oSettings.aoColumns[i].nTf = nTfs[i-iCorrector];
2346 if ( !oSettings.aoColumns[i].bVisible )
2347 {
2348 nTfs[i-iCorrector].parentNode.removeChild( nTfs[i-iCorrector] );
2349 iCorrector++;
2350 }
2351 }
2352 }
2353 }
2354
2355 /*
2356 * Function: _fnDraw
2357 * Purpose: Insert the required TR nodes into the table for display
2358 * Returns: -
2359 * Inputs: object:oSettings - dataTables settings object
2360 */
2361 function _fnDraw( oSettings )
2362 {
2363 var i;
2364 var anRows = [];
2365 var iRowCount = 0;
2366 var bRowError = false;
2367 var iStrips = oSettings.asStripClasses.length;
2368 var iOpenRows = oSettings.aoOpenRows.length;
2369
2370 /* If we are dealing with Ajax - do it here */
2371 if ( oSettings.oFeatures.bServerSide &&
2372 !_fnAjaxUpdate( oSettings ) )
2373 {
2374 return;
2375 }
2376
2377 if ( oSettings.aiDisplay.length !== 0 )
2378 {
2379 var iStart = oSettings._iDisplayStart;
2380 var iEnd = oSettings._iDisplayEnd;
2381
2382 if ( oSettings.oFeatures.bServerSide )
2383 {
2384 iStart = 0;
2385 iEnd = oSettings.aoData.length;
2386 }
2387
2388 for ( var j=iStart ; j<iEnd ; j++ )
2389 {
2390 var nRow = oSettings.aoData[ oSettings.aiDisplay[j] ].nTr;
2391
2392 /* Remove any old stripping classes and then add the new one */
2393 if ( iStrips !== 0 )
2394 {
2395 $(nRow).removeClass( oSettings.asStripClasses.join(' ') );
2396 $(nRow).addClass( oSettings.asStripClasses[ iRowCount % iStrips ] );
2397 }
2398
2399 /* Custom row callback function - might want to manipule the row */
2400 if ( typeof oSettings.fnRowCallback == "function" )
2401 {
2402 nRow = oSettings.fnRowCallback( nRow,
2403 oSettings.aoData[ oSettings.aiDisplay[j] ]._aData, iRowCount, j );
2404 if ( !nRow && !bRowError )
2405 {
2406 alert( "Error: A node was not returned by fnRowCallback" );
2407 bRowError = true;
2408 }
2409 }
2410
2411 anRows.push( nRow );
2412 iRowCount++;
2413
2414 /* If there is an open row - and it is attached to this parent - attach it on redraw */
2415 if ( iOpenRows !== 0 )
2416 {
2417 for ( var k=0 ; k<iOpenRows ; k++ )
2418 {
2419 if ( nRow == oSettings.aoOpenRows[k].nParent )
2420 {
2421 anRows.push( oSettings.aoOpenRows[k].nTr );
2422 }
2423 }
2424 }
2425 }
2426 }
2427 else
2428 {
2429 /* Table is empty - create a row with an empty message in it */
2430 anRows[ 0 ] = document.createElement( 'tr' );
2431
2432 if ( typeof oSettings.asStripClasses[0] != 'undefined' )
2433 {
2434 anRows[ 0 ].className = oSettings.asStripClasses[0];
2435 }
2436
2437 var nTd = document.createElement( 'td' );
2438 nTd.setAttribute( 'valign', "top" );
2439 nTd.colSpan = oSettings.aoColumns.length;
2440 nTd.className = oSettings.oClasses.sRowEmpty;
2441 nTd.innerHTML = oSettings.oLanguage.sZeroRecords;
2442
2443 anRows[ iRowCount ].appendChild( nTd );
2444 }
2445
2446 /* Callback the header and footer custom funcation if there is one */
2447 if ( typeof oSettings.fnHeaderCallback == 'function' )
2448 {
2449 oSettings.fnHeaderCallback( $('thead tr', oSettings.nTable)[0],
2450 _fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(),
2451 oSettings.aiDisplay );
2452 }
2453
2454 if ( typeof oSettings.fnFooterCallback == 'function' )
2455 {
2456 oSettings.fnFooterCallback( $('tfoot tr', oSettings.nTable)[0],
2457 _fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(),
2458 oSettings.aiDisplay );
2459 }
2460
2461 /*
2462 * Need to remove any old row from the display - note we can't just empty the tbody using
2463 * .html('') since this will unbind the jQuery event handlers (even although the node still
2464 * exists!) - note the initially odd ':eq(0)>tr' expression. This basically ensures that we
2465 * only get tr elements of the tbody that the data table has been initialised on. If there
2466 * are nested tables then we don't want to remove those elements.
2467 */
2468 var nTrs = $('tbody:eq(0)>tr', oSettings.nTable);
2469 for ( i=0 ; i<nTrs.length ; i++ )
2470 {
2471 nTrs[i].parentNode.removeChild( nTrs[i] );
2472 }
2473
2474 /* Put the draw table into the dom */
2475 var nBody = $('tbody:eq(0)', oSettings.nTable);
2476 if ( nBody[0] )
2477 {
2478 for ( i=0 ; i<anRows.length ; i++ )
2479 {
2480 nBody[0].appendChild( anRows[i] );
2481 }
2482 }
2483
2484 /* Update the pagination display buttons */
2485 if ( oSettings.oFeatures.bPaginate )
2486 {
2487 _oExt.oPagination[ oSettings.sPaginationType ].fnUpdate( oSettings, function( oSettings ) {
2488 _fnCalculateEnd( oSettings );
2489 _fnDraw( oSettings );
2490 } );
2491 }
2492
2493 /* Show information about the table */
2494 if ( oSettings.oFeatures.bInfo && oSettings.anFeatures.i )
2495 {
2496 /* Update the information */
2497 if ( oSettings.fnRecordsDisplay() === 0 &&
2498 oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() )
2499 {
2500 oSettings.anFeatures.i.innerHTML =
2501 oSettings.oLanguage.sInfoEmpty+ oSettings.oLanguage.sInfoPostFix;
2502 }
2503 else if ( oSettings.fnRecordsDisplay() === 0 )
2504 {
2505 oSettings.anFeatures.i.innerHTML = oSettings.oLanguage.sInfoEmpty +' '+
2506 oSettings.oLanguage.sInfoFiltered.replace('_MAX_',
2507 oSettings.fnRecordsTotal())+ oSettings.oLanguage.sInfoPostFix;
2508 }
2509 else if ( oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() )
2510 {
2511 oSettings.anFeatures.i.innerHTML =
2512 oSettings.oLanguage.sInfo.
2513 replace('_START_',oSettings._iDisplayStart+1).
2514 replace('_END_',oSettings.fnDisplayEnd()).
2515 replace('_TOTAL_',oSettings.fnRecordsDisplay())+
2516 oSettings.oLanguage.sInfoPostFix;
2517 }
2518 else
2519 {
2520 oSettings.anFeatures.i.innerHTML =
2521 oSettings.oLanguage.sInfo.
2522 replace('_START_',oSettings._iDisplayStart+1).
2523 replace('_END_',oSettings.fnDisplayEnd()).
2524 replace('_TOTAL_',oSettings.fnRecordsDisplay()) +' '+
2525 oSettings.oLanguage.sInfoFiltered.replace('_MAX_', oSettings.fnRecordsTotal())+
2526 oSettings.oLanguage.sInfoPostFix;
2527 }
2528 }
2529
2530 /* Alter the sorting classes to take account of the changes */
2531 if ( oSettings.oFeatures.bServerSide && oSettings.oFeatures.bSort )
2532 {
2533 _fnSortingClasses( oSettings );
2534 }
2535
2536 /* Save the table state on each draw */
2537 _fnSaveState( oSettings );
2538
2539 /* Drawing is finished - call the callback if there is one */
2540 if ( typeof oSettings.fnDrawCallback == 'function' )
2541 {
2542 oSettings.fnDrawCallback( oSettings );
2543 }
2544 }
2545
2546 /*
2547 * Function: _fnReDraw
2548 * Purpose: Redraw the table - taking account of the various features which are enabled
2549 * Returns: -
2550 * Inputs: object:oSettings - dataTables settings object
2551 */
2552 function _fnReDraw( oSettings )
2553 {
2554 if ( oSettings.oFeatures.bSort )
2555 {
2556 /* Sorting will refilter and draw for us */
2557 _fnSort( oSettings, oSettings.oPreviousSearch );
2558 }
2559 else if ( oSettings.oFeatures.bFilter )
2560 {
2561 /* Filtering will redraw for us */
2562 _fnFilterComplete( oSettings, oSettings.oPreviousSearch );
2563 }
2564 else
2565 {
2566 _fnCalculateEnd( oSettings );
2567 _fnDraw( oSettings );
2568 }
2569 }
2570
2571 /*
2572 * Function: _fnAjaxUpdate
2573 * Purpose: Update the table using an Ajax call
2574 * Returns: bool: block the table drawing or not
2575 * Inputs: object:oSettings - dataTables settings object
2576 */
2577 function _fnAjaxUpdate( oSettings )
2578 {
2579 if ( oSettings.bAjaxDataGet )
2580 {
2581 _fnProcessingDisplay( oSettings, true );
2582 var iColumns = oSettings.aoColumns.length;
2583 var aoData = [];
2584 var i;
2585
2586 /* Paging and general */
2587 oSettings.iServerDraw++;
2588 aoData.push( { "name": "sEcho", "value": oSettings.iServerDraw } );
2589 aoData.push( { "name": "iColumns", "value": iColumns } );
2590 aoData.push( { "name": "sColumns", "value": _fnColumnOrdering(oSettings) } );
2591 aoData.push( { "name": "iDisplayStart", "value": oSettings._iDisplayStart } );
2592 aoData.push( { "name": "iDisplayLength", "value": oSettings.oFeatures.bPaginate !== false ?
2593 oSettings._iDisplayLength : -1 } );
2594
2595 /* Filtering */
2596 if ( oSettings.oFeatures.bFilter !== false )
2597 {
2598 aoData.push( { "name": "sSearch", "value": oSettings.oPreviousSearch.sSearch } );
2599 aoData.push( { "name": "bEscapeRegex", "value": oSettings.oPreviousSearch.bEscapeRegex } );
2600 for ( i=0 ; i<iColumns ; i++ )
2601 {
2602 aoData.push( { "name": "sSearch_"+i, "value": oSettings.aoPreSearchCols[i].sSearch } );
2603 aoData.push( { "name": "bEscapeRegex_"+i, "value": oSettings.aoPreSearchCols[i].bEscapeRegex } );
2604 }
2605 }
2606
2607 /* Sorting */
2608 if ( oSettings.oFeatures.bSort !== false )
2609 {
2610 var iFixed = oSettings.aaSortingFixed !== null ? oSettings.aaSortingFixed.length : 0;
2611 var iUser = oSettings.aaSorting.length;
2612 aoData.push( { "name": "iSortingCols", "value": iFixed+iUser } );
2613 for ( i=0 ; i<iFixed ; i++ )
2614 {
2615 aoData.push( { "name": "iSortCol_"+i, "value": oSettings.aaSortingFixed[i][0] } );
2616 aoData.push( { "name": "iSortDir_"+i, "value": oSettings.aaSortingFixed[i][1] } );
2617 }
2618
2619 for ( i=0 ; i<iUser ; i++ )
2620 {
2621 aoData.push( { "name": "iSortCol_"+(i+iFixed), "value": oSettings.aaSorting[i][0] } );
2622 aoData.push( { "name": "iSortDir_"+(i+iFixed), "value": oSettings.aaSorting[i][1] } );
2623 }
2624 }
2625
2626 oSettings.fnServerData( oSettings.sAjaxSource, aoData, function(json) {
2627 _fnAjaxUpdateDraw( oSettings, json );
2628 } );
2629 return false;
2630 }
2631 else
2632 {
2633 return true;
2634 }
2635 }
2636
2637 /*
2638 * Function: _fnAjaxUpdateDraw
2639 * Purpose: Data the data from the server (nuking the old) and redraw the table
2640 * Returns: -
2641 * Inputs: object:oSettings - dataTables settings object
2642 * object:json - json data return from the server.
2643 * The following must be defined:
2644 * iTotalRecords, iTotalDisplayRecords, aaData
2645 * The following may be defined:
2646 * sColumns
2647 */
2648 function _fnAjaxUpdateDraw ( oSettings, json )
2649 {
2650 if ( typeof json.sEcho != 'undefined' )
2651 {
2652 /* Protect against old returns over-writing a new one. Possible when you get
2653 * very fast interaction, and later queires are completed much faster
2654 */
2655 if ( json.sEcho*1 < oSettings.iServerDraw )
2656 {
2657 return;
2658 }
2659 else
2660 {
2661 oSettings.iServerDraw = json.sEcho * 1;
2662 }
2663 }
2664
2665 _fnClearTable( oSettings );
2666 oSettings._iRecordsTotal = json.iTotalRecords;
2667 oSettings._iRecordsDisplay = json.iTotalDisplayRecords;
2668
2669 /* Determine if reordering is required */
2670 var sOrdering = _fnColumnOrdering(oSettings);
2671 var bReOrder = (json.sColumns != 'undefined' && sOrdering !== "" && json.sColumns != sOrdering );
2672 if ( bReOrder )
2673 {
2674 var aiIndex = _fnReOrderIndex( oSettings, json.sColumns );
2675 }
2676
2677 for ( var i=0, iLen=json.aaData.length ; i<iLen ; i++ )
2678 {
2679 if ( bReOrder )
2680 {
2681 /* If we need to re-order, then create a new array with the correct order and add it */
2682 var aData = [];
2683 for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ )
2684 {
2685 aData.push( json.aaData[i][ aiIndex[j] ] );
2686 }
2687 _fnAddData( oSettings, aData );
2688 }
2689 else
2690 {
2691 /* No re-order required, sever got it "right" - just straight add */
2692 _fnAddData( oSettings, json.aaData[i] );
2693 }
2694 }
2695 oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
2696
2697 oSettings.bAjaxDataGet = false;
2698 _fnDraw( oSettings );
2699 oSettings.bAjaxDataGet = true;
2700 _fnProcessingDisplay( oSettings, false );
2701 }
2702
2703 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2704 * Options (features) HTML
2705 */
2706
2707 /*
2708 * Function: _fnAddOptionsHtml
2709 * Purpose: Add the options to the page HTML for the table
2710 * Returns: -
2711 * Inputs: object:oSettings - dataTables settings object
2712 */
2713 function _fnAddOptionsHtml ( oSettings )
2714 {
2715 /*
2716 * Create a temporary, empty, div which we can later on replace with what we have generated
2717 * we do it this way to rendering the 'options' html offline - speed :-)
2718 */
2719 var nHolding = document.createElement( 'div' );
2720 oSettings.nTable.parentNode.insertBefore( nHolding, oSettings.nTable );
2721
2722 /*
2723 * All DataTables are wrapped in a div - this is not currently optional - backwards
2724 * compatability. It can be removed if you don't want it.
2725 */
2726 var nWrapper = document.createElement( 'div' );
2727 nWrapper.className = oSettings.oClasses.sWrapper;
2728 if ( oSettings.sTableId !== '' )
2729 {
2730 nWrapper.setAttribute( 'id', oSettings.sTableId+'_wrapper' );
2731 }
2732
2733 /* Track where we want to insert the option */
2734 var nInsertNode = nWrapper;
2735
2736 /* IE don't treat strings as arrays */
2737 var sDom = oSettings.sDomPositioning.split('');
2738
2739 /* Loop over the user set positioning and place the elements as needed */
2740 var nTmp;
2741 for ( var i=0 ; i<sDom.length ; i++ )
2742 {
2743 var cOption = sDom[i];
2744
2745 if ( cOption == '<' )
2746 {
2747 /* New container div */
2748 var nNewNode = document.createElement( 'div' );
2749
2750 /* Check to see if we should append a class name to the container */
2751 var cNext = sDom[i+1];
2752 if ( cNext == "'" || cNext == '"' )
2753 {
2754 var sClass = "";
2755 var j = 2;
2756 while ( sDom[i+j] != cNext )
2757 {
2758 sClass += sDom[i+j];
2759 j++;
2760 }
2761 nNewNode.className = sClass;
2762 i += j; /* Move along the position array */
2763 }
2764
2765 nInsertNode.appendChild( nNewNode );
2766 nInsertNode = nNewNode;
2767 }
2768 else if ( cOption == '>' )
2769 {
2770 /* End container div */
2771 nInsertNode = nInsertNode.parentNode;
2772 }
2773 else if ( cOption == 'l' && oSettings.oFeatures.bPaginate && oSettings.oFeatures.bLengthChange )
2774 {
2775 /* Length */
2776 nTmp = _fnFeatureHtmlLength( oSettings );
2777 oSettings.anFeatures[cOption] = nTmp;
2778 nInsertNode.appendChild( nTmp );
2779 }
2780 else if ( cOption == 'f' && oSettings.oFeatures.bFilter )
2781 {
2782 /* Filter */
2783 nTmp = _fnFeatureHtmlFilter( oSettings );
2784 oSettings.anFeatures[cOption] = nTmp;
2785 nInsertNode.appendChild( nTmp );
2786 }
2787 else if ( cOption == 'r' && oSettings.oFeatures.bProcessing )
2788 {
2789 /* pRocessing */
2790 nTmp = _fnFeatureHtmlProcessing( oSettings );
2791 oSettings.anFeatures[cOption] = nTmp;
2792 nInsertNode.appendChild( nTmp );
2793 }
2794 else if ( cOption == 't' )
2795 {
2796 /* Table */
2797 oSettings.anFeatures[cOption] = oSettings.nTable;
2798 nInsertNode.appendChild( oSettings.nTable );
2799 }
2800 else if ( cOption == 'i' && oSettings.oFeatures.bInfo )
2801 {
2802 /* Info */
2803 nTmp = _fnFeatureHtmlInfo( oSettings );
2804 oSettings.anFeatures[cOption] = nTmp;
2805 nInsertNode.appendChild( nTmp );
2806 }
2807 else if ( cOption == 'p' && oSettings.oFeatures.bPaginate )
2808 {
2809 /* Pagination */
2810 nTmp = _fnFeatureHtmlPaginate( oSettings );
2811 oSettings.anFeatures[cOption] = nTmp;
2812 nInsertNode.appendChild( nTmp );
2813 }
2814 else if ( _oExt.aoFeatures.length !== 0 )
2815 {
2816 var aoFeatures = _oExt.aoFeatures;
2817 for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ )
2818 {
2819 if ( cOption == aoFeatures[k].cFeature )
2820 {
2821 nTmp = aoFeatures[k].fnInit( oSettings );
2822 oSettings.anFeatures[cOption] = nTmp;
2823 nInsertNode.appendChild( nTmp );
2824 break;
2825 }
2826 }
2827 }
2828 }
2829
2830 /* Built our DOM structure - replace the holding div with what we want */
2831 nHolding.parentNode.replaceChild( nWrapper, nHolding );
2832 }
2833
2834 /*
2835 * Function: _fnFeatureHtmlFilter
2836 * Purpose: Generate the node required for filtering text
2837 * Returns: node
2838 * Inputs: object:oSettings - dataTables settings object
2839 */
2840 function _fnFeatureHtmlFilter ( oSettings )
2841 {
2842 var nFilter = document.createElement( 'div' );
2843 if ( oSettings.sTableId !== '' )
2844 {
2845 nFilter.setAttribute( 'id', oSettings.sTableId+'_filter' );
2846 }
2847 nFilter.className = oSettings.oClasses.sFilter;
2848 var sSpace = oSettings.oLanguage.sSearch==="" ? "" : " ";
2849 nFilter.innerHTML = oSettings.oLanguage.sSearch+sSpace+'<input type="text" />';
2850
2851 var jqFilter = $("input", nFilter);
2852 jqFilter.val( oSettings.oPreviousSearch.sSearch.replace('"','&quot;') );
2853 jqFilter.keyup( function(e) {
2854 _fnFilterComplete( oSettings, {
2855 "sSearch": this.value,
2856 "bEscapeRegex": oSettings.oPreviousSearch.bEscapeRegex
2857 } );
2858
2859 /* Prevent default */
2860 return false;
2861 } );
2862
2863 return nFilter;
2864 }
2865
2866 /*
2867 * Function: _fnFeatureHtmlInfo
2868 * Purpose: Generate the node required for the info display
2869 * Returns: node
2870 * Inputs: object:oSettings - dataTables settings object
2871 */
2872 function _fnFeatureHtmlInfo ( oSettings )
2873 {
2874 var nInfo = document.createElement( 'div' );
2875 if ( oSettings.sTableId !== '' )
2876 {
2877 nInfo.setAttribute( 'id', oSettings.sTableId+'_info' );
2878 }
2879 nInfo.className = oSettings.oClasses.sInfo;
2880 return nInfo;
2881 }
2882
2883 /*
2884 * Function: _fnFeatureHtmlPaginate
2885 * Purpose: Generate the node required for default pagination
2886 * Returns: node
2887 * Inputs: object:oSettings - dataTables settings object
2888 */
2889 function _fnFeatureHtmlPaginate ( oSettings )
2890 {
2891 var nPaginate = document.createElement( 'div' );
2892 nPaginate.className = oSettings.oClasses.sPaging+oSettings.sPaginationType;
2893 oSettings.anFeatures.p = nPaginate; /* Need this stored in order to call paging plug-ins */
2894
2895 _oExt.oPagination[ oSettings.sPaginationType ].fnInit( oSettings, function( oSettings ) {
2896 _fnCalculateEnd( oSettings );
2897 _fnDraw( oSettings );
2898 } );
2899 return nPaginate;
2900 }
2901
2902 /*
2903 * Function: _fnFeatureHtmlLength
2904 * Purpose: Generate the node required for user display length changing
2905 * Returns: node
2906 * Inputs: object:oSettings - dataTables settings object
2907 */
2908 function _fnFeatureHtmlLength ( oSettings )
2909 {
2910 /* This can be overruled by not using the _MENU_ var/macro in the language variable */
2911 var sName = (oSettings.sTableId === "") ? "" : 'name="'+oSettings.sTableId+'_length"';
2912 var sStdMenu =
2913 '<select size="1" '+sName+'>'+
2914 '<option value="10">10</option>'+
2915 '<option value="25">25</option>'+
2916 '<option value="50">50</option>'+
2917 '<option value="100">100</option>'+
2918 '</select>';
2919
2920 var nLength = document.createElement( 'div' );
2921 if ( oSettings.sTableId !== '' )
2922 {
2923 nLength.setAttribute( 'id', oSettings.sTableId+'_length' );
2924 }
2925 nLength.className = oSettings.oClasses.sLength;
2926 nLength.innerHTML = oSettings.oLanguage.sLengthMenu.replace( '_MENU_', sStdMenu );
2927
2928 /*
2929 * Set the length to the current display length - thanks to Andrea Pavlovic for this fix,
2930 * and Stefan Skopnik for fixing the fix!
2931 */
2932 $('select option[value="'+oSettings._iDisplayLength+'"]',nLength).attr("selected",true);
2933
2934 $('select', nLength).change( function(e) {
2935 oSettings._iDisplayLength = parseInt($(this).val(), 10);
2936
2937 _fnCalculateEnd( oSettings );
2938
2939 /* If we have space to show extra rows (backing up from the end point - then do so */
2940 if ( oSettings._iDisplayEnd == oSettings.aiDisplay.length )
2941 {
2942 oSettings._iDisplayStart = oSettings._iDisplayEnd - oSettings._iDisplayLength;
2943 if ( oSettings._iDisplayStart < 0 )
2944 {
2945 oSettings._iDisplayStart = 0;
2946 }
2947 }
2948
2949 if ( oSettings._iDisplayLength == -1 )
2950 {
2951 oSettings._iDisplayStart = 0;
2952 }
2953
2954 _fnDraw( oSettings );
2955 } );
2956
2957 return nLength;
2958 }
2959
2960 /*
2961 * Function: _fnFeatureHtmlProcessing
2962 * Purpose: Generate the node required for the processing node
2963 * Returns: node
2964 * Inputs: object:oSettings - dataTables settings object
2965 */
2966 function _fnFeatureHtmlProcessing ( oSettings )
2967 {
2968 var nProcessing = document.createElement( 'div' );
2969
2970 if ( oSettings.sTableId !== '' )
2971 {
2972 nProcessing.setAttribute( 'id', oSettings.sTableId+'_processing' );
2973 }
2974 nProcessing.innerHTML = oSettings.oLanguage.sProcessing;
2975 nProcessing.className = oSettings.oClasses.sProcessing;
2976 oSettings.nTable.parentNode.insertBefore( nProcessing, oSettings.nTable );
2977
2978 return nProcessing;
2979 }
2980
2981 /*
2982 * Function: _fnProcessingDisplay
2983 * Purpose: Display or hide the processing indicator
2984 * Returns: -
2985 * Inputs: object:oSettings - dataTables settings object
2986 * bool:
2987 * true - show the processing indicator
2988 * false - don't show
2989 */
2990 function _fnProcessingDisplay ( oSettings, bShow )
2991 {
2992 if ( oSettings.oFeatures.bProcessing )
2993 {
2994 oSettings.anFeatures.r.style.visibility = bShow ? "visible" : "hidden";
2995 }
2996 }
2997
2998
2999
3000 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
3001 * Filtering
3002 */
3003
3004 /*
3005 * Function: _fnFilterComplete
3006 * Purpose: Filter the table using both the global filter and column based filtering
3007 * Returns: -
3008 * Inputs: object:oSettings - dataTables settings object
3009 * object:oSearch: search information
3010 * int:iForce - optional - force a research of the master array (1) or not (undefined or 0)
3011 */
3012 function _fnFilterComplete ( oSettings, oInput, iForce )
3013 {
3014 /* Filter on everything */
3015 _fnFilter( oSettings, oInput.sSearch, iForce, oInput.bEscapeRegex );
3016
3017 /* Now do the individual column filter */
3018 for ( var i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
3019 {
3020 _fnFilterColumn( oSettings, oSettings.aoPreSearchCols[i].sSearch, i,
3021 oSettings.aoPreSearchCols[i].bEscapeRegex );
3022 }
3023
3024 /* Custom filtering */
3025 if ( _oExt.afnFiltering.length !== 0 )
3026 {
3027 _fnFilterCustom( oSettings );
3028 }
3029
3030 /* Redraw the table */
3031 if ( typeof oSettings.iInitDisplayStart != 'undefined' && oSettings.iInitDisplayStart != -1 )
3032 {
3033 oSettings._iDisplayStart = oSettings.iInitDisplayStart;
3034 oSettings.iInitDisplayStart = -1;
3035 }
3036 else
3037 {
3038 oSettings._iDisplayStart = 0;
3039 }
3040 _fnCalculateEnd( oSettings );
3041 _fnDraw( oSettings );
3042
3043 /* Rebuild search array 'offline' */
3044 _fnBuildSearchArray( oSettings, 0 );
3045 }
3046
3047 /*
3048 * Function: _fnFilterCustom
3049 * Purpose: Apply custom filtering functions
3050 * Returns: -
3051 * Inputs: object:oSettings - dataTables settings object
3052 */
3053 function _fnFilterCustom( oSettings )
3054 {
3055 var afnFilters = _oExt.afnFiltering;
3056 for ( var i=0, iLen=afnFilters.length ; i<iLen ; i++ )
3057 {
3058 var iCorrector = 0;
3059 for ( var j=0, jLen=oSettings.aiDisplay.length ; j<jLen ; j++ )
3060 {
3061 var iDisIndex = oSettings.aiDisplay[j-iCorrector];
3062
3063 /* Check if we should use this row based on the filtering function */
3064 if ( !afnFilters[i]( oSettings, oSettings.aoData[iDisIndex]._aData, iDisIndex ) )
3065 {
3066 oSettings.aiDisplay.splice( j-iCorrector, 1 );
3067 iCorrector++;
3068 }
3069 }
3070 }
3071 }
3072
3073 /*
3074 * Function: _fnFilterColumn
3075 * Purpose: Filter the table on a per-column basis
3076 * Returns: -
3077 * Inputs: object:oSettings - dataTables settings object
3078 * string:sInput - string to filter on
3079 * int:iColumn - column to filter
3080 * bool:bEscapeRegex - escape regex or not
3081 */
3082 function _fnFilterColumn ( oSettings, sInput, iColumn, bEscapeRegex )
3083 {
3084 if ( sInput === "" )
3085 {
3086 return;
3087 }
3088
3089 var iIndexCorrector = 0;
3090 var sRegexMatch = bEscapeRegex ? _fnEscapeRegex( sInput ) : sInput;
3091 var rpSearch = new RegExp( sRegexMatch, "i" );
3092
3093 for ( var i=oSettings.aiDisplay.length-1 ; i>=0 ; i-- )
3094 {
3095 var sData = _fnDataToSearch( oSettings.aoData[ oSettings.aiDisplay[i] ]._aData[iColumn],
3096 oSettings.aoColumns[iColumn].sType );
3097 if ( ! rpSearch.test( sData ) )
3098 {
3099 oSettings.aiDisplay.splice( i, 1 );
3100 iIndexCorrector++;
3101 }
3102 }
3103 }
3104
3105 /*
3106 * Function: _fnFilter
3107 * Purpose: Filter the data table based on user input and draw the table
3108 * Returns: -
3109 * Inputs: object:oSettings - dataTables settings object
3110 * string:sInput - string to filter on
3111 * int:iForce - optional - force a research of the master array (1) or not (undefined or 0)
3112 * bool:bEscapeRegex - escape regex or not
3113 */
3114 function _fnFilter( oSettings, sInput, iForce, bEscapeRegex )
3115 {
3116 var i;
3117
3118 /* Check if we are forcing or not - optional parameter */
3119 if ( typeof iForce == 'undefined' || iForce === null )
3120 {
3121 iForce = 0;
3122 }
3123
3124 /* Need to take account of custom filtering functions always */
3125 if ( _oExt.afnFiltering.length !== 0 )
3126 {
3127 iForce = 1;
3128 }
3129
3130 /* Generate the regular expression to use. Something along the lines of:
3131 * ^(?=.*?\bone\b)(?=.*?\btwo\b)(?=.*?\bthree\b).*$
3132 */
3133 var asSearch = bEscapeRegex ?
3134 _fnEscapeRegex( sInput ).split( ' ' ) :
3135 sInput.split( ' ' );
3136 var sRegExpString = '^(?=.*?'+asSearch.join( ')(?=.*?' )+').*$';
3137 var rpSearch = new RegExp( sRegExpString, "i" ); /* case insensitive */
3138
3139 /*
3140 * If the input is blank - we want the full data set
3141 */
3142 if ( sInput.length <= 0 )
3143 {
3144 oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
3145 oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
3146 }
3147 else
3148 {
3149 /*
3150 * We are starting a new search or the new search string is smaller
3151 * then the old one (i.e. delete). Search from the master array
3152 */
3153 if ( oSettings.aiDisplay.length == oSettings.aiDisplayMaster.length ||
3154 oSettings.oPreviousSearch.sSearch.length > sInput.length || iForce == 1 ||
3155 sInput.indexOf(oSettings.oPreviousSearch.sSearch) !== 0 )
3156 {
3157 /* Nuke the old display array - we are going to rebuild it */
3158 oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
3159
3160 /* Force a rebuild of the search array */
3161 _fnBuildSearchArray( oSettings, 1 );
3162
3163 /* Search through all records to populate the search array
3164 * The the oSettings.aiDisplayMaster and asDataSearch arrays have 1 to 1
3165 * mapping
3166 */
3167 for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ )
3168 {
3169 if ( rpSearch.test(oSettings.asDataSearch[i]) )
3170 {
3171 oSettings.aiDisplay.push( oSettings.aiDisplayMaster[i] );
3172 }
3173 }
3174 }
3175 else
3176 {
3177 /* Using old search array - refine it - do it this way for speed
3178 * Don't have to search the whole master array again
3179 */
3180 var iIndexCorrector = 0;
3181
3182 /* Search the current results */
3183 for ( i=0 ; i<oSettings.asDataSearch.length ; i++ )
3184 {
3185 if ( ! rpSearch.test(oSettings.asDataSearch[i]) )
3186 {
3187 oSettings.aiDisplay.splice( i-iIndexCorrector, 1 );
3188 iIndexCorrector++;
3189 }
3190 }
3191 }
3192 }
3193 oSettings.oPreviousSearch.sSearch = sInput;
3194 oSettings.oPreviousSearch.bEscapeRegex = bEscapeRegex;
3195 }
3196
3197
3198
3199 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
3200 * Sorting
3201 */
3202
3203 /*
3204 * Function: _fnSort
3205 * Purpose: Change the order of the table
3206 * Returns: -
3207 * Inputs: object:oSettings - dataTables settings object
3208 * bool:bApplyClasses - optional - should we apply classes or not
3209 * Notes: We always sort the master array and then apply a filter again
3210 * if it is needed. This probably isn't optimal - but atm I can't think
3211 * of any other way which is (each has disadvantages)
3212 */
3213 function _fnSort ( oSettings, bApplyClasses )
3214 {
3215 /*
3216 * Funny one this - we want to sort aiDisplayMaster - but according to aoData[]._aData
3217 *
3218 * function _fnSortText ( a, b )
3219 * {
3220 * var iTest;
3221 * var oSort = _oExt.oSort;
3222 *
3223 * iTest = oSort['string-asc']( aoData[ a ]._aData[ COL ], aoData[ b ]._aData[ COL ] );
3224 * if ( iTest === 0 )
3225 * ...
3226 * }
3227 */
3228
3229 /* Here is what we are looking to achieve here (custom sort functions add complication...)
3230 * function _fnSortText ( a, b )
3231 * {
3232 * var iTest;
3233 * var oSort = _oExt.oSort;
3234 * iTest = oSort['string-asc']( a[0], b[0] );
3235 * if ( iTest === 0 )
3236 * iTest = oSort['string-asc']( a[1], b[1] );
3237 * if ( iTest === 0 )
3238 * iTest = oSort['string-asc']( a[2], b[2] );
3239 *
3240 * return iTest;
3241 * }
3242 */
3243 var aaSort = [];
3244 var oSort = _oExt.oSort;
3245 var aoData = oSettings.aoData;
3246 var iDataSort;
3247 var iDataType;
3248 var i;
3249
3250 if ( oSettings.aaSorting.length !== 0 || oSettings.aaSortingFixed !== null )
3251 {
3252 if ( oSettings.aaSortingFixed !== null )
3253 {
3254 aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
3255 }
3256 else
3257 {
3258 aaSort = oSettings.aaSorting.slice();
3259 }
3260
3261 if ( !window.runtime )
3262 {
3263 var fnLocalSorting;
3264 var sDynamicSort = "fnLocalSorting = function(a,b){"+
3265 "var iTest;";
3266
3267 for ( i=0 ; i<aaSort.length-1 ; i++ )
3268 {
3269 iDataSort = oSettings.aoColumns[ aaSort[i][0] ].iDataSort;
3270 iDataType = oSettings.aoColumns[ iDataSort ].sType;
3271 sDynamicSort += "iTest = oSort['"+iDataType+"-"+aaSort[i][1]+"']"+
3272 "( aoData[a]._aData["+iDataSort+"], aoData[b]._aData["+iDataSort+"] ); if ( iTest === 0 )";
3273 }
3274
3275 iDataSort = oSettings.aoColumns[ aaSort[aaSort.length-1][0] ].iDataSort;
3276 iDataType = oSettings.aoColumns[ iDataSort ].sType;
3277 sDynamicSort += "iTest = oSort['"+iDataType+"-"+aaSort[aaSort.length-1][1]+"']"+
3278 "( aoData[a]._aData["+iDataSort+"], aoData[b]._aData["+iDataSort+"] ); return iTest;}";
3279
3280 /* The eval has to be done to a variable for IE */
3281 eval( sDynamicSort );
3282 oSettings.aiDisplayMaster.sort( fnLocalSorting );
3283 }
3284 else
3285 {
3286 /*
3287 * Support for Adobe AIR - AIR doesn't allow eval with a function
3288 * Note that for reasonable sized data sets this method is around 1.5 times slower than
3289 * the eval above (hence why it is not used all the time). Oddly enough, it is ever so
3290 * slightly faster for very small sets (presumably the eval has overhead).
3291 * Single column (1083 records) - eval: 32mS AIR: 38mS
3292 * Two columns (1083 records) - eval: 55mS AIR: 66mS
3293 */
3294
3295 /* Build a cached array so the sort doesn't have to process this stuff on every call */
3296 var aAirSort = [];
3297 var iLen = aaSort.length;
3298 for ( i=0 ; i<iLen ; i++ )
3299 {
3300 iDataSort = oSettings.aoColumns[ aaSort[i][0] ].iDataSort;
3301 aAirSort.push( [
3302 iDataSort,
3303 oSettings.aoColumns[ iDataSort ].sType+'-'+aaSort[i][1]
3304 ] );
3305 }
3306
3307 oSettings.aiDisplayMaster.sort( function (a,b) {
3308 var iTest;
3309 for ( var i=0 ; i<iLen ; i++ )
3310 {
3311 iTest = oSort[ aAirSort[i][1] ]( aoData[a]._aData[aAirSort[i][0]], aoData[b]._aData[aAirSort[i][0]] );
3312 if ( iTest !== 0 )
3313 {
3314 return iTest;
3315 }
3316 }
3317 return 0;
3318 } );
3319 }
3320 }
3321
3322 /* Alter the sorting classes to take account of the changes */
3323 if ( typeof bApplyClasses == 'undefined' || bApplyClasses )
3324 {
3325 _fnSortingClasses( oSettings );
3326 }
3327
3328 /* Copy the master data into the draw array and re-draw */
3329 if ( oSettings.oFeatures.bFilter )
3330 {
3331 /* _fnFilter() will redraw the table for us */
3332 _fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
3333 }
3334 else
3335 {
3336 oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
3337 oSettings._iDisplayStart = 0; /* reset display back to page 0 */
3338 _fnCalculateEnd( oSettings );
3339 _fnDraw( oSettings );
3340 }
3341 }
3342
3343 /*
3344 * Function: _fnSortingClasses
3345 * Purpose: Set the sortting classes on the header
3346 * Returns: -
3347 * Inputs: object:oSettings - dataTables settings object
3348 */
3349 function _fnSortingClasses( oSettings )
3350 {
3351 var i, j, iFound;
3352 var aaSort, sClass;
3353 var iColumns = oSettings.aoColumns.length;
3354 var oClasses = oSettings.oClasses;
3355
3356 for ( i=0 ; i<iColumns ; i++ )
3357 {
3358 $(oSettings.aoColumns[i].nTh).removeClass( oClasses.sSortAsc +" "+ oClasses.sSortDesc +
3359 " "+ oClasses.sSortable );
3360 }
3361
3362 if ( oSettings.aaSortingFixed !== null )
3363 {
3364 aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
3365 }
3366 else
3367 {
3368 aaSort = oSettings.aaSorting.slice();
3369 }
3370
3371 /* Apply the required classes to the header */
3372 for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
3373 {
3374 if ( oSettings.aoColumns[i].bSortable && oSettings.aoColumns[i].bVisible )
3375 {
3376 sClass = oClasses.sSortable;
3377 iFound = -1;
3378 for ( j=0 ; j<aaSort.length ; j++ )
3379 {
3380 if ( aaSort[j][0] == i )
3381 {
3382 sClass = ( aaSort[j][1] == "asc" ) ?
3383 oClasses.sSortAsc : oClasses.sSortDesc;
3384 iFound = j;
3385 break;
3386 }
3387 }
3388 $(oSettings.aoColumns[i].nTh).addClass( sClass );
3389
3390 if ( oSettings.bJUI )
3391 {
3392 /* jQuery UI uses extra markup */
3393 var jqSpan = $("span", oSettings.aoColumns[i].nTh);
3394 jqSpan.removeClass(oClasses.sSortJUIAsc +" "+ oClasses.sSortJUIDesc +" "+
3395 oClasses.sSortJUI);
3396
3397 var sSpanClass;
3398 if ( iFound == -1 )
3399 {
3400 sSpanClass = oClasses.sSortJUI;
3401 }
3402 else if ( aaSort[iFound][1] == "asc" )
3403 {
3404 sSpanClass = oClasses.sSortJUIAsc;
3405 }
3406 else
3407 {
3408 sSpanClass = oClasses.sSortJUIDesc;
3409 }
3410
3411 jqSpan.addClass( sSpanClass );
3412 }
3413 }
3414 }
3415
3416 /*
3417 * Apply the required classes to the table body
3418 * Note that this is given as a feature switch since it can significantly slow down a sort
3419 * on large data sets (adding and removing of classes is always slow at the best of times..)
3420 */
3421 if ( oSettings.oFeatures.bSortClasses )
3422 {
3423 var nTrs = _fnGetTrNodes( oSettings );
3424 sClass = oClasses.sSortColumn;
3425 $('td', nTrs).removeClass( sClass+"1 "+sClass+"2 "+sClass+"3" );
3426
3427 var iClass = 1;
3428 for ( i=0 ; i<aaSort.length ; i++ )
3429 {
3430 var iVis = _fnColumnIndexToVisible(oSettings, aaSort[i][0]);
3431 if ( iVis !== null )
3432 {
3433 /* Limit the number of classes to three */
3434 if ( iClass <= 2 )
3435 {
3436 $('td:eq('+iVis+')', nTrs).addClass( sClass+iClass );
3437 }
3438 else
3439 {
3440 $('td:eq('+iVis+')', nTrs).addClass( sClass+'3' );
3441 }
3442 iClass++;
3443 }
3444 }
3445 }
3446 }
3447
3448 /*
3449 * Function: _fnVisibleToColumnIndex
3450 * Purpose: Covert the index of a visible column to the index in the data array (take account
3451 * of hidden columns)
3452 * Returns: int:i - the data index
3453 * Inputs: object:oSettings - dataTables settings object
3454 */
3455 function _fnVisibleToColumnIndex( oSettings, iMatch )
3456 {
3457 var iColumn = -1;
3458
3459 for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
3460 {
3461 if ( oSettings.aoColumns[i].bVisible === true )
3462 {
3463 iColumn++;
3464 }
3465
3466 if ( iColumn == iMatch )
3467 {
3468 return i;
3469 }
3470 }
3471
3472 return null;
3473 }
3474
3475 /*
3476 * Function: _fnColumnIndexToVisible
3477 * Purpose: Covert the index of an index in the data array and convert it to the visible
3478 * column index (take account of hidden columns)
3479 * Returns: int:i - the data index
3480 * Inputs: object:oSettings - dataTables settings object
3481 */
3482 function _fnColumnIndexToVisible( oSettings, iMatch )
3483 {
3484 var iVisible = -1;
3485 for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
3486 {
3487 if ( oSettings.aoColumns[i].bVisible === true )
3488 {
3489 iVisible++;
3490 }
3491
3492 if ( i == iMatch )
3493 {
3494 return oSettings.aoColumns[i].bVisible === true ? iVisible : null;
3495 }
3496 }
3497
3498 return null;
3499 }
3500
3501 /*
3502 * Function: _fnVisbleColumns
3503 * Purpose: Get the number of visible columns
3504 * Returns: int:i - the number of visible columns
3505 * Inputs: object:oS - dataTables settings object
3506 */
3507 function _fnVisbleColumns( oS )
3508 {
3509 var iVis = 0;
3510 for ( var i=0 ; i<oS.aoColumns.length ; i++ )
3511 {
3512 if ( oS.aoColumns[i].bVisible === true )
3513 {
3514 iVis++;
3515 }
3516 }
3517 return iVis;
3518 }
3519
3520 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
3521 * Support functions
3522 */
3523
3524 /*
3525 * Function: _fnBuildSearchArray
3526 * Purpose: Create an array which can be quickly search through
3527 * Returns: -
3528 * Inputs: object:oSettings - dataTables settings object
3529 * int:iMaster - use the master data array - optional
3530 */
3531 function _fnBuildSearchArray ( oSettings, iMaster )
3532 {
3533 /* Clear out the old data */
3534 oSettings.asDataSearch.splice( 0, oSettings.asDataSearch.length );
3535
3536 var aArray = (typeof iMaster != 'undefined' && iMaster == 1) ?
3537 oSettings.aiDisplayMaster : oSettings.aiDisplay;
3538
3539 for ( var i=0, iLen=aArray.length ; i<iLen ; i++ )
3540 {
3541 oSettings.asDataSearch[i] = '';
3542 for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ )
3543 {
3544 if ( oSettings.aoColumns[j].bSearchable )
3545 {
3546 var sData = oSettings.aoData[ aArray[i] ]._aData[j];
3547 oSettings.asDataSearch[i] += _fnDataToSearch( sData, oSettings.aoColumns[j].sType )+' ';
3548 }
3549 }
3550 }
3551 }
3552
3553 /*
3554 * Function: _fnDataToSearch
3555 * Purpose: Convert raw data into something that the user can search on
3556 * Returns: string: - search string
3557 * Inputs: string:sData - data to be modified
3558 * string:sType - data type
3559 */
3560 function _fnDataToSearch ( sData, sType )
3561 {
3562
3563 if ( typeof _oExt.ofnSearch[sType] == "function" )
3564 {
3565 return _oExt.ofnSearch[sType]( sData );
3566 }
3567 else if ( sType == "html" )
3568 {
3569 return sData.replace(/\n/g," ").replace( /<.*?>/g, "" );
3570 }
3571 else if ( typeof sData == "string" )
3572 {
3573 return sData.replace(/\n/g," ");
3574 }
3575 return sData;
3576 }
3577
3578 /*
3579 * Function: _fnCalculateEnd
3580 * Purpose: Rcalculate the end point based on the start point
3581 * Returns: -
3582 * Inputs: object:oSettings - dataTables settings object
3583 */
3584 function _fnCalculateEnd( oSettings )
3585 {
3586 if ( oSettings.oFeatures.bPaginate === false )
3587 {
3588 oSettings._iDisplayEnd = oSettings.aiDisplay.length;
3589 }
3590 else
3591 {
3592 /* Set the end point of the display - based on how many elements there are
3593 * still to display
3594 */
3595 if ( oSettings._iDisplayStart + oSettings._iDisplayLength > oSettings.aiDisplay.length ||
3596 oSettings._iDisplayLength == -1 )
3597 {
3598 oSettings._iDisplayEnd = oSettings.aiDisplay.length;
3599 }
3600 else
3601 {
3602 oSettings._iDisplayEnd = oSettings._iDisplayStart + oSettings._iDisplayLength;
3603 }
3604 }
3605 }
3606
3607 /*
3608 * Function: _fnConvertToWidth
3609 * Purpose: Convert a CSS unit width to pixels (e.g. 2em)
3610 * Returns: int:iWidth - width in pixels
3611 * Inputs: string:sWidth - width to be converted
3612 * node:nParent - parent to get the with for (required for
3613 * relative widths) - optional
3614 */
3615 function _fnConvertToWidth ( sWidth, nParent )
3616 {
3617 if ( !sWidth || sWidth === null || sWidth === '' )
3618 {
3619 return 0;
3620 }
3621
3622 if ( typeof nParent == "undefined" )
3623 {
3624 nParent = document.getElementsByTagName('body')[0];
3625 }
3626
3627 var iWidth;
3628 var nTmp = document.createElement( "div" );
3629 nTmp.style.width = sWidth;
3630
3631 nParent.appendChild( nTmp );
3632 iWidth = nTmp.offsetWidth;
3633 nParent.removeChild( nTmp );
3634
3635 return ( iWidth );
3636 }
3637
3638 /*
3639 * Function: _fnCalculateColumnWidths
3640 * Purpose: Calculate the width of columns for the table
3641 * Returns: -
3642 * Inputs: object:oSettings - dataTables settings object
3643 */
3644 function _fnCalculateColumnWidths ( oSettings )
3645 {
3646 var iTableWidth = oSettings.nTable.offsetWidth;
3647 var iTotalUserIpSize = 0;
3648 var iTmpWidth;
3649 var iVisibleColumns = 0;
3650 var iColums = oSettings.aoColumns.length;
3651 var i;
3652 var oHeaders = $('thead th', oSettings.nTable);
3653
3654 /* Convert any user input sizes into pixel sizes */
3655 for ( i=0 ; i<iColums ; i++ )
3656 {
3657 if ( oSettings.aoColumns[i].bVisible )
3658 {
3659 iVisibleColumns++;
3660
3661 if ( oSettings.aoColumns[i].sWidth !== null )
3662 {
3663 iTmpWidth = _fnConvertToWidth( oSettings.aoColumns[i].sWidth,
3664 oSettings.nTable.parentNode );
3665
3666 /* Total up the user defined widths for later calculations */
3667 iTotalUserIpSize += iTmpWidth;
3668
3669 oSettings.aoColumns[i].sWidth = iTmpWidth+"px";
3670 }
3671 }
3672 }
3673
3674 /* If the number of columns in the DOM equals the number that we
3675 * have to process in dataTables, then we can use the offsets that are
3676 * created by the web-browser. No custom sizes can be set in order for
3677 * this to happen
3678 */
3679 if ( iColums == oHeaders.length && iTotalUserIpSize === 0 && iVisibleColumns == iColums )
3680 {
3681 for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
3682 {
3683 oSettings.aoColumns[i].sWidth = oHeaders[i].offsetWidth+"px";
3684 }
3685 }
3686 else
3687 {
3688 /* Otherwise we are going to have to do some calculations to get
3689 * the width of each column. Construct a 1 row table with the maximum
3690 * string sizes in the data, and any user defined widths
3691 */
3692 var nCalcTmp = oSettings.nTable.cloneNode( false );
3693 nCalcTmp.setAttribute( "id", '' );
3694
3695 var sTableTmp = '<table class="'+nCalcTmp.className+'">';
3696 var sCalcHead = "<tr>";
3697 var sCalcHtml = "<tr>";
3698
3699 /* Construct a tempory table which we will inject (invisibly) into
3700 * the dom - to let the browser do all the hard word
3701 */
3702 for ( i=0 ; i<iColums ; i++ )
3703 {
3704 if ( oSettings.aoColumns[i].bVisible )
3705 {
3706 sCalcHead += '<th>'+oSettings.aoColumns[i].sTitle+'</th>';
3707
3708 if ( oSettings.aoColumns[i].sWidth !== null )
3709 {
3710 var sWidth = '';
3711 if ( oSettings.aoColumns[i].sWidth !== null )
3712 {
3713 sWidth = ' style="width:'+oSettings.aoColumns[i].sWidth+';"';
3714 }
3715
3716 sCalcHtml += '<td'+sWidth+' tag_index="'+i+'">'+fnGetMaxLenString( oSettings, i)+'</td>';
3717 }
3718 else
3719 {
3720 sCalcHtml += '<td tag_index="'+i+'">'+fnGetMaxLenString( oSettings, i)+'</td>';
3721 }
3722 }
3723 }
3724
3725 sCalcHead += "</tr>";
3726 sCalcHtml += "</tr>";
3727
3728 /* Create the tmp table node (thank you jQuery) */
3729 nCalcTmp = $( sTableTmp + sCalcHead + sCalcHtml +'</table>' )[0];
3730 nCalcTmp.style.width = iTableWidth + "px";
3731 nCalcTmp.style.visibility = "hidden";
3732 nCalcTmp.style.position = "absolute"; /* Try to aviod scroll bar */
3733
3734 oSettings.nTable.parentNode.appendChild( nCalcTmp );
3735
3736 var oNodes = $("td", nCalcTmp);
3737 var iIndex;
3738
3739 /* Gather in the browser calculated widths for the rows */
3740 for ( i=0 ; i<oNodes.length ; i++ )
3741 {
3742 iIndex = oNodes[i].getAttribute('tag_index');
3743
3744 oSettings.aoColumns[iIndex].sWidth = $("td", nCalcTmp)[i].offsetWidth +"px";
3745 }
3746
3747 oSettings.nTable.parentNode.removeChild( nCalcTmp );
3748 }
3749 }
3750
3751 /*
3752 * Function: fnGetMaxLenString
3753 * Purpose: Get the maximum strlen for each data column
3754 * Returns: string: - max strlens for each column
3755 * Inputs: object:oSettings - dataTables settings object
3756 * int:iCol - column of interest
3757 */
3758 function fnGetMaxLenString( oSettings, iCol )
3759 {
3760 var iMax = 0;
3761 var iMaxIndex = -1;
3762
3763 for ( var i=0 ; i<oSettings.aoData.length ; i++ )
3764 {
3765 if ( oSettings.aoData[i]._aData[iCol].length > iMax )
3766 {
3767 iMax = oSettings.aoData[i]._aData[iCol].length;
3768 iMaxIndex = i;
3769 }
3770 }
3771
3772 if ( iMaxIndex >= 0 )
3773 {
3774 return oSettings.aoData[iMaxIndex]._aData[iCol];
3775 }
3776 return '';
3777 }
3778
3779 /*
3780 * Function: _fnArrayCmp
3781 * Purpose: Compare two arrays
3782 * Returns: 0 if match, 1 if length is different, 2 if no match
3783 * Inputs: array:aArray1 - first array
3784 * array:aArray2 - second array
3785 */
3786 function _fnArrayCmp( aArray1, aArray2 )
3787 {
3788 if ( aArray1.length != aArray2.length )
3789 {
3790 return 1;
3791 }
3792
3793 for ( var i=0 ; i<aArray1.length ; i++ )
3794 {
3795 if ( aArray1[i] != aArray2[i] )
3796 {
3797 return 2;
3798 }
3799 }
3800
3801 return 0;
3802 }
3803
3804 /*
3805 * Function: _fnDetectType
3806 * Purpose: Get the sort type based on an input string
3807 * Returns: string: - type (defaults to 'string' if no type can be detected)
3808 * Inputs: string:sData - data we wish to know the type of
3809 * Notes: This function makes use of the DataTables plugin objct _oExt
3810 * (.aTypes) such that new types can easily be added.
3811 */
3812 function _fnDetectType( sData )
3813 {
3814 var aTypes = _oExt.aTypes;
3815 var iLen = aTypes.length;
3816
3817 for ( var i=0 ; i<iLen ; i++ )
3818 {
3819 var sType = aTypes[i]( sData );
3820 if ( sType !== null )
3821 {
3822 return sType;
3823 }
3824 }
3825
3826 return 'string';
3827 }
3828
3829 /*
3830 * Function: _fnSettingsFromNode
3831 * Purpose: Return the settings object for a particular table
3832 * Returns: object: Settings object - or null if not found
3833 * Inputs: node:nTable - table we are using as a dataTable
3834 */
3835 function _fnSettingsFromNode ( nTable )
3836 {
3837 for ( var i=0 ; i<_aoSettings.length ; i++ )
3838 {
3839 if ( _aoSettings[i].nTable == nTable )
3840 {
3841 return _aoSettings[i];
3842 }
3843 }
3844
3845 return null;
3846 }
3847
3848 /*
3849 * Function: _fnGetDataMaster
3850 * Purpose: Return an array with the full table data
3851 * Returns: array array:aData - Master data array
3852 * Inputs: object:oSettings - dataTables settings object
3853 */
3854 function _fnGetDataMaster ( oSettings )
3855 {
3856 var aData = [];
3857 var iLen = oSettings.aoData.length;
3858 for ( var i=0 ; i<iLen; i++ )
3859 {
3860 if ( oSettings.aoData[i] === null )
3861 {
3862 aData.push( null );
3863 }
3864 else
3865 {
3866 aData.push( oSettings.aoData[i]._aData );
3867 }
3868 }
3869 return aData;
3870 }
3871
3872 /*
3873 * Function: _fnGetTrNodes
3874 * Purpose: Return an array with the TR nodes for the table
3875 * Returns: array array:aData - TR array
3876 * Inputs: object:oSettings - dataTables settings object
3877 */
3878 function _fnGetTrNodes ( oSettings )
3879 {
3880 var aNodes = [];
3881 var iLen = oSettings.aoData.length;
3882 for ( var i=0 ; i<iLen ; i++ )
3883 {
3884 if ( oSettings.aoData[i] === null )
3885 {
3886 aNodes.push( null );
3887 }
3888 else
3889 {
3890 aNodes.push( oSettings.aoData[i].nTr );
3891 }
3892 }
3893 return aNodes;
3894 }
3895
3896 /*
3897 * Function: _fnEscapeRegex
3898 * Purpose: scape a string stuch that it can be used in a regular expression
3899 * Returns: string: - escaped string
3900 * Inputs: string:sVal - string to escape
3901 */
3902 function _fnEscapeRegex ( sVal )
3903 {
3904 var acEscape = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^' ];
3905 var reReplace = new RegExp( '(\\' + acEscape.join('|\\') + ')', 'g' );
3906 return sVal.replace(reReplace, '\\$1');
3907 }
3908
3909 /*
3910 * Function: _fnReOrderIndex
3911 * Purpose: Figure out how to reorder a display list
3912 * Returns: array int:aiReturn - index list for reordering
3913 * Inputs: object:oSettings - dataTables settings object
3914 */
3915 function _fnReOrderIndex ( oSettings, sColumns )
3916 {
3917 var aColumns = sColumns.split(',');
3918 var aiReturn = [];
3919
3920 for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
3921 {
3922 for ( var j=0 ; j<iLen ; j++ )
3923 {
3924 if ( oSettings.aoColumns[i].sName == aColumns[j] )
3925 {
3926 aiReturn.push( j );
3927 break;
3928 }
3929 }
3930 }
3931
3932 return aiReturn;
3933 }
3934
3935 /*
3936 * Function: _fnColumnOrdering
3937 * Purpose: Get the column ordering that DataTables expects
3938 * Returns: string: - comma separated list of names
3939 * Inputs: object:oSettings - dataTables settings object
3940 */
3941 function _fnColumnOrdering ( oSettings )
3942 {
3943 var sNames = '';
3944 for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
3945 {
3946 sNames += oSettings.aoColumns[i].sName+',';
3947 }
3948 if ( sNames.length == iLen )
3949 {
3950 return "";
3951 }
3952 return sNames.slice(0, -1);
3953 }
3954
3955 /*
3956 * Function: _fnClearTable
3957 * Purpose: Nuke the table
3958 * Returns: -
3959 * Inputs: object:oSettings - dataTables settings object
3960 */
3961 function _fnClearTable( oSettings )
3962 {
3963 oSettings.aoData.length = 0;
3964 oSettings.aiDisplayMaster.length = 0;
3965 oSettings.aiDisplay.length = 0;
3966 _fnCalculateEnd( oSettings );
3967 }
3968
3969 /*
3970 * Function: _fnSaveState
3971 * Purpose: Save the state of a table in a cookie such that the page can be reloaded
3972 * Returns: -
3973 * Inputs: object:oSettings - dataTables settings object
3974 */
3975 function _fnSaveState ( oSettings )
3976 {
3977 if ( !oSettings.oFeatures.bStateSave )
3978 {
3979 return;
3980 }
3981
3982 /* Store the interesting variables */
3983 var i;
3984 var sValue = "{";
3985 sValue += '"iStart": '+oSettings._iDisplayStart+',';
3986 sValue += '"iEnd": '+oSettings._iDisplayEnd+',';
3987 sValue += '"iLength": '+oSettings._iDisplayLength+',';
3988 sValue += '"sFilter": "'+oSettings.oPreviousSearch.sSearch.replace('"','\\"')+'",';
3989 sValue += '"sFilterEsc": '+oSettings.oPreviousSearch.bEscapeRegex+',';
3990
3991 sValue += '"aaSorting": [ ';
3992 for ( i=0 ; i<oSettings.aaSorting.length ; i++ )
3993 {
3994 sValue += "["+oSettings.aaSorting[i][0]+",'"+oSettings.aaSorting[i][1]+"'],";
3995 }
3996 sValue = sValue.substring(0, sValue.length-1);
3997 sValue += "],";
3998
3999 sValue += '"aaSearchCols": [ ';
4000 for ( i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
4001 {
4002 sValue += "['"+oSettings.aoPreSearchCols[i].sSearch.replace("'","\'")+
4003 "',"+oSettings.aoPreSearchCols[i].bEscapeRegex+"],";
4004 }
4005 sValue = sValue.substring(0, sValue.length-1);
4006 sValue += "],";
4007
4008 sValue += '"abVisCols": [ ';
4009 for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
4010 {
4011 sValue += oSettings.aoColumns[i].bVisible+",";
4012 }
4013 sValue = sValue.substring(0, sValue.length-1);
4014 sValue += "]";
4015
4016 sValue += "}";
4017 _fnCreateCookie( "SpryMedia_DataTables_"+oSettings.sInstance, sValue,
4018 oSettings.iCookieDuration );
4019 }
4020
4021 /*
4022 * Function: _fnLoadState
4023 * Purpose: Attempt to load a saved table state from a cookie
4024 * Returns: -
4025 * Inputs: object:oSettings - dataTables settings object
4026 * object:oInit - DataTables init object so we can override settings
4027 */
4028 function _fnLoadState ( oSettings, oInit )
4029 {
4030 if ( !oSettings.oFeatures.bStateSave )
4031 {
4032 return;
4033 }
4034
4035 var oData;
4036 var sData = _fnReadCookie( "SpryMedia_DataTables_"+oSettings.sInstance );
4037 if ( sData !== null && sData !== '' )
4038 {
4039 /* Try/catch the JSON eval - if it is bad then we ignore it */
4040 try
4041 {
4042 /* Use the JSON library for safety - if it is available */
4043 if ( typeof JSON == 'object' && typeof JSON.parse == 'function' )
4044 {
4045 /* DT 1.4.0 used single quotes for a string - JSON.parse doesn't allow this and throws
4046 * an error. So for now we can do this. This can be removed in future it is just to
4047 * allow the tranfrer to 1.4.1+ to occur
4048 */
4049 oData = JSON.parse( sData.replace(/'/g, '"') );
4050 }
4051 else
4052 {
4053 oData = eval( '('+sData+')' );
4054 }
4055 }
4056 catch( e )
4057 {
4058 return;
4059 }
4060
4061 /* Restore key features */
4062 oSettings._iDisplayStart = oData.iStart;
4063 oSettings.iInitDisplayStart = oData.iStart;
4064 oSettings._iDisplayEnd = oData.iEnd;
4065 oSettings._iDisplayLength = oData.iLength;
4066 oSettings.oPreviousSearch.sSearch = oData.sFilter;
4067 oSettings.aaSorting = oData.aaSorting.slice();
4068
4069 /* Search filtering - global reference added in 1.4.1 */
4070 if ( typeof oData.sFilterEsc != 'undefined' )
4071 {
4072 oSettings.oPreviousSearch.bEscapeRegex = oData.sFilterEsc;
4073 }
4074
4075 /* Column filtering - added in 1.5.0 beta 6 */
4076 if ( typeof oData.aaSearchCols != 'undefined' )
4077 {
4078 for ( var i=0 ; i<oData.aaSearchCols.length ; i++ )
4079 {
4080 oSettings.aoPreSearchCols[i] = {
4081 "sSearch": oData.aaSearchCols[i][0],
4082 "bEscapeRegex": oData.aaSearchCols[i][1]
4083 };
4084 }
4085 }
4086
4087 /* Column visibility state - added in 1.5.0 beta 10 */
4088 if ( typeof oData.abVisCols != 'undefined' )
4089 {
4090 /* We need to override the settings in oInit for this */
4091 if ( typeof oInit.aoColumns == 'undefined' )
4092 {
4093 oInit.aoColumns = [];
4094 }
4095
4096 for ( i=0 ; i<oData.abVisCols.length ; i++ )
4097 {
4098 if ( typeof oInit.aoColumns[i] == 'undefined' || oInit.aoColumns[i] === null )
4099 {
4100 oInit.aoColumns[i] = {};
4101 }
4102
4103 oInit.aoColumns[i].bVisible = oData.abVisCols[i];
4104 }
4105 }
4106 }
4107 }
4108
4109 /*
4110 * Function: _fnCreateCookie
4111 * Purpose: Create a new cookie with a value to store the state of a table
4112 * Returns: -
4113 * Inputs: string:sName - name of the cookie to create
4114 * string:sValue - the value the cookie should take
4115 * int:iSecs - duration of the cookie
4116 */
4117 function _fnCreateCookie ( sName, sValue, iSecs )
4118 {
4119 var date = new Date();
4120 date.setTime( date.getTime()+(iSecs*1000) );
4121
4122 /*
4123 * Shocking but true - it would appear IE has major issues with having the path being
4124 * set to anything but root. We need the cookie to be available based on the path, so we
4125 * have to append the pathname to the cookie name. Appalling.
4126 */
4127 sName += '_'+window.location.pathname.replace(/[\/:]/g,"").toLowerCase();
4128
4129 document.cookie = sName+"="+sValue+"; expires="+date.toGMTString()+"; path=/";
4130 }
4131
4132 /*
4133 * Function: _fnReadCookie
4134 * Purpose: Read an old cookie to get a cookie with an old table state
4135 * Returns: string: - contents of the cookie - or null if no cookie with that name found
4136 * Inputs: string:sName - name of the cookie to read
4137 */
4138 function _fnReadCookie ( sName )
4139 {
4140 var sNameEQ = sName +'_'+ window.location.pathname.replace(/[\/:]/g,"").toLowerCase() + "=";
4141 var sCookieContents = document.cookie.split(';');
4142
4143 for( var i=0 ; i<sCookieContents.length ; i++ )
4144 {
4145 var c = sCookieContents[i];
4146
4147 while (c.charAt(0)==' ')
4148 {
4149 c = c.substring(1,c.length);
4150 }
4151
4152 if (c.indexOf(sNameEQ) === 0)
4153 {
4154 return c.substring(sNameEQ.length,c.length);
4155 }
4156 }
4157 return null;
4158 }
4159
4160 /*
4161 * Function: _fnGetUniqueThs
4162 * Purpose: Get an array of unique th elements, one for each column
4163 * Returns: array node:aReturn - list of unique ths
4164 * Inputs: node:nThead - The thead element for the table
4165 */
4166 function _fnGetUniqueThs ( nThead )
4167 {
4168 var nTrs = nThead.getElementsByTagName('tr');
4169
4170 /* Nice simple case */
4171 if ( nTrs.length == 1 )
4172 {
4173 return nTrs[0].getElementsByTagName('th');
4174 }
4175
4176 /* Otherwise we need to figure out the layout array to get the nodes */
4177 var aLayout = [], aReturn = [];
4178 var ROWSPAN = 2, COLSPAN = 3, TDELEM = 4;
4179 var i, j, k, iLen, jLen, iColumnShifted;
4180 var fnShiftCol = function ( a, i, j ) {
4181 while ( typeof a[i][j] != 'undefined' ) {
4182 j++;
4183 }
4184 return j;
4185 };
4186 var fnAddRow = function ( i ) {
4187 if ( typeof aLayout[i] == 'undefined' ) {
4188 aLayout[i] = [];
4189 }
4190 };
4191
4192 /* Calculate a layout array */
4193 for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
4194 {
4195 fnAddRow( i );
4196 var iColumn = 0;
4197 var nTds = [];
4198
4199 for ( j=0, jLen=nTrs[i].childNodes.length ; j<jLen ; j++ )
4200 {
4201 if ( nTrs[i].childNodes[j].nodeName == "TD" || nTrs[i].childNodes[j].nodeName == "TH" )
4202 {
4203 nTds.push( nTrs[i].childNodes[j] );
4204 }
4205 }
4206
4207 for ( j=0, jLen=nTds.length ; j<jLen ; j++ )
4208 {
4209 var iColspan = nTds[j].getAttribute('colspan') * 1;
4210 var iRowspan = nTds[j].getAttribute('rowspan') * 1;
4211
4212 if ( !iColspan || iColspan===0 || iColspan===1 )
4213 {
4214 iColumnShifted = fnShiftCol( aLayout, i, iColumn );
4215 aLayout[i][iColumnShifted] = (nTds[j].nodeName=="TD") ? TDELEM : nTds[j];
4216 if ( iRowspan || iRowspan===0 || iRowspan===1 )
4217 {
4218 for ( k=1 ; k<iRowspan ; k++ )
4219 {
4220 fnAddRow( i+k );
4221 aLayout[i+k][iColumnShifted] = ROWSPAN;
4222 }
4223 }
4224 iColumn++;
4225 }
4226 else
4227 {
4228 iColumnShifted = fnShiftCol( aLayout, i, iColumn );
4229 for ( k=0 ; k<iColspan ; k++ )
4230 {
4231 aLayout[i][iColumnShifted+k] = COLSPAN;
4232 }
4233 iColumn += iColspan;
4234 }
4235 }
4236 }
4237
4238 /* Convert the layout array into a node array
4239 * Note the use of aLayout[0] in the outloop, we want the outer loop to occur the same
4240 * number of times as there are columns. Unusual having nested loops this way around
4241 * but is what we need here.
4242 */
4243 for ( i=0, iLen=aLayout[0].length ; i<iLen ; i++ )
4244 {
4245 for ( j=0, jLen=aLayout.length ; j<jLen ; j++ )
4246 {
4247 if ( typeof aLayout[j][i] == 'object' )
4248 {
4249 aReturn.push( aLayout[j][i] );
4250 }
4251 }
4252 }
4253
4254 return aReturn;
4255 }
4256
4257 /*
4258 * Function: _fnMap
4259 * Purpose: See if a property is defined on one object, if so assign it to the other object
4260 * Returns: - (done by reference)
4261 * Inputs: object:oRet - target object
4262 * object:oSrc - source object
4263 * string:sName - property
4264 * string:sMappedName - name to map too - optional, sName used if not given
4265 */
4266 function _fnMap( oRet, oSrc, sName, sMappedName )
4267 {
4268 if ( typeof sMappedName == 'undefined' )
4269 {
4270 sMappedName = sName;
4271 }
4272 if ( typeof oSrc[sName] != 'undefined' )
4273 {
4274 oRet[sMappedName] = oSrc[sName];
4275 }
4276 }
4277
4278 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
4279 * API
4280 *
4281 * I'm not overly happy with this solution - I'd much rather that there was a way of getting
4282 * a list of all the private functions and do what we need to dynamically - but that doesn't
4283 * appear to be possible. Bonkers. A better solution would be to provide a 'bind' type object
4284 * To do - bind type method in DTs 1.6.
4285 */
4286 this.oApi._fnInitalise = _fnInitalise;
4287 this.oApi._fnLanguageProcess = _fnLanguageProcess;
4288 this.oApi._fnAddColumn = _fnAddColumn;
4289 this.oApi._fnAddData = _fnAddData;
4290 this.oApi._fnGatherData = _fnGatherData;
4291 this.oApi._fnDrawHead = _fnDrawHead;
4292 this.oApi._fnDraw = _fnDraw;
4293 this.oApi._fnAjaxUpdate = _fnAjaxUpdate;
4294 this.oApi._fnAddOptionsHtml = _fnAddOptionsHtml;
4295 this.oApi._fnFeatureHtmlFilter = _fnFeatureHtmlFilter;
4296 this.oApi._fnFeatureHtmlInfo = _fnFeatureHtmlInfo;
4297 this.oApi._fnFeatureHtmlPaginate = _fnFeatureHtmlPaginate;
4298 this.oApi._fnFeatureHtmlLength = _fnFeatureHtmlLength;
4299 this.oApi._fnFeatureHtmlProcessing = _fnFeatureHtmlProcessing;
4300 this.oApi._fnProcessingDisplay = _fnProcessingDisplay;
4301 this.oApi._fnFilterComplete = _fnFilterComplete;
4302 this.oApi._fnFilterColumn = _fnFilterColumn;
4303 this.oApi._fnFilter = _fnFilter;
4304 this.oApi._fnSortingClasses = _fnSortingClasses;
4305 this.oApi._fnVisibleToColumnIndex = _fnVisibleToColumnIndex;
4306 this.oApi._fnColumnIndexToVisible = _fnColumnIndexToVisible;
4307 this.oApi._fnVisbleColumns = _fnVisbleColumns;
4308 this.oApi._fnBuildSearchArray = _fnBuildSearchArray;
4309 this.oApi._fnDataToSearch = _fnDataToSearch;
4310 this.oApi._fnCalculateEnd = _fnCalculateEnd;
4311 this.oApi._fnConvertToWidth = _fnConvertToWidth;
4312 this.oApi._fnCalculateColumnWidths = _fnCalculateColumnWidths;
4313 this.oApi._fnArrayCmp = _fnArrayCmp;
4314 this.oApi._fnDetectType = _fnDetectType;
4315 this.oApi._fnGetDataMaster = _fnGetDataMaster;
4316 this.oApi._fnGetTrNodes = _fnGetTrNodes;
4317 this.oApi._fnEscapeRegex = _fnEscapeRegex;
4318 this.oApi._fnReOrderIndex = _fnReOrderIndex;
4319 this.oApi._fnColumnOrdering = _fnColumnOrdering;
4320 this.oApi._fnClearTable = _fnClearTable;
4321 this.oApi._fnSaveState = _fnSaveState;
4322 this.oApi._fnLoadState = _fnLoadState;
4323 this.oApi._fnCreateCookie = _fnCreateCookie;
4324 this.oApi._fnReadCookie = _fnReadCookie;
4325 this.oApi._fnGetUniqueThs = _fnGetUniqueThs;
4326
4327 /* Want to be able to reference "this" inside the this.each function */
4328 var _that = this;
4329
4330
4331 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
4332 * Constructor
4333 */
4334 return this.each(function()
4335 {
4336 /* Make a complete and independent copy of the settings object */
4337 var oSettings = new classSettings();
4338 _aoSettings.push( oSettings );
4339
4340 var i=0, iLen;
4341 var bInitHandedOff = false;
4342 var bUsePassedData = false;
4343
4344 /* Set the id */
4345 var sId = this.getAttribute( 'id' );
4346 if ( sId !== null )
4347 {
4348 oSettings.sTableId = sId;
4349 oSettings.sInstance = sId;
4350 }
4351 else
4352 {
4353 oSettings.sInstance = _oExt._oExternConfig.iNextUnique ++;
4354 }
4355
4356 /* Set the table node */
4357 oSettings.nTable = this;
4358
4359 /* Bind the API functions to the settings, so we can perform actions whenever oSettings is
4360 * available
4361 */
4362 oSettings.oApi = _that.oApi;
4363
4364 /* Store the features that we have available */
4365 if ( typeof oInit != 'undefined' && oInit !== null )
4366 {
4367 _fnMap( oSettings.oFeatures, oInit, "bPaginate" );
4368 _fnMap( oSettings.oFeatures, oInit, "bLengthChange" );
4369 _fnMap( oSettings.oFeatures, oInit, "bFilter" );
4370 _fnMap( oSettings.oFeatures, oInit, "bSort" );
4371 _fnMap( oSettings.oFeatures, oInit, "bInfo" );
4372 _fnMap( oSettings.oFeatures, oInit, "bProcessing" );
4373 _fnMap( oSettings.oFeatures, oInit, "bAutoWidth" );
4374 _fnMap( oSettings.oFeatures, oInit, "bSortClasses" );
4375 _fnMap( oSettings.oFeatures, oInit, "bServerSide" );
4376 _fnMap( oSettings, oInit, "asStripClasses" );
4377 _fnMap( oSettings, oInit, "fnRowCallback" );
4378 _fnMap( oSettings, oInit, "fnHeaderCallback" );
4379 _fnMap( oSettings, oInit, "fnFooterCallback" );
4380 _fnMap( oSettings, oInit, "fnDrawCallback" );
4381 _fnMap( oSettings, oInit, "fnInitComplete" );
4382 _fnMap( oSettings, oInit, "fnServerData" );
4383 _fnMap( oSettings, oInit, "aaSorting" );
4384 _fnMap( oSettings, oInit, "aaSortingFixed" );
4385 _fnMap( oSettings, oInit, "sPaginationType" );
4386 _fnMap( oSettings, oInit, "sAjaxSource" );
4387 _fnMap( oSettings, oInit, "sDom", "sDomPositioning" );
4388 _fnMap( oSettings, oInit, "oSearch", "oPreviousSearch" );
4389 _fnMap( oSettings, oInit, "aoSearchCols", "aoPreSearchCols" );
4390 _fnMap( oSettings, oInit, "iDisplayLength", "_iDisplayLength" );
4391 _fnMap( oSettings, oInit, "bJQueryUI", "bJUI" );
4392
4393 if ( typeof oInit.bJQueryUI != 'undefined' )
4394 {
4395 /* Use the JUI classes object for display. You could clone the oStdClasses object if
4396 * you want to have multiple tables with multiple independent classes
4397 */
4398 oSettings.oClasses = _oExt.oJUIClasses;
4399
4400 if ( typeof oInit.sDom == 'undefined' )
4401 {
4402 /* Set the DOM to use a layout suitable for jQuery UI's theming */
4403 oSettings.sDomPositioning =
4404 '<"fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix"lfr>'+
4405 't'+
4406 '<"fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix"ip>';
4407 }
4408 }
4409
4410 if ( typeof oInit.iDisplayStart != 'undefined' &&
4411 typeof oSettings.iInitDisplayStart == 'undefined' ) {
4412 /* Display start point, taking into account the save saving */
4413 oSettings.iInitDisplayStart = oInit.iDisplayStart;
4414 oSettings._iDisplayStart = oInit.iDisplayStart;
4415 }
4416
4417 /* Must be done after everything which can be overridden by a cookie! */
4418 if ( typeof oInit.bStateSave != 'undefined' )
4419 {
4420 oSettings.oFeatures.bStateSave = oInit.bStateSave;
4421 _fnLoadState( oSettings, oInit );
4422 }
4423
4424 if ( typeof oInit.aaData != 'undefined' ) {
4425 bUsePassedData = true;
4426 }
4427
4428 /* Backwards compatability */
4429 /* aoColumns / aoData - remove at some point... */
4430 if ( typeof oInit != 'undefined' && typeof oInit.aoData != 'undefined' )
4431 {
4432 oInit.aoColumns = oInit.aoData;
4433 }
4434
4435 /* Language definitions */
4436 if ( typeof oInit.oLanguage != 'undefined' )
4437 {
4438 if ( typeof oInit.oLanguage.sUrl != 'undefined' && oInit.oLanguage.sUrl !== "" )
4439 {
4440 /* Get the language definitions from a file */
4441 oSettings.oLanguage.sUrl = oInit.oLanguage.sUrl;
4442 $.getJSON( oSettings.oLanguage.sUrl, null, function( json ) {
4443 _fnLanguageProcess( oSettings, json, true ); } );
4444 bInitHandedOff = true;
4445 }
4446 else
4447 {
4448 _fnLanguageProcess( oSettings, oInit.oLanguage, false );
4449 }
4450 }
4451 /* Warning: The _fnLanguageProcess function is async to the remainder of this function due
4452 * to the XHR. We use _bInitialised in _fnLanguageProcess() to check this the processing
4453 * below is complete. The reason for spliting it like this is optimisation - we can fire
4454 * off the XHR (if needed) and then continue processing the data.
4455 */
4456 }
4457
4458 /* Add the strip classes now that we know which classes to apply - unless overruled */
4459 if ( typeof oInit == 'undefined' || typeof oInit.asStripClasses == 'undefined' )
4460 {
4461 oSettings.asStripClasses.push( oSettings.oClasses.sStripOdd );
4462 oSettings.asStripClasses.push( oSettings.oClasses.sStripEven );
4463 }
4464
4465 /* See if we should load columns automatically or use defined ones - a bit messy this... */
4466 var nThead = this.getElementsByTagName('thead');
4467 var nThs = nThead.length===0 ? null : _fnGetUniqueThs( nThead[0] );
4468 var bUseCols = typeof oInit != 'undefined' && typeof oInit.aoColumns != 'undefined';
4469 for ( i=0, iLen=bUseCols ? oInit.aoColumns.length : nThs.length ; i<iLen ; i++ )
4470 {
4471 var col = bUseCols ? oInit.aoColumns[i] : null;
4472 var n = nThs ? nThs[i] : null;
4473 _fnAddColumn( oSettings, col, n );
4474 }
4475
4476 /* Sanity check that there is a thead and tfoot. If not let's just create them */
4477 if ( this.getElementsByTagName('thead').length === 0 )
4478 {
4479 this.appendChild( document.createElement( 'thead' ) );
4480 }
4481
4482 if ( this.getElementsByTagName('tbody').length === 0 )
4483 {
4484 this.appendChild( document.createElement( 'tbody' ) );
4485 }
4486
4487 /* Check if there is data passing into the constructor */
4488 if ( bUsePassedData )
4489 {
4490 for ( i=0 ; i<oInit.aaData.length ; i++ )
4491 {
4492 _fnAddData( oSettings, oInit.aaData[ i ] );
4493 }
4494 }
4495 else
4496 {
4497 /* Grab the data from the page */
4498 _fnGatherData( oSettings );
4499 }
4500
4501 /* Copy the data index array */
4502 oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
4503
4504 /* Calculate sizes for columns */
4505 if ( oSettings.oFeatures.bAutoWidth )
4506 {
4507 _fnCalculateColumnWidths( oSettings );
4508 }
4509
4510 /* Initialisation complete - table can be drawn */
4511 oSettings.bInitialised = true;
4512
4513 /* Check if we need to initialise the table (it might not have been handed off to the
4514 * language processor)
4515 */
4516 if ( bInitHandedOff === false )
4517 {
4518 _fnInitalise( oSettings );
4519 }
4520 });
4521 };
4522 })(jQuery);