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