1/*! DataTables 2.2.2 2 * © SpryMedia Ltd - datatables.net/license 3 */ 4 5(function( factory ) { 6 "use strict"; 7 8 if ( typeof define === 'function' && define.amd ) { 9 // AMD 10 define( ['jquery'], function ( $ ) { 11 return factory( $, window, document ); 12 } ); 13 } 14 else if ( typeof exports === 'object' ) { 15 // CommonJS 16 // jQuery's factory checks for a global window - if it isn't present then it 17 // returns a factory function that expects the window object 18 var jq = require('jquery'); 19 20 if (typeof window === 'undefined') { 21 module.exports = function (root, $) { 22 if ( ! root ) { 23 // CommonJS environments without a window global must pass a 24 // root. This will give an error otherwise 25 root = window; 26 } 27 28 if ( ! $ ) { 29 $ = jq( root ); 30 } 31 32 return factory( $, root, root.document ); 33 }; 34 } 35 else { 36 module.exports = factory( jq, window, window.document ); 37 } 38 } 39 else { 40 // Browser 41 window.DataTable = factory( jQuery, window, document ); 42 } 43}(function( $, window, document ) { 44 "use strict"; 45 46 47 var DataTable = function ( selector, options ) 48 { 49 // Check if called with a window or jQuery object for DOM less applications 50 // This is for backwards compatibility 51 if (DataTable.factory(selector, options)) { 52 return DataTable; 53 } 54 55 // When creating with `new`, create a new DataTable, returning the API instance 56 if (this instanceof DataTable) { 57 return $(selector).DataTable(options); 58 } 59 else { 60 // Argument switching 61 options = selector; 62 } 63 64 var _that = this; 65 var emptyInit = options === undefined; 66 var len = this.length; 67 68 if ( emptyInit ) { 69 options = {}; 70 } 71 72 // Method to get DT API instance from jQuery object 73 this.api = function () 74 { 75 return new _Api( this ); 76 }; 77 78 this.each(function() { 79 // For each initialisation we want to give it a clean initialisation 80 // object that can be bashed around 81 var o = {}; 82 var oInit = len > 1 ? // optimisation for single table case 83 _fnExtend( o, options, true ) : 84 options; 85 86 87 var i=0, iLen; 88 var sId = this.getAttribute( 'id' ); 89 var defaults = DataTable.defaults; 90 var $this = $(this); 91 92 93 /* Sanity check */ 94 if ( this.nodeName.toLowerCase() != 'table' ) 95 { 96 _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); 97 return; 98 } 99 100 $(this).trigger( 'options.dt', oInit ); 101 102 /* Backwards compatibility for the defaults */ 103 _fnCompatOpts( defaults ); 104 _fnCompatCols( defaults.column ); 105 106 /* Convert the camel-case defaults to Hungarian */ 107 _fnCamelToHungarian( defaults, defaults, true ); 108 _fnCamelToHungarian( defaults.column, defaults.column, true ); 109 110 /* Setting up the initialisation object */ 111 _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true ); 112 113 114 115 /* Check to see if we are re-initialising a table */ 116 var allSettings = DataTable.settings; 117 for ( i=0, iLen=allSettings.length ; i<iLen ; i++ ) 118 { 119 var s = allSettings[i]; 120 121 /* Base check on table node */ 122 if ( 123 s.nTable == this || 124 (s.nTHead && s.nTHead.parentNode == this) || 125 (s.nTFoot && s.nTFoot.parentNode == this) 126 ) { 127 var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve; 128 var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy; 129 130 if ( emptyInit || bRetrieve ) 131 { 132 return s.oInstance; 133 } 134 else if ( bDestroy ) 135 { 136 new DataTable.Api(s).destroy(); 137 break; 138 } 139 else 140 { 141 _fnLog( s, 0, 'Cannot reinitialise DataTable', 3 ); 142 return; 143 } 144 } 145 146 /* If the element we are initialising has the same ID as a table which was previously 147 * initialised, but the table nodes don't match (from before) then we destroy the old 148 * instance by simply deleting it. This is under the assumption that the table has been 149 * destroyed by other methods. Anyone using non-id selectors will need to do this manually 150 */ 151 if ( s.sTableId == this.id ) 152 { 153 allSettings.splice( i, 1 ); 154 break; 155 } 156 } 157 158 /* Ensure the table has an ID - required for accessibility */ 159 if ( sId === null || sId === "" ) 160 { 161 sId = "DataTables_Table_"+(DataTable.ext._unique++); 162 this.id = sId; 163 } 164 165 /* Create the settings object for this table and set some of the default parameters */ 166 var oSettings = $.extend( true, {}, DataTable.models.oSettings, { 167 "sDestroyWidth": $this[0].style.width, 168 "sInstance": sId, 169 "sTableId": sId, 170 colgroup: $('<colgroup>').prependTo(this), 171 fastData: function (row, column, type) {
172 return _fnGetCellData(oSettings, row, column, type); 173 } 174 } ); 175 oSettings.nTable = this; 176 oSettings.oInit = oInit; 177 178 allSettings.push( oSettings ); 179 180 // Make a single API instance available for internal handling 181 oSettings.api = new _Api( oSettings ); 182 183 // Need to add the instance after the instance after the settings object has been added 184 // to the settings array, so we can self reference the table instance if more than one 185 oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable(); 186 187 // Backwards compatibility, before we apply all the defaults 188 _fnCompatOpts( oInit ); 189 190 // If the length menu is given, but the init display length is not, use the length menu 191 if ( oInit.aLengthMenu && ! oInit.iDisplayLength ) 192 { 193 oInit.iDisplayLength = Array.isArray(oInit.aLengthMenu[0]) 194 ? oInit.aLengthMenu[0][0] 195 : $.isPlainObject( oInit.aLengthMenu[0] ) 196 ? oInit.aLengthMenu[0].value 197 : oInit.aLengthMenu[0]; 198 } 199 200 // Apply the defaults and init options to make a single init object will all 201 // options defined from defaults and instance options. 202 oInit = _fnExtend( $.extend( true, {}, defaults ), oInit ); 203 204 205 // Map the initialisation options onto the settings object 206 _fnMap( oSettings.oFeatures, oInit, [ 207 "bPaginate", 208 "bLengthChange", 209 "bFilter", 210 "bSort", 211 "bSortMulti", 212 "bInfo", 213 "bProcessing", 214 "bAutoWidth", 215 "bSortClasses", 216 "bServerSide", 217 "bDeferRender" 218 ] ); 219 _fnMap( oSettings, oInit, [ 220 "ajax", 221 "fnFormatNumber", 222 "sServerMethod", 223 "aaSorting", 224 "aaSortingFixed", 225 "aLengthMenu", 226 "sPaginationType", 227 "iStateDuration", 228 "bSortCellsTop", 229 "iTabIndex", 230 "sDom", 231 "fnStateLoadCallback", 232 "fnStateSaveCallback", 233 "renderer", 234 "searchDelay", 235 "rowId", 236 "caption", 237 "layout", 238 "orderDescReverse", 239 "typeDetect", 240 [ "iCookieDuration", "iStateDuration" ], // backwards compat 241 [ "oSearch", "oPreviousSearch" ], 242 [ "aoSearchCols", "aoPreSearchCols" ], 243 [ "iDisplayLength", "_iDisplayLength" ] 244 ] ); 245 _fnMap( oSettings.oScroll, oInit, [ 246 [ "sScrollX", "sX" ], 247 [ "sScrollXInner", "sXInner" ], 248 [ "sScrollY", "sY" ], 249 [ "bScrollCollapse", "bCollapse" ] 250 ] ); 251 _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" ); 252 253 /* Callback functions which are array driven */ 254 _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback ); 255 _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams ); 256 _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams ); 257 _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded ); 258 _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback ); 259 _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow ); 260 _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback ); 261 _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback ); 262 _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete ); 263 _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback ); 264 265 oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId ); 266 267 /* Browser support detection */ 268 _fnBrowserDetect( oSettings ); 269 270 var oClasses = oSettings.oClasses; 271 272 $.extend( oClasses, DataTable.ext.classes, oInit.oClasses ); 273 $this.addClass( oClasses.table ); 274 275 if (! oSettings.oFeatures.bPaginate) { 276 oInit.iDisplayStart = 0; 277 } 278 279 if ( oSettings.iInitDisplayStart === undefined ) 280 { 281 /* Display start point, taking into account the save saving */ 282 oSettings.iInitDisplayStart = oInit.iDisplayStart; 283 oSettings._iDisplayStart = oInit.iDisplayStart; 284 } 285 286 var defer = oInit.iDeferLoading; 287 if ( defer !== null ) 288 { 289 oSettings.deferLoading = true; 290 291 var tmp = Array.isArray(defer); 292 oSettings._iRecordsDisplay = tmp ? defer[0] : defer; 293 oSettings._iRecordsTotal = tmp ? defer[1] : defer; 294 } 295 296 /* 297 * Columns 298 * See if we should load columns automatically or use defined ones 299 */ 300 var columnsInit = []; 301 var thead = this.getElementsByTagName('thead'); 302 var initHeaderLayout = _fnDetectHeader( oSettings, thead[0] ); 303 304 // If we don't have a columns array, then generate one with nulls 305 if ( oInit.aoColumns ) { 306 columnsInit = oInit.aoColumns; 307 } 308 else if ( initHeaderLayout.length ) { 309 for ( i=0, iLen=initHeaderLayout[0].length ; i<iLen ; i++ ) { 310 columnsInit.push( null ); 311 } 312 } 313 314 // Add the columns 315 for ( i=0, iLen=columnsInit.length ; i<iLen ; i++ ) { 316 _fnAddColumn( oSettings ); 317 } 318 319 // Apply the column definitions 320 _fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, columnsInit, initHeaderLayout, function (iCol, oDef) { 321 _fnColumnOptions( oSettings, iCol, oDef ); 322 } ); 323 324 /* HTML5 attribute detection - build an mData object automatically if the 325 * attributes are found 326 */ 327 var rowOne = $this.children('tbody').find('tr').eq(0); 328 329 if ( rowOne.length ) { 330 var a = function ( cell, name ) { 331 return cell.getAttribute( 'data-'+name ) !== null ? name : null; 332 }; 333 334 $( rowOne[0] ).children('th, td').each( function (i, cell) { 335 var col = oSettings.aoColumns[i]; 336 337 if (! col) { 338 _fnLog( oSettings, 0, 'Incorrect column count', 18 ); 339 } 340 341 if ( col.mData === i ) { 342 var sort = a( cell, 'sort' ) || a( cell, 'order' ); 343 var filter = a( cell, 'filter' ) || a( cell, 'search' ); 344 345 if ( sort !== null || filter !== null ) { 346 col.mData = { 347 _: i+'.display', 348 sort: sort !== null ? i+'.@data-'+sort : undefined, 349 type: sort !== null ? i+'.@data-'+sort : undefined, 350 filter: filter !== null ? i+'.@data-'+filter : undefined 351 }; 352 col._isArrayHost = true; 353 354 _fnColumnOptions( oSettings, i ); 355 } 356 } 357 } ); 358 } 359 360 // Must be done after everything which can be overridden by the state saving! 361 _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState ); 362 363 var features = oSettings.oFeatures; 364 if ( oInit.bStateSave ) 365 { 366 features.bStateSave = true; 367 } 368 369 // If aaSorting is not defined, then we use the first indicator in asSorting 370 // in case that has been altered, so the default sort reflects that option 371 if ( oInit.aaSorting === undefined ) { 372 var sorting = oSettings.aaSorting; 373 for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) { 374 sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0]; 375 } 376 } 377 378 // Do a first pass on the sorting classes (allows any size changes to be taken into 379 // account, and also will apply sorting disabled classes if disabled 380 _fnSortingClasses( oSettings ); 381 382 _fnCallbackReg( oSettings, 'aoDrawCallback', function () { 383 if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) { 384 _fnSortingClasses( oSettings ); 385 } 386 } ); 387 388 389 /* 390 * Table HTML init 391 * Cache the header, body and footer as required, creating them if needed 392 */ 393 var caption = $this.children('caption'); 394 395 if ( oSettings.caption ) { 396 if ( caption.length === 0 ) { 397 caption = $('<caption/>').appendTo( $this ); 398 } 399 400 caption.html( oSettings.caption ); 401 } 402 403 // Store the caption side, so we can remove the element from the document 404 // when creating the element 405 if (caption.length) { 406 caption[0]._captionSide = caption.css('caption-side'); 407 oSettings.captionNode = caption[0]; 408 } 409 410 if ( thead.length === 0 ) { 411 thead = $('<thead/>').appendTo($this); 412 } 413 oSettings.nTHead = thead[0]; 414 415 var tbody = $this.children('tbody'); 416 if ( tbody.length === 0 ) { 417 tbody = $('<tbody/>').insertAfter(thead); 418 } 419 oSettings.nTBody = tbody[0]; 420 421 var tfoot = $this.children('tfoot'); 422 if ( tfoot.length === 0 ) { 423 // If we are a scrolling table, and no footer has been given, then we need to create 424 // a tfoot element for the caption element to be appended to 425 tfoot = $('<tfoot/>').appendTo($this); 426 } 427 oSettings.nTFoot = tfoot[0]; 428 429 // Copy the data index array 430 oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); 431 432 // Initialisation complete - table can be drawn
433 oSettings.bInitialised = true; 434 435 // Language definitions 436 var oLanguage = oSettings.oLanguage; 437 $.extend( true, oLanguage, oInit.oLanguage ); 438 439 if ( oLanguage.sUrl ) { 440 // Get the language definitions from a file 441 $.ajax( { 442 dataType: 'json', 443 url: oLanguage.sUrl, 444 success: function ( json ) { 445 _fnCamelToHungarian( defaults.oLanguage, json ); 446 $.extend( true, oLanguage, json, oSettings.oInit.oLanguage ); 447 448 _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true); 449 _fnInitialise( oSettings ); 450 }, 451 error: function () { 452 // Error occurred loading language file 453 _fnLog( oSettings, 0, 'i18n file loading error', 21 ); 454 455 // Continue on as best we can 456 _fnInitialise( oSettings ); 457 } 458 } ); 459 } 460 else { 461 _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true); 462 _fnInitialise( oSettings ); 463 } 464 } ); 465 _that = null; 466 return this; 467 }; 468 469 470 471 /** 472 * DataTables extensions 473 * 474 * This namespace acts as a collection area for plug-ins that can be used to 475 * extend DataTables capabilities. Indeed many of the build in methods 476 * use this method to provide their own capabilities (sorting methods for 477 * example). 478 * 479 * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy 480 * reasons 481 * 482 * @namespace 483 */ 484 DataTable.ext = _ext = { 485 /** 486 * Buttons. For use with the Buttons extension for DataTables. This is 487 * defined here so other extensions can define buttons regardless of load 488 * order. It is _not_ used by DataTables core. 489 * 490 * @type object 491 * @default {} 492 */ 493 buttons: {}, 494 495 496 /** 497 * Element class names 498 * 499 * @type object 500 * @default {} 501 */ 502 classes: {}, 503 504 505 /** 506 * DataTables build type (expanded by the download builder) 507 * 508 * @type string 509 */ 510 builder: "-source-", 511 512 513 /** 514 * Error reporting. 515 * 516 * How should DataTables report an error. Can take the value 'alert', 517 * 'throw', 'none' or a function. 518 * 519 * @type string|function 520 * @default alert 521 */ 522 errMode: "alert", 523 524 525 /** 526 * Legacy so v1 plug-ins don't throw js errors on load 527 */ 528 feature: [], 529 530 /** 531 * Feature plug-ins. 532 * 533 * This is an object of callbacks which provide the features for DataTables 534 * to be initialised via the `layout` option. 535 */ 536 features: {}, 537 538 539 /** 540 * Row searching. 541 * 542 * This method of searching is complimentary to the default type based 543 * searching, and a lot more comprehensive as it allows you complete control 544 * over the searching logic. Each element in this array is a function 545 * (parameters described below) that is called for every row in the table, 546 * and your logic decides if it should be included in the searching data set 547 * or not. 548 * 549 * Searching functions have the following input parameters: 550 * 551 * 1. `{object}` DataTables settings object: see 552 * {@link DataTable.models.oSettings} 553 * 2. `{array|object}` Data for the row to be processed (same as the 554 * original format that was passed in as the data source, or an array 555 * from a DOM data source 556 * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which 557 * can be useful to retrieve the `TR` element if you need DOM interaction. 558 * 559 * And the following return is expected: 560 * 561 * * {boolean} Include the row in the searched result set (true) or not 562 * (false) 563 * 564 * Note that as with the main search ability in DataTables, technically this 565 * is "filtering", since it is subtractive. However, for consistency in 566 * naming we call it searching here. 567 * 568 * @type array 569 * @default [] 570 * 571 * @example 572 * // The following example shows custom search being applied to the 573 * // fourth column (i.e. the data[3] index) based on two input values 574 * // from the end-user, matching the data in a certain range. 575 * $.fn.dataTable.ext.search.push( 576 * function( settings, data, dataIndex ) { 577 * var min = document.getElementById('min').value * 1; 578 * var max = document.getElementById('max').value * 1; 579 * var version = data[3] == "-" ? 0 : data[3]*1; 580 * 581 * if ( min == "" && max == "" ) { 582 * return true; 583 * } 584 * else if ( min == "" && version < max ) { 585 * return true; 586 * } 587 * else if ( min < version && "" == max ) { 588 * return true; 589 * } 590 * else if ( min < version && version < max ) { 591 * return true; 592 * } 593 * return false;
594 * } 595 * ); 596 */ 597 search: [], 598 599 600 /** 601 * Selector extensions 602 * 603 * The `selector` option can be used to extend the options available for the 604 * selector modifier options (`selector-modifier` object data type) that 605 * each of the three built in selector types offer (row, column and cell + 606 * their plural counterparts). For example the Select extension uses this 607 * mechanism to provide an option to select only rows, columns and cells 608 * that have been marked as selected by the end user (`{selected: true}`), 609 * which can be used in conjunction with the existing built in selector 610 * options. 611 * 612 * Each property is an array to which functions can be pushed. The functions 613 * take three attributes: 614 * 615 * * Settings object for the host table 616 * * Options object (`selector-modifier` object type) 617 * * Array of selected item indexes 618 * 619 * The return is an array of the resulting item indexes after the custom 620 * selector has been applied. 621 * 622 * @type object 623 */ 624 selector: { 625 cell: [], 626 column: [], 627 row: [] 628 }, 629 630 631 /** 632 * Legacy configuration options. Enable and disable legacy options that 633 * are available in DataTables. 634 * 635 * @type object 636 */ 637 legacy: { 638 /** 639 * Enable / disable DataTables 1.9 compatible server-side processing 640 * requests 641 * 642 * @type boolean 643 * @default null 644 */ 645 ajax: null 646 }, 647 648 649 /** 650 * Pagination plug-in methods. 651 * 652 * Each entry in this object is a function and defines which buttons should 653 * be shown by the pagination rendering method that is used for the table: 654 * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the 655 * buttons are displayed in the document, while the functions here tell it 656 * what buttons to display. This is done by returning an array of button 657 * descriptions (what each button will do). 658 * 659 * Pagination types (the four built in options and any additional plug-in 660 * options defined here) can be used through the `paginationType` 661 * initialisation parameter. 662 * 663 * The functions defined take two parameters: 664 * 665 * 1. `{int} page` The current page index 666 * 2. `{int} pages` The number of pages in the table 667 * 668 * Each function is expected to return an array where each element of the 669 * array can be one of: 670 * 671 * * `first` - Jump to first page when activated 672 * * `last` - Jump to last page when activated 673 * * `previous` - Show previous page when activated 674 * * `next` - Show next page when activated 675 * * `{int}` - Show page of the index given 676 * * `{array}` - A nested array containing the above elements to add a 677 * containing 'DIV' element (might be useful for styling). 678 * 679 * Note that DataTables v1.9- used this object slightly differently whereby 680 * an object with two functions would be defined for each plug-in. That 681 * ability is still supported by DataTables 1.10+ to provide backwards 682 * compatibility, but this option of use is now decremented and no longer 683 * documented in DataTables 1.10+. 684 * 685 * @type object 686 * @default {} 687 * 688 * @example 689 * // Show previous, next and current page buttons only 690 * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { 691 * return [ 'previous', page, 'next' ]; 692 * }; 693 */ 694 pager: {}, 695 696 697 renderer: { 698 pageButton: {}, 699 header: {} 700 }, 701 702 703 /** 704 * Ordering plug-ins - custom data source 705 * 706 * The extension options for ordering of data available here is complimentary 707 * to the default type based ordering that DataTables typically uses. It 708 * allows much greater control over the the data that is being used to 709 * order a column, but is necessarily therefore more complex. 710 * 711 * This type of ordering is useful if you want to do ordering based on data 712 * live from the DOM (for example the contents of an 'input' element) rather 713 * than just the static string that DataTables knows of. 714 * 715 * The way these plug-ins work is that you create an array of the values you 716 * wish to be ordering for the column in question and then return that 717 * array. The data in the array much be in the index order of the rows in 718 * the table (not the currently ordering order!). Which order data gathering 719 * function is run here depends on the `dt-init columns.orderDataType` 720 * parameter that is used for the column (if any). 721 * 722 * The functions defined take two parameters: 723 * 724 * 1. `{object}` DataTables settings object: see 725 * {@link DataTable.models.oSettings} 726 * 2. `{int}` Target column index 727 * 728 * Each function is expected to return an array: 729 * 730 * * `{array}` Data for the column to be ordering upon 731 * 732 * @type array 733 * 734 * @example 735 * // Ordering using `input` node values 736 * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) 737 * { 738 * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { 739 * return $('input', td).val(); 740 * } ); 741 * } 742 */ 743 order: {}, 744 745 746 /** 747 * Type based plug-ins. 748 * 749 * Each column in DataTables has a type assigned to it, either by automatic 750 * detection or by direct assignment using the `type` option for the column. 751 * The type of a column will effect how it is ordering and search (plug-ins 752 * can also make use of the column type if required). 753 * 754 * @namespace 755 */ 756 type: { 757 /** 758 * Automatic column class assignment 759 */ 760 className: {}, 761 762 /** 763 * Type detection functions. 764 * 765 * The functions defined in this object are used to automatically detect 766 * a column's type, making initialisation of DataTables super easy, even 767 * when complex data is in the table. 768 * 769 * The functions defined take two parameters: 770 * 771 * 1. `{*}` Data from the column cell to be analysed 772 * 2. `{settings}` DataTables settings object. This can be used to 773 * perform context specific type detection - for example detection 774 * based on language settings such as using a comma for a decimal 775 * place. Generally speaking the options from the settings will not 776 * be required 777 *
778 * Each function is expected to return: 779 * 780 * * `{string|null}` Data type detected, or null if unknown (and thus 781 * pass it on to the other type detection functions. 782 * 783 * @type array 784 * 785 * @example 786 * // Currency type detection plug-in: 787 * $.fn.dataTable.ext.type.detect.push( 788 * function ( data, settings ) { 789 * // Check the numeric part 790 * if ( ! data.substring(1).match(/[0-9]/) ) { 791 * return null; 792 * } 793 * 794 * // Check prefixed by currency 795 * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) { 796 * return 'currency'; 797 * } 798 * return null; 799 * } 800 * ); 801 */ 802 detect: [], 803 804 /** 805 * Automatic renderer assignment 806 */ 807 render: {}, 808 809 810 /** 811 * Type based search formatting. 812 * 813 * The type based searching functions can be used to pre-format the 814 * data to be search on. For example, it can be used to strip HTML 815 * tags or to de-format telephone numbers for numeric only searching. 816 * 817 * Note that is a search is not defined for a column of a given type, 818 * no search formatting will be performed. 819 * 820 * Pre-processing of searching data plug-ins - When you assign the sType 821 * for a column (or have it automatically detected for you by DataTables 822 * or a type detection plug-in), you will typically be using this for 823 * custom sorting, but it can also be used to provide custom searching 824 * by allowing you to pre-processing the data and returning the data in 825 * the format that should be searched upon. This is done by adding 826 * functions this object with a parameter name which matches the sType 827 * for that target column. This is the corollary of <i>afnSortData</i> 828 * for searching data. 829 * 830 * The functions defined take a single parameter: 831 * 832 * 1. `{*}` Data from the column cell to be prepared for searching 833 * 834 * Each function is expected to return: 835 * 836 * * `{string|null}` Formatted string that will be used for the searching. 837 * 838 * @type object 839 * @default {} 840 * 841 * @example 842 * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { 843 * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); 844 * } 845 */ 846 search: {}, 847 848 849 /** 850 * Type based ordering. 851 * 852 * The column type tells DataTables what ordering to apply to the table 853 * when a column is sorted upon. The order for each type that is defined, 854 * is defined by the functions available in this object. 855 * 856 * Each ordering option can be described by three properties added to 857 * this object: 858 * 859 * * `{type}-pre` - Pre-formatting function 860 * * `{type}-asc` - Ascending order function 861 * * `{type}-desc` - Descending order function 862 * 863 * All three can be used together, only `{type}-pre` or only 864 * `{type}-asc` and `{type}-desc` together. It is generally recommended 865 * that only `{type}-pre` is used, as this provides the optimal 866 * implementation in terms of speed, although the others are provided 867 * for compatibility with existing Javascript sort functions. 868 * 869 * `{type}-pre`: Functions defined take a single parameter: 870 * 871 * 1. `{*}` Data from the column cell to be prepared for ordering 872 * 873 * And return: 874 * 875 * * `{*}` Data to be sorted upon 876 * 877 * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort 878 * functions, taking two parameters: 879 * 880 * 1. `{*}` Data to compare to the second parameter 881 * 2. `{*}` Data to compare to the first parameter 882 * 883 * And returning: 884 * 885 * * `{*}` Ordering match: <0 if first parameter should be sorted lower 886 * than the second parameter, ===0 if the two parameters are equal and 887 * >0 if the first parameter should be sorted height than the second 888 * parameter. 889 * 890 * @type object 891 * @default {} 892 * 893 * @example 894 * // Numeric ordering of formatted numbers with a pre-formatter 895 * $.extend( $.fn.dataTable.ext.type.order, { 896 * "string-pre": function(x) { 897 * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); 898 * return parseFloat( a ); 899 * } 900 * } ); 901 * 902 * @example 903 * // Case-sensitive string ordering, with no pre-formatting method 904 * $.extend( $.fn.dataTable.ext.order, { 905 * "string-case-asc": function(x,y) { 906 * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); 907 * }, 908 * "string-case-desc": function(x,y) { 909 * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); 910 * } 911 * } ); 912 */ 913 order: {} 914 }, 915 916 /** 917 * Unique DataTables instance counter 918 * 919 * @type int 920 * @private 921 */ 922 _unique: 0, 923 924 925 // 926 // Depreciated 927 // The following properties are retained for backwards compatibility only. 928 // The should not be used in new projects and will be removed in a future 929 // version 930 // 931 932 /** 933 * Version check function. 934 * @type function 935 * @depreciated Since 1.10 936 */ 937 fnVersionCheck: DataTable.fnVersionCheck, 938 939 940 /** 941 * Index for what 'this' index API functions should use 942 * @type int 943 * @deprecated Since v1.10 944 */ 945 iApiIndex: 0, 946 947 948 /** 949 * Software version 950 * @type string 951 * @deprecated Since v1.10 952 */
953 sVersion: DataTable.version 954 }; 955 956 957 // 958 // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts 959 // 960 $.extend( _ext, { 961 afnFiltering: _ext.search, 962 aTypes: _ext.type.detect, 963 ofnSearch: _ext.type.search, 964 oSort: _ext.type.order, 965 afnSortData: _ext.order, 966 aoFeatures: _ext.feature, 967 oStdClasses: _ext.classes, 968 oPagination: _ext.pager 969 } ); 970 971 972 $.extend( DataTable.ext.classes, { 973 container: 'dt-container', 974 empty: { 975 row: 'dt-empty' 976 }, 977 info: { 978 container: 'dt-info' 979 }, 980 layout: { 981 row: 'dt-layout-row', 982 cell: 'dt-layout-cell', 983 tableRow: 'dt-layout-table', 984 tableCell: '', 985 start: 'dt-layout-start', 986 end: 'dt-layout-end', 987 full: 'dt-layout-full' 988 }, 989 length: { 990 container: 'dt-length', 991 select: 'dt-input' 992 }, 993 order: { 994 canAsc: 'dt-orderable-asc', 995 canDesc: 'dt-orderable-desc', 996 isAsc: 'dt-ordering-asc', 997 isDesc: 'dt-ordering-desc', 998 none: 'dt-orderable-none', 999 position: 'sorting_' 1000 }, 1001 processing: { 1002 container: 'dt-processing' 1003 }, 1004 scrolling: { 1005 body: 'dt-scroll-body', 1006 container: 'dt-scroll', 1007 footer: { 1008 self: 'dt-scroll-foot', 1009 inner: 'dt-scroll-footInner' 1010 }, 1011 header: { 1012 self: 'dt-scroll-head', 1013 inner: 'dt-scroll-headInner' 1014 } 1015 }, 1016 search: { 1017 container: 'dt-search', 1018 input: 'dt-input' 1019 }, 1020 table: 'dataTable', 1021 tbody: { 1022 cell: '', 1023 row: '' 1024 }, 1025 thead: { 1026 cell: '', 1027 row: '' 1028 }, 1029 tfoot: { 1030 cell: '', 1031 row: '' 1032 }, 1033 paging: { 1034 active: 'current', 1035 button: 'dt-paging-button', 1036 container: 'dt-paging', 1037 disabled: 'disabled', 1038 nav: '' 1039 } 1040 } ); 1041 1042 1043 /*
1044 * It is useful to have variables which are scoped locally so only the 1045 * DataTables functions can access them and they don't leak into global space. 1046 * At the same time these functions are often useful over multiple files in the 1047 * core and API, so we list, or at least document, all variables which are used 1048 * by DataTables as private variables here. This also ensures that there is no 1049 * clashing of variable names and that they can easily referenced for reuse. 1050 */ 1051 1052 1053 // Defined else where 1054 // _selector_run 1055 // _selector_opts 1056 // _selector_row_indexes 1057 1058 var _ext; // DataTable.ext 1059 var _Api; // DataTable.Api 1060 var _api_register; // DataTable.Api.register 1061 var _api_registerPlural; // DataTable.Api.registerPlural 1062 1063 var _re_dic = {}; 1064 var _re_new_lines = /[\r\n\u2028]/g; 1065 var _re_html = /<([^>]*>)/g; 1066 var _max_str_len = Math.pow(2, 28); 1067 1068 // This is not strict ISO8601 - Date.parse() is quite lax, although 1069 // implementations differ between browsers. 1070 var _re_date = /^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/; 1071 1072 // Escape regular expression special characters 1073 var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); 1074 1075 // https://en.wikipedia.org/wiki/Foreign_exchange_market 1076 // - \u20BD - Russian ruble. 1077 // - \u20a9 - South Korean Won 1078 // - \u20BA - Turkish Lira 1079 // - \u20B9 - Indian Rupee 1080 // - R - Brazil (R$) and South Africa 1081 // - fr - Swiss Franc 1082 // - kr - Swedish krona, Norwegian krone and Danish krone 1083 // - \u2009 is thin space and \u202F is narrow no-break space, both used in many 1084 // - É - Bitcoin 1085 // - Î - Ethereum 1086 // standards as thousands separators. 1087 var _re_formatted_numeric = /['\u00A0,$£â¬Â¥%\u2009\u202F\u20BD\u20a9\u20BArfkÉÎ]/gi; 1088 1089 1090 var _empty = function ( d ) { 1091 return !d || d === true || d === '-' ? true : false; 1092 }; 1093 1094 1095 var _intVal = function ( s ) { 1096 var integer = parseInt( s, 10 ); 1097 return !isNaN(integer) && isFinite(s) ? integer : null; 1098 }; 1099 1100 // Convert from a formatted number with characters other than `.` as the 1101 // decimal place, to a Javascript number 1102 var _numToDecimal = function ( num, decimalPoint ) { 1103 // Cache created regular expressions for speed as this function is called often 1104 if ( ! _re_dic[ decimalPoint ] ) { 1105 _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); 1106 } 1107 return typeof num === 'string' && decimalPoint !== '.' ? 1108 num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : 1109 num; 1110 }; 1111 1112 1113 var _isNumber = function ( d, decimalPoint, formatted, allowEmpty ) { 1114 var type = typeof d; 1115 var strType = type === 'string'; 1116 1117 if ( type === 'number' || type === 'bigint') { 1118 return true; 1119 } 1120 1121 // If empty return immediately so there must be a number if it is a 1122 // formatted string (this stops the string "k", or "kr", etc being detected 1123 // as a formatted number for currency 1124 if ( allowEmpty && _empty( d ) ) { 1125 return true; 1126 } 1127 1128 if ( decimalPoint && strType ) { 1129 d = _numToDecimal( d, decimalPoint ); 1130 } 1131 1132 if ( formatted && strType ) { 1133 d = d.replace( _re_formatted_numeric, '' ); 1134 } 1135 1136 return !isNaN( parseFloat(d) ) && isFinite( d ); 1137 }; 1138 1139 1140 // A string without HTML in it can be considered to be HTML still 1141 var _isHtml = function ( d ) { 1142 return _empty( d ) || typeof d === 'string'; 1143 }; 1144 1145 // Is a string a number surrounded by HTML? 1146 var _htmlNumeric = function ( d, decimalPoint, formatted, allowEmpty ) { 1147 if ( allowEmpty && _empty( d ) ) { 1148 return true; 1149 } 1150 1151 // input and select strings mean that this isn't just a number 1152 if (typeof d === 'string' && d.match(/<(input|select)/i)) { 1153 return null; 1154 } 1155 1156 var html = _isHtml( d ); 1157 return ! html ? 1158 null : 1159 _isNumber( _stripHtml( d ), decimalPoint, formatted, allowEmpty ) ? 1160 true : 1161 null; 1162 }; 1163 1164 1165 var _pluck = function ( a, prop, prop2 ) { 1166 var out = []; 1167 var i=0, ien=a.length; 1168 1169 // Could have the test in the loop for slightly smaller code, but speed 1170 // is essential here 1171 if ( prop2 !== undefined ) { 1172 for ( ; i<ien ; i++ ) { 1173 if ( a[i] && a[i][ prop ] ) { 1174 out.push( a[i][ prop ][ prop2 ] ); 1175 } 1176 } 1177 } 1178 else { 1179 for ( ; i<ien ; i++ ) { 1180 if ( a[i] ) { 1181 out.push( a[i][ prop ] ); 1182 } 1183 } 1184 } 1185 1186 return out; 1187 }; 1188 1189 1190 // Basically the same as _pluck, but rather than looping over `a` we use `order` 1191 // as the indexes to pick from `a` 1192 var _pluck_order = function ( a, order, prop, prop2 ) 1193 { 1194 var out = []; 1195 var i=0, ien=order.length; 1196 1197 // Could have the test in the loop for slightly smaller code, but speed 1198 // is essential here 1199 if ( prop2 !== undefined ) { 1200 for ( ; i<ien ; i++ ) { 1201 if ( a[ order[i] ] && a[ order[i] ][ prop ] ) { 1202 out.push( a[ order[i] ][ prop ][ prop2 ] ); 1203 } 1204 } 1205 } 1206 else { 1207 for ( ; i<ien ; i++ ) { 1208 if ( a[ order[i] ] ) { 1209 out.push( a[ order[i] ][ prop ] ); 1210 } 1211 } 1212 } 1213 1214 return out; 1215 }; 1216 1217 1218 var _range = function ( len, start ) 1219 { 1220 var out = []; 1221 var end; 1222 1223 if ( start === undefined ) { 1224 start = 0; 1225 end = len; 1226 } 1227 else { 1228 end = start; 1229 start = len; 1230 } 1231 1232 for ( var i=start ; i<end ; i++ ) { 1233 out.push( i ); 1234 } 1235 1236 return out; 1237 }; 1238 1239 1240 var _removeEmpty = function ( a ) 1241 { 1242 var out = []; 1243 1244 for ( var i=0, ien=a.length ; i<ien ; i++ ) { 1245 if ( a[i] ) { // careful - will remove all falsy values! 1246 out.push( a[i] ); 1247 } 1248 } 1249 1250 return out; 1251 }; 1252 1253 // Replaceable function in api.util 1254 var _stripHtml = function (input) { 1255 if (! input || typeof input !== 'string') { 1256 return input; 1257 } 1258 1259 // Irrelevant check to workaround CodeQL's false positive on the regex 1260 if (input.length > _max_str_len) { 1261 throw new Error('Exceeded max str len'); 1262 } 1263 1264 var previous; 1265 1266 input = input.replace(_re_html, ''); // Complete tags 1267 1268 // Safety for incomplete script tag - use do / while to ensure that 1269 // we get all instances 1270 do { 1271 previous = input; 1272 input = input.replace(/<script/i, ''); 1273 } while (input !== previous); 1274 1275 return previous; 1276 }; 1277 1278 // Replaceable function in api.util 1279 var _escapeHtml = function ( d ) { 1280 if (Array.isArray(d)) { 1281 d = d.join(','); 1282 } 1283 1284 return typeof d === 'string' ? 1285 d 1286 .replace(/&/g, '&') 1287 .replace(/</g, '<') 1288 .replace(/>/g, '>') 1289 .replace(/"/g, '"') : 1290 d; 1291 }; 1292 1293 // Remove diacritics from a string by decomposing it and then removing 1294 // non-ascii characters 1295 var _normalize = function (str, both) { 1296 if (typeof str !== 'string') { 1297 return str; 1298 } 1299 1300 // It is faster to just run `normalize` than it is to check if 1301 // we need to with a regex! (Check as it isn't available in old 1302 // Safari) 1303 var res = str.normalize 1304 ? str.normalize("NFD") 1305 : str; 1306 1307 // Equally, here we check if a regex is needed or not 1308 return res.length !== str.length 1309 ? (both === true ? str + ' ' : '' ) + res.replace(/[\u0300-\u036f]/g, "") 1310 : res; 1311 } 1312 1313 /** 1314 * Determine if all values in the array are unique. This means we can short 1315 * cut the _unique method at the cost of a single loop. A sorted array is used 1316 * to easily check the values. 1317 * 1318 * @param {array} src Source array 1319 * @return {boolean} true if all unique, false otherwise 1320 * @ignore 1321 */ 1322 var _areAllUnique = function ( src ) { 1323 if ( src.length < 2 ) { 1324 return true; 1325 } 1326 1327 var sorted = src.slice().sort(); 1328 var last = sorted[0]; 1329 1330 for ( var i=1, ien=sorted.length ; i<ien ; i++ ) { 1331 if ( sorted[i] === last ) { 1332 return false;
1333 } 1334 1335 last = sorted[i]; 1336 } 1337 1338 return true; 1339 }; 1340 1341 1342 /** 1343 * Find the unique elements in a source array. 1344 * 1345 * @param {array} src Source array 1346 * @return {array} Array of unique items 1347 * @ignore 1348 */ 1349 var _unique = function ( src ) 1350 { 1351 if (Array.from && Set) { 1352 return Array.from(new Set(src)); 1353 } 1354 1355 if ( _areAllUnique( src ) ) { 1356 return src.slice(); 1357 } 1358 1359 // A faster unique method is to use object keys to identify used values, 1360 // but this doesn't work with arrays or objects, which we must also 1361 // consider. See jsperf.app/compare-array-unique-versions/4 for more 1362 // information. 1363 var 1364 out = [], 1365 val, 1366 i, ien=src.length, 1367 j, k=0; 1368 1369 again: for ( i=0 ; i<ien ; i++ ) { 1370 val = src[i]; 1371 1372 for ( j=0 ; j<k ; j++ ) { 1373 if ( out[j] === val ) { 1374 continue again; 1375 } 1376 } 1377 1378 out.push( val ); 1379 k++; 1380 } 1381 1382 return out; 1383 }; 1384 1385 // Surprisingly this is faster than [].concat.apply 1386 // https://jsperf.com/flatten-an-array-loop-vs-reduce/2 1387 var _flatten = function (out, val) { 1388 if (Array.isArray(val)) { 1389 for (var i=0 ; i<val.length ; i++) { 1390 _flatten(out, val[i]); 1391 } 1392 } 1393 else { 1394 out.push(val); 1395 } 1396 1397 return out; 1398 } 1399 1400 // Similar to jQuery's addClass, but use classList.add 1401 function _addClass(el, name) { 1402 if (name) { 1403 name.split(' ').forEach(function (n) { 1404 if (n) { 1405 // `add` does deduplication, so no need to check `contains` 1406 el.classList.add(n); 1407 } 1408 }); 1409 } 1410 } 1411 1412 /** 1413 * DataTables utility methods 1414 * 1415 * This namespace provides helper methods that DataTables uses internally to 1416 * create a DataTable, but which are not exclusively used only for DataTables. 1417 * These methods can be used by extension authors to save the duplication of 1418 * code. 1419 * 1420 * @namespace 1421 */ 1422 DataTable.util = { 1423 /** 1424 * Return a string with diacritic characters decomposed 1425 * @param {*} mixed Function or string to normalize 1426 * @param {*} both Return original string and the normalized string 1427 * @returns String or undefined 1428 */ 1429 diacritics: function (mixed, both) { 1430 var type = typeof mixed; 1431 1432 if (type !== 'function') { 1433 return _normalize(mixed, both); 1434 } 1435 _normalize = mixed; 1436 }, 1437 1438 /** 1439 * Debounce a function 1440 * 1441 * @param {function} fn Function to be called 1442 * @param {integer} freq Call frequency in mS 1443 * @return {function} Wrapped function 1444 */ 1445 debounce: function ( fn, timeout ) { 1446 var timer; 1447 1448 return function () { 1449 var that = this; 1450 var args = arguments; 1451 1452 clearTimeout(timer); 1453 1454 timer = setTimeout( function () { 1455 fn.apply(that, args); 1456 }, timeout || 250 ); 1457 }; 1458 }, 1459 1460 /** 1461 * Throttle the calls to a function. Arguments and context are maintained 1462 * for the throttled function. 1463 * 1464 * @param {function} fn Function to be called 1465 * @param {integer} freq Call frequency in mS 1466 * @return {function} Wrapped function 1467 */ 1468 throttle: function ( fn, freq ) { 1469 var 1470 frequency = freq !== undefined ? freq : 200, 1471 last, 1472 timer; 1473 1474 return function () { 1475 var 1476 that = this, 1477 now = +new Date(), 1478 args = arguments; 1479 1480 if ( last && now < last + frequency ) { 1481 clearTimeout( timer ); 1482 1483 timer = setTimeout( function () { 1484 last = undefined; 1485 fn.apply( that, args ); 1486 }, frequency ); 1487 } 1488 else { 1489 last = now; 1490 fn.apply( that, args ); 1491 } 1492 }; 1493 }, 1494 1495 /** 1496 * Escape a string such that it can be used in a regular expression 1497 * 1498 * @param {string} val string to escape 1499 * @returns {string} escaped string 1500 */ 1501 escapeRegex: function ( val ) { 1502 return val.replace( _re_escape_regex, '\\$1' ); 1503 }, 1504 1505 /** 1506 * Create a function that will write to a nested object or array 1507 * @param {*} source JSON notation string 1508 * @returns Write function 1509 */ 1510 set: function ( source ) { 1511 if ( $.isPlainObject( source ) ) { 1512 /* Unlike get, only the underscore (global) option is used for for 1513 * setting data since we don't know the type here. This is why an object 1514 * option is not documented for `mData` (which is read/write), but it is 1515 * for `mRender` which is read only. 1516 */ 1517 return DataTable.util.set( source._ ); 1518 } 1519 else if ( source === null ) { 1520 // Nothing to do when the data source is null 1521 return function () {}; 1522 } 1523 else if ( typeof source === 'function' ) { 1524 return function (data, val, meta) { 1525 source( data, 'set', val, meta ); 1526 }; 1527 } 1528 else if ( 1529 typeof source === 'string' && (source.indexOf('.') !== -1 || 1530 source.indexOf('[') !== -1 || source.indexOf('(') !== -1) 1531 ) { 1532 // Like the get, we need to get data from a nested object 1533 var setData = function (data, val, src) { 1534 var a = _fnSplitObjNotation( src ), b; 1535 var aLast = a[a.length-1]; 1536 var arrayNotation, funcNotation, o, innerSrc; 1537 1538 for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ ) { 1539 // Protect against prototype pollution 1540 if (a[i] === '__proto__' || a[i] === 'constructor') { 1541 throw new Error('Cannot set prototype values'); 1542 } 1543 1544 // Check if we are dealing with an array notation request 1545 arrayNotation = a[i].match(__reArray); 1546 funcNotation = a[i].match(__reFn); 1547 1548 if ( arrayNotation ) { 1549 a[i] = a[i].replace(__reArray, ''); 1550 data[ a[i] ] = []; 1551 1552 // Get the remainder of the nested object to set so we can recurse 1553 b = a.slice(); 1554 b.splice( 0, i+1 ); 1555 innerSrc = b.join('.'); 1556 1557 // Traverse each entry in the array setting the properties requested 1558 if ( Array.isArray( val ) ) { 1559 for ( var j=0, jLen=val.length ; j<jLen ; j++ ) { 1560 o = {}; 1561 setData( o, val[j], innerSrc ); 1562 data[ a[i] ].push( o ); 1563 } 1564 } 1565 else { 1566 // We've been asked to save data to an array, but it 1567 // isn't array data to be saved. Best that can be done 1568 // is to just save the value. 1569 data[ a[i] ] = val; 1570 } 1571 1572 // The inner call to setData has already traversed through the remainder 1573 // of the source and has set the data, thus we can exit here 1574 return; 1575 } 1576 else if ( funcNotation ) { 1577 // Function call 1578 a[i] = a[i].replace(__reFn, ''); 1579 data = data[ a[i] ]( val ); 1580 } 1581 1582 // If the nested object doesn't currently exist - since we are 1583 // trying to set the value - create it 1584 if ( data[ a[i] ] === null || data[ a[i] ] === undefined ) { 1585 data[ a[i] ] = {}; 1586 } 1587 data = data[ a[i] ]; 1588 } 1589 1590 // Last item in the input - i.e, the actual set 1591 if ( aLast.match(__reFn ) ) { 1592 // Function call 1593 data = data[ aLast.replace(__reFn, '') ]( val ); 1594 } 1595 else { 1596 // If array notation is used, we just want to strip it and use the property name 1597 // and assign the value. If it isn't used, then we get the result we want anyway 1598 data[ aLast.replace(__reArray, '') ] = val; 1599 } 1600 }; 1601 1602 return function (data, val) { // meta is also passed in, but not used 1603 return setData( data, val, source ); 1604 }; 1605 } 1606 else { 1607 // Array or flat object mapping 1608 return function (data, val) { // meta is also passed in, but not used 1609 data[source] = val; 1610 }; 1611 } 1612 }, 1613 1614 /** 1615 * Create a function that will read nested objects from arrays, based on JSON notation 1616 * @param {*} source JSON notation string 1617 * @returns Value read 1618 */ 1619 get: function ( source ) { 1620 if ( $.isPlainObject( source ) ) { 1621 // Build an object of get functions, and wrap them in a single call 1622 var o = {}; 1623 $.each( source, function (key, val) { 1624 if ( val ) { 1625 o[key] = DataTable.util.get( val ); 1626 } 1627 } ); 1628
1629 return function (data, type, row, meta) { 1630 var t = o[type] || o._; 1631 return t !== undefined ? 1632 t(data, type, row, meta) : 1633 data; 1634 }; 1635 } 1636 else if ( source === null ) { 1637 // Give an empty string for rendering / sorting etc 1638 return function (data) { // type, row and meta also passed, but not used 1639 return data; 1640 }; 1641 } 1642 else if ( typeof source === 'function' ) { 1643 return function (data, type, row, meta) { 1644 return source( data, type, row, meta ); 1645 }; 1646 } 1647 else if ( 1648 typeof source === 'string' && (source.indexOf('.') !== -1 || 1649 source.indexOf('[') !== -1 || source.indexOf('(') !== -1) 1650 ) { 1651 /* If there is a . in the source string then the data source is in a 1652 * nested object so we loop over the data for each level to get the next 1653 * level down. On each loop we test for undefined, and if found immediately 1654 * return. This allows entire objects to be missing and sDefaultContent to 1655 * be used if defined, rather than throwing an error 1656 */ 1657 var fetchData = function (data, type, src) { 1658 var arrayNotation, funcNotation, out, innerSrc; 1659 1660 if ( src !== "" ) { 1661 var a = _fnSplitObjNotation( src ); 1662 1663 for ( var i=0, iLen=a.length ; i<iLen ; i++ ) { 1664 // Check if we are dealing with special notation 1665 arrayNotation = a[i].match(__reArray); 1666 funcNotation = a[i].match(__reFn); 1667 1668 if ( arrayNotation ) { 1669 // Array notation 1670 a[i] = a[i].replace(__reArray, ''); 1671 1672 // Condition allows simply [] to be passed in 1673 if ( a[i] !== "" ) { 1674 data = data[ a[i] ]; 1675 } 1676 out = []; 1677 1678 // Get the remainder of the nested object to get 1679 a.splice( 0, i+1 ); 1680 innerSrc = a.join('.'); 1681 1682 // Traverse each entry in the array getting the properties requested 1683 if ( Array.isArray( data ) ) { 1684 for ( var j=0, jLen=data.length ; j<jLen ; j++ ) { 1685 out.push( fetchData( data[j], type, innerSrc ) ); 1686 } 1687 } 1688 1689 // If a string is given in between the array notation indicators, that 1690 // is used to join the strings together, otherwise an array is returned 1691 var join = arrayNotation[0].substring(1, arrayNotation[0].length-1); 1692 data = (join==="") ? out : out.join(join); 1693 1694 // The inner call to fetchData has already traversed through the remainder 1695 // of the source requested, so we exit from the loop 1696 break; 1697 } 1698 else if ( funcNotation ) { 1699 // Function call 1700 a[i] = a[i].replace(__reFn, ''); 1701 data = data[ a[i] ](); 1702 continue; 1703 } 1704 1705 if (data === null || data[ a[i] ] === null) { 1706 return null; 1707 } 1708 else if ( data === undefined || data[ a[i] ] === undefined ) { 1709 return undefined; 1710 } 1711 1712 data = data[ a[i] ]; 1713 } 1714 } 1715 1716 return data; 1717 }; 1718 1719 return function (data, type) { // row and meta also passed, but not used 1720 return fetchData( data, type, source ); 1721 }; 1722 } 1723 else { 1724 // Array or flat object mapping 1725 return function (data) { // row and meta also passed, but not used 1726 return data[source]; 1727 }; 1728 } 1729 }, 1730 1731 stripHtml: function (mixed) { 1732 var type = typeof mixed; 1733 1734 if (type === 'function') { 1735 _stripHtml = mixed; 1736 return; 1737 } 1738 else if (type === 'string') { 1739 return _stripHtml(mixed); 1740 } 1741 return mixed; 1742 }, 1743 1744 escapeHtml: function (mixed) { 1745 var type = typeof mixed; 1746 1747 if (type === 'function') { 1748 _escapeHtml = mixed; 1749 return; 1750 } 1751 else if (type === 'string' || Array.isArray(mixed)) { 1752 return _escapeHtml(mixed); 1753 } 1754 return mixed; 1755 }, 1756 1757 unique: _unique 1758 }; 1759 1760 1761 1762 /** 1763 * Create a mapping object that allows camel case parameters to be looked up 1764 * for their Hungarian counterparts. The mapping is stored in a private 1765 * parameter called `_hungarianMap` which can be accessed on the source object. 1766 * @param {object} o 1767 * @memberof DataTable#oApi 1768 */ 1769 function _fnHungarianMap ( o ) 1770 { 1771 var 1772 hungarian = 'a aa ai ao as b fn i m o s ', 1773 match, 1774 newKey, 1775 map = {}; 1776 1777 $.each( o, function (key) { 1778 match = key.match(/^([^A-Z]+?)([A-Z])/); 1779 1780 if ( match && hungarian.indexOf(match[1]+' ') !== -1 ) 1781 { 1782 newKey = key.replace( match[0], match[2].toLowerCase() ); 1783 map[ newKey ] = key; 1784 1785 if ( match[1] === 'o' ) 1786 { 1787 _fnHungarianMap( o[key] ); 1788 } 1789 } 1790 } ); 1791 1792 o._hungarianMap = map; 1793 } 1794 1795 1796 /** 1797 * Convert from camel case parameters to Hungarian, based on a Hungarian map 1798 * created by _fnHungarianMap. 1799 * @param {object} src The model object which holds all parameters that can be 1800 * mapped. 1801 * @param {object} user The object to convert from camel case to Hungarian. 1802 * @param {boolean} force When set to `true`, properties which already have a 1803 * Hungarian value in the `user` object will be overwritten. Otherwise they 1804 * won't be. 1805 * @memberof DataTable#oApi 1806 */ 1807 function _fnCamelToHungarian ( src, user, force ) 1808 { 1809 if ( ! src._hungarianMap ) { 1810 _fnHungarianMap( src ); 1811 } 1812 1813 var hungarianKey; 1814 1815 $.each( user, function (key) { 1816 hungarianKey = src._hungarianMap[ key ]; 1817 1818 if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) ) 1819 { 1820 // For objects, we need to buzz down into the object to copy parameters 1821 if ( hungarianKey.charAt(0) === 'o' ) 1822 { 1823 // Copy the camelCase options over to the hungarian 1824 if ( ! user[ hungarianKey ] ) { 1825 user[ hungarianKey ] = {}; 1826 } 1827 $.extend( true, user[hungarianKey], user[key] ); 1828 1829 _fnCamelToHungarian( src[hungarianKey], user[hungarianKey], force ); 1830 } 1831 else { 1832 user[hungarianKey] = user[ key ]; 1833 } 1834 } 1835 } ); 1836 } 1837 1838 /** 1839 * Map one parameter onto another 1840 * @param {object} o Object to map 1841 * @param {*} knew The new parameter name 1842 * @param {*} old The old parameter name 1843 */ 1844 var _fnCompatMap = function ( o, knew, old ) { 1845 if ( o[ knew ] !== undefined ) { 1846 o[ old ] = o[ knew ]; 1847 } 1848 }; 1849 1850 1851 /** 1852 * Provide backwards compatibility for the main DT options. Note that the new 1853 * options are mapped onto the old parameters, so this is an external interface 1854 * change only. 1855 * @param {object} init Object to map 1856 */ 1857 function _fnCompatOpts ( init ) 1858 { 1859 _fnCompatMap( init, 'ordering', 'bSort' ); 1860 _fnCompatMap( init, 'orderMulti', 'bSortMulti' ); 1861 _fnCompatMap( init, 'orderClasses', 'bSortClasses' ); 1862 _fnCompatMap( init, 'orderCellsTop', 'bSortCellsTop' ); 1863 _fnCompatMap( init, 'order', 'aaSorting' ); 1864 _fnCompatMap( init, 'orderFixed', 'aaSortingFixed' ); 1865 _fnCompatMap( init, 'paging', 'bPaginate' ); 1866 _fnCompatMap( init, 'pagingType', 'sPaginationType' ); 1867 _fnCompatMap( init, 'pageLength', 'iDisplayLength' ); 1868 _fnCompatMap( init, 'searching', 'bFilter' ); 1869 1870 // Boolean initialisation of x-scrolling 1871 if ( typeof init.sScrollX === 'boolean' ) { 1872 init.sScrollX = init.sScrollX ? '100%' : ''; 1873 } 1874 if ( typeof init.scrollX === 'boolean' ) { 1875 init.scrollX = init.scrollX ? '100%' : ''; 1876 } 1877 1878 // Column search objects are in an array, so it needs to be converted 1879 // element by element 1880 var searchCols = init.aoSearchCols; 1881 1882 if ( searchCols ) { 1883 for ( var i=0, ien=searchCols.length ; i<ien ; i++ ) { 1884 if ( searchCols[i] ) { 1885 _fnCamelToHungarian( DataTable.models.oSearch, searchCols[i] ); 1886 } 1887 } 1888 } 1889 1890 // Enable search delay if server-side processing is enabled 1891 if (init.serverSide && ! init.searchDelay) { 1892 init.searchDelay = 400; 1893 } 1894 } 1895 1896 1897 /** 1898 * Provide backwards compatibility for column options. Note that the new options 1899 * are mapped onto the old parameters, so this is an external interface change 1900 * only. 1901 * @param {object} init Object to map 1902 */ 1903 function _fnCompatCols ( init ) 1904 { 1905 _fnCompatMap( init, 'orderable', 'bSortable' ); 1906 _fnCompatMap( init, 'orderData', 'aDataSort' ); 1907 _fnCompatMap( init, 'orderSequence', 'asSorting' ); 1908 _fnCompatMap( init, 'orderDataType', 'sortDataType' ); 1909 1910 // orderData can be given as an integer 1911 var dataSort = init.aDataSort; 1912 if ( typeof dataSort === 'number' && ! Array.isArray( dataSort ) ) { 1913 init.aDataSort = [ dataSort ]; 1914 } 1915 } 1916 1917 1918 /** 1919 * Browser feature detection for capabilities, quirks 1920 * @param {object} settings dataTables settings object 1921 * @memberof DataTable#oApi 1922 */ 1923 function _fnBrowserDetect( settings ) 1924 { 1925 // We don't need to do this every time DataTables is constructed, the values 1926 // calculated are specific to the browser and OS configuration which we 1927 // don't expect to change between initialisations 1928 if ( ! DataTable.__browser ) { 1929 var browser = {}; 1930 DataTable.__browser = browser; 1931 1932 // Scrolling feature / quirks detection 1933 var n = $('<div/>') 1934 .css( { 1935 position: 'fixed', 1936 top: 0, 1937 left: -1 * window.pageXOffset, // allow for scrolling 1938 height: 1, 1939 width: 1, 1940 overflow: 'hidden' 1941 } ) 1942 .append( 1943 $('<div/>') 1944 .css( { 1945 position: 'absolute', 1946 top: 1, 1947 left: 1, 1948 width: 100, 1949 overflow: 'scroll' 1950 } ) 1951 .append( 1952 $('<div/>') 1953 .css( { 1954 width: '100%', 1955 height: 10 1956 } ) 1957 ) 1958 ) 1959 .appendTo( 'body' ); 1960 1961 var outer = n.children(); 1962 var inner = outer.children(); 1963 1964 // Get scrollbar width 1965 browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth; 1966 1967 // In rtl text layout, some browsers (most, but not all) will place the 1968 // scrollbar on the left, rather than the right. 1969 browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1; 1970 1971 n.remove(); 1972 } 1973 1974 $.extend( settings.oBrowser, DataTable.__browser ); 1975 settings.oScroll.iBarWidth = DataTable.__browser.barWidth; 1976 } 1977 1978 /** 1979 * Add a column to the list used for the table with default values 1980 * @param {object} oSettings dataTables settings object 1981 * @memberof DataTable#oApi 1982 */ 1983 function _fnAddColumn( oSettings ) 1984 { 1985 // Add column to aoColumns array 1986 var oDefaults = DataTable.defaults.column; 1987 var iCol = oSettings.aoColumns.length; 1988 var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, { 1989 "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], 1990 "mData": oDefaults.mData ? oDefaults.mData : iCol, 1991 idx: iCol, 1992 searchFixed: {},
1993 colEl: $('<col>').attr('data-dt-column', iCol) 1994 } ); 1995 oSettings.aoColumns.push( oCol ); 1996 1997 // Add search object for column specific search. Note that the `searchCols[ iCol ]` 1998 // passed into extend can be undefined. This allows the user to give a default 1999 // with only some of the parameters defined, and also not give a default 2000 var searchCols = oSettings.aoPreSearchCols; 2001 searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] ); 2002 } 2003 2004 2005 /** 2006 * Apply options for a column 2007 * @param {object} oSettings dataTables settings object 2008 * @param {int} iCol column index to consider 2009 * @param {object} oOptions object with sType, bVisible and bSearchable etc 2010 * @memberof DataTable#oApi 2011 */ 2012 function _fnColumnOptions( oSettings, iCol, oOptions ) 2013 { 2014 var oCol = oSettings.aoColumns[ iCol ]; 2015 2016 /* User specified column options */ 2017 if ( oOptions !== undefined && oOptions !== null ) 2018 { 2019 // Backwards compatibility 2020 _fnCompatCols( oOptions ); 2021 2022 // Map camel case parameters to their Hungarian counterparts 2023 _fnCamelToHungarian( DataTable.defaults.column, oOptions, true ); 2024 2025 /* Backwards compatibility for mDataProp */ 2026 if ( oOptions.mDataProp !== undefined && !oOptions.mData ) 2027 { 2028 oOptions.mData = oOptions.mDataProp; 2029 } 2030 2031 if ( oOptions.sType ) 2032 { 2033 oCol._sManualType = oOptions.sType; 2034 } 2035 2036 // `class` is a reserved word in Javascript, so we need to provide 2037 // the ability to use a valid name for the camel case input 2038 if ( oOptions.className && ! oOptions.sClass ) 2039 { 2040 oOptions.sClass = oOptions.className; 2041 } 2042 2043 var origClass = oCol.sClass; 2044 2045 $.extend( oCol, oOptions ); 2046 _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); 2047 2048 // Merge class from previously defined classes with this one, rather than just 2049 // overwriting it in the extend above 2050 if (origClass !== oCol.sClass) { 2051 oCol.sClass = origClass + ' ' + oCol.sClass; 2052 } 2053 2054 /* iDataSort to be applied (backwards compatibility), but aDataSort will take 2055 * priority if defined 2056 */ 2057 if ( oOptions.iDataSort !== undefined ) 2058 { 2059 oCol.aDataSort = [ oOptions.iDataSort ]; 2060 } 2061 _fnMap( oCol, oOptions, "aDataSort" ); 2062 } 2063 2064 /* Cache the data get and set functions for speed */ 2065 var mDataSrc = oCol.mData; 2066 var mData = _fnGetObjectDataFn( mDataSrc ); 2067 2068 // The `render` option can be given as an array to access the helper rendering methods. 2069 // The first element is the rendering method to use, the rest are the parameters to pass 2070 if ( oCol.mRender && Array.isArray( oCol.mRender ) ) { 2071 var copy = oCol.mRender.slice(); 2072 var name = copy.shift(); 2073 2074 oCol.mRender = DataTable.render[name].apply(window, copy); 2075 } 2076 2077 oCol._render = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null; 2078 2079 var attrTest = function( src ) { 2080 return typeof src === 'string' && src.indexOf('@') !== -1; 2081 }; 2082 oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && ( 2083 attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter) 2084 ); 2085 oCol._setter = null; 2086 2087 oCol.fnGetData = function (rowData, type, meta) { 2088 var innerData = mData( rowData, type, undefined, meta ); 2089 2090 return oCol._render && type ? 2091 oCol._render( innerData, type, rowData, meta ) : 2092 innerData; 2093 }; 2094 oCol.fnSetData = function ( rowData, val, meta ) { 2095 return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta ); 2096 }; 2097 2098 // Indicate if DataTables should read DOM data as an object or array 2099 // Used in _fnGetRowElements 2100 if ( typeof mDataSrc !== 'number' && ! oCol._isArrayHost ) { 2101 oSettings._rowReadObject = true; 2102 } 2103 2104 /* Feature sorting overrides column specific when off */ 2105 if ( !oSettings.oFeatures.bSort ) 2106 { 2107 oCol.bSortable = false; 2108 } 2109 } 2110 2111 2112 /** 2113 * Adjust the table column widths for new data. Note: you would probably want to 2114 * do a redraw after calling this function! 2115 * @param {object} settings dataTables settings object 2116 * @memberof DataTable#oApi 2117 */ 2118 function _fnAdjustColumnSizing ( settings ) 2119 { 2120 _fnCalculateColumnWidths( settings ); 2121 _fnColumnSizes( settings ); 2122 2123 var scroll = settings.oScroll;
2124 if ( scroll.sY !== '' || scroll.sX !== '') { 2125 _fnScrollDraw( settings ); 2126 } 2127 2128 _fnCallbackFire( settings, null, 'column-sizing', [settings] ); 2129 } 2130 2131 /** 2132 * Apply column sizes 2133 * 2134 * @param {*} settings DataTables settings object 2135 */ 2136 function _fnColumnSizes ( settings ) 2137 { 2138 var cols = settings.aoColumns; 2139 2140 for (var i=0 ; i<cols.length ; i++) { 2141 var width = _fnColumnsSumWidth(settings, [i], false, false); 2142 2143 cols[i].colEl.css('width', width); 2144 2145 if (settings.oScroll.sX) { 2146 cols[i].colEl.css('min-width', width); 2147 } 2148 } 2149 } 2150 2151 2152 /** 2153 * Convert the index of a visible column to the index in the data array (take account 2154 * of hidden columns) 2155 * @param {object} oSettings dataTables settings object 2156 * @param {int} iMatch Visible column index to lookup 2157 * @returns {int} i the data index 2158 * @memberof DataTable#oApi 2159 */ 2160 function _fnVisibleToColumnIndex( oSettings, iMatch ) 2161 { 2162 var aiVis = _fnGetColumns( oSettings, 'bVisible' ); 2163 2164 return typeof aiVis[iMatch] === 'number' ? 2165 aiVis[iMatch] : 2166 null; 2167 } 2168 2169 2170 /** 2171 * Convert the index of an index in the data array and convert it to the visible 2172 * column index (take account of hidden columns) 2173 * @param {int} iMatch Column index to lookup 2174 * @param {object} oSettings dataTables settings object 2175 * @returns {int} i the data index 2176 * @memberof DataTable#oApi 2177 */ 2178 function _fnColumnIndexToVisible( oSettings, iMatch ) 2179 { 2180 var aiVis = _fnGetColumns( oSettings, 'bVisible' ); 2181 var iPos = aiVis.indexOf(iMatch); 2182 2183 return iPos !== -1 ? iPos : null; 2184 } 2185 2186 2187 /** 2188 * Get the number of visible columns 2189 * @param {object} oSettings dataTables settings object 2190 * @returns {int} i the number of visible columns 2191 * @memberof DataTable#oApi 2192 */ 2193 function _fnVisbleColumns( settings ) 2194 { 2195 var layout = settings.aoHeader; 2196 var columns = settings.aoColumns; 2197 var vis = 0; 2198 2199 if ( layout.length ) { 2200 for ( var i=0, ien=layout[0].length ; i<ien ; i++ ) { 2201 if ( columns[i].bVisible && $(layout[0][i].cell).css('display') !== 'none' ) { 2202 vis++; 2203 } 2204 } 2205 } 2206 2207 return vis; 2208 } 2209 2210 2211 /** 2212 * Get an array of column indexes that match a given property 2213 * @param {object} oSettings dataTables settings object 2214 * @param {string} sParam Parameter in aoColumns to look for - typically 2215 * bVisible or bSearchable 2216 * @returns {array} Array of indexes with matched properties 2217 * @memberof DataTable#oApi 2218 */ 2219 function _fnGetColumns( oSettings, sParam ) 2220 { 2221 var a = []; 2222 2223 oSettings.aoColumns.map( function(val, i) { 2224 if ( val[sParam] ) { 2225 a.push( i ); 2226 } 2227 } ); 2228 2229 return a; 2230 } 2231 2232 /** 2233 * Allow the result from a type detection function to be `true` while 2234 * translating that into a string. Old type detection functions will 2235 * return the type name if it passes. An obect store would be better, 2236 * but not backwards compatible. 2237 * 2238 * @param {*} typeDetect Object or function for type detection 2239 * @param {*} res Result from the type detection function 2240 * @returns Type name or false 2241 */ 2242 function _typeResult (typeDetect, res) { 2243 return res === true 2244 ? typeDetect._name 2245 : res; 2246 } 2247 2248 /** 2249 * Calculate the 'type' of a column 2250 * @param {object} settings dataTables settings object 2251 * @memberof DataTable#oApi 2252 */ 2253 function _fnColumnTypes ( settings ) 2254 { 2255 var columns = settings.aoColumns; 2256 var data = settings.aoData; 2257 var types = DataTable.ext.type.detect; 2258 var i, ien, j, jen, k, ken; 2259 var col, detectedType, cache; 2260 2261 // For each column, spin over the data type detection functions, seeing if one matches 2262 for ( i=0, ien=columns.length ; i<ien ; i++ ) { 2263 col = columns[i]; 2264 cache = []; 2265 2266 if ( ! col.sType && col._sManualType ) { 2267 col.sType = col._sManualType; 2268 } 2269 else if ( ! col.sType ) { 2270 // With SSP type detection can be unreliable and error prone, so we provide a way 2271 // to turn it off. 2272 if (! settings.typeDetect) { 2273 return; 2274 } 2275 2276 for ( j=0, jen=types.length ; j<jen ; j++ ) { 2277 var typeDetect = types[j]; 2278 2279 // There can be either one, or three type detection functions 2280 var oneOf = typeDetect.oneOf; 2281 var allOf = typeDetect.allOf || typeDetect; 2282 var init = typeDetect.init; 2283 var one = false;
2284 2285 detectedType = null; 2286 2287 // Fast detect based on column assignment 2288 if (init) { 2289 detectedType = _typeResult(typeDetect, init(settings, col, i)); 2290 2291 if (detectedType) { 2292 col.sType = detectedType; 2293 break; 2294 } 2295 } 2296 2297 for ( k=0, ken=data.length ; k<ken ; k++ ) { 2298 if (! data[k]) { 2299 continue; 2300 } 2301 2302 // Use a cache array so we only need to get the type data 2303 // from the formatter once (when using multiple detectors) 2304 if ( cache[k] === undefined ) { 2305 cache[k] = _fnGetCellData( settings, k, i, 'type' ); 2306 } 2307 2308 // Only one data point in the column needs to match this function 2309 if (oneOf && ! one) { 2310 one = _typeResult(typeDetect, oneOf( cache[k], settings )); 2311 } 2312 2313 // All data points need to match this function 2314 detectedType = _typeResult(typeDetect, allOf( cache[k], settings )); 2315 2316 // If null, then this type can't apply to this column, so 2317 // rather than testing all cells, break out. There is an 2318 // exception for the last type which is `html`. We need to 2319 // scan all rows since it is possible to mix string and HTML 2320 // types 2321 if ( ! detectedType && j !== types.length-3 ) { 2322 break; 2323 } 2324 2325 // Only a single match is needed for html type since it is 2326 // bottom of the pile and very similar to string - but it 2327 // must not be empty 2328 if ( detectedType === 'html' && ! _empty(cache[k]) ) { 2329 break; 2330 } 2331 } 2332 2333 // Type is valid for all data points in the column - use this 2334 // type 2335 if ( (oneOf && one && detectedType) || (!oneOf && detectedType) ) { 2336 col.sType = detectedType; 2337 break; 2338 } 2339 } 2340 2341 // Fall back - if no type was detected, always use string 2342 if ( ! col.sType ) { 2343 col.sType = 'string'; 2344 } 2345 } 2346 2347 // Set class names for header / footer for auto type classes 2348 var autoClass = _ext.type.className[col.sType]; 2349 2350 if (autoClass) { 2351 _columnAutoClass(settings.aoHeader, i, autoClass); 2352 _columnAutoClass(settings.aoFooter, i, autoClass); 2353 } 2354 2355 var renderer = _ext.type.render[col.sType]; 2356 2357 // This can only happen once! There is no way to remove 2358 // a renderer. After the first time the renderer has 2359 // already been set so createTr will run the renderer itself. 2360 if (renderer && ! col._render) { 2361 col._render = DataTable.util.get(renderer); 2362 2363 _columnAutoRender(settings, i); 2364 } 2365 } 2366 } 2367 2368 /** 2369 * Apply an auto detected renderer to data which doesn't yet have 2370 * a renderer 2371 */ 2372 function _columnAutoRender(settings, colIdx) { 2373 var data = settings.aoData; 2374 2375 for (var i=0 ; i<data.length ; i++) { 2376 if (data[i].nTr) { 2377 // We have to update the display here since there is no 2378 // invalidation check for the data 2379 var display = _fnGetCellData( settings, i, colIdx, 'display' ); 2380 2381 data[i].displayData[colIdx] = display; 2382 _fnWriteCell(data[i].anCells[colIdx], display); 2383 2384 // No need to update sort / filter data since it has 2385 // been invalidated and will be re-read with the 2386 // renderer now applied 2387 } 2388 } 2389 } 2390 2391 /** 2392 * Apply a class name to a column's header cells 2393 */ 2394 function _columnAutoClass(container, colIdx, className) { 2395 container.forEach(function (row) { 2396 if (row[colIdx] && row[colIdx].unique) { 2397 _addClass(row[colIdx].cell, className); 2398 } 2399 }); 2400 } 2401 2402 /** 2403 * Take the column definitions and static columns arrays and calculate how 2404 * they relate to column indexes. The callback function will then apply the 2405 * definition found for a column to a suitable configuration object. 2406 * @param {object} oSettings dataTables settings object 2407 * @param {array} aoColDefs The aoColumnDefs array that is to be applied 2408 * @param {array} aoCols The aoColumns array that defines columns individually 2409 * @param {array} headerLayout Layout for header as it was loaded 2410 * @param {function} fn Callback function - takes two parameters, the calculated 2411 * column index and the definition for that column. 2412 * @memberof DataTable#oApi 2413 */ 2414 function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, headerLayout, fn ) 2415 { 2416 var i, iLen, j, jLen, k, kLen, def; 2417 var columns = oSettings.aoColumns; 2418 2419 if ( aoCols ) { 2420 for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) { 2421 if (aoCols[i] && aoCols[i].name) { 2422 columns[i].sName = aoCols[i].name; 2423 } 2424 } 2425 } 2426 2427 // Column definitions with aTargets 2428 if ( aoColDefs ) 2429 { 2430 /* Loop over the definitions array - loop in reverse so first instance has priority */ 2431 for ( i=aoColDefs.length-1 ; i>=0 ; i-- ) 2432 { 2433 def = aoColDefs[i]; 2434 2435 /* Each definition can target multiple columns, as it is an array */ 2436 var aTargets = def.target !== undefined 2437 ? def.target 2438 : def.targets !== undefined 2439 ? def.targets 2440 : def.aTargets; 2441 2442 if ( ! Array.isArray( aTargets ) ) 2443 { 2444 aTargets = [ aTargets ]; 2445 } 2446 2447 for ( j=0, jLen=aTargets.length ; j<jLen ; j++ ) 2448 { 2449 var target = aTargets[j]; 2450 2451 if ( typeof target === 'number' && target >= 0 ) 2452 { 2453 /* Add columns that we don't yet know about */ 2454 while( columns.length <= target ) 2455 { 2456 _fnAddColumn( oSettings ); 2457 } 2458
2459 /* Integer, basic index */ 2460 fn( target, def ); 2461 } 2462 else if ( typeof target === 'number' && target < 0 ) 2463 { 2464 /* Negative integer, right to left column counting */ 2465 fn( columns.length+target, def ); 2466 } 2467 else if ( typeof target === 'string' ) 2468 { 2469 for ( k=0, kLen=columns.length ; k<kLen ; k++ ) { 2470 if (target === '_all') { 2471 // Apply to all columns 2472 fn( k, def ); 2473 } 2474 else if (target.indexOf(':name') !== -1) { 2475 // Column selector 2476 if (columns[k].sName === target.replace(':name', '')) { 2477 fn( k, def ); 2478 } 2479 } 2480 else { 2481 // Cell selector 2482 headerLayout.forEach(function (row) { 2483 if (row[k]) { 2484 var cell = $(row[k].cell); 2485 2486 // Legacy support. Note that it means that we don't support 2487 // an element name selector only, since they are treated as 2488 // class names for 1.x compat. 2489 if (target.match(/^[a-z][\w-]*$/i)) { 2490 target = '.' + target; 2491 } 2492 2493 if (cell.is( target )) { 2494 fn( k, def ); 2495 } 2496 } 2497 }); 2498 } 2499 } 2500 } 2501 } 2502 } 2503 } 2504 2505 // Statically defined columns array 2506 if ( aoCols ) { 2507 for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) { 2508 fn( i, aoCols[i] ); 2509 } 2510 } 2511 } 2512 2513 2514 /** 2515 * Get the width for a given set of columns 2516 * 2517 * @param {*} settings DataTables settings object 2518 * @param {*} targets Columns - comma separated string or array of numbers 2519 * @param {*} original Use the original width (true) or calculated (false) 2520 * @param {*} incVisible Include visible columns (true) or not (false) 2521 * @returns Combined CSS value 2522 */ 2523 function _fnColumnsSumWidth( settings, targets, original, incVisible ) { 2524 if ( ! Array.isArray( targets ) ) { 2525 targets = _fnColumnsFromHeader( targets ); 2526 } 2527 2528 var sum = 0; 2529 var unit; 2530 var columns = settings.aoColumns; 2531 2532 for ( var i=0, ien=targets.length ; i<ien ; i++ ) { 2533 var column = columns[ targets[i] ]; 2534 var definedWidth = original ? 2535 column.sWidthOrig : 2536 column.sWidth; 2537 2538 if ( ! incVisible && column.bVisible === false ) { 2539 continue; 2540 } 2541 2542 if ( definedWidth === null || definedWidth === undefined ) { 2543 return null; // can't determine a defined width - browser defined 2544 } 2545 else if ( typeof definedWidth === 'number' ) { 2546 unit = 'px'; 2547 sum += definedWidth; 2548 } 2549 else { 2550 var matched = definedWidth.match(/([\d\.]+)([^\d]*)/); 2551 2552 if ( matched ) { 2553 sum += matched[1] * 1; 2554 unit = matched.length === 3 ? 2555 matched[2] : 2556 'px'; 2557 } 2558 } 2559 } 2560 2561 return sum + unit; 2562 } 2563 2564 function _fnColumnsFromHeader( cell ) 2565 { 2566 var attr = $(cell).closest('[data-dt-column]').attr('data-dt-column'); 2567 2568 if ( ! attr ) { 2569 return []; 2570 } 2571 2572 return attr.split(',').map( function (val) { 2573 return val * 1; 2574 } ); 2575 } 2576 /** 2577 * Add a data array to the table, creating DOM node etc. This is the parallel to 2578 * _fnGatherData, but for adding rows from a Javascript source, rather than a 2579 * DOM source. 2580 * @param {object} settings dataTables settings object 2581 * @param {array} data data array to be added 2582 * @param {node} [tr] TR element to add to the table - optional. If not given, 2583 * DataTables will create a row automatically 2584 * @param {array} [tds] Array of TD|TH elements for the row - must be given 2585 * if nTr is. 2586 * @returns {int} >=0 if successful (index of new aoData entry), -1 if failed 2587 * @memberof DataTable#oApi 2588 */ 2589 function _fnAddData ( settings, dataIn, tr, tds ) 2590 { 2591 /* Create the object for storing information about this new row */ 2592 var rowIdx = settings.aoData.length; 2593 var rowModel = $.extend( true, {}, DataTable.models.oRow, { 2594 src: tr ? 'dom' : 'data', 2595 idx: rowIdx 2596 } ); 2597 2598 rowModel._aData = dataIn; 2599 settings.aoData.push( rowModel ); 2600 2601 var columns = settings.aoColumns; 2602 2603 for ( var i=0, iLen=columns.length ; i<iLen ; i++ ) 2604 { 2605 // Invalidate the column types as the new data needs to be revalidated 2606 columns[i].sType = null; 2607 } 2608 2609 /* Add to the display array */ 2610 settings.aiDisplayMaster.push( rowIdx ); 2611 2612 var id = settings.rowIdFn( dataIn ); 2613 if ( id !== undefined ) { 2614 settings.aIds[ id ] = rowModel; 2615 } 2616 2617 /* Create the DOM information, or register it if already present */ 2618 if ( tr || ! settings.oFeatures.bDeferRender ) 2619 { 2620 _fnCreateTr( settings, rowIdx, tr, tds ); 2621 } 2622 2623 return rowIdx; 2624 } 2625 2626 2627 /** 2628 * Add one or more TR elements to the table. Generally we'd expect to 2629 * use this for reading data from a DOM sourced table, but it could be 2630 * used for an TR element. Note that if a TR is given, it is used (i.e.
2631 * it is not cloned). 2632 * @param {object} settings dataTables settings object 2633 * @param {array|node|jQuery} trs The TR element(s) to add to the table 2634 * @returns {array} Array of indexes for the added rows 2635 * @memberof DataTable#oApi 2636 */ 2637 function _fnAddTr( settings, trs ) 2638 { 2639 var row; 2640 2641 // Allow an individual node to be passed in 2642 if ( ! (trs instanceof $) ) { 2643 trs = $(trs); 2644 } 2645 2646 return trs.map( function (i, el) { 2647 row = _fnGetRowElements( settings, el ); 2648 return _fnAddData( settings, row.data, el, row.cells ); 2649 } ); 2650 } 2651 2652 2653 /** 2654 * Get the data for a given cell from the internal cache, taking into account data mapping 2655 * @param {object} settings dataTables settings object 2656 * @param {int} rowIdx aoData row id 2657 * @param {int} colIdx Column index 2658 * @param {string} type data get type ('display', 'type' 'filter|search' 'sort|order') 2659 * @returns {*} Cell data 2660 * @memberof DataTable#oApi 2661 */ 2662 function _fnGetCellData( settings, rowIdx, colIdx, type ) 2663 { 2664 if (type === 'search') { 2665 type = 'filter'; 2666 } 2667 else if (type === 'order') { 2668 type = 'sort'; 2669 } 2670 2671 var row = settings.aoData[rowIdx]; 2672 2673 if (! row) { 2674 return undefined; 2675 } 2676 2677 var draw = settings.iDraw; 2678 var col = settings.aoColumns[colIdx]; 2679 var rowData = row._aData; 2680 var defaultContent = col.sDefaultContent; 2681 var cellData = col.fnGetData( rowData, type, { 2682 settings: settings, 2683 row: rowIdx, 2684 col: colIdx 2685 } ); 2686 2687 // Allow for a node being returned for non-display types 2688 if (type !== 'display' && cellData && typeof cellData === 'object' && cellData.nodeName) { 2689 cellData = cellData.innerHTML; 2690 } 2691 2692 if ( cellData === undefined ) { 2693 if ( settings.iDrawError != draw && defaultContent === null ) { 2694 _fnLog( settings, 0, "Requested unknown parameter "+ 2695 (typeof col.mData=='function' ? '{function}' : "'"+col.mData+"'")+ 2696 " for row "+rowIdx+", column "+colIdx, 4 ); 2697 settings.iDrawError = draw; 2698 } 2699 return defaultContent; 2700 } 2701 2702 // When the data source is null and a specific data type is requested (i.e. 2703 // not the original data), we can use default column data 2704 if ( (cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined ) { 2705 cellData = defaultContent; 2706 } 2707 else if ( typeof cellData === 'function' ) { 2708 // If the data source is a function, then we run it and use the return, 2709 // executing in the scope of the data object (for instances) 2710 return cellData.call( rowData ); 2711 } 2712 2713 if ( cellData === null && type === 'display' ) { 2714 return ''; 2715 } 2716 2717 if ( type === 'filter' ) { 2718 var fomatters = DataTable.ext.type.search; 2719 2720 if ( fomatters[ col.sType ] ) { 2721 cellData = fomatters[ col.sType ]( cellData ); 2722 } 2723 } 2724 2725 return cellData; 2726 } 2727 2728 2729 /** 2730 * Set the value for a specific cell, into the internal data cache 2731 * @param {object} settings dataTables settings object 2732 * @param {int} rowIdx aoData row id 2733 * @param {int} colIdx Column index 2734 * @param {*} val Value to set 2735 * @memberof DataTable#oApi 2736 */ 2737 function _fnSetCellData( settings, rowIdx, colIdx, val ) 2738 { 2739 var col = settings.aoColumns[colIdx]; 2740 var rowData = settings.aoData[rowIdx]._aData; 2741 2742 col.fnSetData( rowData, val, { 2743 settings: settings, 2744 row: rowIdx, 2745 col: colIdx 2746 } ); 2747 } 2748 2749 /** 2750 * Write a value to a cell 2751 * @param {*} td Cell 2752 * @param {*} val Value 2753 */ 2754 function _fnWriteCell(td, val) 2755 { 2756 if (val && typeof val === 'object' && val.nodeName) { 2757 $(td) 2758 .empty() 2759 .append(val); 2760 } 2761 else { 2762 td.innerHTML = val; 2763 } 2764 } 2765 2766 2767 // Private variable that is used to match action syntax in the data property object 2768 var __reArray = /\[.*?\]$/; 2769 var __reFn = /\(\)$/; 2770 2771 /** 2772 * Split string on periods, taking into account escaped periods 2773 * @param {string} str String to split 2774 * @return {array} Split string 2775 */ 2776 function _fnSplitObjNotation( str ) 2777 { 2778 var parts = str.match(/(\\.|[^.])+/g) || ['']; 2779 2780 return parts.map( function ( s ) { 2781 return s.replace(/\\\./g, '.'); 2782 } ); 2783 } 2784 2785 2786 /** 2787 * Return a function that can be used to get data from a source object, taking 2788 * into account the ability to use nested objects as a source 2789 * @param {string|int|function} mSource The data source for the object 2790 * @returns {function} Data get function 2791 * @memberof DataTable#oApi 2792 */ 2793 var _fnGetObjectDataFn = DataTable.util.get; 2794 2795 2796 /** 2797 * Return a function that can be used to set data from a source object, taking 2798 * into account the ability to use nested objects as a source 2799 * @param {string|int|function} mSource The data source for the object 2800 * @returns {function} Data set function 2801 * @memberof DataTable#oApi 2802 */ 2803 var _fnSetObjectDataFn = DataTable.util.set; 2804 2805 2806 /** 2807 * Return an array with the full table data 2808 * @param {object} oSettings dataTables settings object 2809 * @returns array {array} aData Master data array 2810 * @memberof DataTable#oApi 2811 */ 2812 function _fnGetDataMaster ( settings ) 2813 { 2814 return _pluck( settings.aoData, '_aData' ); 2815 } 2816 2817 2818 /** 2819 * Nuke the table 2820 * @param {object} oSettings dataTables settings object 2821 * @memberof DataTable#oApi 2822 */ 2823 function _fnClearTable( settings ) 2824 { 2825 settings.aoData.length = 0; 2826 settings.aiDisplayMaster.length = 0; 2827 settings.aiDisplay.length = 0; 2828 settings.aIds = {}; 2829 } 2830 2831 2832 /**
2833 * Mark cached data as invalid such that a re-read of the data will occur when 2834 * the cached data is next requested. Also update from the data source object. 2835 * 2836 * @param {object} settings DataTables settings object 2837 * @param {int} rowIdx Row index to invalidate 2838 * @param {string} [src] Source to invalidate from: undefined, 'auto', 'dom' 2839 * or 'data' 2840 * @param {int} [colIdx] Column index to invalidate. If undefined the whole 2841 * row will be invalidated 2842 * @memberof DataTable#oApi 2843 * 2844 * @todo For the modularisation of v1.11 this will need to become a callback, so 2845 * the sort and filter methods can subscribe to it. That will required 2846 * initialisation options for sorting, which is why it is not already baked in 2847 */ 2848 function _fnInvalidate( settings, rowIdx, src, colIdx ) 2849 { 2850 var row = settings.aoData[ rowIdx ]; 2851 var i, ien; 2852 2853 // Remove the cached data for the row 2854 row._aSortData = null; 2855 row._aFilterData = null; 2856 row.displayData = null; 2857 2858 // Are we reading last data from DOM or the data object? 2859 if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) { 2860 // Read the data from the DOM 2861 row._aData = _fnGetRowElements( 2862 settings, row, colIdx, colIdx === undefined ? undefined : row._aData 2863 ) 2864 .data; 2865 } 2866 else { 2867 // Reading from data object, update the DOM 2868 var cells = row.anCells; 2869 var display = _fnGetRowDisplay(settings, rowIdx); 2870 2871 if ( cells ) { 2872 if ( colIdx !== undefined ) { 2873 _fnWriteCell(cells[colIdx], display[colIdx]); 2874 } 2875 else { 2876 for ( i=0, ien=cells.length ; i<ien ; i++ ) { 2877 _fnWriteCell(cells[i], display[i]); 2878 } 2879 } 2880 } 2881 } 2882 2883 // Column specific invalidation 2884 var cols = settings.aoColumns; 2885 if ( colIdx !== undefined ) { 2886 // Type - the data might have changed 2887 cols[ colIdx ].sType = null; 2888 2889 // Max length string. Its a fairly cheep recalculation, so not worth 2890 // something more complicated 2891 cols[ colIdx ].maxLenString = null; 2892 } 2893 else { 2894 for ( i=0, ien=cols.length ; i<ien ; i++ ) { 2895 cols[i].sType = null; 2896 cols[i].maxLenString = null; 2897 } 2898 2899 // Update DataTables special `DT_*` attributes for the row 2900 _fnRowAttributes( settings, row ); 2901 } 2902 } 2903 2904 2905 /** 2906 * Build a data source object from an HTML row, reading the contents of the 2907 * cells that are in the row. 2908 * 2909 * @param {object} settings DataTables settings object 2910 * @param {node|object} TR element from which to read data or existing row 2911 * object from which to re-read the data from the cells 2912 * @param {int} [colIdx] Optional column index 2913 * @param {array|object} [d] Data source object. If `colIdx` is given then this 2914 * parameter should also be given and will be used to write the data into. 2915 * Only the column in question will be written 2916 * @returns {object} Object with two parameters: `data` the data read, in 2917 * document order, and `cells` and array of nodes (they can be useful to the 2918 * caller, so rather than needing a second traversal to get them, just return 2919 * them from here). 2920 * @memberof DataTable#oApi 2921 */ 2922 function _fnGetRowElements( settings, row, colIdx, d ) 2923 { 2924 var 2925 tds = [], 2926 td = row.firstChild, 2927 name, col, i=0, contents, 2928 columns = settings.aoColumns, 2929 objectRead = settings._rowReadObject; 2930 2931 // Allow the data object to be passed in, or construct 2932 d = d !== undefined ? 2933 d : 2934 objectRead ? 2935 {} : 2936 []; 2937 2938 var attr = function ( str, td ) { 2939 if ( typeof str === 'string' ) { 2940 var idx = str.indexOf('@'); 2941 2942 if ( idx !== -1 ) { 2943 var attr = str.substring( idx+1 ); 2944 var setter = _fnSetObjectDataFn( str ); 2945 setter( d, td.getAttribute( attr ) ); 2946 } 2947 } 2948 }; 2949 2950 // Read data from a cell and store into the data object 2951 var cellProcess = function ( cell ) { 2952 if ( colIdx === undefined || colIdx === i ) { 2953 col = columns[i]; 2954 contents = (cell.innerHTML).trim(); 2955 2956 if ( col && col._bAttrSrc ) { 2957 var setter = _fnSetObjectDataFn( col.mData._ ); 2958 setter( d, contents ); 2959 2960 attr( col.mData.sort, cell ); 2961 attr( col.mData.type, cell ); 2962 attr( col.mData.filter, cell ); 2963 } 2964 else { 2965 // Depending on the `data` option for the columns the data can 2966 // be read to either an object or an array.
2967 if ( objectRead ) { 2968 if ( ! col._setter ) { 2969 // Cache the setter function 2970 col._setter = _fnSetObjectDataFn( col.mData ); 2971 } 2972 col._setter( d, contents ); 2973 } 2974 else { 2975 d[i] = contents; 2976 } 2977 } 2978 } 2979 2980 i++; 2981 }; 2982 2983 if ( td ) { 2984 // `tr` element was passed in 2985 while ( td ) { 2986 name = td.nodeName.toUpperCase(); 2987 2988 if ( name == "TD" || name == "TH" ) { 2989 cellProcess( td ); 2990 tds.push( td ); 2991 } 2992 2993 td = td.nextSibling; 2994 } 2995 } 2996 else { 2997 // Existing row object passed in 2998 tds = row.anCells; 2999 3000 for ( var j=0, jen=tds.length ; j<jen ; j++ ) { 3001 cellProcess( tds[j] ); 3002 } 3003 } 3004 3005 // Read the ID from the DOM if present 3006 var rowNode = row.firstChild ? row : row.nTr; 3007 3008 if ( rowNode ) { 3009 var id = rowNode.getAttribute( 'id' ); 3010 3011 if ( id ) { 3012 _fnSetObjectDataFn( settings.rowId )( d, id ); 3013 } 3014 } 3015 3016 return { 3017 data: d, 3018 cells: tds 3019 }; 3020 } 3021 3022 /** 3023 * Render and cache a row's display data for the columns, if required 3024 * @returns 3025 */ 3026 function _fnGetRowDisplay (settings, rowIdx) { 3027 var rowModal = settings.aoData[rowIdx]; 3028 var columns = settings.aoColumns; 3029 3030 if (! rowModal.displayData) { 3031 // Need to render and cache 3032 rowModal.displayData = []; 3033 3034 for ( var colIdx=0, len=columns.length ; colIdx<len ; colIdx++ ) { 3035 rowModal.displayData.push( 3036 _fnGetCellData( settings, rowIdx, colIdx, 'display' ) 3037 ); 3038 } 3039 } 3040 3041 return rowModal.displayData; 3042 } 3043 3044 /** 3045 * Create a new TR element (and it's TD children) for a row 3046 * @param {object} oSettings dataTables settings object 3047 * @param {int} iRow Row to consider 3048 * @param {node} [nTrIn] TR element to add to the table - optional. If not given, 3049 * DataTables will create a row automatically 3050 * @param {array} [anTds] Array of TD|TH elements for the row - must be given 3051 * if nTr is. 3052 * @memberof DataTable#oApi 3053 */ 3054 function _fnCreateTr ( oSettings, iRow, nTrIn, anTds ) 3055 { 3056 var 3057 row = oSettings.aoData[iRow], 3058 rowData = row._aData, 3059 cells = [], 3060 nTr, nTd, oCol, 3061 i, iLen, create, 3062 trClass = oSettings.oClasses.tbody.row; 3063 3064 if ( row.nTr === null ) 3065 { 3066 nTr = nTrIn || document.createElement('tr'); 3067 3068 row.nTr = nTr; 3069 row.anCells = cells; 3070 3071 _addClass(nTr, trClass); 3072 3073 /* Use a private property on the node to allow reserve mapping from the node 3074 * to the aoData array for fast look up 3075 */ 3076 nTr._DT_RowIndex = iRow; 3077 3078 /* Special parameters can be given by the data source to be used on the row */ 3079 _fnRowAttributes( oSettings, row ); 3080 3081 /* Process each column */ 3082 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) 3083 { 3084 oCol = oSettings.aoColumns[i]; 3085 create = nTrIn && anTds[i] ? false : true; 3086 3087 nTd = create ? document.createElement( oCol.sCellType ) : anTds[i]; 3088 3089 if (! nTd) { 3090 _fnLog( oSettings, 0, 'Incorrect column count', 18 ); 3091 } 3092 3093 nTd._DT_CellIndex = { 3094 row: iRow, 3095 column: i 3096 }; 3097 3098 cells.push( nTd ); 3099 3100 var display = _fnGetRowDisplay(oSettings, iRow); 3101 3102 // Need to create the HTML if new, or if a rendering function is defined 3103 if ( 3104 create || 3105 ( 3106 (oCol.mRender || oCol.mData !== i) && 3107 (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display') 3108 ) 3109 ) { 3110 _fnWriteCell(nTd, display[i]); 3111 } 3112 3113 // column class 3114 _addClass(nTd, oCol.sClass); 3115 3116 // Visibility - add or remove as required 3117 if ( oCol.bVisible && create ) 3118 { 3119 nTr.appendChild( nTd ); 3120 } 3121 else if ( ! oCol.bVisible && ! create ) 3122 { 3123 nTd.parentNode.removeChild( nTd ); 3124 } 3125 3126 if ( oCol.fnCreatedCell ) 3127 { 3128 oCol.fnCreatedCell.call( oSettings.oInstance, 3129 nTd, _fnGetCellData( oSettings, iRow, i ), rowData, iRow, i 3130 ); 3131 } 3132 } 3133 3134 _fnCallbackFire( oSettings, 'aoRowCreatedCallback', 'row-created', [nTr, rowData, iRow, cells] ); 3135 } 3136 else { 3137 _addClass(row.nTr, trClass); 3138 } 3139 } 3140 3141 3142 /** 3143 * Add attributes to a row based on the special `DT_*` parameters in a data 3144 * source object. 3145 * @param {object} settings DataTables settings object 3146 * @param {object} DataTables row object for the row to be modified 3147 * @memberof DataTable#oApi 3148 */ 3149 function _fnRowAttributes( settings, row ) 3150 { 3151 var tr = row.nTr; 3152 var data = row._aData; 3153 3154 if ( tr ) { 3155 var id = settings.rowIdFn( data ); 3156 3157 if ( id ) { 3158 tr.id = id; 3159 } 3160 3161 if ( data.DT_RowClass ) { 3162 // Remove any classes added by DT_RowClass before 3163 var a = data.DT_RowClass.split(' '); 3164 row.__rowc = row.__rowc ? 3165 _unique( row.__rowc.concat( a ) ) : 3166 a; 3167 3168 $(tr) 3169 .removeClass( row.__rowc.join(' ') ) 3170 .addClass( data.DT_RowClass ); 3171 } 3172 3173 if ( data.DT_RowAttr ) { 3174 $(tr).attr( data.DT_RowAttr ); 3175 } 3176 3177 if ( data.DT_RowData ) { 3178 $(tr).data( data.DT_RowData ); 3179 } 3180 } 3181 } 3182 3183 3184 /** 3185 * Create the HTML header for the table 3186 * @param {object} oSettings dataTables settings object 3187 * @memberof DataTable#oApi 3188 */ 3189 function _fnBuildHead( settings, side ) 3190 { 3191 var classes = settings.oClasses; 3192 var columns = settings.aoColumns; 3193 var i, ien, row; 3194 var target = side === 'header' 3195 ? settings.nTHead 3196 : settings.nTFoot; 3197 var titleProp = side === 'header' ? 'sTitle' : side; 3198 3199 // Footer might be defined 3200 if (! target) { 3201 return; 3202 } 3203 3204 // If no cells yet and we have content for them, then create 3205 if (side === 'header' || _pluck(settings.aoColumns, titleProp).join('')) { 3206 row = $('tr', target); 3207 3208 // Add a row if needed 3209 if (! row.length) { 3210 row = $('<tr/>').appendTo(target) 3211 } 3212 3213 // Add the number of cells needed to make up to the number of columns 3214 if (row.length === 1) { 3215 var cellCount = 0; 3216 3217 $('td, th', row).each(function () { 3218 cellCount += this.colSpan; 3219 }); 3220 3221 for ( i=cellCount, ien=columns.length ; i<ien ; i++ ) { 3222 $('<th/>') 3223 .html( columns[i][titleProp] || '' ) 3224 .appendTo( row ); 3225 } 3226 } 3227 } 3228 3229 var detected = _fnDetectHeader( settings, target, true ); 3230 3231 if (side === 'header') { 3232 settings.aoHeader = detected;
3233 $('tr', target).addClass(classes.thead.row); 3234 } 3235 else { 3236 settings.aoFooter = detected; 3237 $('tr', target).addClass(classes.tfoot.row); 3238 } 3239 3240 // Every cell needs to be passed through the renderer 3241 $(target).children('tr').children('th, td') 3242 .each( function () { 3243 _fnRenderer( settings, side )( 3244 settings, $(this), classes 3245 ); 3246 } ); 3247 } 3248 3249 /** 3250 * Build a layout structure for a header or footer 3251 * 3252 * @param {*} settings DataTables settings 3253 * @param {*} source Source layout array 3254 * @param {*} incColumns What columns should be included 3255 * @returns Layout array 3256 */ 3257 function _fnHeaderLayout( settings, source, incColumns ) 3258 { 3259 var row, column, cell; 3260 var local = []; 3261 var structure = []; 3262 var columns = settings.aoColumns; 3263 var columnCount = columns.length; 3264 var rowspan, colspan; 3265 3266 if ( ! source ) { 3267 return; 3268 } 3269 3270 // Default is to work on only visible columns 3271 if ( ! incColumns ) { 3272 incColumns = _range(columnCount) 3273 .filter(function (idx) { 3274 return columns[idx].bVisible; 3275 }); 3276 } 3277 3278 // Make a copy of the master layout array, but with only the columns we want 3279 for ( row=0 ; row<source.length ; row++ ) { 3280 // Remove any columns we haven't selected 3281 local[row] = source[row].slice().filter(function (cell, i) { 3282 return incColumns.includes(i); 3283 }); 3284 3285 // Prep the structure array - it needs an element for each row 3286 structure.push( [] ); 3287 } 3288 3289 for ( row=0 ; row<local.length ; row++ ) { 3290 for ( column=0 ; column<local[row].length ; column++ ) { 3291 rowspan = 1; 3292 colspan = 1; 3293 3294 // Check to see if there is already a cell (row/colspan) covering our target 3295 // insert point. If there is, then there is nothing to do. 3296 if ( structure[row][column] === undefined ) { 3297 cell = local[row][column].cell; 3298 3299 // Expand for rowspan 3300 while ( 3301 local[row+rowspan] !== undefined && 3302 local[row][column].cell == local[row+rowspan][column].cell 3303 ) { 3304 structure[row+rowspan][column] = null; 3305 rowspan++; 3306 } 3307 3308 // And for colspan 3309 while ( 3310 local[row][column+colspan] !== undefined && 3311 local[row][column].cell == local[row][column+colspan].cell 3312 ) { 3313 // Which also needs to go over rows 3314 for ( var k=0 ; k<rowspan ; k++ ) { 3315 structure[row+k][column+colspan] = null; 3316 } 3317 3318 colspan++; 3319 } 3320 3321 var titleSpan = $('span.dt-column-title', cell); 3322 3323 structure[row][column] = { 3324 cell: cell, 3325 colspan: colspan, 3326 rowspan: rowspan, 3327 title: titleSpan.length 3328 ? titleSpan.html() 3329 : $(cell).html() 3330 }; 3331 } 3332 } 3333 } 3334 3335 return structure; 3336 } 3337 3338 3339 /** 3340 * Draw the header (or footer) element based on the column visibility states. 3341 * 3342 * @param object oSettings dataTables settings object 3343 * @param array aoSource Layout array from _fnDetectHeader 3344 * @memberof DataTable#oApi 3345 */ 3346 function _fnDrawHead( settings, source ) 3347 { 3348 var layout = _fnHeaderLayout(settings, source); 3349 var tr, n; 3350 3351 for ( var row=0 ; row<source.length ; row++ ) { 3352 tr = source[row].row; 3353 3354 // All cells are going to be replaced, so empty out the row 3355 // Can't use $().empty() as that kills event handlers 3356 if (tr) { 3357 while( (n = tr.firstChild) ) { 3358 tr.removeChild( n ); 3359 } 3360 } 3361 3362 for ( var column=0 ; column<layout[row].length ; column++ ) { 3363 var point = layout[row][column]; 3364 3365 if (point) { 3366 $(point.cell) 3367 .appendTo(tr) 3368 .attr('rowspan', point.rowspan) 3369 .attr('colspan', point.colspan); 3370 } 3371 } 3372 } 3373 } 3374 3375 3376 /** 3377 * Insert the required TR nodes into the table for display 3378 * @param {object} oSettings dataTables settings object 3379 * @param ajaxComplete true after ajax call to complete rendering 3380 * @memberof DataTable#oApi 3381 */ 3382 function _fnDraw( oSettings, ajaxComplete ) 3383 { 3384 // Allow for state saving and a custom start position 3385 _fnStart( oSettings ); 3386 3387 /* Provide a pre-callback function which can be used to cancel the draw is false is returned */ 3388 var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] ); 3389 if ( aPreDraw.indexOf(false) !== -1 ) 3390 { 3391 _fnProcessingDisplay( oSettings, false ); 3392 return; 3393 } 3394 3395 var anRows = []; 3396 var iRowCount = 0; 3397 var bServerSide = _fnDataSource( oSettings ) == 'ssp'; 3398 var aiDisplay = oSettings.aiDisplay; 3399 var iDisplayStart = oSettings._iDisplayStart; 3400 var iDisplayEnd = oSettings.fnDisplayEnd(); 3401 var columns = oSettings.aoColumns; 3402 var body = $(oSettings.nTBody); 3403 3404 oSettings.bDrawing = true; 3405 3406 /* Server-side processing draw intercept */ 3407 if ( oSettings.deferLoading ) 3408 { 3409 oSettings.deferLoading = false;
3410 oSettings.iDraw++; 3411 _fnProcessingDisplay( oSettings, false ); 3412 } 3413 else if ( !bServerSide ) 3414 { 3415 oSettings.iDraw++; 3416 } 3417 else if ( !oSettings.bDestroying && !ajaxComplete) 3418 { 3419 // Show loading message for server-side processing 3420 if (oSettings.iDraw === 0) { 3421 body.empty().append(_emptyRow(oSettings)); 3422 } 3423 3424 _fnAjaxUpdate( oSettings ); 3425 return; 3426 } 3427 3428 if ( aiDisplay.length !== 0 ) 3429 { 3430 var iStart = bServerSide ? 0 : iDisplayStart; 3431 var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd; 3432 3433 for ( var j=iStart ; j<iEnd ; j++ ) 3434 { 3435 var iDataIndex = aiDisplay[j]; 3436 var aoData = oSettings.aoData[ iDataIndex ]; 3437 if ( aoData.nTr === null ) 3438 { 3439 _fnCreateTr( oSettings, iDataIndex ); 3440 } 3441 3442 var nRow = aoData.nTr; 3443 3444 // Add various classes as needed 3445 for (var i=0 ; i<columns.length ; i++) { 3446 var col = columns[i]; 3447 var td = aoData.anCells[i]; 3448 3449 _addClass(td, _ext.type.className[col.sType]); // auto class 3450 _addClass(td, oSettings.oClasses.tbody.cell); // all cells 3451 } 3452 3453 // Row callback functions - might want to manipulate the row 3454 // iRowCount and j are not currently documented. Are they at all 3455 // useful? 3456 _fnCallbackFire( oSettings, 'aoRowCallback', null, 3457 [nRow, aoData._aData, iRowCount, j, iDataIndex] ); 3458 3459 anRows.push( nRow ); 3460 iRowCount++; 3461 } 3462 } 3463 else 3464 { 3465 anRows[ 0 ] = _emptyRow(oSettings); 3466 } 3467 3468 /* Header and footer callbacks */ 3469 _fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0], 3470 _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] ); 3471 3472 _fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0], 3473 _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] ); 3474 3475 // replaceChildren is faster, but only became widespread in 2020, 3476 // so a fall back in jQuery is provided for older browsers. 3477 if (body[0].replaceChildren) { 3478 body[0].replaceChildren.apply(body[0], anRows); 3479 } 3480 else { 3481 body.children().detach(); 3482 body.append( $(anRows) ); 3483 } 3484 3485 // Empty table needs a specific class 3486 $(oSettings.nTableWrapper).toggleClass('dt-empty-footer', $('tr', oSettings.nTFoot).length === 0); 3487 3488 /* Call all required callback functions for the end of a draw */ 3489 _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings], true ); 3490 3491 /* Draw is complete, sorting and filtering must be as well */ 3492 oSettings.bSorted = false; 3493 oSettings.bFiltered = false; 3494 oSettings.bDrawing = false; 3495 } 3496 3497 3498 /** 3499 * Redraw the table - taking account of the various features which are enabled 3500 * @param {object} oSettings dataTables settings object 3501 * @param {boolean} [holdPosition] Keep the current paging position. By default 3502 * the paging is reset to the first page 3503 * @memberof DataTable#oApi 3504 */ 3505 function _fnReDraw( settings, holdPosition, recompute ) 3506 { 3507 var 3508 features = settings.oFeatures, 3509 sort = features.bSort, 3510 filter = features.bFilter; 3511 3512 if (recompute === undefined || recompute === true) { 3513 // Resolve any column types that are unknown due to addition or invalidation 3514 _fnColumnTypes( settings ); 3515 3516 if ( sort ) { 3517 _fnSort( settings ); 3518 } 3519 3520 if ( filter ) { 3521 _fnFilterComplete( settings, settings.oPreviousSearch ); 3522 } 3523 else { 3524 // No filtering, so we want to just use the display master 3525 settings.aiDisplay = settings.aiDisplayMaster.slice(); 3526 } 3527 } 3528 3529 if ( holdPosition !== true ) { 3530 settings._iDisplayStart = 0; 3531 } 3532 3533 // Let any modules know about the draw hold position state (used by 3534 // scrolling internally) 3535 settings._drawHold = holdPosition; 3536 3537 _fnDraw( settings ); 3538 3539 settings._drawHold = false; 3540 } 3541 3542 3543 /* 3544 * Table is empty - create a row with an empty message in it 3545 */ 3546 function _emptyRow ( settings ) { 3547 var oLang = settings.oLanguage; 3548 var zero = oLang.sZeroRecords; 3549 var dataSrc = _fnDataSource( settings ); 3550 3551 if ( 3552 (settings.iDraw < 1 && dataSrc === 'ssp') || 3553 (settings.iDraw <= 1 && dataSrc === 'ajax') 3554 ) { 3555 zero = oLang.sLoadingRecords; 3556 } 3557 else if ( oLang.sEmptyTable && settings.fnRecordsTotal() === 0 ) 3558 { 3559 zero = oLang.sEmptyTable; 3560 } 3561 3562 return $( '<tr/>' ) 3563 .append( $('<td />', { 3564 'colSpan': _fnVisbleColumns( settings ), 3565 'class': settings.oClasses.empty.row 3566 } ).html( zero ) )[0]; 3567 } 3568 3569 3570 /** 3571 * Expand the layout items into an object for the rendering function 3572 */ 3573 function _layoutItems (row, align, items) { 3574 if ( Array.isArray(items)) { 3575 for (var i=0 ; i<items.length ; i++) { 3576 _layoutItems(row, align, items[i]); 3577 } 3578 3579 return; 3580 } 3581 3582 var rowCell = row[align]; 3583 3584 // If it is an object, then there can be multiple features contained in it 3585 if ( $.isPlainObject( items ) ) { 3586 // A feature plugin cannot be named "features" due to this check 3587 if (items.features) { 3588 if (items.rowId) { 3589 row.id = items.rowId; 3590 } 3591 if (items.rowClass) { 3592 row.className = items.rowClass; 3593 } 3594 3595 rowCell.id = items.id; 3596 rowCell.className = items.className; 3597 3598 _layoutItems(row, align, items.features); 3599 } 3600 else { 3601 Object.keys(items).map(function (key) { 3602 rowCell.contents.push( { 3603 feature: key, 3604 opts: items[key] 3605 }); 3606 }); 3607 } 3608 } 3609 else { 3610 rowCell.contents.push(items); 3611 } 3612 } 3613 3614 /** 3615 * Find, or create a layout row 3616 */ 3617 function _layoutGetRow(rows, rowNum, align) { 3618 var row; 3619 3620 // Find existing rows 3621 for (var i=0; i<rows.length; i++) { 3622 row = rows[i]; 3623 3624 if (row.rowNum === rowNum) { 3625 // full is on its own, but start and end share a row 3626 if ( 3627 (align === 'full' && row.full) || 3628 ((align === 'start' || align === 'end') && (row.start || row.end)) 3629 ) { 3630 if (! row[align]) { 3631 row[align] = { 3632 contents: [] 3633 }; 3634 } 3635 3636 return row; 3637 } 3638 } 3639 } 3640 3641 // If we get this far, then there was no match, create a new row 3642 row = { 3643 rowNum: rowNum 3644 }; 3645 3646 row[align] = { 3647 contents: [] 3648 }; 3649 3650 rows.push(row); 3651 3652 return row; 3653 } 3654 3655 /** 3656 * Convert a `layout` object given by a user to the object structure needed 3657 * for the renderer. This is done twice, once for above and once for below
3658 * the table. Ordering must also be considered. 3659 * 3660 * @param {*} settings DataTables settings object 3661 * @param {*} layout Layout object to convert 3662 * @param {string} side `top` or `bottom` 3663 * @returns Converted array structure - one item for each row. 3664 */ 3665 function _layoutArray ( settings, layout, side ) { 3666 var rows = []; 3667 3668 // Split out into an array 3669 $.each( layout, function ( pos, items ) { 3670 if (items === null) { 3671 return; 3672 } 3673 3674 var parts = pos.match(/^([a-z]+)([0-9]*)([A-Za-z]*)$/); 3675 var rowNum = parts[2] 3676 ? parts[2] * 1 3677 : 0; 3678 var align = parts[3] 3679 ? parts[3].toLowerCase() 3680 : 'full'; 3681 3682 // Filter out the side we aren't interested in 3683 if (parts[1] !== side) { 3684 return; 3685 } 3686 3687 // Get or create the row we should attach to 3688 var row = _layoutGetRow(rows, rowNum, align); 3689 3690 _layoutItems(row, align, items); 3691 }); 3692 3693 // Order by item identifier 3694 rows.sort( function ( a, b ) { 3695 var order1 = a.rowNum; 3696 var order2 = b.rowNum; 3697 3698 // If both in the same row, then the row with `full` comes first 3699 if (order1 === order2) { 3700 var ret = a.full && ! b.full ? -1 : 1; 3701 3702 return side === 'bottom' 3703 ? ret * -1 3704 : ret; 3705 } 3706 3707 return order2 - order1; 3708 } ); 3709 3710 // Invert for below the table 3711 if ( side === 'bottom' ) { 3712 rows.reverse(); 3713 } 3714 3715 for (var row = 0; row<rows.length; row++) { 3716 delete rows[row].rowNum; 3717 3718 _layoutResolve(settings, rows[row]); 3719 } 3720 3721 return rows; 3722 } 3723 3724 3725 /** 3726 * Convert the contents of a row's layout object to nodes that can be inserted 3727 * into the document by a renderer. Execute functions, look up plug-ins, etc. 3728 * 3729 * @param {*} settings DataTables settings object 3730 * @param {*} row Layout object for this row 3731 */ 3732 function _layoutResolve( settings, row ) { 3733 var getFeature = function (feature, opts) { 3734 if ( ! _ext.features[ feature ] ) { 3735 _fnLog( settings, 0, 'Unknown feature: '+ feature ); 3736 } 3737 3738 return _ext.features[ feature ].apply( this, [settings, opts] ); 3739 }; 3740 3741 var resolve = function ( item ) { 3742 if (! row[ item ]) { 3743 return; 3744 } 3745 3746 var line = row[ item ].contents; 3747 3748 for ( var i=0, ien=line.length ; i<ien ; i++ ) { 3749 if ( ! line[i] ) { 3750 continue; 3751 } 3752 else if ( typeof line[i] === 'string' ) { 3753 line[i] = getFeature( line[i], null ); 3754 } 3755 else if ( $.isPlainObject(line[i]) ) { 3756 // If it's an object, it just has feature and opts properties from 3757 // the transform in _layoutArray 3758 line[i] = getFeature(line[i].feature, line[i].opts); 3759 } 3760 else if ( typeof line[i].node === 'function' ) { 3761 line[i] = line[i].node( settings ); 3762 } 3763 else if ( typeof line[i] === 'function' ) { 3764 var inst = line[i]( settings ); 3765 3766 line[i] = typeof inst.node === 'function' ? 3767 inst.node() : 3768 inst; 3769 } 3770 } 3771 }; 3772 3773 resolve('start'); 3774 resolve('end'); 3775 resolve('full'); 3776 } 3777 3778 3779 /** 3780 * Add the options to the page HTML for the table 3781 * @param {object} settings DataTables settings object 3782 * @memberof DataTable#oApi 3783 */ 3784 function _fnAddOptionsHtml ( settings ) 3785 { 3786 var classes = settings.oClasses; 3787 var table = $(settings.nTable); 3788 3789 // Wrapper div around everything DataTables controls 3790 var insert = $('<div/>') 3791 .attr({ 3792 id: settings.sTableId+'_wrapper', 3793 'class': classes.container 3794 }) 3795 .insertBefore(table); 3796 3797 settings.nTableWrapper = insert[0]; 3798 3799 if (settings.sDom) { 3800 // Legacy 3801 _fnLayoutDom(settings, settings.sDom, insert); 3802 } 3803 else { 3804 var top = _layoutArray( settings, settings.layout, 'top' ); 3805 var bottom = _layoutArray( settings, settings.layout, 'bottom' ); 3806 var renderer = _fnRenderer( settings, 'layout' ); 3807 3808 // Everything above - the renderer will actually insert the contents into the document 3809 top.forEach(function (item) { 3810 renderer( settings, insert, item ); 3811 }); 3812 3813 // The table - always the center of attention 3814 renderer( settings, insert, { 3815 full: { 3816 table: true, 3817 contents: [ _fnFeatureHtmlTable(settings) ] 3818 } 3819 } ); 3820 3821 // Everything below 3822 bottom.forEach(function (item) { 3823 renderer( settings, insert, item ); 3824 }); 3825 } 3826 3827 // Processing floats on top, so it isn't an inserted feature 3828 _processingHtml( settings ); 3829 } 3830 3831 /** 3832 * Draw the table with the legacy DOM property 3833 * @param {*} settings DT settings object 3834 * @param {*} dom DOM string 3835 * @param {*} insert Insert point 3836 */ 3837 function _fnLayoutDom( settings, dom, insert ) 3838 { 3839 var parts = dom.match(/(".*?")|('.*?')|./g); 3840 var featureNode, option, newNode, next, attr; 3841 3842 for ( var i=0 ; i<parts.length ; i++ ) { 3843 featureNode = null; 3844 option = parts[i]; 3845 3846 if ( option == '<' ) { 3847 // New container div
3848 newNode = $('<div/>'); 3849 3850 // Check to see if we should append an id and/or a class name to the container 3851 next = parts[i+1]; 3852 3853 if ( next[0] == "'" || next[0] == '"' ) { 3854 attr = next.replace(/['"]/g, ''); 3855 3856 var id = '', className; 3857 3858 /* The attribute can be in the format of "#id.class", "#id" or "class" This logic 3859 * breaks the string into parts and applies them as needed 3860 */ 3861 if ( attr.indexOf('.') != -1 ) { 3862 var split = attr.split('.'); 3863 3864 id = split[0]; 3865 className = split[1]; 3866 } 3867 else if ( attr[0] == "#" ) { 3868 id = attr; 3869 } 3870 else { 3871 className = attr; 3872 } 3873 3874 newNode 3875 .attr('id', id.substring(1)) 3876 .addClass(className); 3877 3878 i++; // Move along the position array 3879 } 3880 3881 insert.append( newNode ); 3882 insert = newNode; 3883 } 3884 else if ( option == '>' ) { 3885 // End container div 3886 insert = insert.parent(); 3887 } 3888 else if ( option == 't' ) { 3889 // Table 3890 featureNode = _fnFeatureHtmlTable( settings ); 3891 } 3892 else 3893 { 3894 DataTable.ext.feature.forEach(function(feature) { 3895 if ( option == feature.cFeature ) { 3896 featureNode = feature.fnInit( settings ); 3897 } 3898 }); 3899 } 3900 3901 // Add to the display 3902 if ( featureNode ) { 3903 insert.append( featureNode ); 3904 } 3905 } 3906 } 3907 3908 3909 /** 3910 * Use the DOM source to create up an array of header cells. The idea here is to 3911 * create a layout grid (array) of rows x columns, which contains a reference 3912 * to the cell that that point in the grid (regardless of col/rowspan), such that 3913 * any column / row could be removed and the new grid constructed 3914 * @param {node} thead The header/footer element for the table 3915 * @returns {array} Calculated layout array 3916 * @memberof DataTable#oApi 3917 */ 3918 function _fnDetectHeader ( settings, thead, write ) 3919 { 3920 var columns = settings.aoColumns; 3921 var rows = $(thead).children('tr'); 3922 var row, cell; 3923 var i, k, l, iLen, shifted, column, colspan, rowspan; 3924 var isHeader = thead && thead.nodeName.toLowerCase() === 'thead'; 3925 var layout = []; 3926 var unique; 3927 var shift = function ( a, i, j ) { 3928 var k = a[i]; 3929 while ( k[j] ) { 3930 j++; 3931 } 3932 return j; 3933 }; 3934 3935 // We know how many rows there are in the layout - so prep it 3936 for ( i=0, iLen=rows.length ; i<iLen ; i++ ) { 3937 layout.push( [] ); 3938 } 3939 3940 for ( i=0, iLen=rows.length ; i<iLen ; i++ ) { 3941 row = rows[i]; 3942 column = 0; 3943 3944 // For every cell in the row.. 3945 cell = row.firstChild; 3946 while ( cell ) { 3947 if ( 3948 cell.nodeName.toUpperCase() == 'TD' || 3949 cell.nodeName.toUpperCase() == 'TH' 3950 ) { 3951 var cols = []; 3952 3953 // Get the col and rowspan attributes from the DOM and sanitise them 3954 colspan = cell.getAttribute('colspan') * 1; 3955 rowspan = cell.getAttribute('rowspan') * 1; 3956 colspan = (!colspan || colspan===0 || colspan===1) ? 1 : colspan; 3957 rowspan = (!rowspan || rowspan===0 || rowspan===1) ? 1 : rowspan; 3958 3959 // There might be colspan cells already in this row, so shift our target 3960 // accordingly 3961 shifted = shift( layout, i, column ); 3962 3963 // Cache calculation for unique columns 3964 unique = colspan === 1 ? 3965 true : 3966 false; 3967 3968 // Perform header setup 3969 if ( write ) { 3970 if (unique) { 3971 // Allow column options to be set from HTML attributes 3972 _fnColumnOptions( settings, shifted, $(cell).data() ); 3973 3974 // Get the width for the column. This can be defined from the 3975 // width attribute, style attribute or `columns.width` option 3976 var columnDef = columns[shifted]; 3977 var width = cell.getAttribute('width') || null; 3978 var t = cell.style.width.match(/width:\s*(\d+[pxem%]+)/); 3979 if ( t ) { 3980 width = t[1]; 3981 } 3982 3983 columnDef.sWidthOrig = columnDef.sWidth || width; 3984 3985 if (isHeader) { 3986 // Column title handling - can be user set, or read from the DOM 3987 // This happens before the render, so the original is still in place 3988 if ( columnDef.sTitle !== null && ! columnDef.autoTitle ) { 3989 cell.innerHTML = columnDef.sTitle; 3990 } 3991 3992 if (! columnDef.sTitle && unique) { 3993 columnDef.sTitle = _stripHtml(cell.innerHTML); 3994 columnDef.autoTitle = true; 3995 } 3996 } 3997 else { 3998 // Footer specific operations 3999 if (columnDef.footer) { 4000 cell.innerHTML = columnDef.footer; 4001 } 4002 } 4003 4004 // Fall back to the aria-label attribute on the table header if no ariaTitle is 4005 // provided. 4006 if (! columnDef.ariaTitle) { 4007 columnDef.ariaTitle = $(cell).attr("aria-label") || columnDef.sTitle; 4008 } 4009 4010 // Column specific class names 4011 if ( columnDef.className ) { 4012 $(cell).addClass( columnDef.className ); 4013 } 4014 } 4015 4016 // Wrap the column title so we can write to it in future 4017 if ( $('span.dt-column-title', cell).length === 0) { 4018 $('<span>') 4019 .addClass('dt-column-title') 4020 .append(cell.childNodes) 4021 .appendTo(cell); 4022 } 4023 4024 if ( isHeader && $('span.dt-column-order', cell).length === 0) { 4025 $('<span>') 4026 .addClass('dt-column-order') 4027 .appendTo(cell); 4028 } 4029 } 4030 4031 // If there is col / rowspan, copy the information into the layout grid 4032 for ( l=0 ; l<colspan ; l++ ) { 4033 for ( k=0 ; k<rowspan ; k++ ) { 4034 layout[i+k][shifted+l] = { 4035 cell: cell, 4036 unique: unique 4037 }; 4038
4039 layout[i+k].row = row; 4040 } 4041 4042 cols.push( shifted+l ); 4043 } 4044 4045 // Assign an attribute so spanning cells can still be identified 4046 // as belonging to a column 4047 cell.setAttribute('data-dt-column', _unique(cols).join(',')); 4048 } 4049 4050 cell = cell.nextSibling; 4051 } 4052 } 4053 4054 return layout; 4055 } 4056 4057 /** 4058 * Set the start position for draw 4059 * @param {object} oSettings dataTables settings object 4060 */ 4061 function _fnStart( oSettings ) 4062 { 4063 var bServerSide = _fnDataSource( oSettings ) == 'ssp'; 4064 var iInitDisplayStart = oSettings.iInitDisplayStart; 4065 4066 // Check and see if we have an initial draw position from state saving 4067 if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 ) 4068 { 4069 oSettings._iDisplayStart = bServerSide ? 4070 iInitDisplayStart : 4071 iInitDisplayStart >= oSettings.fnRecordsDisplay() ? 4072 0 : 4073 iInitDisplayStart; 4074 4075 oSettings.iInitDisplayStart = -1; 4076 } 4077 } 4078 4079 /** 4080 * Create an Ajax call based on the table's settings, taking into account that 4081 * parameters can have multiple forms, and backwards compatibility. 4082 * 4083 * @param {object} oSettings dataTables settings object 4084 * @param {array} data Data to send to the server, required by 4085 * DataTables - may be augmented by developer callbacks 4086 * @param {function} fn Callback function to run when data is obtained 4087 */ 4088 function _fnBuildAjax( oSettings, data, fn ) 4089 { 4090 var ajaxData; 4091 var ajax = oSettings.ajax; 4092 var instance = oSettings.oInstance; 4093 var callback = function ( json ) { 4094 var status = oSettings.jqXHR 4095 ? oSettings.jqXHR.status 4096 : null; 4097 4098 if ( json === null || (typeof status === 'number' && status == 204 ) ) { 4099 json = {}; 4100 _fnAjaxDataSrc( oSettings, json, [] ); 4101 } 4102 4103 var error = json.error || json.sError; 4104 if ( error ) { 4105 _fnLog( oSettings, 0, error ); 4106 } 4107 4108 // Microsoft often wrap JSON as a string in another JSON object 4109 // Let's handle that automatically 4110 if (json.d && typeof json.d === 'string') { 4111 try { 4112 json = JSON.parse(json.d); 4113 } 4114 catch (e) { 4115 // noop 4116 } 4117 } 4118 4119 oSettings.json = json; 4120 4121 _fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR], true ); 4122 fn( json ); 4123 }; 4124 4125 if ( $.isPlainObject( ajax ) && ajax.data ) 4126 { 4127 ajaxData = ajax.data; 4128 4129 var newData = typeof ajaxData === 'function' ? 4130 ajaxData( data, oSettings ) : // fn can manipulate data or return 4131 ajaxData; // an object object or array to merge 4132 4133 // If the function returned something, use that alone 4134 data = typeof ajaxData === 'function' && newData ? 4135 newData : 4136 $.extend( true, data, newData ); 4137 4138 // Remove the data property as we've resolved it already and don't want 4139 // jQuery to do it again (it is restored at the end of the function) 4140 delete ajax.data; 4141 } 4142 4143 var baseAjax = { 4144 "url": typeof ajax === 'string' ? 4145 ajax : 4146 '', 4147 "data": data, 4148 "success": callback, 4149 "dataType": "json", 4150 "cache": false, 4151 "type": oSettings.sServerMethod, 4152 "error": function (xhr, error) { 4153 var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR], true ); 4154 4155 if ( ret.indexOf(true) === -1 ) { 4156 if ( error == "parsererror" ) { 4157 _fnLog( oSettings, 0, 'Invalid JSON response', 1 ); 4158 } 4159 else if ( xhr.readyState === 4 ) { 4160 _fnLog( oSettings, 0, 'Ajax error', 7 ); 4161 } 4162 } 4163 4164 _fnProcessingDisplay( oSettings, false ); 4165 } 4166 }; 4167 4168 // If `ajax` option is an object, extend and override our default base 4169 if ( $.isPlainObject( ajax ) ) { 4170 $.extend( baseAjax, ajax ) 4171 } 4172 4173 // Store the data submitted for the API 4174 oSettings.oAjaxData = data; 4175 4176 // Allow plug-ins and external processes to modify the data 4177 _fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data, baseAjax], true ); 4178 4179 if ( typeof ajax === 'function' ) 4180 { 4181 // Is a function - let the caller define what needs to be done 4182 oSettings.jqXHR = ajax.call( instance, data, callback, oSettings ); 4183 } 4184 else if (ajax.url === '') { 4185 // No url, so don't load any data. Just apply an empty data array 4186 // to the object for the callback. 4187 var empty = {}; 4188 4189 DataTable.util.set(ajax.dataSrc)(empty, []); 4190 callback(empty); 4191 } 4192 else { 4193 // Object to extend the base settings 4194 oSettings.jqXHR = $.ajax( baseAjax ); 4195 } 4196 4197 // Restore for next time around 4198 if ( ajaxData ) { 4199 ajax.data = ajaxData; 4200 } 4201 } 4202 4203 4204 /** 4205 * Update the table using an Ajax call 4206 * @param {object} settings dataTables settings object 4207 * @returns {boolean} Block the table drawing or not 4208 * @memberof DataTable#oApi 4209 */ 4210 function _fnAjaxUpdate( settings ) 4211 { 4212 settings.iDraw++; 4213 _fnProcessingDisplay( settings, true ); 4214 4215 _fnBuildAjax( 4216 settings, 4217 _fnAjaxParameters( settings ), 4218 function(json) { 4219 _fnAjaxUpdateDraw( settings, json ); 4220 } 4221 ); 4222 } 4223 4224 4225 /** 4226 * Build up the parameters in an object needed for a server-side processing 4227 * request. 4228 * @param {object} oSettings dataTables settings object 4229 * @returns {bool} block the table drawing or not 4230 * @memberof DataTable#oApi 4231 */ 4232 function _fnAjaxParameters( settings ) 4233 { 4234 var 4235 columns = settings.aoColumns, 4236 features = settings.oFeatures, 4237 preSearch = settings.oPreviousSearch, 4238 preColSearch = settings.aoPreSearchCols, 4239 colData = function ( idx, prop ) { 4240 return typeof columns[idx][prop] === 'function' ? 4241 'function' : 4242 columns[idx][prop]; 4243 }; 4244 4245 return { 4246 draw: settings.iDraw,
4247 columns: columns.map( function ( column, i ) { 4248 return { 4249 data: colData(i, 'mData'), 4250 name: column.sName, 4251 searchable: column.bSearchable, 4252 orderable: column.bSortable, 4253 search: { 4254 value: preColSearch[i].search, 4255 regex: preColSearch[i].regex, 4256 fixed: Object.keys(column.searchFixed).map( function(name) { 4257 return { 4258 name: name, 4259 term: column.searchFixed[name].toString() 4260 } 4261 }) 4262 } 4263 }; 4264 } ), 4265 order: _fnSortFlatten( settings ).map( function ( val ) { 4266 return { 4267 column: val.col, 4268 dir: val.dir, 4269 name: colData(val.col, 'sName') 4270 }; 4271 } ), 4272 start: settings._iDisplayStart, 4273 length: features.bPaginate ? 4274 settings._iDisplayLength : 4275 -1, 4276 search: { 4277 value: preSearch.search, 4278 regex: preSearch.regex, 4279 fixed: Object.keys(settings.searchFixed).map( function(name) { 4280 return { 4281 name: name, 4282 term: settings.searchFixed[name].toString() 4283 } 4284 }) 4285 } 4286 }; 4287 } 4288 4289 4290 /** 4291 * Data the data from the server (nuking the old) and redraw the table 4292 * @param {object} oSettings dataTables settings object 4293 * @param {object} json json data return from the server. 4294 * @param {string} json.sEcho Tracking flag for DataTables to match requests 4295 * @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering 4296 * @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering 4297 * @param {array} json.aaData The data to display on this page 4298 * @param {string} [json.sColumns] Column ordering (sName, comma separated) 4299 * @memberof DataTable#oApi 4300 */ 4301 function _fnAjaxUpdateDraw ( settings, json ) 4302 { 4303 var data = _fnAjaxDataSrc(settings, json); 4304 var draw = _fnAjaxDataSrcParam(settings, 'draw', json); 4305 var recordsTotal = _fnAjaxDataSrcParam(settings, 'recordsTotal', json); 4306 var recordsFiltered = _fnAjaxDataSrcParam(settings, 'recordsFiltered', json); 4307 4308 if ( draw !== undefined ) { 4309 // Protect against out of sequence returns 4310 if ( draw*1 < settings.iDraw ) { 4311 return; 4312 } 4313 settings.iDraw = draw * 1; 4314 } 4315 4316 // No data in returned object, so rather than an array, we show an empty table 4317 if ( ! data ) { 4318 data = []; 4319 } 4320 4321 _fnClearTable( settings ); 4322 settings._iRecordsTotal = parseInt(recordsTotal, 10); 4323 settings._iRecordsDisplay = parseInt(recordsFiltered, 10); 4324 4325 for ( var i=0, ien=data.length ; i<ien ; i++ ) { 4326 _fnAddData( settings, data[i] ); 4327 } 4328 settings.aiDisplay = settings.aiDisplayMaster.slice(); 4329 4330 _fnColumnTypes(settings); 4331 _fnDraw( settings, true ); 4332 _fnInitComplete( settings ); 4333 _fnProcessingDisplay( settings, false ); 4334 } 4335 4336 4337 /** 4338 * Get the data from the JSON data source to use for drawing a table. Using 4339 * `_fnGetObjectDataFn` allows the data to be sourced from a property of the 4340 * source object, or from a processing function. 4341 * @param {object} settings dataTables settings object 4342 * @param {object} json Data source object / array from the server 4343 * @return {array} Array of data to use 4344 */ 4345 function _fnAjaxDataSrc ( settings, json, write ) 4346 { 4347 var dataProp = 'data'; 4348 4349 if ($.isPlainObject( settings.ajax ) && settings.ajax.dataSrc !== undefined) { 4350 // Could in inside a `dataSrc` object, or not! 4351 var dataSrc = settings.ajax.dataSrc; 4352 4353 // string, function and object are valid types 4354 if (typeof dataSrc === 'string' || typeof dataSrc === 'function') { 4355 dataProp = dataSrc; 4356 } 4357 else if (dataSrc.data !== undefined) { 4358 dataProp = dataSrc.data; 4359 } 4360 } 4361 4362 if ( ! write ) { 4363 if ( dataProp === 'data' ) { 4364 // If the default, then we still want to support the old style, and safely ignore 4365 // it if possible 4366 return json.aaData || json[dataProp]; 4367 } 4368 4369 return dataProp !== "" ? 4370 _fnGetObjectDataFn( dataProp )( json ) : 4371 json; 4372 } 4373 4374 // set 4375 _fnSetObjectDataFn( dataProp )( json, write ); 4376 } 4377 4378 /** 4379 * Very similar to _fnAjaxDataSrc, but for the other SSP properties 4380 * @param {*} settings DataTables settings object 4381 * @param {*} param Target parameter 4382 * @param {*} json JSON data 4383 * @returns Resolved value 4384 */ 4385 function _fnAjaxDataSrcParam (settings, param, json) { 4386 var dataSrc = $.isPlainObject( settings.ajax ) 4387 ? settings.ajax.dataSrc 4388 : null; 4389 4390 if (dataSrc && dataSrc[param]) { 4391 // Get from custom location 4392 return _fnGetObjectDataFn( dataSrc[param] )( json ); 4393 } 4394 4395 // else - Default behaviour 4396 var old = ''; 4397 4398 // Legacy support 4399 if (param === 'draw') { 4400 old = 'sEcho'; 4401 } 4402 else if (param === 'recordsTotal') { 4403 old = 'iTotalRecords'; 4404 } 4405 else if (param === 'recordsFiltered') { 4406 old = 'iTotalDisplayRecords'; 4407 } 4408 4409 return json[old] !== undefined 4410 ? json[old] 4411 : json[param]; 4412 } 4413 4414 4415 /** 4416 * Filter the table using both the global filter and column based filtering 4417 * @param {object} settings dataTables settings object 4418 * @param {object} input search information 4419 * @memberof DataTable#oApi 4420 */ 4421 function _fnFilterComplete ( settings, input ) 4422 { 4423 var columnsSearch = settings.aoPreSearchCols; 4424 4425 // In server-side processing all filtering is done by the server, so no point hanging around here 4426 if ( _fnDataSource( settings ) != 'ssp' ) 4427 { 4428 // Check if any of the rows were invalidated 4429 _fnFilterData( settings ); 4430 4431 // Start from the full data set 4432 settings.aiDisplay = settings.aiDisplayMaster.slice(); 4433 4434 // Global filter first 4435 _fnFilter( settings.aiDisplay, settings, input.search, input ); 4436 4437 $.each(settings.searchFixed, function (name, term) { 4438 _fnFilter(settings.aiDisplay, settings, term, {}); 4439 }); 4440 4441 // Then individual column filters 4442 for ( var i=0 ; i<columnsSearch.length ; i++ ) 4443 { 4444 var col = columnsSearch[i]; 4445 4446 _fnFilter( 4447 settings.aiDisplay, 4448 settings, 4449 col.search, 4450 col, 4451 i 4452 ); 4453 4454 $.each(settings.aoColumns[i].searchFixed, function (name, term) { 4455 _fnFilter(settings.aiDisplay, settings, term, {}, i); 4456 }); 4457 } 4458 4459 // And finally global filtering 4460 _fnFilterCustom( settings ); 4461 } 4462 4463 // Tell the draw function we have been filtering 4464 settings.bFiltered = true; 4465 4466 _fnCallbackFire( settings, null, 'search', [settings] ); 4467 } 4468 4469 4470 /** 4471 * Apply custom filtering functions 4472 * 4473 * This is legacy now that we have named functions, but it is widely used 4474 * from 1.x, so it is not yet deprecated. 4475 * @param {object} oSettings dataTables settings object 4476 * @memberof DataTable#oApi 4477 */ 4478 function _fnFilterCustom( settings ) 4479 { 4480 var filters = DataTable.ext.search; 4481 var displayRows = settings.aiDisplay; 4482 var row, rowIdx; 4483 4484 for ( var i=0, ien=filters.length ; i<ien ; i++ ) { 4485 var rows = []; 4486 4487 // Loop over each row and see if it should be included 4488 for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) { 4489 rowIdx = displayRows[ j ]; 4490 row = settings.aoData[ rowIdx ]; 4491 4492 if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) { 4493 rows.push( rowIdx ); 4494 } 4495 } 4496 4497 // So the array reference doesn't break set the results into the 4498 // existing array 4499 displayRows.length = 0; 4500 _fnArrayApply(displayRows, rows); 4501 } 4502 } 4503 4504 4505 /** 4506 * Filter the data table based on user input and draw the table 4507 */ 4508 function _fnFilter( searchRows, settings, input, options, column ) 4509 { 4510 if ( input === '' ) { 4511 return; 4512 } 4513 4514 var i = 0; 4515 var matched = []; 4516 4517 // Search term can be a function, regex or string - if a string we apply our 4518 // smart filtering regex (assuming the options require that) 4519 var searchFunc = typeof input === 'function' ? input : null; 4520 var rpSearch = input instanceof RegExp 4521 ? input 4522 : searchFunc 4523 ? null 4524 : _fnFilterCreateSearch( input, options ); 4525 4526 // Then for each row, does the test pass. If not, lop the row from the array 4527 for (i=0 ; i<searchRows.length ; i++) { 4528 var row = settings.aoData[ searchRows[i] ]; 4529 var data = column === undefined 4530 ? row._sFilterRow 4531 : row._aFilterData[ column ]; 4532 4533 if ( (searchFunc && searchFunc(data, row._aData, searchRows[i], column)) || (rpSearch && rpSearch.test(data)) ) { 4534 matched.push(searchRows[i]); 4535 } 4536 } 4537 4538 // Mutate the searchRows array 4539 searchRows.length = matched.length; 4540 4541 for (i=0 ; i<matched.length ; i++) { 4542 searchRows[i] = matched[i]; 4543 } 4544 } 4545 4546 4547 /** 4548 * Build a regular expression object suitable for searching a table 4549 * @param {string} sSearch string to search for 4550 * @param {bool} bRegex treat as a regular expression or not 4551 * @param {bool} bSmart perform smart filtering or not 4552 * @param {bool} bCaseInsensitive Do case insensitive matching or not 4553 * @returns {RegExp} constructed object 4554 * @memberof DataTable#oApi 4555 */ 4556 function _fnFilterCreateSearch( search, inOpts ) 4557 { 4558 var not = []; 4559 var options = $.extend({}, { 4560 boundary: false, 4561 caseInsensitive: true, 4562 exact: false, 4563 regex: false, 4564 smart: true 4565 }, inOpts); 4566 4567 if (typeof search !== 'string') { 4568 search = search.toString(); 4569 } 4570 4571 // Remove diacritics if normalize is set up to do so 4572 search = _normalize(search); 4573 4574 if (options.exact) { 4575 return new RegExp( 4576 '^'+_fnEscapeRegex(search)+'$', 4577 options.caseInsensitive ? 'i' : '' 4578 ); 4579 } 4580 4581 search = options.regex ? 4582 search : 4583 _fnEscapeRegex( search ); 4584 4585 if ( options.smart ) { 4586 /* For smart filtering we want to allow the search to work regardless of 4587 * word order. We also want double quoted text to be preserved, so word 4588 * order is important - a la google. And a negative look around for 4589 * finding rows which don't contain a given string. 4590 * 4591 * So this is the sort of thing we want to generate: 4592 * 4593 * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$ 4594 */ 4595 var parts = search.match( /!?["\u201C][^"\u201D]+["\u201D]|[^ ]+/g ) || ['']; 4596 var a = parts.map( function ( word ) { 4597 var negative = false;
4598 var m; 4599 4600 // Determine if it is a "does not include" 4601 if ( word.charAt(0) === '!' ) { 4602 negative = true; 4603 word = word.substring(1); 4604 } 4605 4606 // Strip the quotes from around matched phrases 4607 if ( word.charAt(0) === '"' ) { 4608 m = word.match( /^"(.*)"$/ ); 4609 word = m ? m[1] : word; 4610 } 4611 else if ( word.charAt(0) === '\u201C' ) { 4612 // Smart quote match (iPhone users) 4613 m = word.match( /^\u201C(.*)\u201D$/ ); 4614 word = m ? m[1] : word; 4615 } 4616 4617 // For our "not" case, we need to modify the string that is 4618 // allowed to match at the end of the expression. 4619 if (negative) { 4620 if (word.length > 1) { 4621 not.push('(?!'+word+')'); 4622 } 4623 4624 word = ''; 4625 } 4626 4627 return word.replace(/"/g, ''); 4628 } ); 4629 4630 var match = not.length 4631 ? not.join('') 4632 : ''; 4633 4634 var boundary = options.boundary 4635 ? '\\b' 4636 : ''; 4637 4638 search = '^(?=.*?'+boundary+a.join( ')(?=.*?'+boundary )+')('+match+'.)*$'; 4639 } 4640 4641 return new RegExp( search, options.caseInsensitive ? 'i' : '' ); 4642 } 4643 4644 4645 /** 4646 * Escape a string such that it can be used in a regular expression 4647 * @param {string} sVal string to escape 4648 * @returns {string} escaped string 4649 * @memberof DataTable#oApi 4650 */ 4651 var _fnEscapeRegex = DataTable.util.escapeRegex; 4652 4653 var __filter_div = $('<div>')[0]; 4654 var __filter_div_textContent = __filter_div.textContent !== undefined; 4655 4656 // Update the filtering data for each row if needed (by invalidation or first run) 4657 function _fnFilterData ( settings ) 4658 { 4659 var columns = settings.aoColumns; 4660 var data = settings.aoData; 4661 var column; 4662 var j, jen, filterData, cellData, row; 4663 var wasInvalidated = false; 4664 4665 for ( var rowIdx=0 ; rowIdx<data.length ; rowIdx++ ) { 4666 if (! data[rowIdx]) { 4667 continue; 4668 } 4669 4670 row = data[rowIdx]; 4671 4672 if ( ! row._aFilterData ) { 4673 filterData = []; 4674 4675 for ( j=0, jen=columns.length ; j<jen ; j++ ) { 4676 column = columns[j]; 4677 4678 if ( column.bSearchable ) { 4679 cellData = _fnGetCellData( settings, rowIdx, j, 'filter' ); 4680 4681 // Search in DataTables is string based 4682 if ( cellData === null ) { 4683 cellData = ''; 4684 } 4685 4686 if ( typeof cellData !== 'string' && cellData.toString ) { 4687 cellData = cellData.toString(); 4688 } 4689 } 4690 else { 4691 cellData = ''; 4692 } 4693 4694 // If it looks like there is an HTML entity in the string, 4695 // attempt to decode it so sorting works as expected. Note that 4696 // we could use a single line of jQuery to do this, but the DOM 4697 // method used here is much faster https://jsperf.com/html-decode 4698 if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) { 4699 __filter_div.innerHTML = cellData; 4700 cellData = __filter_div_textContent ? 4701 __filter_div.textContent : 4702 __filter_div.innerText; 4703 } 4704 4705 if ( cellData.replace ) { 4706 cellData = cellData.replace(/[\r\n\u2028]/g, ''); 4707 } 4708 4709 filterData.push( cellData ); 4710 } 4711 4712 row._aFilterData = filterData; 4713 row._sFilterRow = filterData.join(' '); 4714 wasInvalidated = true; 4715 } 4716 } 4717 4718 return wasInvalidated; 4719 } 4720 4721 4722 /** 4723 * Draw the table for the first time, adding all required features 4724 * @param {object} settings dataTables settings object 4725 * @memberof DataTable#oApi 4726 */ 4727 function _fnInitialise ( settings ) 4728 { 4729 var i; 4730 var init = settings.oInit; 4731 var deferLoading = settings.deferLoading; 4732 var dataSrc = _fnDataSource( settings ); 4733 4734 // Ensure that the table data is fully initialised 4735 if ( ! settings.bInitialised ) { 4736 setTimeout( function(){ _fnInitialise( settings ); }, 200 ); 4737 return; 4738 } 4739 4740 // Build the header / footer for the table 4741 _fnBuildHead( settings, 'header' ); 4742 _fnBuildHead( settings, 'footer' ); 4743 4744 // Load the table's state (if needed) and then render around it and draw 4745 _fnLoadState( settings, init, function () { 4746 // Then draw the header / footer 4747 _fnDrawHead( settings, settings.aoHeader ); 4748 _fnDrawHead( settings, settings.aoFooter ); 4749 4750 // Cache the paging start point, as the first redraw will reset it 4751 var iAjaxStart = settings.iInitDisplayStart 4752 4753 // Local data load 4754 // Check if there is data passing into the constructor 4755 if ( init.aaData ) { 4756 for ( i=0 ; i<init.aaData.length ; i++ ) { 4757 _fnAddData( settings, init.aaData[ i ] ); 4758 } 4759 } 4760 else if ( deferLoading || dataSrc == 'dom' ) { 4761 // Grab the data from the page 4762 _fnAddTr( settings, $(settings.nTBody).children('tr') ); 4763 } 4764 4765 // Filter not yet applied - copy the display master 4766 settings.aiDisplay = settings.aiDisplayMaster.slice(); 4767 4768 // Enable features 4769 _fnAddOptionsHtml( settings ); 4770 _fnSortInit( settings ); 4771 4772 _colGroup( settings ); 4773 4774 /* Okay to show that something is going on now */ 4775 _fnProcessingDisplay( settings, true ); 4776 4777 _fnCallbackFire( settings, null, 'preInit', [settings], true ); 4778 4779 // If there is default sorting required - let's do it. The sort function 4780 // will do the drawing for us. Otherwise we draw the table regardless of the 4781 // Ajax source - this allows the table to look initialised for Ajax sourcing 4782 // data (show 'loading' message possibly) 4783 _fnReDraw( settings ); 4784 4785 // Server-side processing init complete is done by _fnAjaxUpdateDraw 4786 if ( dataSrc != 'ssp' || deferLoading ) { 4787 // if there is an ajax source load the data 4788 if ( dataSrc == 'ajax' ) { 4789 _fnBuildAjax( settings, {}, function(json) { 4790 var aData = _fnAjaxDataSrc( settings, json ); 4791 4792 // Got the data - add it to the table 4793 for ( i=0 ; i<aData.length ; i++ ) { 4794 _fnAddData( settings, aData[i] ); 4795 } 4796
4797 // Reset the init display for cookie saving. We've already done 4798 // a filter, and therefore cleared it before. So we need to make 4799 // it appear 'fresh' 4800 settings.iInitDisplayStart = iAjaxStart; 4801 4802 _fnReDraw( settings ); 4803 _fnProcessingDisplay( settings, false ); 4804 _fnInitComplete( settings ); 4805 }, settings ); 4806 } 4807 else { 4808 _fnInitComplete( settings ); 4809 _fnProcessingDisplay( settings, false ); 4810 } 4811 } 4812 } ); 4813 } 4814 4815 4816 /** 4817 * Draw the table for the first time, adding all required features 4818 * @param {object} settings dataTables settings object 4819 * @memberof DataTable#oApi 4820 */ 4821 function _fnInitComplete ( settings ) 4822 { 4823 if (settings._bInitComplete) { 4824 return; 4825 } 4826 4827 var args = [settings, settings.json]; 4828 4829 settings._bInitComplete = true; 4830 4831 // Table is fully set up and we have data, so calculate the 4832 // column widths 4833 _fnAdjustColumnSizing( settings ); 4834 4835 _fnCallbackFire( settings, null, 'plugin-init', args, true ); 4836 _fnCallbackFire( settings, 'aoInitComplete', 'init', args, true ); 4837 } 4838 4839 function _fnLengthChange ( settings, val ) 4840 { 4841 var len = parseInt( val, 10 ); 4842 settings._iDisplayLength = len; 4843 4844 _fnLengthOverflow( settings ); 4845 4846 // Fire length change event 4847 _fnCallbackFire( settings, null, 'length', [settings, len] ); 4848 } 4849 4850 /** 4851 * Alter the display settings to change the page 4852 * @param {object} settings DataTables settings object 4853 * @param {string|int} action Paging action to take: "first", "previous", 4854 * "next" or "last" or page number to jump to (integer) 4855 * @param [bool] redraw Automatically draw the update or not 4856 * @returns {bool} true page has changed, false - no change 4857 * @memberof DataTable#oApi 4858 */ 4859 function _fnPageChange ( settings, action, redraw ) 4860 { 4861 var 4862 start = settings._iDisplayStart, 4863 len = settings._iDisplayLength, 4864 records = settings.fnRecordsDisplay(); 4865 4866 if ( records === 0 || len === -1 ) 4867 { 4868 start = 0; 4869 } 4870 else if ( typeof action === "number" ) 4871 { 4872 start = action * len; 4873 4874 if ( start > records ) 4875 { 4876 start = 0; 4877 } 4878 } 4879 else if ( action == "first" ) 4880 { 4881 start = 0; 4882 } 4883 else if ( action == "previous" ) 4884 { 4885 start = len >= 0 ? 4886 start - len : 4887 0; 4888 4889 if ( start < 0 ) 4890 { 4891 start = 0; 4892 } 4893 } 4894 else if ( action == "next" ) 4895 { 4896 if ( start + len < records ) 4897 { 4898 start += len; 4899 } 4900 } 4901 else if ( action == "last" ) 4902 { 4903 start = Math.floor( (records-1) / len) * len; 4904 } 4905 else if ( action === 'ellipsis' ) 4906 { 4907 return; 4908 } 4909 else 4910 { 4911 _fnLog( settings, 0, "Unknown paging action: "+action, 5 ); 4912 } 4913 4914 var changed = settings._iDisplayStart !== start; 4915 settings._iDisplayStart = start; 4916 4917 _fnCallbackFire( settings, null, changed ? 'page' : 'page-nc', [settings] ); 4918 4919 if ( changed && redraw ) { 4920 _fnDraw( settings ); 4921 } 4922 4923 return changed; 4924 } 4925 4926 4927 /** 4928 * Generate the node required for the processing node 4929 * @param {object} settings DataTables settings object 4930 */ 4931 function _processingHtml ( settings ) 4932 { 4933 var table = settings.nTable; 4934 var scrolling = settings.oScroll.sX !== '' || settings.oScroll.sY !== ''; 4935 4936 if ( settings.oFeatures.bProcessing ) { 4937 var n = $('<div/>', { 4938 'id': settings.sTableId + '_processing', 4939 'class': settings.oClasses.processing.container, 4940 'role': 'status' 4941 } ) 4942 .html( settings.oLanguage.sProcessing ) 4943 .append('<div><div></div><div></div><div></div><div></div></div>'); 4944 4945 // Different positioning depending on if scrolling is enabled or not 4946 if (scrolling) { 4947 n.prependTo( $('div.dt-scroll', settings.nTableWrapper) ); 4948 } 4949 else { 4950 n.insertBefore( table ); 4951 } 4952 4953 $(table).on( 'processing.dt.DT', function (e, s, show) { 4954 n.css( 'display', show ? 'block' : 'none' ); 4955 } ); 4956 } 4957 } 4958 4959 4960 /** 4961 * Display or hide the processing indicator 4962 * @param {object} settings DataTables settings object 4963 * @param {bool} show Show the processing indicator (true) or not (false) 4964 */ 4965 function _fnProcessingDisplay ( settings, show ) 4966 { 4967 // Ignore cases when we are still redrawing 4968 if (settings.bDrawing && show === false) { 4969 return; 4970 } 4971 4972 _fnCallbackFire( settings, null, 'processing', [settings, show] ); 4973 } 4974 4975 /** 4976 * Show the processing element if an action takes longer than a given time 4977 * 4978 * @param {*} settings DataTables settings object 4979 * @param {*} enable Do (true) or not (false) async processing (local feature enablement) 4980 * @param {*} run Function to run 4981 */ 4982 function _fnProcessingRun( settings, enable, run ) { 4983 if (! enable) { 4984 // Immediate execution, synchronous 4985 run(); 4986 } 4987 else { 4988 _fnProcessingDisplay(settings, true); 4989 4990 // Allow the processing display to show if needed 4991 setTimeout(function () { 4992 run(); 4993 4994 _fnProcessingDisplay(settings, false); 4995 }, 0); 4996 } 4997 } 4998 /** 4999 * Add any control elements for the table - specifically scrolling 5000 * @param {object} settings dataTables settings object 5001 * @returns {node} Node to add to the DOM 5002 * @memberof DataTable#oApi 5003 */ 5004 function _fnFeatureHtmlTable ( settings ) 5005 { 5006 var table = $(settings.nTable); 5007 5008 // Scrolling from here on in 5009 var scroll = settings.oScroll; 5010
5011 if ( scroll.sX === '' && scroll.sY === '' ) { 5012 return settings.nTable; 5013 } 5014 5015 var scrollX = scroll.sX; 5016 var scrollY = scroll.sY; 5017 var classes = settings.oClasses.scrolling; 5018 var caption = settings.captionNode; 5019 var captionSide = caption ? caption._captionSide : null; 5020 var headerClone = $( table[0].cloneNode(false) ); 5021 var footerClone = $( table[0].cloneNode(false) ); 5022 var footer = table.children('tfoot'); 5023 var _div = '<div/>'; 5024 var size = function ( s ) { 5025 return !s ? null : _fnStringToCss( s ); 5026 }; 5027 5028 if ( ! footer.length ) { 5029 footer = null; 5030 } 5031 5032 /* 5033 * The HTML structure that we want to generate in this function is: 5034 * div - scroller 5035 * div - scroll head 5036 * div - scroll head inner 5037 * table - scroll head table 5038 * thead - thead 5039 * div - scroll body 5040 * table - table (master table) 5041 * thead - thead clone for sizing 5042 * tbody - tbody 5043 * div - scroll foot 5044 * div - scroll foot inner 5045 * table - scroll foot table 5046 * tfoot - tfoot 5047 */ 5048 var scroller = $( _div, { 'class': classes.container } ) 5049 .append( 5050 $(_div, { 'class': classes.header.self } ) 5051 .css( { 5052 overflow: 'hidden', 5053 position: 'relative', 5054 border: 0, 5055 width: scrollX ? size(scrollX) : '100%' 5056 } ) 5057 .append( 5058 $(_div, { 'class': classes.header.inner } ) 5059 .css( { 5060 'box-sizing': 'content-box', 5061 width: scroll.sXInner || '100%' 5062 } ) 5063 .append( 5064 headerClone 5065 .removeAttr('id') 5066 .css( 'margin-left', 0 ) 5067 .append( captionSide === 'top' ? caption : null ) 5068 .append( 5069 table.children('thead') 5070 ) 5071 ) 5072 ) 5073 ) 5074 .append( 5075 $(_div, { 'class': classes.body } ) 5076 .css( { 5077 position: 'relative', 5078 overflow: 'auto', 5079 width: size( scrollX ) 5080 } ) 5081 .append( table ) 5082 ); 5083 5084 if ( footer ) { 5085 scroller.append( 5086 $(_div, { 'class': classes.footer.self } ) 5087 .css( { 5088 overflow: 'hidden', 5089 border: 0, 5090 width: scrollX ? size(scrollX) : '100%' 5091 } ) 5092 .append( 5093 $(_div, { 'class': classes.footer.inner } ) 5094 .append( 5095 footerClone 5096 .removeAttr('id') 5097 .css( 'margin-left', 0 ) 5098 .append( captionSide === 'bottom' ? caption : null ) 5099 .append( 5100 table.children('tfoot') 5101 ) 5102 ) 5103 ) 5104 ); 5105 } 5106 5107 var children = scroller.children(); 5108 var scrollHead = children[0]; 5109 var scrollBody = children[1]; 5110 var scrollFoot = footer ? children[2] : null; 5111 5112 // When the body is scrolled, then we also want to scroll the headers 5113 $(scrollBody).on( 'scroll.DT', function () { 5114 var scrollLeft = this.scrollLeft; 5115 5116 scrollHead.scrollLeft = scrollLeft; 5117 5118 if ( footer ) { 5119 scrollFoot.scrollLeft = scrollLeft; 5120 } 5121 } ); 5122 5123 // When focus is put on the header cells, we might need to scroll the body 5124 $('th, td', scrollHead).on('focus', function () { 5125 var scrollLeft = scrollHead.scrollLeft; 5126 5127 scrollBody.scrollLeft = scrollLeft; 5128 5129 if ( footer ) { 5130 scrollBody.scrollLeft = scrollLeft; 5131 } 5132 }); 5133 5134 $(scrollBody).css('max-height', scrollY); 5135 if (! scroll.bCollapse) { 5136 $(scrollBody).css('height', scrollY); 5137 } 5138 5139 settings.nScrollHead = scrollHead; 5140 settings.nScrollBody = scrollBody; 5141 settings.nScrollFoot = scrollFoot; 5142 5143 // On redraw - align columns 5144 settings.aoDrawCallback.push(_fnScrollDraw); 5145 5146 return scroller[0]; 5147 } 5148 5149 5150 5151 /** 5152 * Update the header, footer and body tables for resizing - i.e. column 5153 * alignment. 5154 * 5155 * Welcome to the most horrible function DataTables. The process that this 5156 * function follows is basically: 5157 * 1. Re-create the table inside the scrolling div 5158 * 2. Correct colgroup > col values if needed 5159 * 3. Copy colgroup > col over to header and footer 5160 * 4. Clean up 5161 * 5162 * @param {object} settings dataTables settings object 5163 * @memberof DataTable#oApi 5164 */ 5165 function _fnScrollDraw ( settings ) 5166 { 5167 // Given that this is such a monster function, a lot of variables are use 5168 // to try and keep the minimised size as small as possible 5169 var 5170 scroll = settings.oScroll, 5171 barWidth = scroll.iBarWidth, 5172 divHeader = $(settings.nScrollHead), 5173 divHeaderInner = divHeader.children('div'), 5174 divHeaderTable = divHeaderInner.children('table'), 5175 divBodyEl = settings.nScrollBody,
5176 divBody = $(divBodyEl), 5177 divFooter = $(settings.nScrollFoot), 5178 divFooterInner = divFooter.children('div'), 5179 divFooterTable = divFooterInner.children('table'), 5180 header = $(settings.nTHead), 5181 table = $(settings.nTable), 5182 footer = settings.nTFoot && $('th, td', settings.nTFoot).length ? $(settings.nTFoot) : null, 5183 browser = settings.oBrowser, 5184 headerCopy, footerCopy; 5185 5186 // If the scrollbar visibility has changed from the last draw, we need to 5187 // adjust the column sizes as the table width will have changed to account 5188 // for the scrollbar 5189 var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight; 5190 5191 if ( settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined ) { 5192 settings.scrollBarVis = scrollBarVis; 5193 _fnAdjustColumnSizing( settings ); 5194 return; // adjust column sizing will call this function again 5195 } 5196 else { 5197 settings.scrollBarVis = scrollBarVis; 5198 } 5199 5200 // 1. Re-create the table inside the scrolling div 5201 // Remove the old minimised thead and tfoot elements in the inner table 5202 table.children('thead, tfoot').remove(); 5203 5204 // Clone the current header and footer elements and then place it into the inner table 5205 headerCopy = header.clone().prependTo( table ); 5206 headerCopy.find('th, td').removeAttr('tabindex'); 5207 headerCopy.find('[id]').removeAttr('id'); 5208 5209 if ( footer ) { 5210 footerCopy = footer.clone().prependTo( table ); 5211 footerCopy.find('[id]').removeAttr('id'); 5212 } 5213 5214 // 2. Correct colgroup > col values if needed 5215 // It is possible that the cell sizes are smaller than the content, so we need to 5216 // correct colgroup>col for such cases. This can happen if the auto width detection 5217 // uses a cell which has a longer string, but isn't the widest! For example 5218 // "Chief Executive Officer (CEO)" is the longest string in the demo, but 5219 // "Systems Administrator" is actually the widest string since it doesn't collapse. 5220 // Note the use of translating into a column index to get the `col` element. This 5221 // is because of Responsive which might remove `col` elements, knocking the alignment 5222 // of the indexes out. 5223 if (settings.aiDisplay.length) { 5224 // Get the column sizes from the first row in the table. This should really be a 5225 // [].find, but it wasn't supported in Chrome until Sept 2015, and DT has 10 year 5226 // browser support 5227 var firstTr = null; 5228 var start = _fnDataSource( settings ) !== 'ssp' 5229 ? settings._iDisplayStart 5230 : 0; 5231 5232 for (i=start ; i<start + settings.aiDisplay.length ; i++) { 5233 var idx = settings.aiDisplay[i]; 5234 var tr = settings.aoData[idx].nTr; 5235 5236 if (tr) { 5237 firstTr = tr; 5238 break; 5239 } 5240 } 5241 5242 if (firstTr) { 5243 var colSizes = $(firstTr).children('th, td').map(function (vis) { 5244 return { 5245 idx: _fnVisibleToColumnIndex(settings, vis), 5246 width: $(this).outerWidth() 5247 }; 5248 }); 5249 5250 // Check against what the colgroup > col is set to and correct if needed 5251 for (var i=0 ; i<colSizes.length ; i++) { 5252 var colEl = settings.aoColumns[ colSizes[i].idx ].colEl[0]; 5253 var colWidth = colEl.style.width.replace('px', ''); 5254 5255 if (colWidth !== colSizes[i].width) { 5256 colEl.style.width = colSizes[i].width + 'px'; 5257 5258 if (scroll.sX) { 5259 colEl.style.minWidth = colSizes[i].width + 'px'; 5260 } 5261 } 5262 } 5263 } 5264 } 5265 5266 // 3. Copy the colgroup over to the header and footer 5267 divHeaderTable 5268 .find('colgroup') 5269 .remove(); 5270 5271 divHeaderTable.append(settings.colgroup.clone()); 5272 5273 if ( footer ) { 5274 divFooterTable 5275 .find('colgroup') 5276 .remove(); 5277 5278 divFooterTable.append(settings.colgroup.clone()); 5279 } 5280 5281 // "Hide" the header and footer that we used for the sizing. We need to keep 5282 // the content of the cell so that the width applied to the header and body 5283 // both match, but we want to hide it completely. 5284 $('th, td', headerCopy).each(function () { 5285 $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">'); 5286 }); 5287 5288 if ( footer ) { 5289 $('th, td', footerCopy).each(function () { 5290 $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">'); 5291 }); 5292 } 5293 5294 // 4. Clean up 5295 // Figure out if there are scrollbar present - if so then we need a the header and footer to 5296 // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar) 5297 var isScrolling = Math.floor(table.height()) > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll"; 5298 var paddingSide = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' ); 5299 5300 // Set the width's of the header and footer tables 5301 var outerWidth = table.outerWidth(); 5302 5303 divHeaderTable.css('width', _fnStringToCss( outerWidth )); 5304 divHeaderInner 5305 .css('width', _fnStringToCss( outerWidth )) 5306 .css(paddingSide, isScrolling ? barWidth+"px" : "0px"); 5307 5308 if ( footer ) { 5309 divFooterTable.css('width', _fnStringToCss( outerWidth )); 5310 divFooterInner 5311 .css('width', _fnStringToCss( outerWidth )) 5312 .css(paddingSide, isScrolling ? barWidth+"px" : "0px"); 5313 } 5314 5315 // Correct DOM ordering for colgroup - comes before the thead 5316 table.children('colgroup').prependTo(table); 5317 5318 // Adjust the position of the header in case we loose the y-scrollbar 5319 divBody.trigger('scroll'); 5320 5321 // If sorting or filtering has occurred, jump the scrolling back to the top 5322 // only if we aren't holding the position 5323 if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) { 5324 divBodyEl.scrollTop = 0; 5325 } 5326 } 5327 5328 /** 5329 * Calculate the width of columns for the table 5330 * @param {object} settings dataTables settings object 5331 * @memberof DataTable#oApi 5332 */ 5333 function _fnCalculateColumnWidths ( settings ) 5334 { 5335 // Not interested in doing column width calculation if auto-width is disabled 5336 if (! settings.oFeatures.bAutoWidth) { 5337 return; 5338 } 5339 5340 var 5341 table = settings.nTable, 5342 columns = settings.aoColumns, 5343 scroll = settings.oScroll, 5344 scrollY = scroll.sY, 5345 scrollX = scroll.sX, 5346 scrollXInner = scroll.sXInner, 5347 visibleColumns = _fnGetColumns( settings, 'bVisible' ), 5348 tableWidthAttr = table.getAttribute('width'), // from DOM element 5349 tableContainer = table.parentNode, 5350 i, column, columnIdx; 5351 5352 var styleWidth = table.style.width; 5353 var containerWidth = _fnWrapperWidth(settings); 5354 5355 // Don't re-run for the same width as the last time 5356 if (containerWidth === settings.containerWidth) { 5357 return false;
5358 } 5359 5360 settings.containerWidth = containerWidth; 5361 5362 // If there is no width applied as a CSS style or as an attribute, we assume that 5363 // the width is intended to be 100%, which is usually is in CSS, but it is very 5364 // difficult to correctly parse the rules to get the final result. 5365 if ( ! styleWidth && ! tableWidthAttr) { 5366 table.style.width = '100%'; 5367 styleWidth = '100%'; 5368 } 5369 5370 if ( styleWidth && styleWidth.indexOf('%') !== -1 ) { 5371 tableWidthAttr = styleWidth; 5372 } 5373 5374 // Let plug-ins know that we are doing a recalc, in case they have changed any of the 5375 // visible columns their own way (e.g. Responsive uses display:none). 5376 _fnCallbackFire( 5377 settings, 5378 null, 5379 'column-calc', 5380 {visible: visibleColumns}, 5381 false 5382 ); 5383 5384 // Construct a single row, worst case, table with the widest 5385 // node in the data, assign any user defined widths, then insert it into 5386 // the DOM and allow the browser to do all the hard work of calculating 5387 // table widths 5388 var tmpTable = $(table.cloneNode()) 5389 .css( 'visibility', 'hidden' ) 5390 .removeAttr( 'id' ); 5391 5392 // Clean up the table body 5393 tmpTable.append('<tbody>') 5394 var tr = $('<tr/>').appendTo( tmpTable.find('tbody') ); 5395 5396 // Clone the table header and footer - we can't use the header / footer 5397 // from the cloned table, since if scrolling is active, the table's 5398 // real header and footer are contained in different table tags 5399 tmpTable 5400 .append( $(settings.nTHead).clone() ) 5401 .append( $(settings.nTFoot).clone() ); 5402 5403 // Remove any assigned widths from the footer (from scrolling) 5404 tmpTable.find('tfoot th, tfoot td').css('width', ''); 5405 5406 // Apply custom sizing to the cloned header 5407 tmpTable.find('thead th, thead td').each( function () { 5408 // Get the `width` from the header layout 5409 var width = _fnColumnsSumWidth( settings, this, true, false ); 5410 5411 if ( width ) { 5412 this.style.width = width; 5413 5414 // For scrollX we need to force the column width otherwise the 5415 // browser will collapse it. If this width is smaller than the 5416 // width the column requires, then it will have no effect 5417 if ( scrollX ) { 5418 this.style.minWidth = width; 5419 5420 $( this ).append( $('<div/>').css( { 5421 width: width, 5422 margin: 0, 5423 padding: 0, 5424 border: 0, 5425 height: 1 5426 } ) ); 5427 } 5428 } 5429 else { 5430 this.style.width = ''; 5431 } 5432 } ); 5433 5434 // Find the widest piece of data for each column and put it into the table 5435 for ( i=0 ; i<visibleColumns.length ; i++ ) { 5436 columnIdx = visibleColumns[i]; 5437 column = columns[ columnIdx ]; 5438 5439 var longest = _fnGetMaxLenString(settings, columnIdx); 5440 var autoClass = _ext.type.className[column.sType]; 5441 var text = longest + column.sContentPadding; 5442 var insert = longest.indexOf('<') === -1 5443 ? document.createTextNode(text) 5444 : text 5445 5446 $('<td/>') 5447 .addClass(autoClass) 5448 .addClass(column.sClass) 5449 .append(insert) 5450 .appendTo(tr); 5451 } 5452 5453 // Tidy the temporary table - remove name attributes so there aren't 5454 // duplicated in the dom (radio elements for example) 5455 $('[name]', tmpTable).removeAttr('name'); 5456 5457 // Table has been built, attach to the document so we can work with it. 5458 // A holding element is used, positioned at the top of the container 5459 // with minimal height, so it has no effect on if the container scrolls 5460 // or not. Otherwise it might trigger scrolling when it actually isn't 5461 // needed 5462 var holder = $('<div/>').css( scrollX || scrollY ? 5463 { 5464 position: 'absolute', 5465 top: 0, 5466 left: 0, 5467 height: 1, 5468 right: 0, 5469 overflow: 'hidden' 5470 } : 5471 {} 5472 ) 5473 .append( tmpTable ) 5474 .appendTo( tableContainer ); 5475 5476 // When scrolling (X or Y) we want to set the width of the table as 5477 // appropriate. However, when not scrolling leave the table width as it 5478 // is. This results in slightly different, but I think correct behaviour 5479 if ( scrollX && scrollXInner ) { 5480 tmpTable.width( scrollXInner ); 5481 } 5482 else if ( scrollX ) { 5483 tmpTable.css( 'width', 'auto' ); 5484 tmpTable.removeAttr('width'); 5485 5486 // If there is no width attribute or style, then allow the table to 5487 // collapse 5488 if ( tmpTable.outerWidth() < tableContainer.clientWidth && tableWidthAttr ) { 5489 tmpTable.outerWidth( tableContainer.clientWidth ); 5490 } 5491 } 5492 else if ( scrollY ) { 5493 tmpTable.outerWidth( tableContainer.clientWidth ); 5494 } 5495 else if ( tableWidthAttr ) { 5496 tmpTable.outerWidth( tableWidthAttr ); 5497 } 5498 5499 // Get the width of each column in the constructed table 5500 var total = 0; 5501 var bodyCells = tmpTable.find('tbody tr').eq(0).children(); 5502 5503 for ( i=0 ; i<visibleColumns.length ; i++ ) { 5504 // Use getBounding for sub-pixel accuracy, which we then want to round up! 5505 var bounding = bodyCells[i].getBoundingClientRect().width; 5506 5507 // Total is tracked to remove any sub-pixel errors as the outerWidth 5508 // of the table might not equal the total given here 5509 total += bounding; 5510 5511 // Width for each column to use 5512 columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding ); 5513 } 5514 5515 table.style.width = _fnStringToCss( total ); 5516
5517 // Finished with the table - ditch it 5518 holder.remove(); 5519 5520 // If there is a width attr, we want to attach an event listener which 5521 // allows the table sizing to automatically adjust when the window is 5522 // resized. Use the width attr rather than CSS, since we can't know if the 5523 // CSS is a relative value or absolute - DOM read is always px. 5524 if ( tableWidthAttr ) { 5525 table.style.width = _fnStringToCss( tableWidthAttr ); 5526 } 5527 5528 if ( (tableWidthAttr || scrollX) && ! settings._reszEvt ) { 5529 var resize = DataTable.util.throttle( function () { 5530 var newWidth = _fnWrapperWidth(settings); 5531 5532 // Don't do it if destroying or the container width is 0 5533 if (! settings.bDestroying && newWidth !== 0) { 5534 _fnAdjustColumnSizing( settings ); 5535 } 5536 } ); 5537 5538 // For browsers that support it (~2020 onwards for wide support) we can watch for the 5539 // container changing width. 5540 if (window.ResizeObserver) { 5541 // This is a tricky beast - if the element is visible when `.observe()` is called, 5542 // then the callback is immediately run. Which we don't want. If the element isn't 5543 // visible, then it isn't run, but we want it to run when it is then made visible. 5544 // This flag allows the above to be satisfied. 5545 var first = $(settings.nTableWrapper).is(':visible'); 5546 5547 // Use an empty div to attach the observer so it isn't impacted by height changes 5548 var resizer = $('<div>') 5549 .css({ 5550 width: '100%', 5551 height: 0 5552 }) 5553 .addClass('dt-autosize') 5554 .appendTo(settings.nTableWrapper); 5555 5556 settings.resizeObserver = new ResizeObserver(function (e) { 5557 if (first) { 5558 first = false; 5559 } 5560 else { 5561 resize(); 5562 } 5563 }); 5564 5565 settings.resizeObserver.observe(resizer[0]); 5566 } 5567 else { 5568 // For old browsers, the best we can do is listen for a window resize 5569 $(window).on('resize.DT-'+settings.sInstance, resize); 5570 } 5571 5572 settings._reszEvt = true; 5573 } 5574 } 5575 5576 /** 5577 * Get the width of the DataTables wrapper element 5578 * 5579 * @param {*} settings DataTables settings object 5580 * @returns Width 5581 */ 5582 function _fnWrapperWidth(settings) { 5583 return $(settings.nTableWrapper).is(':visible') 5584 ? $(settings.nTableWrapper).width() 5585 : 0; 5586 } 5587 5588 /** 5589 * Get the maximum strlen for each data column 5590 * @param {object} settings dataTables settings object 5591 * @param {int} colIdx column of interest 5592 * @returns {string} string of the max length 5593 * @memberof DataTable#oApi 5594 */ 5595 function _fnGetMaxLenString( settings, colIdx ) 5596 { 5597 var column = settings.aoColumns[colIdx]; 5598 5599 if (! column.maxLenString) { 5600 var s, max='', maxLen = -1; 5601 5602 for ( var i=0, ien=settings.aiDisplayMaster.length ; i<ien ; i++ ) { 5603 var rowIdx = settings.aiDisplayMaster[i]; 5604 var data = _fnGetRowDisplay(settings, rowIdx)[colIdx]; 5605 5606 var cellString = data && typeof data === 'object' && data.nodeType 5607 ? data.innerHTML 5608 : data+''; 5609 5610 // Remove id / name attributes from elements so they 5611 // don't interfere with existing elements 5612 cellString = cellString 5613 .replace(/id=".*?"/g, '') 5614 .replace(/name=".*?"/g, ''); 5615 5616 s = _stripHtml(cellString) 5617 .replace( / /g, ' ' ); 5618 5619 if ( s.length > maxLen ) { 5620 // We want the HTML in the string, but the length that 5621 // is important is the stripped string 5622 max = cellString; 5623 maxLen = s.length; 5624 } 5625 } 5626 5627 column.maxLenString = max; 5628 } 5629 5630 return column.maxLenString; 5631 } 5632 5633 5634 /** 5635 * Append a CSS unit (only if required) to a string 5636 * @param {string} value to css-ify 5637 * @returns {string} value with css unit 5638 * @memberof DataTable#oApi 5639 */ 5640 function _fnStringToCss( s ) 5641 { 5642 if ( s === null ) { 5643 return '0px'; 5644 } 5645 5646 if ( typeof s == 'number' ) { 5647 return s < 0 ? 5648 '0px' : 5649 s+'px'; 5650 } 5651 5652 // Check it has a unit character already 5653 return s.match(/\d$/) ? 5654 s+'px' : 5655 s; 5656 } 5657 5658 /** 5659 * Re-insert the `col` elements for current visibility 5660 * 5661 * @param {*} settings DT settings 5662 */ 5663 function _colGroup( settings ) { 5664 var cols = settings.aoColumns; 5665 5666 settings.colgroup.empty(); 5667 5668 for (i=0 ; i<cols.length ; i++) { 5669 if (cols[i].bVisible) { 5670 settings.colgroup.append(cols[i].colEl); 5671 } 5672 } 5673 } 5674 5675 5676 function _fnSortInit( settings ) { 5677 var target = settings.nTHead; 5678 var headerRows = target.querySelectorAll('tr'); 5679 var legacyTop = settings.bSortCellsTop; 5680 var notSelector = ':not([data-dt-order="disable"]):not([data-dt-order="icon-only"])'; 5681 5682 // Legacy support for `orderCellsTop` 5683 if (legacyTop === true) { 5684 target = headerRows[0]; 5685 } 5686 else if (legacyTop === false) { 5687 target = headerRows[ headerRows.length - 1 ]; 5688 } 5689 5690 _fnSortAttachListener( 5691 settings, 5692 target, 5693 target === settings.nTHead 5694 ? 'tr'+notSelector+' th'+notSelector+', tr'+notSelector+' td'+notSelector 5695 : 'th'+notSelector+', td'+notSelector 5696 ); 5697 5698 // Need to resolve the user input array into our internal structure 5699 var order = []; 5700 _fnSortResolve( settings, order, settings.aaSorting ); 5701 5702 settings.aaSorting = order; 5703 } 5704 5705 5706 function _fnSortAttachListener(settings, node, selector, column, callback) { 5707 _fnBindAction( node, selector, function (e) { 5708 var run = false;
5709 var columns = column === undefined 5710 ? _fnColumnsFromHeader( e.target ) 5711 : [column]; 5712 5713 if ( columns.length ) { 5714 for ( var i=0, ien=columns.length ; i<ien ; i++ ) { 5715 var ret = _fnSortAdd( settings, columns[i], i, e.shiftKey ); 5716 5717 if (ret !== false) { 5718 run = true; 5719 } 5720 5721 // If the first entry is no sort, then subsequent 5722 // sort columns are ignored 5723 if (settings.aaSorting.length === 1 && settings.aaSorting[0][1] === '') { 5724 break; 5725 } 5726 } 5727 5728 if (run) { 5729 _fnProcessingRun(settings, true, function () { 5730 _fnSort( settings ); 5731 _fnSortDisplay( settings, settings.aiDisplay ); 5732 5733 _fnReDraw( settings, false, false ); 5734 5735 if (callback) { 5736 callback(); 5737 } 5738 }); 5739 } 5740 } 5741 } ); 5742 } 5743 5744 /** 5745 * Sort the display array to match the master's order 5746 * @param {*} settings 5747 */ 5748 function _fnSortDisplay(settings, display) { 5749 if (display.length < 2) { 5750 return; 5751 } 5752 5753 var master = settings.aiDisplayMaster; 5754 var masterMap = {}; 5755 var map = {}; 5756 var i; 5757 5758 // Rather than needing an `indexOf` on master array, we can create a map 5759 for (i=0 ; i<master.length ; i++) { 5760 masterMap[master[i]] = i; 5761 } 5762 5763 // And then cache what would be the indexOf fom the display 5764 for (i=0 ; i<display.length ; i++) { 5765 map[display[i]] = masterMap[display[i]]; 5766 } 5767 5768 display.sort(function(a, b){ 5769 // Short version of this function is simply `master.indexOf(a) - master.indexOf(b);` 5770 return map[a] - map[b]; 5771 }); 5772 } 5773 5774 5775 function _fnSortResolve (settings, nestedSort, sort) { 5776 var push = function ( a ) { 5777 if ($.isPlainObject(a)) { 5778 if (a.idx !== undefined) { 5779 // Index based ordering 5780 nestedSort.push([a.idx, a.dir]); 5781 } 5782 else if (a.name) { 5783 // Name based ordering 5784 var cols = _pluck( settings.aoColumns, 'sName'); 5785 var idx = cols.indexOf(a.name); 5786 5787 if (idx !== -1) { 5788 nestedSort.push([idx, a.dir]); 5789 } 5790 } 5791 } 5792 else { 5793 // Plain column index and direction pair 5794 nestedSort.push(a); 5795 } 5796 }; 5797 5798 if ( $.isPlainObject(sort) ) { 5799 // Object 5800 push(sort); 5801 } 5802 else if ( sort.length && typeof sort[0] === 'number' ) { 5803 // 1D array 5804 push(sort); 5805 } 5806 else if ( sort.length ) { 5807 // 2D array 5808 for (var z=0; z<sort.length; z++) { 5809 push(sort[z]); // Object or array 5810 } 5811 } 5812 } 5813 5814 5815 function _fnSortFlatten ( settings ) 5816 { 5817 var 5818 i, k, kLen, 5819 aSort = [], 5820 extSort = DataTable.ext.type.order, 5821 aoColumns = settings.aoColumns, 5822 aDataSort, iCol, sType, srcCol, 5823 fixed = settings.aaSortingFixed, 5824 fixedObj = $.isPlainObject( fixed ), 5825 nestedSort = []; 5826 5827 if ( ! settings.oFeatures.bSort ) { 5828 return aSort; 5829 } 5830 5831 // Build the sort array, with pre-fix and post-fix options if they have been 5832 // specified 5833 if ( Array.isArray( fixed ) ) { 5834 _fnSortResolve( settings, nestedSort, fixed ); 5835 } 5836 5837 if ( fixedObj && fixed.pre ) { 5838 _fnSortResolve( settings, nestedSort, fixed.pre ); 5839 } 5840 5841 _fnSortResolve( settings, nestedSort, settings.aaSorting ); 5842 5843 if (fixedObj && fixed.post ) { 5844 _fnSortResolve( settings, nestedSort, fixed.post ); 5845 } 5846 5847 for ( i=0 ; i<nestedSort.length ; i++ ) 5848 { 5849 srcCol = nestedSort[i][0]; 5850 5851 if ( aoColumns[ srcCol ] ) { 5852 aDataSort = aoColumns[ srcCol ].aDataSort; 5853 5854 for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ ) 5855 { 5856 iCol = aDataSort[k]; 5857 sType = aoColumns[ iCol ].sType || 'string'; 5858 5859 if ( nestedSort[i]._idx === undefined ) { 5860 nestedSort[i]._idx = aoColumns[iCol].asSorting.indexOf(nestedSort[i][1]); 5861 } 5862 5863 if ( nestedSort[i][1] ) { 5864 aSort.push( { 5865 src: srcCol, 5866 col: iCol, 5867 dir: nestedSort[i][1], 5868 index: nestedSort[i]._idx, 5869 type: sType, 5870 formatter: extSort[ sType+"-pre" ], 5871 sorter: extSort[ sType+"-"+nestedSort[i][1] ] 5872 } ); 5873 } 5874 } 5875 } 5876 } 5877 5878 return aSort; 5879 } 5880 5881 /** 5882 * Change the order of the table 5883 * @param {object} oSettings dataTables settings object 5884 * @memberof DataTable#oApi 5885 */ 5886 function _fnSort ( oSettings, col, dir ) 5887 { 5888 var 5889 i, ien, iLen, 5890 aiOrig = [], 5891 extSort = DataTable.ext.type.order, 5892 aoData = oSettings.aoData, 5893 sortCol, 5894 displayMaster = oSettings.aiDisplayMaster, 5895 aSort; 5896 5897 // Make sure the columns all have types defined 5898 _fnColumnTypes(oSettings); 5899 5900 // Allow a specific column to be sorted, which will _not_ alter the display 5901 // master 5902 if (col !== undefined) { 5903 var srcCol = oSettings.aoColumns[col]; 5904 5905 aSort = [{ 5906 src: col, 5907 col: col, 5908 dir: dir, 5909 index: 0, 5910 type: srcCol.sType, 5911 formatter: extSort[ srcCol.sType+"-pre" ], 5912 sorter: extSort[ srcCol.sType+"-"+dir ] 5913 }]; 5914 displayMaster = displayMaster.slice(); 5915 } 5916 else { 5917 aSort = _fnSortFlatten( oSettings ); 5918 } 5919 5920 for ( i=0, ien=aSort.length ; i<ien ; i++ ) { 5921 sortCol = aSort[i]; 5922 5923 // Load the data needed for the sort, for each cell 5924 _fnSortData( oSettings, sortCol.col ); 5925 } 5926 5927 /* No sorting required if server-side or no sorting array */ 5928 if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 ) 5929 { 5930 // Reset the initial positions on each pass so we get a stable sort 5931 for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) { 5932 aiOrig[ i ] = i; 5933 } 5934 5935 // If the first sort is desc, then reverse the array to preserve original 5936 // order, just in reverse 5937 if (aSort.length && aSort[0].dir === 'desc' && oSettings.orderDescReverse) { 5938 aiOrig.reverse(); 5939 } 5940
5941 /* Do the sort - here we want multi-column sorting based on a given data source (column) 5942 * and sorting function (from oSort) in a certain direction. It's reasonably complex to 5943 * follow on it's own, but this is what we want (example two column sorting): 5944 * fnLocalSorting = function(a,b){ 5945 * var test; 5946 * test = oSort['string-asc']('data11', 'data12'); 5947 * if (test !== 0) 5948 * return test; 5949 * test = oSort['numeric-desc']('data21', 'data22'); 5950 * if (test !== 0) 5951 * return test; 5952 * return oSort['numeric-asc']( aiOrig[a], aiOrig[b] ); 5953 * } 5954 * Basically we have a test for each sorting column, if the data in that column is equal, 5955 * test the next column. If all columns match, then we use a numeric sort on the row 5956 * positions in the original data array to provide a stable sort. 5957 */ 5958 displayMaster.sort( function ( a, b ) { 5959 var 5960 x, y, k, test, sort, 5961 len=aSort.length, 5962 dataA = aoData[a]._aSortData, 5963 dataB = aoData[b]._aSortData; 5964 5965 for ( k=0 ; k<len ; k++ ) { 5966 sort = aSort[k]; 5967 5968 // Data, which may have already been through a `-pre` function 5969 x = dataA[ sort.col ]; 5970 y = dataB[ sort.col ]; 5971 5972 if (sort.sorter) { 5973 // If there is a custom sorter (`-asc` or `-desc`) for this 5974 // data type, use it 5975 test = sort.sorter(x, y); 5976 5977 if ( test !== 0 ) { 5978 return test; 5979 } 5980 } 5981 else { 5982 // Otherwise, use generic sorting 5983 test = x<y ? -1 : x>y ? 1 : 0; 5984 5985 if ( test !== 0 ) { 5986 return sort.dir === 'asc' ? test : -test; 5987 } 5988 } 5989 } 5990 5991 x = aiOrig[a]; 5992 y = aiOrig[b]; 5993 5994 return x<y ? -1 : x>y ? 1 : 0; 5995 } ); 5996 } 5997 else if ( aSort.length === 0 ) { 5998 // Apply index order 5999 displayMaster.sort(function (x, y) { 6000 return x<y ? -1 : x>y ? 1 : 0; 6001 }); 6002 } 6003 6004 if (col === undefined) { 6005 // Tell the draw function that we have sorted the data 6006 oSettings.bSorted = true; 6007 oSettings.sortDetails = aSort; 6008 6009 _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort] ); 6010 } 6011
6012 return displayMaster; 6013 } 6014 6015 6016 /** 6017 * Function to run on user sort request 6018 * @param {object} settings dataTables settings object 6019 * @param {node} attachTo node to attach the handler to 6020 * @param {int} colIdx column sorting index 6021 * @param {int} addIndex Counter 6022 * @param {boolean} [shift=false] Shift click add 6023 * @param {function} [callback] callback function 6024 * @memberof DataTable#oApi 6025 */ 6026 function _fnSortAdd ( settings, colIdx, addIndex, shift ) 6027 { 6028 var col = settings.aoColumns[ colIdx ]; 6029 var sorting = settings.aaSorting; 6030 var asSorting = col.asSorting; 6031 var nextSortIdx; 6032 var next = function ( a, overflow ) { 6033 var idx = a._idx; 6034 if ( idx === undefined ) { 6035 idx = asSorting.indexOf(a[1]); 6036 } 6037 6038 return idx+1 < asSorting.length ? 6039 idx+1 : 6040 overflow ? 6041 null : 6042 0; 6043 }; 6044 6045 if ( ! col.bSortable ) { 6046 return false; 6047 } 6048 6049 // Convert to 2D array if needed 6050 if ( typeof sorting[0] === 'number' ) { 6051 sorting = settings.aaSorting = [ sorting ]; 6052 } 6053 6054 // If appending the sort then we are multi-column sorting 6055 if ( (shift || addIndex) && settings.oFeatures.bSortMulti ) { 6056 // Are we already doing some kind of sort on this column? 6057 var sortIdx = _pluck(sorting, '0').indexOf(colIdx); 6058 6059 if ( sortIdx !== -1 ) { 6060 // Yes, modify the sort 6061 nextSortIdx = next( sorting[sortIdx], true ); 6062 6063 if ( nextSortIdx === null && sorting.length === 1 ) { 6064 nextSortIdx = 0; // can't remove sorting completely 6065 } 6066 6067 if ( nextSortIdx === null ) { 6068 sorting.splice( sortIdx, 1 ); 6069 } 6070 else { 6071 sorting[sortIdx][1] = asSorting[ nextSortIdx ]; 6072 sorting[sortIdx]._idx = nextSortIdx; 6073 } 6074 } 6075 else if (shift) { 6076 // No sort on this column yet, being added by shift click 6077 // add it as itself 6078 sorting.push( [ colIdx, asSorting[0], 0 ] ); 6079 sorting[sorting.length-1]._idx = 0; 6080 } 6081 else { 6082 // No sort on this column yet, being added from a colspan 6083 // so add with same direction as first column 6084 sorting.push( [ colIdx, sorting[0][1], 0 ] ); 6085 sorting[sorting.length-1]._idx = 0; 6086 } 6087 } 6088 else if ( sorting.length && sorting[0][0] == colIdx ) { 6089 // Single column - already sorting on this column, modify the sort 6090 nextSortIdx = next( sorting[0] ); 6091 6092 sorting.length = 1; 6093 sorting[0][1] = asSorting[ nextSortIdx ]; 6094 sorting[0]._idx = nextSortIdx; 6095 } 6096 else { 6097 // Single column - sort only on this column 6098 sorting.length = 0; 6099 sorting.push( [ colIdx, asSorting[0] ] ); 6100 sorting[0]._idx = 0; 6101 } 6102 } 6103 6104 6105 /** 6106 * Set the sorting classes on table's body, Note: it is safe to call this function 6107 * when bSort and bSortClasses are false 6108 * @param {object} oSettings dataTables settings object 6109 * @memberof DataTable#oApi 6110 */ 6111 function _fnSortingClasses( settings ) 6112 { 6113 var oldSort = settings.aLastSort; 6114 var sortClass = settings.oClasses.order.position; 6115 var sort = _fnSortFlatten( settings ); 6116 var features = settings.oFeatures; 6117 var i, ien, colIdx; 6118 6119 if ( features.bSort && features.bSortClasses ) { 6120 // Remove old sorting classes 6121 for ( i=0, ien=oldSort.length ; i<ien ; i++ ) { 6122 colIdx = oldSort[i].src; 6123 6124 // Remove column sorting 6125 $( _pluck( settings.aoData, 'anCells', colIdx ) ) 6126 .removeClass( sortClass + (i<2 ? i+1 : 3) ); 6127 } 6128 6129 // Add new column sorting 6130 for ( i=0, ien=sort.length ; i<ien ; i++ ) { 6131 colIdx = sort[i].src; 6132 6133 $( _pluck( settings.aoData, 'anCells', colIdx ) ) 6134 .addClass( sortClass + (i<2 ? i+1 : 3) ); 6135 } 6136 } 6137 6138 settings.aLastSort = sort; 6139 } 6140 6141 6142 // Get the data to sort a column, be it from cache, fresh (populating the 6143 // cache), or from a sort formatter 6144 function _fnSortData( settings, colIdx ) 6145 { 6146 // Custom sorting function - provided by the sort data type 6147 var column = settings.aoColumns[ colIdx ]; 6148 var customSort = DataTable.ext.order[ column.sSortDataType ]; 6149 var customData; 6150 6151 if ( customSort ) { 6152 customData = customSort.call( settings.oInstance, settings, colIdx, 6153 _fnColumnIndexToVisible( settings, colIdx ) 6154 ); 6155 } 6156 6157 // Use / populate cache 6158 var row, cellData; 6159 var formatter = DataTable.ext.type.order[ column.sType+"-pre" ]; 6160 var data = settings.aoData; 6161 6162 for ( var rowIdx=0 ; rowIdx<data.length ; rowIdx++ ) { 6163 // Sparse array 6164 if (! data[rowIdx]) { 6165 continue; 6166 } 6167 6168 row = data[rowIdx]; 6169 6170 if ( ! row._aSortData ) { 6171 row._aSortData = []; 6172 } 6173 6174 if ( ! row._aSortData[colIdx] || customSort ) { 6175 cellData = customSort ? 6176 customData[rowIdx] : // If there was a custom sort function, use data from there 6177 _fnGetCellData( settings, rowIdx, colIdx, 'sort' ); 6178 6179 row._aSortData[ colIdx ] = formatter ? 6180 formatter( cellData, settings ) : 6181 cellData; 6182 } 6183 } 6184 } 6185 6186 6187 /** 6188 * State information for a table 6189 * 6190 * @param {*} settings 6191 * @returns State object 6192 */ 6193 function _fnSaveState ( settings ) 6194 { 6195 if (settings._bLoadingState) { 6196 return; 6197 } 6198 6199 // Sort state saving uses [[idx, order]] structure. 6200 var sorting = []; 6201 _fnSortResolve(settings, sorting, settings.aaSorting ); 6202 6203 /* Store the interesting variables */ 6204 var columns = settings.aoColumns; 6205 var state = { 6206 time: +new Date(), 6207 start: settings._iDisplayStart, 6208 length: settings._iDisplayLength, 6209 order: sorting.map(function (sort) { 6210 // If a column name is available, use it 6211 return columns[sort[0]] && columns[sort[0]].sName 6212 ? [ columns[sort[0]].sName, sort[1] ] 6213 : sort.slice(); 6214 } ), 6215 search: $.extend({}, settings.oPreviousSearch), 6216 columns: settings.aoColumns.map( function ( col, i ) { 6217 return { 6218 name: col.sName, 6219 visible: col.bVisible, 6220 search: $.extend({}, settings.aoPreSearchCols[i]) 6221 }; 6222 } ) 6223 }; 6224 6225 settings.oSavedState = state; 6226 _fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] ); 6227
6228 if ( settings.oFeatures.bStateSave && !settings.bDestroying ) 6229 { 6230 settings.fnStateSaveCallback.call( settings.oInstance, settings, state ); 6231 } 6232 } 6233 6234 6235 /** 6236 * Attempt to load a saved table state 6237 * @param {object} oSettings dataTables settings object 6238 * @param {object} oInit DataTables init object so we can override settings 6239 * @param {function} callback Callback to execute when the state has been loaded 6240 * @memberof DataTable#oApi 6241 */ 6242 function _fnLoadState ( settings, init, callback ) 6243 { 6244 if ( ! settings.oFeatures.bStateSave ) { 6245 callback(); 6246 return; 6247 } 6248 6249 var loaded = function(state) { 6250 _fnImplementState(settings, state, callback); 6251 } 6252 6253 var state = settings.fnStateLoadCallback.call( settings.oInstance, settings, loaded ); 6254 6255 if ( state !== undefined ) { 6256 _fnImplementState( settings, state, callback ); 6257 } 6258 // otherwise, wait for the loaded callback to be executed 6259 6260 return true; 6261 } 6262 6263 function _fnImplementState ( settings, s, callback) { 6264 var i, ien; 6265 var columns = settings.aoColumns; 6266 var currentNames = _pluck(settings.aoColumns, 'sName'); 6267 6268 settings._bLoadingState = true; 6269 6270 // When StateRestore was introduced the state could now be implemented at any time 6271 // Not just initialisation. To do this an api instance is required in some places 6272 var api = settings._bInitComplete ? new DataTable.Api(settings) : null; 6273 6274 if ( ! s || ! s.time ) { 6275 settings._bLoadingState = false; 6276 callback(); 6277 return; 6278 } 6279 6280 // Reject old data 6281 var duration = settings.iStateDuration; 6282 if ( duration > 0 && s.time < +new Date() - (duration*1000) ) { 6283 settings._bLoadingState = false; 6284 callback(); 6285 return; 6286 } 6287 6288 // Allow custom and plug-in manipulation functions to alter the saved data set and 6289 // cancelling of loading by returning false 6290 var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, s] ); 6291 if ( abStateLoad.indexOf(false) !== -1 ) { 6292 settings._bLoadingState = false; 6293 callback(); 6294 return; 6295 } 6296 6297 // Store the saved state so it might be accessed at any time 6298 settings.oLoadedState = $.extend( true, {}, s ); 6299 6300 // This is needed for ColReorder, which has to happen first to allow all 6301 // the stored indexes to be usable. It is not publicly documented. 6302 _fnCallbackFire( settings, null, 'stateLoadInit', [settings, s], true ); 6303 6304 // Page Length 6305 if ( s.length !== undefined ) { 6306 // If already initialised just set the value directly so that the select element is also updated 6307 if (api) { 6308 api.page.len(s.length) 6309 } 6310 else { 6311 settings._iDisplayLength = s.length; 6312 } 6313 } 6314 6315 // Restore key features 6316 if ( s.start !== undefined ) { 6317 if(api === null) { 6318 settings._iDisplayStart = s.start; 6319 settings.iInitDisplayStart = s.start; 6320 } 6321 else { 6322 _fnPageChange(settings, s.start/settings._iDisplayLength); 6323 } 6324 } 6325 6326 // Order 6327 if ( s.order !== undefined ) { 6328 settings.aaSorting = []; 6329 $.each( s.order, function ( i, col ) { 6330 var set = [ col[0], col[1] ]; 6331 6332 // A column name was stored and should be used for restore 6333 if (typeof col[0] === 'string') { 6334 var idx = currentNames.indexOf(col[0]); 6335 6336 // Find the name from the current list of column names, or fallback to index 0 6337 set[0] = idx >= 0 6338 ? idx 6339 : 0; 6340 } 6341 else if (set[0] >= columns.length) { 6342 // If a column name, but it is out of bounds, set to 0 6343 set[0] = 0; 6344 } 6345 6346 settings.aaSorting.push(set); 6347 } ); 6348 } 6349 6350 // Search 6351 if ( s.search !== undefined ) { 6352 $.extend( settings.oPreviousSearch, s.search ); 6353 } 6354 6355 // Columns 6356 if ( s.columns ) { 6357 var set = s.columns; 6358 var incoming = _pluck(s.columns, 'name'); 6359 6360 // Check if it is a 2.2 style state object with a `name` property for the columns, and if 6361 // the name was defined. If so, then create a new array that will map the state object 6362 // given, to the current columns (don't bother if they are already matching tho). 6363 if (incoming.join('').length && incoming.join('') !== currentNames.join('')) { 6364 set = []; 6365 6366 // For each column, try to find the name in the incoming array 6367 for (i=0 ; i<currentNames.length ; i++) { 6368 if (currentNames[i] != '') { 6369 var idx = incoming.indexOf(currentNames[i]); 6370 6371 if (idx >= 0) { 6372 set.push(s.columns[idx]); 6373 } 6374 else { 6375 // No matching column name in the state's columns, so this might be a new 6376 // column and thus can't have a state already. 6377 set.push({}); 6378 } 6379 } 6380 else { 6381 // If no name, but other columns did have a name, then there is no knowing
6382 // where this one came from originally so it can't be restored. 6383 set.push({}); 6384 } 6385 } 6386 } 6387 6388 // If the number of columns to restore is different from current, then all bets are off. 6389 if (set.length === columns.length) { 6390 for ( i=0, ien=set.length ; i<ien ; i++ ) { 6391 var col = set[i]; 6392 6393 // Visibility 6394 if ( col.visible !== undefined ) { 6395 // If the api is defined, the table has been initialised so we need to use it rather than internal settings 6396 if (api) { 6397 // Don't redraw the columns on every iteration of this loop, we will do this at the end instead 6398 api.column(i).visible(col.visible, false); 6399 } 6400 else { 6401 columns[i].bVisible = col.visible; 6402 } 6403 } 6404 6405 // Search 6406 if ( col.search !== undefined ) { 6407 $.extend( settings.aoPreSearchCols[i], col.search ); 6408 } 6409 } 6410 6411 // If the api is defined then we need to adjust the columns once the visibility has been changed 6412 if (api) { 6413 api.columns.adjust(); 6414 } 6415 } 6416 } 6417 6418 settings._bLoadingState = false; 6419 _fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, s] ); 6420 callback(); 6421 } 6422 6423 /** 6424 * Log an error message 6425 * @param {object} settings dataTables settings object 6426 * @param {int} level log error messages, or display them to the user 6427 * @param {string} msg error message 6428 * @param {int} tn Technical note id to get more information about the error. 6429 * @memberof DataTable#oApi 6430 */ 6431 function _fnLog( settings, level, msg, tn ) 6432 { 6433 msg = 'DataTables warning: '+ 6434 (settings ? 'table id='+settings.sTableId+' - ' : '')+msg; 6435 6436 if ( tn ) { 6437 msg += '. For more information about this error, please see '+ 6438 'https://datatables.net/tn/'+tn; 6439 } 6440 6441 if ( ! level ) { 6442 // Backwards compatibility pre 1.10 6443 var ext = DataTable.ext; 6444 var type = ext.sErrMode || ext.errMode; 6445 6446 if ( settings ) { 6447 _fnCallbackFire( settings, null, 'dt-error', [ settings, tn, msg ], true ); 6448 } 6449 6450 if ( type == 'alert' ) { 6451 alert( msg ); 6452 } 6453 else if ( type == 'throw' ) { 6454 throw new Error(msg); 6455 } 6456 else if ( typeof type == 'function' ) { 6457 type( settings, tn, msg ); 6458 } 6459 } 6460 else if ( window.console && console.log ) { 6461 console.log( msg ); 6462 } 6463 } 6464 6465 6466 /** 6467 * See if a property is defined on one object, if so assign it to the other object 6468 * @param {object} ret target object 6469 * @param {object} src source object 6470 * @param {string} name property 6471 * @param {string} [mappedName] name to map too - optional, name used if not given 6472 * @memberof DataTable#oApi 6473 */ 6474 function _fnMap( ret, src, name, mappedName ) 6475 { 6476 if ( Array.isArray( name ) ) { 6477 $.each( name, function (i, val) { 6478 if ( Array.isArray( val ) ) { 6479 _fnMap( ret, src, val[0], val[1] ); 6480 } 6481 else { 6482 _fnMap( ret, src, val ); 6483 } 6484 } ); 6485 6486 return; 6487 } 6488 6489 if ( mappedName === undefined ) { 6490 mappedName = name; 6491 } 6492 6493 if ( src[name] !== undefined ) { 6494 ret[mappedName] = src[name]; 6495 } 6496 } 6497 6498 6499 /** 6500 * Extend objects - very similar to jQuery.extend, but deep copy objects, and 6501 * shallow copy arrays. The reason we need to do this, is that we don't want to 6502 * deep copy array init values (such as aaSorting) since the dev wouldn't be 6503 * able to override them, but we do want to deep copy arrays. 6504 * @param {object} out Object to extend 6505 * @param {object} extender Object from which the properties will be applied to 6506 * out 6507 * @param {boolean} breakRefs If true, then arrays will be sliced to take an 6508 * independent copy with the exception of the `data` or `aaData` parameters 6509 * if they are present. This is so you can pass in a collection to 6510 * DataTables and have that used as your data source without breaking the 6511 * references 6512 * @returns {object} out Reference, just for convenience - out === the return. 6513 * @memberof DataTable#oApi 6514 * @todo This doesn't take account of arrays inside the deep copied objects. 6515 */ 6516 function _fnExtend( out, extender, breakRefs ) 6517 { 6518 var val; 6519 6520 for ( var prop in extender ) { 6521 if ( Object.prototype.hasOwnProperty.call(extender, prop) ) { 6522 val = extender[prop]; 6523 6524 if ( $.isPlainObject( val ) ) { 6525 if ( ! $.isPlainObject( out[prop] ) ) { 6526 out[prop] = {}; 6527 } 6528 $.extend( true, out[prop], val ); 6529 } 6530 else if ( breakRefs && prop !== 'data' && prop !== 'aaData' && Array.isArray(val) ) { 6531 out[prop] = val.slice(); 6532 } 6533 else { 6534 out[prop] = val; 6535 } 6536 } 6537 } 6538 6539 return out; 6540 } 6541 6542 6543 /** 6544 * Bind an event handers to allow a click or return key to activate the callback. 6545 * This is good for accessibility since a return on the keyboard will have the 6546 * same effect as a click, if the element has focus. 6547 * @param {element} n Element to bind the action to 6548 * @param {object|string} selector Selector (for delegated events) or data object 6549 * to pass to the triggered function 6550 * @param {function} fn Callback function for when the event is triggered 6551 * @memberof DataTable#oApi 6552 */ 6553 function _fnBindAction( n, selector, fn ) 6554 { 6555 $(n) 6556 .on( 'click.DT', selector, function (e) { 6557 fn(e); 6558 } ) 6559 .on( 'keypress.DT', selector, function (e){ 6560 if ( e.which === 13 ) { 6561 e.preventDefault(); 6562 fn(e); 6563 } 6564 } ) 6565 .on( 'selectstart.DT', selector, function () { 6566 // Don't want a double click resulting in text selection 6567 return false;
6568 } ); 6569 } 6570 6571 6572 /** 6573 * Register a callback function. Easily allows a callback function to be added to 6574 * an array store of callback functions that can then all be called together. 6575 * @param {object} settings dataTables settings object 6576 * @param {string} store Name of the array storage for the callbacks in oSettings 6577 * @param {function} fn Function to be called back 6578 * @memberof DataTable#oApi 6579 */ 6580 function _fnCallbackReg( settings, store, fn ) 6581 { 6582 if ( fn ) { 6583 settings[store].push(fn); 6584 } 6585 } 6586 6587 6588 /** 6589 * Fire callback functions and trigger events. Note that the loop over the 6590 * callback array store is done backwards! Further note that you do not want to 6591 * fire off triggers in time sensitive applications (for example cell creation) 6592 * as its slow. 6593 * @param {object} settings dataTables settings object 6594 * @param {string} callbackArr Name of the array storage for the callbacks in 6595 * oSettings 6596 * @param {string} eventName Name of the jQuery custom event to trigger. If 6597 * null no trigger is fired 6598 * @param {array} args Array of arguments to pass to the callback function / 6599 * trigger 6600 * @param {boolean} [bubbles] True if the event should bubble 6601 * @memberof DataTable#oApi 6602 */ 6603 function _fnCallbackFire( settings, callbackArr, eventName, args, bubbles ) 6604 { 6605 var ret = []; 6606 6607 if ( callbackArr ) { 6608 ret = settings[callbackArr].slice().reverse().map( function (val) { 6609 return val.apply( settings.oInstance, args ); 6610 } ); 6611 } 6612 6613 if ( eventName !== null) { 6614 var e = $.Event( eventName+'.dt' ); 6615 var table = $(settings.nTable); 6616 6617 // Expose the DataTables API on the event object for easy access 6618 e.dt = settings.api; 6619 6620 table[bubbles ? 'trigger' : 'triggerHandler']( e, args ); 6621 6622 // If not yet attached to the document, trigger the event 6623 // on the body directly to sort of simulate the bubble 6624 if (bubbles && table.parents('body').length === 0) { 6625 $('body').trigger( e, args ); 6626 } 6627 6628 ret.push( e.result ); 6629 } 6630 6631 return ret; 6632 } 6633 6634 6635 function _fnLengthOverflow ( settings ) 6636 { 6637 var 6638 start = settings._iDisplayStart, 6639 end = settings.fnDisplayEnd(), 6640 len = settings._iDisplayLength; 6641 6642 /* If we have space to show extra rows (backing up from the end point - then do so */ 6643 if ( start >= end ) 6644 { 6645 start = end - len; 6646 } 6647 6648 // Keep the start record on the current page 6649 start -= (start % len); 6650 6651 if ( len === -1 || start < 0 ) 6652 { 6653 start = 0; 6654 } 6655 6656 settings._iDisplayStart = start; 6657 } 6658 6659 6660 function _fnRenderer( settings, type ) 6661 { 6662 var renderer = settings.renderer; 6663 var host = DataTable.ext.renderer[type]; 6664 6665 if ( $.isPlainObject( renderer ) && renderer[type] ) { 6666 // Specific renderer for this type. If available use it, otherwise use 6667 // the default. 6668 return host[renderer[type]] || host._; 6669 } 6670 else if ( typeof renderer === 'string' ) { 6671 // Common renderer - if there is one available for this type use it, 6672 // otherwise use the default 6673 return host[renderer] || host._; 6674 } 6675 6676 // Use the default 6677 return host._; 6678 } 6679 6680 6681 /** 6682 * Detect the data source being used for the table. Used to simplify the code 6683 * a little (ajax) and to make it compress a little smaller. 6684 * 6685 * @param {object} settings dataTables settings object 6686 * @returns {string} Data source 6687 * @memberof DataTable#oApi 6688 */ 6689 function _fnDataSource ( settings ) 6690 { 6691 if ( settings.oFeatures.bServerSide ) { 6692 return 'ssp'; 6693 } 6694 else if ( settings.ajax ) { 6695 return 'ajax'; 6696 } 6697 return 'dom'; 6698 } 6699 6700 /** 6701 * Common replacement for language strings 6702 * 6703 * @param {*} settings DT settings object 6704 * @param {*} str String with values to replace 6705 * @param {*} entries Plural number for _ENTRIES_ - can be undefined 6706 * @returns String 6707 */ 6708 function _fnMacros ( settings, str, entries ) 6709 { 6710 // When infinite scrolling, we are always starting at 1. _iDisplayStart is 6711 // used only internally 6712 var 6713 formatter = settings.fnFormatNumber, 6714 start = settings._iDisplayStart+1, 6715 len = settings._iDisplayLength, 6716 vis = settings.fnRecordsDisplay(), 6717 max = settings.fnRecordsTotal(), 6718 all = len === -1; 6719 6720 return str. 6721 replace(/_START_/g, formatter.call( settings, start ) ). 6722 replace(/_END_/g, formatter.call( settings, settings.fnDisplayEnd() ) ). 6723 replace(/_MAX_/g, formatter.call( settings, max ) ). 6724 replace(/_TOTAL_/g, formatter.call( settings, vis ) ). 6725 replace(/_PAGE_/g, formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ). 6726 replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) ). 6727 replace(/_ENTRIES_/g, settings.api.i18n('entries', '', entries) ). 6728 replace(/_ENTRIES-MAX_/g, settings.api.i18n('entries', '', max) ). 6729 replace(/_ENTRIES-TOTAL_/g, settings.api.i18n('entries', '', vis) ); 6730 } 6731 6732 /** 6733 * Add elements to an array as quickly as possible, but stack stafe. 6734 * 6735 * @param {*} arr Array to add the data to 6736 * @param {*} data Data array that is to be added 6737 * @returns 6738 */ 6739 function _fnArrayApply(arr, data) { 6740 if (! data) { 6741 return; 6742 } 6743 6744 // Chrome can throw a max stack error if apply is called with 6745 // too large an array, but apply is faster. 6746 if (data.length < 10000) { 6747 arr.push.apply(arr, data); 6748 } 6749 else { 6750 for (i=0 ; i<data.length ; i++) { 6751 arr.push(data[i]); 6752 } 6753 } 6754 } 6755 6756 6757 6758 /** 6759 * Computed structure of the DataTables API, defined by the options passed to 6760 * `DataTable.Api.register()` when building the API. 6761 * 6762 * The structure is built in order to speed creation and extension of the Api 6763 * objects since the extensions are effectively pre-parsed. 6764 * 6765 * The array is an array of objects with the following structure, where this 6766 * base array represents the Api prototype base: 6767 * 6768 * [ 6769 * { 6770 * name: 'data' -- string - Property name 6771 * val: function () {}, -- function - Api method (or undefined if just an object 6772 * methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result 6773 * propExt: [ ... ] -- array - Array of Api object definitions to extend the property 6774 * }, 6775 * { 6776 * name: 'row' 6777 * val: {}, 6778 * methodExt: [ ... ], 6779 * propExt: [ 6780 * { 6781 * name: 'data' 6782 * val: function () {}, 6783 * methodExt: [ ... ], 6784 * propExt: [ ... ] 6785 * }, 6786 * ... 6787 * ] 6788 * } 6789 * ] 6790 * 6791 * @type {Array} 6792 * @ignore 6793 */ 6794 var __apiStruct = []; 6795 6796 6797 /** 6798 * `Array.prototype` reference. 6799 * 6800 * @type object 6801 * @ignore 6802 */ 6803 var __arrayProto = Array.prototype; 6804 6805 6806 /** 6807 * Abstraction for `context` parameter of the `Api` constructor to allow it to 6808 * take several different forms for ease of use. 6809 * 6810 * Each of the input parameter types will be converted to a DataTables settings 6811 * object where possible. 6812 * 6813 * @param {string|node|jQuery|object} mixed DataTable identifier. Can be one 6814 * of: 6815 * 6816 * * `string` - jQuery selector. Any DataTables' matching the given selector 6817 * with be found and used. 6818 * * `node` - `TABLE` node which has already been formed into a DataTable. 6819 * * `jQuery` - A jQuery object of `TABLE` nodes. 6820 * * `object` - DataTables settings object 6821 * * `DataTables.Api` - API instance 6822 * @return {array|null} Matching DataTables settings objects. `null` or 6823 * `undefined` is returned if no matching DataTable is found. 6824 * @ignore 6825 */ 6826 var _toSettings = function ( mixed ) 6827 { 6828 var idx, jq; 6829 var settings = DataTable.settings; 6830 var tables = _pluck(settings, 'nTable'); 6831 6832 if ( ! mixed ) { 6833 return []; 6834 } 6835 else if ( mixed.nTable && mixed.oFeatures ) { 6836 // DataTables settings object 6837 return [ mixed ]; 6838 } 6839 else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) { 6840 // Table node 6841 idx = tables.indexOf(mixed); 6842 return idx !== -1 ? [ settings[idx] ] : null; 6843 } 6844 else if ( mixed && typeof mixed.settings === 'function' ) { 6845 return mixed.settings().toArray(); 6846 } 6847 else if ( typeof mixed === 'string' ) { 6848 // jQuery selector 6849 jq = $(mixed).get(); 6850 } 6851 else if ( mixed instanceof $ ) { 6852 // jQuery object (also DataTables instance) 6853 jq = mixed.get(); 6854 } 6855 6856 if ( jq ) { 6857 return settings.filter(function (v, idx) { 6858 return jq.includes(tables[idx]); 6859 }); 6860 } 6861 }; 6862 6863 6864 /** 6865 * DataTables API class - used to control and interface with one or more 6866 * DataTables enhanced tables. 6867 * 6868 * The API class is heavily based on jQuery, presenting a chainable interface 6869 * that you can use to interact with tables. Each instance of the API class has 6870 * a "context" - i.e. the tables that it will operate on. This could be a single 6871 * table, all tables on a page or a sub-set thereof. 6872 * 6873 * Additionally the API is designed to allow you to easily work with the data in 6874 * the tables, retrieving and manipulating it as required. This is done by 6875 * presenting the API class as an array like interface. The contents of the 6876 * array depend upon the actions requested by each method (for example 6877 * `rows().nodes()` will return an array of nodes, while `rows().data()` will
6878 * return an array of objects or arrays depending upon your table's 6879 * configuration). The API object has a number of array like methods (`push`, 6880 * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`, 6881 * `unique` etc) to assist your working with the data held in a table. 6882 * 6883 * Most methods (those which return an Api instance) are chainable, which means 6884 * the return from a method call also has all of the methods available that the 6885 * top level object had. For example, these two calls are equivalent: 6886 * 6887 * // Not chained 6888 * api.row.add( {...} ); 6889 * api.draw(); 6890 * 6891 * // Chained 6892 * api.row.add( {...} ).draw(); 6893 * 6894 * @class DataTable.Api 6895 * @param {array|object|string|jQuery} context DataTable identifier. This is 6896 * used to define which DataTables enhanced tables this API will operate on. 6897 * Can be one of: 6898 * 6899 * * `string` - jQuery selector. Any DataTables' matching the given selector 6900 * with be found and used. 6901 * * `node` - `TABLE` node which has already been formed into a DataTable. 6902 * * `jQuery` - A jQuery object of `TABLE` nodes. 6903 * * `object` - DataTables settings object 6904 * @param {array} [data] Data to initialise the Api instance with. 6905 * 6906 * @example 6907 * // Direct initialisation during DataTables construction 6908 * var api = $('#example').DataTable(); 6909 * 6910 * @example 6911 * // Initialisation using a DataTables jQuery object 6912 * var api = $('#example').dataTable().api(); 6913 * 6914 * @example 6915 * // Initialisation as a constructor 6916 * var api = new DataTable.Api( 'table.dataTable' ); 6917 */ 6918 _Api = function ( context, data ) 6919 { 6920 if ( ! (this instanceof _Api) ) { 6921 return new _Api( context, data ); 6922 } 6923 6924 var i; 6925 var settings = []; 6926 var ctxSettings = function ( o ) { 6927 var a = _toSettings( o ); 6928 if ( a ) { 6929 settings.push.apply( settings, a ); 6930 } 6931 }; 6932 6933 if ( Array.isArray( context ) ) { 6934 for ( i=0 ; i<context.length ; i++ ) { 6935 ctxSettings( context[i] ); 6936 } 6937 } 6938 else { 6939 ctxSettings( context ); 6940 } 6941 6942 // Remove duplicates 6943 this.context = settings.length > 1 6944 ? _unique( settings ) 6945 : settings; 6946 6947 // Initial data 6948 _fnArrayApply(this, data); 6949 6950 // selector 6951 this.selector = { 6952 rows: null, 6953 cols: null, 6954 opts: null 6955 }; 6956 6957 _Api.extend( this, this, __apiStruct ); 6958 }; 6959 6960 DataTable.Api = _Api; 6961 6962 // Don't destroy the existing prototype, just extend it. Required for jQuery 2's 6963 // isPlainObject. 6964 $.extend( _Api.prototype, { 6965 any: function () 6966 { 6967 return this.count() !== 0; 6968 }, 6969 6970 context: [], // array of table settings objects 6971 6972 count: function () 6973 { 6974 return this.flatten().length; 6975 }, 6976 6977 each: function ( fn ) 6978 { 6979 for ( var i=0, ien=this.length ; i<ien; i++ ) { 6980 fn.call( this, this[i], i, this ); 6981 } 6982 6983 return this; 6984 }, 6985 6986 eq: function ( idx ) 6987 { 6988 var ctx = this.context; 6989 6990 return ctx.length > idx ? 6991 new _Api( ctx[idx], this[idx] ) : 6992 null; 6993 }, 6994 6995 filter: function ( fn ) 6996 { 6997 var a = __arrayProto.filter.call( this, fn, this ); 6998 6999 return new _Api( this.context, a ); 7000 }, 7001 7002 flatten: function () 7003 { 7004 var a = []; 7005 7006 return new _Api( this.context, a.concat.apply( a, this.toArray() ) ); 7007 }, 7008 7009 get: function ( idx ) 7010 { 7011 return this[ idx ]; 7012 }, 7013 7014 join: __arrayProto.join, 7015 7016 includes: function ( find ) { 7017 return this.indexOf( find ) === -1 ? false : true; 7018 }, 7019 7020 indexOf: __arrayProto.indexOf, 7021 7022 iterator: function ( flatten, type, fn, alwaysNew ) { 7023 var 7024 a = [], ret, 7025 i, ien, j, jen, 7026 context = this.context, 7027 rows, items, item, 7028 selector = this.selector; 7029 7030 // Argument shifting 7031 if ( typeof flatten === 'string' ) { 7032 alwaysNew = fn; 7033 fn = type; 7034 type = flatten; 7035 flatten = false; 7036 } 7037 7038 for ( i=0, ien=context.length ; i<ien ; i++ ) { 7039 var apiInst = new _Api( context[i] ); 7040 7041 if ( type === 'table' ) { 7042 ret = fn.call( apiInst, context[i], i ); 7043 7044 if ( ret !== undefined ) { 7045 a.push( ret ); 7046 } 7047 } 7048 else if ( type === 'columns' || type === 'rows' ) { 7049 // this has same length as context - one entry for each table 7050 ret = fn.call( apiInst, context[i], this[i], i ); 7051 7052 if ( ret !== undefined ) { 7053 a.push( ret ); 7054 } 7055 } 7056 else if ( type === 'every' || type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) { 7057 // columns and rows share the same structure. 7058 // 'this' is an array of column indexes for each context 7059 items = this[i]; 7060 7061 if ( type === 'column-rows' ) { 7062 rows = _selector_row_indexes( context[i], selector.opts ); 7063 } 7064 7065 for ( j=0, jen=items.length ; j<jen ; j++ ) { 7066 item = items[j]; 7067 7068 if ( type === 'cell' ) { 7069 ret = fn.call( apiInst, context[i], item.row, item.column, i, j ); 7070 } 7071 else { 7072 ret = fn.call( apiInst, context[i], item, i, j, rows ); 7073 } 7074 7075 if ( ret !== undefined ) { 7076 a.push( ret ); 7077 } 7078 } 7079 } 7080 } 7081 7082 if ( a.length || alwaysNew ) { 7083 var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a ); 7084 var apiSelector = api.selector; 7085 apiSelector.rows = selector.rows; 7086 apiSelector.cols = selector.cols; 7087 apiSelector.opts = selector.opts; 7088 return api; 7089 } 7090 return this; 7091 }, 7092 7093 lastIndexOf: __arrayProto.lastIndexOf, 7094 7095 length: 0, 7096 7097 map: function ( fn ) 7098 { 7099 var a = __arrayProto.map.call( this, fn, this ); 7100 7101 return new _Api( this.context, a ); 7102 }, 7103 7104 pluck: function ( prop ) 7105 { 7106 var fn = DataTable.util.get(prop); 7107 7108 return this.map( function ( el ) { 7109 return fn(el); 7110 } ); 7111 }, 7112 7113 pop: __arrayProto.pop, 7114 7115 push: __arrayProto.push, 7116 7117 reduce: __arrayProto.reduce, 7118 7119 reduceRight: __arrayProto.reduceRight, 7120 7121 reverse: __arrayProto.reverse, 7122 7123 // Object with rows, columns and opts 7124 selector: null, 7125 7126 shift: __arrayProto.shift, 7127 7128 slice: function () { 7129 return new _Api( this.context, this ); 7130 }, 7131 7132 sort: __arrayProto.sort, 7133 7134 splice: __arrayProto.splice, 7135 7136 toArray: function () 7137 { 7138 return __arrayProto.slice.call( this ); 7139 }, 7140 7141 to$: function () 7142 { 7143 return $( this ); 7144 }, 7145 7146 toJQuery: function () 7147 { 7148 return $( this ); 7149 }, 7150 7151 unique: function () 7152 { 7153 return new _Api( this.context, _unique(this.toArray()) ); 7154 }, 7155 7156 unshift: __arrayProto.unshift 7157 } ); 7158 7159 7160 function _api_scope( scope, fn, struc ) { 7161 return function () { 7162 var ret = fn.apply( scope || this, arguments ); 7163 7164 // Method extension 7165 _Api.extend( ret, ret, struc.methodExt ); 7166 return ret; 7167 }; 7168 } 7169 7170 function _api_find( src, name ) { 7171 for ( var i=0, ien=src.length ; i<ien ; i++ ) { 7172 if ( src[i].name === name ) { 7173 return src[i]; 7174 } 7175 } 7176 return null; 7177 } 7178 7179 window.__apiStruct = __apiStruct; 7180 7181 _Api.extend = function ( scope, obj, ext ) 7182 { 7183 // Only extend API instances and static properties of the API 7184 if ( ! ext.length || ! obj || ( ! (obj instanceof _Api) && ! obj.__dt_wrapper ) ) { 7185 return; 7186 } 7187 7188 var 7189 i, ien, 7190 struct; 7191 7192 for ( i=0, ien=ext.length ; i<ien ; i++ ) { 7193 struct = ext[i]; 7194 7195 if (struct.name === '__proto__') { 7196 continue; 7197 } 7198 7199 // Value 7200 obj[ struct.name ] = struct.type === 'function' ? 7201 _api_scope( scope, struct.val, struct ) : 7202 struct.type === 'object' ? 7203 {} : 7204 struct.val; 7205 7206 obj[ struct.name ].__dt_wrapper = true; 7207 7208 // Property extension 7209 _Api.extend( scope, obj[ struct.name ], struct.propExt ); 7210 } 7211 }; 7212 7213 // [ 7214 // { 7215 // name: 'data' -- string - Property name 7216 // val: function () {}, -- function - Api method (or undefined if just an object 7217 // methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result 7218 // propExt: [ ... ] -- array - Array of Api object definitions to extend the property 7219 // }, 7220 // { 7221 // name: 'row' 7222 // val: {}, 7223 // methodExt: [ ... ], 7224 // propExt: [ 7225 // { 7226 // name: 'data' 7227 // val: function () {}, 7228 // methodExt: [ ... ], 7229 // propExt: [ ... ] 7230 // }, 7231 // ... 7232 // ] 7233 // } 7234 // ] 7235 7236 7237 _Api.register = _api_register = function ( name, val ) 7238 { 7239 if ( Array.isArray( name ) ) { 7240 for ( var j=0, jen=name.length ; j<jen ; j++ ) { 7241 _Api.register( name[j], val ); 7242 } 7243 return; 7244 } 7245 7246 var 7247 i, ien, 7248 heir = name.split('.'), 7249 struct = __apiStruct, 7250 key, method; 7251 7252 for ( i=0, ien=heir.length ; i<ien ; i++ ) { 7253 method = heir[i].indexOf('()') !== -1; 7254 key = method ?
7255 heir[i].replace('()', '') : 7256 heir[i]; 7257 7258 var src = _api_find( struct, key ); 7259 if ( ! src ) { 7260 src = { 7261 name: key, 7262 val: {}, 7263 methodExt: [], 7264 propExt: [], 7265 type: 'object' 7266 }; 7267 struct.push( src ); 7268 } 7269 7270 if ( i === ien-1 ) { 7271 src.val = val; 7272 src.type = typeof val === 'function' ? 7273 'function' : 7274 $.isPlainObject( val ) ? 7275 'object' : 7276 'other'; 7277 } 7278 else { 7279 struct = method ? 7280 src.methodExt : 7281 src.propExt; 7282 } 7283 } 7284 }; 7285 7286 _Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) { 7287 _Api.register( pluralName, val ); 7288 7289 _Api.register( singularName, function () { 7290 var ret = val.apply( this, arguments ); 7291 7292 if ( ret === this ) { 7293 // Returned item is the API instance that was passed in, return it 7294 return this; 7295 } 7296 else if ( ret instanceof _Api ) { 7297 // New API instance returned, want the value from the first item 7298 // in the returned array for the singular result. 7299 return ret.length ? 7300 Array.isArray( ret[0] ) ? 7301 new _Api( ret.context, ret[0] ) : // Array results are 'enhanced' 7302 ret[0] : 7303 undefined; 7304 } 7305 7306 // Non-API return - just fire it back 7307 return ret; 7308 } ); 7309 }; 7310 7311 7312 /** 7313 * Selector for HTML tables. Apply the given selector to the give array of 7314 * DataTables settings objects. 7315 * 7316 * @param {string|integer} [selector] jQuery selector string or integer 7317 * @param {array} Array of DataTables settings objects to be filtered 7318 * @return {array} 7319 * @ignore 7320 */ 7321 var __table_selector = function ( selector, a ) 7322 { 7323 if ( Array.isArray(selector) ) { 7324 var result = []; 7325 7326 selector.forEach(function (sel) { 7327 var inner = __table_selector(sel, a); 7328 7329 _fnArrayApply(result, inner); 7330 }); 7331 7332 return result.filter( function (item) { 7333 return item; 7334 }); 7335 } 7336 7337 // Integer is used to pick out a table by index 7338 if ( typeof selector === 'number' ) { 7339 return [ a[ selector ] ]; 7340 } 7341 7342 // Perform a jQuery selector on the table nodes 7343 var nodes = a.map( function (el) { 7344 return el.nTable; 7345 } ); 7346 7347 return $(nodes) 7348 .filter( selector ) 7349 .map( function () { 7350 // Need to translate back from the table node to the settings 7351 var idx = nodes.indexOf(this); 7352 return a[ idx ]; 7353 } ) 7354 .toArray(); 7355 }; 7356 7357 7358 7359 /** 7360 * Context selector for the API's context (i.e. the tables the API instance 7361 * refers to. 7362 * 7363 * @name DataTable.Api#tables 7364 * @param {string|integer} [selector] Selector to pick which tables the iterator 7365 * should operate on. If not given, all tables in the current context are 7366 * used. This can be given as a jQuery selector (for example `':gt(0)'`) to 7367 * select multiple tables or as an integer to select a single table. 7368 * @returns {DataTable.Api} Returns a new API instance if a selector is given. 7369 */ 7370 _api_register( 'tables()', function ( selector ) { 7371 // A new instance is created if there was a selector specified 7372 return selector !== undefined && selector !== null ? 7373 new _Api( __table_selector( selector, this.context ) ) : 7374 this; 7375 } ); 7376 7377 7378 _api_register( 'table()', function ( selector ) { 7379 var tables = this.tables( selector ); 7380 var ctx = tables.context; 7381 7382 // Truncate to the first matched table 7383 return ctx.length ? 7384 new _Api( ctx[0] ) : 7385 tables; 7386 } ); 7387 7388 // Common methods, combined to reduce size 7389 [ 7390 ['nodes', 'node', 'nTable'], 7391 ['body', 'body', 'nTBody'], 7392 ['header', 'header', 'nTHead'], 7393 ['footer', 'footer', 'nTFoot'], 7394 ].forEach(function (item) { 7395 _api_registerPlural( 7396 'tables().' + item[0] + '()', 7397 'table().' + item[1] + '()' , 7398 function () { 7399 return this.iterator( 'table', function ( ctx ) { 7400 return ctx[item[2]]; 7401 }, 1 ); 7402 } 7403 ); 7404 }); 7405 7406 // Structure methods 7407 [ 7408 ['header', 'aoHeader'], 7409 ['footer', 'aoFooter'], 7410 ].forEach(function (item) { 7411 _api_register( 'table().' + item[0] + '.structure()' , function (selector) { 7412 var indexes = this.columns(selector).indexes().flatten(); 7413 var ctx = this.context[0]; 7414 7415 return _fnHeaderLayout(ctx, ctx[item[1]], indexes); 7416 } ); 7417 }) 7418 7419 7420 _api_registerPlural( 'tables().containers()', 'table().container()' , function () { 7421 return this.iterator( 'table', function ( ctx ) { 7422 return ctx.nTableWrapper; 7423 }, 1 ); 7424 } ); 7425 7426 _api_register( 'tables().every()', function ( fn ) { 7427 var that = this; 7428 7429 return this.iterator('table', function (s, i) { 7430 fn.call(that.table(i), i); 7431 }); 7432 }); 7433 7434 _api_register( 'caption()', function ( value, side ) { 7435 var context = this.context; 7436 7437 // Getter - return existing node's content 7438 if ( value === undefined ) { 7439 var caption = context[0].captionNode; 7440 7441 return caption && context.length ? 7442 caption.innerHTML : 7443 null; 7444 } 7445 7446 return this.iterator( 'table', function ( ctx ) { 7447 var table = $(ctx.nTable); 7448 var caption = $(ctx.captionNode); 7449 var container = $(ctx.nTableWrapper); 7450 7451 // Create the node if it doesn't exist yet 7452 if ( ! caption.length ) { 7453 caption = $('<caption/>').html( value ); 7454 ctx.captionNode = caption[0]; 7455 7456 // If side isn't set, we need to insert into the document to let the 7457 // CSS decide so we can read it back, otherwise there is no way to 7458 // know if the CSS would put it top or bottom for scrolling
7459 if (! side) { 7460 table.prepend(caption); 7461 7462 side = caption.css('caption-side'); 7463 } 7464 } 7465 7466 caption.html( value ); 7467 7468 if ( side ) { 7469 caption.css( 'caption-side', side ); 7470 caption[0]._captionSide = side; 7471 } 7472 7473 if (container.find('div.dataTables_scroll').length) { 7474 var selector = (side === 'top' ? 'Head' : 'Foot'); 7475 7476 container.find('div.dataTables_scroll'+ selector +' table').prepend(caption); 7477 } 7478 else { 7479 table.prepend(caption); 7480 } 7481 }, 1 ); 7482 } ); 7483 7484 _api_register( 'caption.node()', function () { 7485 var ctx = this.context; 7486 7487 return ctx.length ? ctx[0].captionNode : null; 7488 } ); 7489 7490 7491 /** 7492 * Redraw the tables in the current context. 7493 */ 7494 _api_register( 'draw()', function ( paging ) { 7495 return this.iterator( 'table', function ( settings ) { 7496 if ( paging === 'page' ) { 7497 _fnDraw( settings ); 7498 } 7499 else { 7500 if ( typeof paging === 'string' ) { 7501 paging = paging === 'full-hold' ? 7502 false : 7503 true; 7504 } 7505 7506 _fnReDraw( settings, paging===false ); 7507 } 7508 } ); 7509 } ); 7510 7511 7512 7513 /** 7514 * Get the current page index. 7515 * 7516 * @return {integer} Current page index (zero based) 7517 *//** 7518 * Set the current page. 7519 * 7520 * Note that if you attempt to show a page which does not exist, DataTables will 7521 * not throw an error, but rather reset the paging. 7522 * 7523 * @param {integer|string} action The paging action to take. This can be one of: 7524 * * `integer` - The page index to jump to 7525 * * `string` - An action to take: 7526 * * `first` - Jump to first page. 7527 * * `next` - Jump to the next page 7528 * * `previous` - Jump to previous page 7529 * * `last` - Jump to the last page. 7530 * @returns {DataTables.Api} this 7531 */ 7532 _api_register( 'page()', function ( action ) { 7533 if ( action === undefined ) { 7534 return this.page.info().page; // not an expensive call 7535 } 7536 7537 // else, have an action to take on all tables 7538 return this.iterator( 'table', function ( settings ) { 7539 _fnPageChange( settings, action ); 7540 } ); 7541 } ); 7542 7543 7544 /** 7545 * Paging information for the first table in the current context. 7546 * 7547 * If you require paging information for another table, use the `table()` method 7548 * with a suitable selector. 7549 * 7550 * @return {object} Object with the following properties set: 7551 * * `page` - Current page index (zero based - i.e. the first page is `0`) 7552 * * `pages` - Total number of pages 7553 * * `start` - Display index for the first record shown on the current page 7554 * * `end` - Display index for the last record shown on the current page 7555 * * `length` - Display length (number of records). Note that generally `start 7556 * + length = end`, but this is not always true, for example if there are 7557 * only 2 records to show on the final page, with a length of 10. 7558 * * `recordsTotal` - Full data set length 7559 * * `recordsDisplay` - Data set length once the current filtering criterion 7560 * are applied. 7561 */ 7562 _api_register( 'page.info()', function () { 7563 if ( this.context.length === 0 ) { 7564 return undefined; 7565 } 7566 7567 var 7568 settings = this.context[0], 7569 start = settings._iDisplayStart, 7570 len = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1, 7571 visRecords = settings.fnRecordsDisplay(), 7572 all = len === -1; 7573 7574 return { 7575 "page": all ? 0 : Math.floor( start / len ), 7576 "pages": all ? 1 : Math.ceil( visRecords / len ), 7577 "start": start, 7578 "end": settings.fnDisplayEnd(), 7579 "length": len, 7580 "recordsTotal": settings.fnRecordsTotal(), 7581 "recordsDisplay": visRecords, 7582 "serverSide": _fnDataSource( settings ) === 'ssp' 7583 }; 7584 } ); 7585 7586 7587 /** 7588 * Get the current page length. 7589 * 7590 * @return {integer} Current page length. Note `-1` indicates that all records 7591 * are to be shown. 7592 *//** 7593 * Set the current page length. 7594 * 7595 * @param {integer} Page length to set. Use `-1` to show all records. 7596 * @returns {DataTables.Api} this 7597 */ 7598 _api_register( 'page.len()', function ( len ) { 7599 // Note that we can't call this function 'length()' because `length` 7600 // is a Javascript property of functions which defines how many arguments 7601 // the function expects. 7602 if ( len === undefined ) { 7603 return this.context.length !== 0 ? 7604 this.context[0]._iDisplayLength : 7605 undefined; 7606 } 7607 7608 // else, set the page length 7609 return this.iterator( 'table', function ( settings ) { 7610 _fnLengthChange( settings, len ); 7611 } ); 7612 } ); 7613 7614 7615 7616 var __reload = function ( settings, holdPosition, callback ) { 7617 // Use the draw event to trigger a callback 7618 if ( callback ) { 7619 var api = new _Api( settings ); 7620 7621 api.one( 'draw', function () { 7622 callback( api.ajax.json() ); 7623 } ); 7624 } 7625 7626 if ( _fnDataSource( settings ) == 'ssp' ) { 7627 _fnReDraw( settings, holdPosition ); 7628 } 7629 else { 7630 _fnProcessingDisplay( settings, true ); 7631 7632 // Cancel an existing request 7633 var xhr = settings.jqXHR; 7634 if ( xhr && xhr.readyState !== 4 ) { 7635 xhr.abort(); 7636 } 7637 7638 // Trigger xhr 7639 _fnBuildAjax( settings, {}, function( json ) { 7640 _fnClearTable( settings ); 7641 7642 var data = _fnAjaxDataSrc( settings, json ); 7643 for ( var i=0, ien=data.length ; i<ien ; i++ ) { 7644 _fnAddData( settings, data[i] ); 7645 } 7646 7647 _fnReDraw( settings, holdPosition ); 7648 _fnInitComplete( settings ); 7649 _fnProcessingDisplay( settings, false ); 7650 } ); 7651 } 7652 }; 7653 7654 7655 /** 7656 * Get the JSON response from the last Ajax request that DataTables made to the 7657 * server. Note that this returns the JSON from the first table in the current 7658 * context. 7659 * 7660 * @return {object} JSON received from the server. 7661 */ 7662 _api_register( 'ajax.json()', function () { 7663 var ctx = this.context; 7664 7665 if ( ctx.length > 0 ) { 7666 return ctx[0].json; 7667 } 7668 7669 // else return undefined; 7670 } ); 7671 7672 7673 /** 7674 * Get the data submitted in the last Ajax request 7675 */ 7676 _api_register( 'ajax.params()', function () { 7677 var ctx = this.context; 7678 7679 if ( ctx.length > 0 ) { 7680 return ctx[0].oAjaxData; 7681 } 7682 7683 // else return undefined; 7684 } ); 7685 7686 7687 /** 7688 * Reload tables from the Ajax data source. Note that this function will 7689 * automatically re-draw the table when the remote data has been loaded. 7690 * 7691 * @param {boolean} [reset=true] Reset (default) or hold the current paging 7692 * position. A full re-sort and re-filter is performed when this method is 7693 * called, which is why the pagination reset is the default action. 7694 * @returns {DataTables.Api} this 7695 */ 7696 _api_register( 'ajax.reload()', function ( callback, resetPaging ) { 7697 return this.iterator( 'table', function (settings) { 7698 __reload( settings, resetPaging===false, callback ); 7699 } ); 7700 } ); 7701 7702 7703 /** 7704 * Get the current Ajax URL. Note that this returns the URL from the first 7705 * table in the current context. 7706 * 7707 * @return {string} Current Ajax source URL 7708 *//** 7709 * Set the Ajax URL. Note that this will set the URL for all tables in the 7710 * current context. 7711 * 7712 * @param {string} url URL to set. 7713 * @returns {DataTables.Api} this 7714 */ 7715 _api_register( 'ajax.url()', function ( url ) { 7716 var ctx = this.context; 7717 7718 if ( url === undefined ) { 7719 // get 7720 if ( ctx.length === 0 ) { 7721 return undefined; 7722 } 7723 ctx = ctx[0]; 7724
7725 return $.isPlainObject( ctx.ajax ) ? 7726 ctx.ajax.url : 7727 ctx.ajax; 7728 } 7729 7730 // set 7731 return this.iterator( 'table', function ( settings ) { 7732 if ( $.isPlainObject( settings.ajax ) ) { 7733 settings.ajax.url = url; 7734 } 7735 else { 7736 settings.ajax = url; 7737 } 7738 } ); 7739 } ); 7740 7741 7742 /** 7743 * Load data from the newly set Ajax URL. Note that this method is only 7744 * available when `ajax.url()` is used to set a URL. Additionally, this method 7745 * has the same effect as calling `ajax.reload()` but is provided for 7746 * convenience when setting a new URL. Like `ajax.reload()` it will 7747 * automatically redraw the table once the remote data has been loaded. 7748 * 7749 * @returns {DataTables.Api} this 7750 */ 7751 _api_register( 'ajax.url().load()', function ( callback, resetPaging ) { 7752 // Same as a reload, but makes sense to present it for easy access after a 7753 // url change 7754 return this.iterator( 'table', function ( ctx ) { 7755 __reload( ctx, resetPaging===false, callback ); 7756 } ); 7757 } ); 7758 7759 7760 7761 7762 var _selector_run = function ( type, selector, selectFn, settings, opts ) 7763 { 7764 var 7765 out = [], res, 7766 a, i, ien, j, jen, 7767 selectorType = typeof selector; 7768 7769 // Can't just check for isArray here, as an API or jQuery instance might be 7770 // given with their array like look 7771 if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) { 7772 selector = [ selector ]; 7773 } 7774 7775 for ( i=0, ien=selector.length ; i<ien ; i++ ) { 7776 // Only split on simple strings - complex expressions will be jQuery selectors 7777 a = selector[i] && selector[i].split && ! selector[i].match(/[[(:]/) ? 7778 selector[i].split(',') : 7779 [ selector[i] ]; 7780 7781 for ( j=0, jen=a.length ; j<jen ; j++ ) { 7782 res = selectFn( typeof a[j] === 'string' ? (a[j]).trim() : a[j] ); 7783 7784 // Remove empty items 7785 res = res.filter( function (item) { 7786 return item !== null && item !== undefined; 7787 }); 7788 7789 if ( res && res.length ) { 7790 out = out.concat( res ); 7791 } 7792 } 7793 } 7794 7795 // selector extensions 7796 var ext = _ext.selector[ type ]; 7797 if ( ext.length ) { 7798 for ( i=0, ien=ext.length ; i<ien ; i++ ) { 7799 out = ext[i]( settings, opts, out ); 7800 } 7801 } 7802 7803 return _unique( out ); 7804 }; 7805 7806 7807 var _selector_opts = function ( opts ) 7808 { 7809 if ( ! opts ) { 7810 opts = {}; 7811 } 7812 7813 // Backwards compatibility for 1.9- which used the terminology filter rather 7814 // than search 7815 if ( opts.filter && opts.search === undefined ) { 7816 opts.search = opts.filter; 7817 } 7818 7819 return $.extend( { 7820 search: 'none', 7821 order: 'current', 7822 page: 'all' 7823 }, opts ); 7824 }; 7825 7826 7827 // Reduce the API instance to the first item found 7828 var _selector_first = function ( old ) 7829 { 7830 var inst = new _Api(old.context[0]); 7831 7832 // Use a push rather than passing to the constructor, since it will 7833 // merge arrays down automatically, which isn't what is wanted here 7834 if (old.length) { 7835 inst.push( old[0] ); 7836 } 7837 7838 inst.selector = old.selector; 7839 7840 // Limit to a single row / column / cell 7841 if (inst.length && inst[0].length > 1) { 7842 inst[0].splice(1); 7843 } 7844 7845 return inst; 7846 }; 7847 7848 7849 var _selector_row_indexes = function ( settings, opts ) 7850 { 7851 var 7852 i, ien, tmp, a=[], 7853 displayFiltered = settings.aiDisplay, 7854 displayMaster = settings.aiDisplayMaster; 7855 7856 var 7857 search = opts.search, // none, applied, removed 7858 order = opts.order, // applied, current, index (original - compatibility with 1.9) 7859 page = opts.page; // all, current 7860 7861 if ( _fnDataSource( settings ) == 'ssp' ) { 7862 // In server-side processing mode, most options are irrelevant since 7863 // rows not shown don't exist and the index order is the applied order 7864 // Removed is a special case - for consistency just return an empty 7865 // array 7866 return search === 'removed' ? 7867 [] : 7868 _range( 0, displayMaster.length ); 7869 } 7870 7871 if ( page == 'current' ) { 7872 // Current page implies that order=current and filter=applied, since it is 7873 // fairly senseless otherwise, regardless of what order and search actually 7874 // are 7875 for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i<ien ; i++ ) { 7876 a.push( displayFiltered[i] ); 7877 } 7878 } 7879 else if ( order == 'current' || order == 'applied' ) { 7880 if ( search == 'none') {
7881 a = displayMaster.slice(); 7882 } 7883 else if ( search == 'applied' ) { 7884 a = displayFiltered.slice(); 7885 } 7886 else if ( search == 'removed' ) { 7887 // O(n+m) solution by creating a hash map 7888 var displayFilteredMap = {}; 7889 7890 for ( i=0, ien=displayFiltered.length ; i<ien ; i++ ) { 7891 displayFilteredMap[displayFiltered[i]] = null; 7892 } 7893 7894 displayMaster.forEach(function (item) { 7895 if (! Object.prototype.hasOwnProperty.call(displayFilteredMap, item)) { 7896 a.push(item); 7897 } 7898 }); 7899 } 7900 } 7901 else if ( order == 'index' || order == 'original' ) { 7902 for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) { 7903 if (! settings.aoData[i]) { 7904 continue; 7905 } 7906 7907 if ( search == 'none' ) { 7908 a.push( i ); 7909 } 7910 else { // applied | removed 7911 tmp = displayFiltered.indexOf(i); 7912 7913 if ((tmp === -1 && search == 'removed') || 7914 (tmp >= 0 && search == 'applied') ) 7915 { 7916 a.push( i ); 7917 } 7918 } 7919 } 7920 } 7921 else if ( typeof order === 'number' ) { 7922 // Order the rows by the given column 7923 var ordered = _fnSort(settings, order, 'asc'); 7924 7925 if (search === 'none') { 7926 a = ordered; 7927 } 7928 else { // applied | removed 7929 for (i=0; i<ordered.length; i++) { 7930 tmp = displayFiltered.indexOf(ordered[i]); 7931 7932 if ((tmp === -1 && search == 'removed') || 7933 (tmp >= 0 && search == 'applied') ) 7934 { 7935 a.push( ordered[i] ); 7936 } 7937 } 7938 } 7939 } 7940 7941 return a; 7942 }; 7943 7944 7945 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 7946 * Rows 7947 * 7948 * {} - no selector - use all available rows 7949 * {integer} - row aoData index 7950 * {node} - TR node 7951 * {string} - jQuery selector to apply to the TR elements 7952 * {array} - jQuery array of nodes, or simply an array of TR nodes 7953 * 7954 */ 7955 var __row_selector = function ( settings, selector, opts ) 7956 { 7957 var rows; 7958 var run = function ( sel ) { 7959 var selInt = _intVal( sel ); 7960 var aoData = settings.aoData; 7961 7962 // Short cut - selector is a number and no options provided (default is 7963 // all records, so no need to check if the index is in there, since it 7964 // must be - dev error if the index doesn't exist). 7965 if ( selInt !== null && ! opts ) { 7966 return [ selInt ]; 7967 } 7968 7969 if ( ! rows ) { 7970 rows = _selector_row_indexes( settings, opts ); 7971 } 7972 7973 if ( selInt !== null && rows.indexOf(selInt) !== -1 ) { 7974 // Selector - integer 7975 return [ selInt ]; 7976 } 7977 else if ( sel === null || sel === undefined || sel === '' ) { 7978 // Selector - none 7979 return rows; 7980 } 7981 7982 // Selector - function 7983 if ( typeof sel === 'function' ) { 7984 return rows.map( function (idx) { 7985 var row = aoData[ idx ]; 7986 return sel( idx, row._aData, row.nTr ) ? idx : null; 7987 } ); 7988 } 7989 7990 // Selector - node 7991 if ( sel.nodeName ) { 7992 var rowIdx = sel._DT_RowIndex; // Property added by DT for fast lookup 7993 var cellIdx = sel._DT_CellIndex; 7994 7995 if ( rowIdx !== undefined ) { 7996 // Make sure that the row is actually still present in the table 7997 return aoData[ rowIdx ] && aoData[ rowIdx ].nTr === sel ? 7998 [ rowIdx ] : 7999 []; 8000 } 8001 else if ( cellIdx ) { 8002 return aoData[ cellIdx.row ] && aoData[ cellIdx.row ].nTr === sel.parentNode ? 8003 [ cellIdx.row ] : 8004 []; 8005 } 8006 else { 8007 var host = $(sel).closest('*[data-dt-row]'); 8008 return host.length ? 8009 [ host.data('dt-row') ] : 8010 []; 8011 } 8012 } 8013 8014 // ID selector. Want to always be able to select rows by id, regardless 8015 // of if the tr element has been created or not, so can't rely upon 8016 // jQuery here - hence a custom implementation. This does not match 8017 // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything, 8018 // but to select it using a CSS selector engine (like Sizzle or 8019 // querySelect) it would need to need to be escaped for some characters. 8020 // DataTables simplifies this for row selectors since you can select 8021 // only a row. A # indicates an id any anything that follows is the id - 8022 // unescaped. 8023 if ( typeof sel === 'string' && sel.charAt(0) === '#' ) { 8024 // get row index from id 8025 var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ]; 8026 if ( rowObj !== undefined ) { 8027 return [ rowObj.idx ]; 8028 } 8029 8030 // need to fall through to jQuery in case there is DOM id that 8031 // matches 8032 } 8033
8034 // Get nodes in the order from the `rows` array with null values removed 8035 var nodes = _removeEmpty( 8036 _pluck_order( settings.aoData, rows, 'nTr' ) 8037 ); 8038 8039 // Selector - jQuery selector string, array of nodes or jQuery object/ 8040 // As jQuery's .filter() allows jQuery objects to be passed in filter, 8041 // it also allows arrays, so this will cope with all three options 8042 return $(nodes) 8043 .filter( sel ) 8044 .map( function () { 8045 return this._DT_RowIndex; 8046 } ) 8047 .toArray(); 8048 }; 8049 8050 var matched = _selector_run( 'row', selector, run, settings, opts ); 8051 8052 if (opts.order === 'current' || opts.order === 'applied') { 8053 _fnSortDisplay(settings, matched); 8054 } 8055 8056 return matched; 8057 }; 8058 8059 8060 _api_register( 'rows()', function ( selector, opts ) { 8061 // argument shifting 8062 if ( selector === undefined ) { 8063 selector = ''; 8064 } 8065 else if ( $.isPlainObject( selector ) ) { 8066 opts = selector; 8067 selector = ''; 8068 } 8069 8070 opts = _selector_opts( opts ); 8071 8072 var inst = this.iterator( 'table', function ( settings ) { 8073 return __row_selector( settings, selector, opts ); 8074 }, 1 ); 8075 8076 // Want argument shifting here and in __row_selector? 8077 inst.selector.rows = selector; 8078 inst.selector.opts = opts; 8079 8080 return inst; 8081 } ); 8082 8083 _api_register( 'rows().nodes()', function () { 8084 return this.iterator( 'row', function ( settings, row ) { 8085 return settings.aoData[ row ].nTr || undefined; 8086 }, 1 ); 8087 } ); 8088 8089 _api_register( 'rows().data()', function () { 8090 return this.iterator( true, 'rows', function ( settings, rows ) { 8091 return _pluck_order( settings.aoData, rows, '_aData' ); 8092 }, 1 ); 8093 } ); 8094 8095 _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) { 8096 return this.iterator( 'row', function ( settings, row ) { 8097 var r = settings.aoData[ row ]; 8098 return type === 'search' ? r._aFilterData : r._aSortData; 8099 }, 1 ); 8100 } ); 8101 8102 _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) { 8103 return this.iterator( 'row', function ( settings, row ) { 8104 _fnInvalidate( settings, row, src ); 8105 } ); 8106 } ); 8107 8108 _api_registerPlural( 'rows().indexes()', 'row().index()', function () { 8109 return this.iterator( 'row', function ( settings, row ) { 8110 return row; 8111 }, 1 ); 8112 } ); 8113 8114 _api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) { 8115 var a = []; 8116 var context = this.context; 8117 8118 // `iterator` will drop undefined values, but in this case we want them 8119 for ( var i=0, ien=context.length ; i<ien ; i++ ) { 8120 for ( var j=0, jen=this[i].length ; j<jen ; j++ ) { 8121 var id = context[i].rowIdFn( context[i].aoData[ this[i][j] ]._aData ); 8122 a.push( (hash === true ? '#' : '' )+ id ); 8123 } 8124 } 8125 8126 return new _Api( context, a ); 8127 } ); 8128 8129 _api_registerPlural( 'rows().remove()', 'row().remove()', function () { 8130 this.iterator( 'row', function ( settings, row ) { 8131 var data = settings.aoData; 8132 var rowData = data[ row ]; 8133 8134 // Delete from the display arrays 8135 var idx = settings.aiDisplayMaster.indexOf(row); 8136 if (idx !== -1) { 8137 settings.aiDisplayMaster.splice(idx, 1); 8138 } 8139 8140 // For server-side processing tables - subtract the deleted row from the count 8141 if ( settings._iRecordsDisplay > 0 ) { 8142 settings._iRecordsDisplay--; 8143 } 8144 8145 // Check for an 'overflow' they case for displaying the table 8146 _fnLengthOverflow( settings ); 8147 8148 // Remove the row's ID reference if there is one 8149 var id = settings.rowIdFn( rowData._aData ); 8150 if ( id !== undefined ) { 8151 delete settings.aIds[ id ]; 8152 } 8153 8154 data[row] = null; 8155 } ); 8156 8157 return this; 8158 } ); 8159 8160 8161 _api_register( 'rows.add()', function ( rows ) { 8162 var newRows = this.iterator( 'table', function ( settings ) { 8163 var row, i, ien; 8164 var out = []; 8165 8166 for ( i=0, ien=rows.length ; i<ien ; i++ ) { 8167 row = rows[i]; 8168 8169 if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) { 8170 out.push( _fnAddTr( settings, row )[0] ); 8171 } 8172 else { 8173 out.push( _fnAddData( settings, row ) ); 8174 } 8175 } 8176 8177 return out; 8178 }, 1 ); 8179 8180 // Return an Api.rows() extended instance, so rows().nodes() etc can be used 8181 var modRows = this.rows( -1 ); 8182 modRows.pop(); 8183 _fnArrayApply(modRows, newRows); 8184 8185 return modRows; 8186 } ); 8187 8188 8189 8190 8191 8192 /** 8193 * 8194 */ 8195 _api_register( 'row()', function ( selector, opts ) { 8196 return _selector_first( this.rows( selector, opts ) ); 8197 } ); 8198 8199 8200 _api_register( 'row().data()', function ( data ) { 8201 var ctx = this.context; 8202 8203 if ( data === undefined ) { 8204 // Get 8205 return ctx.length && this.length && this[0].length ? 8206 ctx[0].aoData[ this[0] ]._aData : 8207 undefined; 8208 } 8209 8210 // Set 8211 var row = ctx[0].aoData[ this[0] ]; 8212 row._aData = data; 8213 8214 // If the DOM has an id, and the data source is an array 8215 if ( Array.isArray( data ) && row.nTr && row.nTr.id ) { 8216 _fnSetObjectDataFn( ctx[0].rowId )( data, row.nTr.id ); 8217 } 8218 8219 // Automatically invalidate 8220 _fnInvalidate( ctx[0], this[0], 'data' ); 8221 8222 return this; 8223 } ); 8224 8225 8226 _api_register( 'row().node()', function () { 8227 var ctx = this.context; 8228 8229 if (ctx.length && this.length && this[0].length) { 8230 var row = ctx[0].aoData[ this[0] ]; 8231 8232 if (row && row.nTr) { 8233 return row.nTr; 8234 } 8235 } 8236 8237 return null; 8238 } ); 8239 8240 8241 _api_register( 'row.add()', function ( row ) { 8242 // Allow a jQuery object to be passed in - only a single row is added from 8243 // it though - the first element in the set 8244 if ( row instanceof $ && row.length ) { 8245 row = row[0]; 8246 } 8247 8248 var rows = this.iterator( 'table', function ( settings ) { 8249 if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) { 8250 return _fnAddTr( settings, row )[0]; 8251 } 8252 return _fnAddData( settings, row ); 8253 } ); 8254 8255 // Return an Api.rows() extended instance, with the newly added row selected 8256 return this.row( rows[0] ); 8257 } ); 8258 8259 8260 $(document).on('plugin-init.dt', function (e, context) { 8261 var api = new _Api( context ); 8262 8263 api.on( 'stateSaveParams.DT', function ( e, settings, d ) { 8264 // This could be more compact with the API, but it is a lot faster as a simple 8265 // internal loop 8266 var idFn = settings.rowIdFn; 8267 var rows = settings.aiDisplayMaster; 8268 var ids = []; 8269 8270 for (var i=0 ; i<rows.length ; i++) { 8271 var rowIdx = rows[i]; 8272 var data = settings.aoData[rowIdx]; 8273 8274 if (data._detailsShow) { 8275 ids.push( '#' + idFn(data._aData) ); 8276 } 8277 } 8278 8279 d.childRows = ids; 8280 }); 8281 8282 // For future state loads (e.g. with StateRestore) 8283 api.on( 'stateLoaded.DT', function (e, settings, state) { 8284 __details_state_load( api, state ); 8285 }); 8286 8287 // And the initial load state 8288 __details_state_load( api, api.state.loaded() ); 8289 }); 8290 8291 var __details_state_load = function (api, state) 8292 { 8293 if ( state && state.childRows ) { 8294 api 8295 .rows( state.childRows.map(function (id) { 8296 // Escape any `:` characters from the row id. Accounts for 8297 // already escaped characters. 8298 return id.replace(/([^:\\]*(?:\\.[^:\\]*)*):/g, "$1\\:"); 8299 }) ) 8300 .every( function () { 8301 _fnCallbackFire( api.settings()[0], null, 'requestChild', [ this ] ) 8302 }); 8303 } 8304 } 8305 8306 var __details_add = function ( ctx, row, data, klass ) 8307 { 8308 // Convert to array of TR elements 8309 var rows = []; 8310 var addRow = function ( r, k ) { 8311 // Recursion to allow for arrays of jQuery objects 8312 if ( Array.isArray( r ) || r instanceof $ ) { 8313 for ( var i=0, ien=r.length ; i<ien ; i++ ) { 8314 addRow( r[i], k ); 8315 } 8316 return; 8317 } 8318 8319 // If we get a TR element, then just add it directly - up to the dev 8320 // to add the correct number of columns etc 8321 if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) { 8322 r.setAttribute( 'data-dt-row', row.idx ); 8323 rows.push( r ); 8324 } 8325 else { 8326 // Otherwise create a row with a wrapper 8327 var created = $('<tr><td></td></tr>') 8328 .attr( 'data-dt-row', row.idx ) 8329 .addClass( k ); 8330 8331 $('td', created) 8332 .addClass( k ) 8333 .html( r )[0].colSpan = _fnVisbleColumns( ctx ); 8334 8335 rows.push( created[0] ); 8336 } 8337 }; 8338 8339 addRow( data, klass ); 8340 8341 if ( row._details ) { 8342 row._details.detach(); 8343 } 8344 8345 row._details = $(rows); 8346 8347 // If the children were already shown, that state should be retained 8348 if ( row._detailsShow ) { 8349 row._details.insertAfter( row.nTr ); 8350 } 8351 }; 8352 8353 8354 // Make state saving of child row details async to allow them to be batch processed 8355 var __details_state = DataTable.util.throttle( 8356 function (ctx) { 8357 _fnSaveState( ctx[0] ) 8358 }, 8359 500 8360 ); 8361 8362 8363 var __details_remove = function ( api, idx ) 8364 { 8365 var ctx = api.context; 8366 8367 if ( ctx.length ) { 8368 var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ]; 8369 8370 if ( row && row._details ) { 8371 row._details.remove(); 8372 8373 row._detailsShow = undefined; 8374 row._details = undefined; 8375 $( row.nTr ).removeClass( 'dt-hasChild' ); 8376 __details_state( ctx ); 8377 } 8378 } 8379 }; 8380 8381
8382 var __details_display = function ( api, show ) { 8383 var ctx = api.context; 8384 8385 if ( ctx.length && api.length ) { 8386 var row = ctx[0].aoData[ api[0] ]; 8387 8388 if ( row._details ) { 8389 row._detailsShow = show; 8390 8391 if ( show ) { 8392 row._details.insertAfter( row.nTr ); 8393 $( row.nTr ).addClass( 'dt-hasChild' ); 8394 } 8395 else { 8396 row._details.detach(); 8397 $( row.nTr ).removeClass( 'dt-hasChild' ); 8398 } 8399 8400 _fnCallbackFire( ctx[0], null, 'childRow', [ show, api.row( api[0] ) ] ) 8401 8402 __details_events( ctx[0] ); 8403 __details_state( ctx ); 8404 } 8405 } 8406 }; 8407 8408 8409 var __details_events = function ( settings ) 8410 { 8411 var api = new _Api( settings ); 8412 var namespace = '.dt.DT_details'; 8413 var drawEvent = 'draw'+namespace; 8414 var colvisEvent = 'column-sizing'+namespace; 8415 var destroyEvent = 'destroy'+namespace; 8416 var data = settings.aoData; 8417 8418 api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent ); 8419 8420 if ( _pluck( data, '_details' ).length > 0 ) { 8421 // On each draw, insert the required elements into the document 8422 api.on( drawEvent, function ( e, ctx ) { 8423 if ( settings !== ctx ) { 8424 return; 8425 } 8426 8427 api.rows( {page:'current'} ).eq(0).each( function (idx) { 8428 // Internal data grab 8429 var row = data[ idx ]; 8430 8431 if ( row._detailsShow ) { 8432 row._details.insertAfter( row.nTr ); 8433 } 8434 } ); 8435 } ); 8436 8437 // Column visibility change - update the colspan 8438 api.on( colvisEvent, function ( e, ctx ) { 8439 if ( settings !== ctx ) { 8440 return; 8441 } 8442 8443 // Update the colspan for the details rows (note, only if it already has 8444 // a colspan) 8445 var row, visible = _fnVisbleColumns( ctx ); 8446 8447 for ( var i=0, ien=data.length ; i<ien ; i++ ) { 8448 row = data[i]; 8449 8450 if ( row && row._details ) { 8451 row._details.each(function () { 8452 var el = $(this).children('td'); 8453 8454 if (el.length == 1) { 8455 el.attr('colspan', visible); 8456 } 8457 }); 8458 } 8459 } 8460 } ); 8461 8462 // Table destroyed - nuke any child rows 8463 api.on( destroyEvent, function ( e, ctx ) { 8464 if ( settings !== ctx ) { 8465 return; 8466 } 8467 8468 for ( var i=0, ien=data.length ; i<ien ; i++ ) { 8469 if ( data[i] && data[i]._details ) { 8470 __details_remove( api, i ); 8471 } 8472 } 8473 } ); 8474 } 8475 }; 8476 8477 // Strings for the method names to help minification 8478 var _emp = ''; 8479 var _child_obj = _emp+'row().child'; 8480 var _child_mth = _child_obj+'()'; 8481 8482 // data can be: 8483 // tr 8484 // string 8485 // jQuery or array of any of the above 8486 _api_register( _child_mth, function ( data, klass ) { 8487 var ctx = this.context; 8488 8489 if ( data === undefined ) { 8490 // get 8491 return ctx.length && this.length && ctx[0].aoData[ this[0] ] 8492 ? ctx[0].aoData[ this[0] ]._details 8493 : undefined; 8494 } 8495 else if ( data === true ) { 8496 // show 8497 this.child.show(); 8498 } 8499 else if ( data === false ) { 8500 // remove 8501 __details_remove( this ); 8502 } 8503 else if ( ctx.length && this.length ) { 8504 // set 8505 __details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass ); 8506 } 8507 8508 return this; 8509 } ); 8510 8511 8512 _api_register( [ 8513 _child_obj+'.show()', 8514 _child_mth+'.show()' // only when `child()` was called with parameters (without 8515 ], function () { // it returns an object and this method is not executed) 8516 __details_display( this, true ); 8517 return this; 8518 } ); 8519 8520 8521 _api_register( [ 8522 _child_obj+'.hide()', 8523 _child_mth+'.hide()' // only when `child()` was called with parameters (without 8524 ], function () { // it returns an object and this method is not executed) 8525 __details_display( this, false ); 8526 return this; 8527 } ); 8528 8529 8530 _api_register( [ 8531 _child_obj+'.remove()', 8532 _child_mth+'.remove()' // only when `child()` was called with parameters (without 8533 ], function () { // it returns an object and this method is not executed) 8534 __details_remove( this ); 8535 return this; 8536 } ); 8537 8538 8539 _api_register( _child_obj+'.isShown()', function () { 8540 var ctx = this.context; 8541 8542 if ( ctx.length && this.length && ctx[0].aoData[ this[0] ] ) { 8543 // _detailsShown as false or undefined will fall through to return false 8544 return ctx[0].aoData[ this[0] ]._detailsShow || false; 8545 } 8546 return false;
8547 } ); 8548 8549 8550 8551 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 8552 * Columns 8553 * 8554 * {integer} - column index (>=0 count from left, <0 count from right) 8555 * "{integer}:visIdx" - visible column index (i.e. translate to column index) (>=0 count from left, <0 count from right) 8556 * "{integer}:visible" - alias for {integer}:visIdx (>=0 count from left, <0 count from right) 8557 * "{string}:name" - column name 8558 * "{string}" - jQuery selector on column header nodes 8559 * 8560 */ 8561 8562 // can be an array of these items, comma separated list, or an array of comma 8563 // separated lists 8564 8565 var __re_column_selector = /^([^:]+)?:(name|title|visIdx|visible)$/; 8566 8567 8568 // r1 and r2 are redundant - but it means that the parameters match for the 8569 // iterator callback in columns().data() 8570 var __columnData = function ( settings, column, r1, r2, rows, type ) { 8571 var a = []; 8572 for ( var row=0, ien=rows.length ; row<ien ; row++ ) { 8573 a.push( _fnGetCellData( settings, rows[row], column, type ) ); 8574 } 8575 return a; 8576 }; 8577 8578 8579 var __column_header = function ( settings, column, row ) { 8580 var header = settings.aoHeader; 8581 var target = row !== undefined 8582 ? row 8583 : settings.bSortCellsTop // legacy support 8584 ? 0 8585 : header.length - 1; 8586 8587 return header[target][column].cell; 8588 }; 8589 8590 var __column_selector = function ( settings, selector, opts ) 8591 { 8592 var 8593 columns = settings.aoColumns, 8594 names = _pluck( columns, 'sName' ), 8595 titles = _pluck( columns, 'sTitle' ), 8596 cells = DataTable.util.get('[].[].cell')(settings.aoHeader), 8597 nodes = _unique( _flatten([], cells) ); 8598 8599 var run = function ( s ) { 8600 var selInt = _intVal( s ); 8601 8602 // Selector - all 8603 if ( s === '' ) { 8604 return _range( columns.length ); 8605 } 8606 8607 // Selector - index 8608 if ( selInt !== null ) { 8609 return [ selInt >= 0 ? 8610 selInt : // Count from left 8611 columns.length + selInt // Count from right (+ because its a negative value) 8612 ]; 8613 } 8614 8615 // Selector = function 8616 if ( typeof s === 'function' ) { 8617 var rows = _selector_row_indexes( settings, opts ); 8618 8619 return columns.map(function (col, idx) { 8620 return s( 8621 idx, 8622 __columnData( settings, idx, 0, 0, rows ), 8623 __column_header( settings, idx ) 8624 ) ? idx : null; 8625 }); 8626 } 8627 8628 // jQuery or string selector 8629 var match = typeof s === 'string' ? 8630 s.match( __re_column_selector ) : 8631 ''; 8632 8633 if ( match ) { 8634 switch( match[2] ) { 8635 case 'visIdx': 8636 case 'visible': 8637 // Selector is a column index 8638 if (match[1] && match[1].match(/^\d+$/)) { 8639 var idx = parseInt( match[1], 10 ); 8640 8641 // Visible index given, convert to column index 8642 if ( idx < 0 ) { 8643 // Counting from the right 8644 var visColumns = columns.map( function (col,i) { 8645 return col.bVisible ? i : null; 8646 } ); 8647 return [ visColumns[ visColumns.length + idx ] ]; 8648 } 8649 // Counting from the left 8650 return [ _fnVisibleToColumnIndex( settings, idx ) ]; 8651 } 8652 8653 return columns.map( function (col, idx) { 8654 // Not visible, can't match 8655 if (! col.bVisible) { 8656 return null; 8657 } 8658 8659 // Selector 8660 if (match[1]) { 8661 return $(nodes[idx]).filter(match[1]).length > 0 ? idx : null; 8662 } 8663 8664 // `:visible` on its own 8665 return idx; 8666 } ); 8667 8668 case 'name': 8669 // match by name. `names` is column index complete and in order 8670 return names.map( function (name, i) { 8671 return name === match[1] ? i : null; 8672 } ); 8673 8674 case 'title': 8675 // match by column title 8676 return titles.map( function (title, i) { 8677 return title === match[1] ? i : null; 8678 } ); 8679 8680 default: 8681 return []; 8682 } 8683 } 8684 8685 // Cell in the table body 8686 if ( s.nodeName && s._DT_CellIndex ) { 8687 return [ s._DT_CellIndex.column ]; 8688 } 8689 8690 // jQuery selector on the TH elements for the columns 8691 var jqResult = $( nodes ) 8692 .filter( s ) 8693 .map( function () { 8694 return _fnColumnsFromHeader( this ); // `nodes` is column index complete and in order 8695 } ) 8696 .toArray() 8697 .sort(function (a, b) { 8698 return a - b; 8699 }); 8700 8701 if ( jqResult.length || ! s.nodeName ) {
8702 return jqResult; 8703 } 8704 8705 // Otherwise a node which might have a `dt-column` data attribute, or be 8706 // a child or such an element 8707 var host = $(s).closest('*[data-dt-column]'); 8708 return host.length ? 8709 [ host.data('dt-column') ] : 8710 []; 8711 }; 8712 8713 return _selector_run( 'column', selector, run, settings, opts ); 8714 }; 8715 8716 8717 var __setColumnVis = function ( settings, column, vis ) { 8718 var 8719 cols = settings.aoColumns, 8720 col = cols[ column ], 8721 data = settings.aoData, 8722 cells, i, ien, tr; 8723 8724 // Get 8725 if ( vis === undefined ) { 8726 return col.bVisible; 8727 } 8728 8729 // Set 8730 // No change 8731 if ( col.bVisible === vis ) { 8732 return false; 8733 } 8734 8735 if ( vis ) { 8736 // Insert column 8737 // Need to decide if we should use appendChild or insertBefore 8738 var insertBefore = _pluck(cols, 'bVisible').indexOf(true, column+1); 8739 8740 for ( i=0, ien=data.length ; i<ien ; i++ ) { 8741 if (data[i]) { 8742 tr = data[i].nTr; 8743 cells = data[i].anCells; 8744 8745 if ( tr ) { 8746 // insertBefore can act like appendChild if 2nd arg is null 8747 tr.insertBefore( cells[ column ], cells[ insertBefore ] || null ); 8748 } 8749 } 8750 } 8751 } 8752 else { 8753 // Remove column 8754 $( _pluck( settings.aoData, 'anCells', column ) ).detach(); 8755 } 8756 8757 // Common actions 8758 col.bVisible = vis; 8759 8760 _colGroup(settings); 8761 8762 return true; 8763 }; 8764 8765 8766 _api_register( 'columns()', function ( selector, opts ) { 8767 // argument shifting 8768 if ( selector === undefined ) { 8769 selector = ''; 8770 } 8771 else if ( $.isPlainObject( selector ) ) { 8772 opts = selector; 8773 selector = ''; 8774 } 8775 8776 opts = _selector_opts( opts ); 8777 8778 var inst = this.iterator( 'table', function ( settings ) { 8779 return __column_selector( settings, selector, opts ); 8780 }, 1 ); 8781 8782 // Want argument shifting here and in _row_selector? 8783 inst.selector.cols = selector; 8784 inst.selector.opts = opts; 8785 8786 return inst; 8787 } ); 8788 8789 _api_registerPlural( 'columns().header()', 'column().header()', function ( row ) { 8790 return this.iterator( 'column', function (settings, column) { 8791 return __column_header(settings, column, row); 8792 }, 1 ); 8793 } ); 8794 8795 _api_registerPlural( 'columns().footer()', 'column().footer()', function ( row ) { 8796 return this.iterator( 'column', function ( settings, column ) { 8797 var footer = settings.aoFooter; 8798 8799 if (! footer.length) { 8800 return null; 8801 } 8802 8803 return settings.aoFooter[row !== undefined ? row : 0][column].cell; 8804 }, 1 ); 8805 } ); 8806 8807 _api_registerPlural( 'columns().data()', 'column().data()', function () { 8808 return this.iterator( 'column-rows', __columnData, 1 ); 8809 } ); 8810 8811 _api_registerPlural( 'columns().render()', 'column().render()', function ( type ) { 8812 return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { 8813 return __columnData( settings, column, i, j, rows, type ); 8814 }, 1 ); 8815 } ); 8816 8817 _api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () { 8818 return this.iterator( 'column', function ( settings, column ) { 8819 return settings.aoColumns[column].mData; 8820 }, 1 ); 8821 } ); 8822 8823 _api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) { 8824 return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { 8825 return _pluck_order( settings.aoData, rows, 8826 type === 'search' ? '_aFilterData' : '_aSortData', column 8827 ); 8828 }, 1 ); 8829 } ); 8830 8831 _api_registerPlural( 'columns().init()', 'column().init()', function () { 8832 return this.iterator( 'column', function ( settings, column ) { 8833 return settings.aoColumns[column]; 8834 }, 1 ); 8835 } ); 8836 8837 _api_registerPlural( 'columns().nodes()', 'column().nodes()', function () { 8838 return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { 8839 return _pluck_order( settings.aoData, rows, 'anCells', column ) ; 8840 }, 1 ); 8841 } ); 8842 8843 _api_registerPlural( 'columns().titles()', 'column().title()', function (title, row) { 8844 return this.iterator( 'column', function ( settings, column ) { 8845 // Argument shifting 8846 if (typeof title === 'number') { 8847 row = title; 8848 title = undefined; 8849 } 8850 8851 var span = $('span.dt-column-title', this.column(column).header(row)); 8852 8853 if (title !== undefined) { 8854 span.html(title); 8855 return this; 8856 } 8857 8858 return span.html(); 8859 }, 1 ); 8860 } ); 8861 8862 _api_registerPlural( 'columns().types()', 'column().type()', function () { 8863 return this.iterator( 'column', function ( settings, column ) { 8864 var type = settings.aoColumns[column].sType; 8865
8866 // If the type was invalidated, then resolve it. This actually does 8867 // all columns at the moment. Would only happen once if getting all 8868 // column's data types. 8869 if (! type) { 8870 _fnColumnTypes(settings); 8871 } 8872 8873 return type; 8874 }, 1 ); 8875 } ); 8876 8877 _api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) { 8878 var that = this; 8879 var changed = []; 8880 var ret = this.iterator( 'column', function ( settings, column ) { 8881 if ( vis === undefined ) { 8882 return settings.aoColumns[ column ].bVisible; 8883 } // else 8884 8885 if (__setColumnVis( settings, column, vis )) { 8886 changed.push(column); 8887 } 8888 } ); 8889 8890 // Group the column visibility changes 8891 if ( vis !== undefined ) { 8892 this.iterator( 'table', function ( settings ) { 8893 // Redraw the header after changes 8894 _fnDrawHead( settings, settings.aoHeader ); 8895 _fnDrawHead( settings, settings.aoFooter ); 8896 8897 // Update colspan for no records display. Child rows and extensions will use their own 8898 // listeners to do this - only need to update the empty table item here 8899 if ( ! settings.aiDisplay.length ) { 8900 $(settings.nTBody).find('td[colspan]').attr('colspan', _fnVisbleColumns(settings)); 8901 } 8902 8903 _fnSaveState( settings ); 8904 8905 // Second loop once the first is done for events 8906 that.iterator( 'column', function ( settings, column ) { 8907 if (changed.includes(column)) { 8908 _fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] ); 8909 } 8910 } ); 8911 8912 if ( changed.length && (calc === undefined || calc) ) { 8913 that.columns.adjust(); 8914 } 8915 }); 8916 } 8917 8918 return ret; 8919 } ); 8920 8921 _api_registerPlural( 'columns().widths()', 'column().width()', function () { 8922 // Injects a fake row into the table for just a moment so the widths can 8923 // be read, regardless of colspan in the header and rows being present in 8924 // the body 8925 var columns = this.columns(':visible').count(); 8926 var row = $('<tr>').html('<td>' + Array(columns).join('</td><td>') + '</td>'); 8927 8928 $(this.table().body()).append(row); 8929 8930 var widths = row.children().map(function () { 8931 return $(this).outerWidth(); 8932 }); 8933 8934 row.remove(); 8935 8936 return this.iterator( 'column', function ( settings, column ) { 8937 var visIdx = _fnColumnIndexToVisible( settings, column ); 8938 8939 return visIdx !== null ? widths[visIdx] : 0; 8940 }, 1); 8941 } ); 8942 8943 _api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) { 8944 return this.iterator( 'column', function ( settings, column ) { 8945 return type === 'visible' ? 8946 _fnColumnIndexToVisible( settings, column ) : 8947 column; 8948 }, 1 ); 8949 } ); 8950 8951 _api_register( 'columns.adjust()', function () { 8952 return this.iterator( 'table', function ( settings ) { 8953 // Force a column sizing to happen with a manual call - otherwise it can skip 8954 // if the size hasn't changed 8955 settings.containerWidth = -1; 8956 8957 _fnAdjustColumnSizing( settings ); 8958 }, 1 ); 8959 } ); 8960 8961 _api_register( 'column.index()', function ( type, idx ) { 8962 if ( this.context.length !== 0 ) { 8963 var ctx = this.context[0]; 8964 8965 if ( type === 'fromVisible' || type === 'toData' ) { 8966 return _fnVisibleToColumnIndex( ctx, idx ); 8967 } 8968 else if ( type === 'fromData' || type === 'toVisible' ) { 8969 return _fnColumnIndexToVisible( ctx, idx ); 8970 } 8971 } 8972 } ); 8973 8974 _api_register( 'column()', function ( selector, opts ) { 8975 return _selector_first( this.columns( selector, opts ) ); 8976 } ); 8977 8978 var __cell_selector = function ( settings, selector, opts ) 8979 { 8980 var data = settings.aoData; 8981 var rows = _selector_row_indexes( settings, opts ); 8982 var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) ); 8983 var allCells = $(_flatten( [], cells )); 8984 var row; 8985 var columns = settings.aoColumns.length; 8986 var a, i, ien, j, o, host; 8987 8988 var run = function ( s ) { 8989 var fnSelector = typeof s === 'function'; 8990 8991 if ( s === null || s === undefined || fnSelector ) { 8992 // All cells and function selectors 8993 a = []; 8994 8995 for ( i=0, ien=rows.length ; i<ien ; i++ ) { 8996 row = rows[i]; 8997 8998 for ( j=0 ; j<columns ; j++ ) { 8999 o = { 9000 row: row, 9001 column: j 9002 }; 9003 9004 if ( fnSelector ) { 9005 // Selector - function 9006 host = data[ row ]; 9007 9008 if ( s( o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null ) ) { 9009 a.push( o ); 9010 } 9011 } 9012 else { 9013 // Selector - all 9014 a.push( o ); 9015 } 9016 } 9017 } 9018 9019 return a; 9020 } 9021 9022 // Selector - index 9023 if ( $.isPlainObject( s ) ) { 9024 // Valid cell index and its in the array of selectable rows 9025 return s.column !== undefined && s.row !== undefined && rows.indexOf(s.row) !== -1 ? 9026 [s] : 9027 []; 9028 } 9029 9030 // Selector - jQuery filtered cells 9031 var jqResult = allCells 9032 .filter( s ) 9033 .map( function (i, el) { 9034 return { // use a new object, in case someone changes the values 9035 row: el._DT_CellIndex.row, 9036 column: el._DT_CellIndex.column 9037 }; 9038 } ) 9039 .toArray(); 9040 9041 if ( jqResult.length || ! s.nodeName ) {
9042 return jqResult; 9043 } 9044 9045 // Otherwise the selector is a node, and there is one last option - the 9046 // element might be a child of an element which has dt-row and dt-column 9047 // data attributes 9048 host = $(s).closest('*[data-dt-row]'); 9049 return host.length ? 9050 [ { 9051 row: host.data('dt-row'), 9052 column: host.data('dt-column') 9053 } ] : 9054 []; 9055 }; 9056 9057 return _selector_run( 'cell', selector, run, settings, opts ); 9058 }; 9059 9060 9061 9062 9063 _api_register( 'cells()', function ( rowSelector, columnSelector, opts ) { 9064 // Argument shifting 9065 if ( $.isPlainObject( rowSelector ) ) { 9066 // Indexes 9067 if ( rowSelector.row === undefined ) { 9068 // Selector options in first parameter 9069 opts = rowSelector; 9070 rowSelector = null; 9071 } 9072 else { 9073 // Cell index objects in first parameter 9074 opts = columnSelector; 9075 columnSelector = null; 9076 } 9077 } 9078 if ( $.isPlainObject( columnSelector ) ) { 9079 opts = columnSelector; 9080 columnSelector = null; 9081 } 9082 9083 // Cell selector 9084 if ( columnSelector === null || columnSelector === undefined ) { 9085 return this.iterator( 'table', function ( settings ) { 9086 return __cell_selector( settings, rowSelector, _selector_opts( opts ) ); 9087 } ); 9088 } 9089 9090 // The default built in options need to apply to row and columns 9091 var internalOpts = opts ? { 9092 page: opts.page, 9093 order: opts.order, 9094 search: opts.search 9095 } : {}; 9096 9097 // Row + column selector 9098 var columns = this.columns( columnSelector, internalOpts ); 9099 var rows = this.rows( rowSelector, internalOpts ); 9100 var i, ien, j, jen; 9101 9102 var cellsNoOpts = this.iterator( 'table', function ( settings, idx ) { 9103 var a = []; 9104 9105 for ( i=0, ien=rows[idx].length ; i<ien ; i++ ) { 9106 for ( j=0, jen=columns[idx].length ; j<jen ; j++ ) { 9107 a.push( { 9108 row: rows[idx][i], 9109 column: columns[idx][j] 9110 } ); 9111 } 9112 } 9113 9114 return a; 9115 }, 1 ); 9116 9117 // There is currently only one extension which uses a cell selector extension 9118 // It is a _major_ performance drag to run this if it isn't needed, so this is 9119 // an extension specific check at the moment 9120 var cells = opts && opts.selected ? 9121 this.cells( cellsNoOpts, opts ) : 9122 cellsNoOpts; 9123 9124 $.extend( cells.selector, { 9125 cols: columnSelector, 9126 rows: rowSelector, 9127 opts: opts 9128 } ); 9129 9130 return cells; 9131 } ); 9132 9133 9134 _api_registerPlural( 'cells().nodes()', 'cell().node()', function () { 9135 return this.iterator( 'cell', function ( settings, row, column ) { 9136 var data = settings.aoData[ row ]; 9137 9138 return data && data.anCells ? 9139 data.anCells[ column ] : 9140 undefined; 9141 }, 1 ); 9142 } ); 9143 9144 9145 _api_register( 'cells().data()', function () { 9146 return this.iterator( 'cell', function ( settings, row, column ) { 9147 return _fnGetCellData( settings, row, column ); 9148 }, 1 ); 9149 } ); 9150 9151 9152 _api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) { 9153 type = type === 'search' ? '_aFilterData' : '_aSortData'; 9154 9155 return this.iterator( 'cell', function ( settings, row, column ) { 9156 return settings.aoData[ row ][ type ][ column ]; 9157 }, 1 ); 9158 } ); 9159 9160 9161 _api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) { 9162 return this.iterator( 'cell', function ( settings, row, column ) { 9163 return _fnGetCellData( settings, row, column, type ); 9164 }, 1 ); 9165 } ); 9166 9167 9168 _api_registerPlural( 'cells().indexes()', 'cell().index()', function () { 9169 return this.iterator( 'cell', function ( settings, row, column ) { 9170 return { 9171 row: row, 9172 column: column, 9173 columnVisible: _fnColumnIndexToVisible( settings, column ) 9174 }; 9175 }, 1 ); 9176 } ); 9177 9178 9179 _api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) { 9180 return this.iterator( 'cell', function ( settings, row, column ) { 9181 _fnInvalidate( settings, row, src, column ); 9182 } ); 9183 } ); 9184 9185 9186 9187 _api_register( 'cell()', function ( rowSelector, columnSelector, opts ) { 9188 return _selector_first( this.cells( rowSelector, columnSelector, opts ) ); 9189 } ); 9190 9191 9192 _api_register( 'cell().data()', function ( data ) { 9193 var ctx = this.context; 9194 var cell = this[0]; 9195 9196 if ( data === undefined ) { 9197 // Get 9198 return ctx.length && cell.length ? 9199 _fnGetCellData( ctx[0], cell[0].row, cell[0].column ) : 9200 undefined; 9201 } 9202 9203 // Set 9204 _fnSetCellData( ctx[0], cell[0].row, cell[0].column, data ); 9205 _fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column ); 9206 9207 return this; 9208 } ); 9209 9210 9211 9212 /** 9213 * Get current ordering (sorting) that has been applied to the table. 9214 * 9215 * @returns {array} 2D array containing the sorting information for the first 9216 * table in the current context. Each element in the parent array represents 9217 * a column being sorted upon (i.e. multi-sorting with two columns would have 9218 * 2 inner arrays). The inner arrays may have 2 or 3 elements. The first is 9219 * the column index that the sorting condition applies to, the second is the 9220 * direction of the sort (`desc` or `asc`) and, optionally, the third is the 9221 * index of the sorting order from the `column.sorting` initialisation array.
9222 *//** 9223 * Set the ordering for the table. 9224 * 9225 * @param {integer} order Column index to sort upon. 9226 * @param {string} direction Direction of the sort to be applied (`asc` or `desc`) 9227 * @returns {DataTables.Api} this 9228 *//** 9229 * Set the ordering for the table. 9230 * 9231 * @param {array} order 1D array of sorting information to be applied. 9232 * @param {array} [...] Optional additional sorting conditions 9233 * @returns {DataTables.Api} this 9234 *//** 9235 * Set the ordering for the table. 9236 * 9237 * @param {array} order 2D array of sorting information to be applied. 9238 * @returns {DataTables.Api} this 9239 */ 9240 _api_register( 'order()', function ( order, dir ) { 9241 var ctx = this.context; 9242 var args = Array.prototype.slice.call( arguments ); 9243 9244 if ( order === undefined ) { 9245 // get 9246 return ctx.length !== 0 ? 9247 ctx[0].aaSorting : 9248 undefined; 9249 } 9250 9251 // set 9252 if ( typeof order === 'number' ) { 9253 // Simple column / direction passed in 9254 order = [ [ order, dir ] ]; 9255 } 9256 else if ( args.length > 1 ) { 9257 // Arguments passed in (list of 1D arrays) 9258 order = args; 9259 } 9260 // otherwise a 2D array was passed in 9261 9262 return this.iterator( 'table', function ( settings ) { 9263 settings.aaSorting = Array.isArray(order) ? order.slice() : order; 9264 } ); 9265 } ); 9266 9267 9268 /** 9269 * Attach a sort listener to an element for a given column 9270 * 9271 * @param {node|jQuery|string} node Identifier for the element(s) to attach the 9272 * listener to. This can take the form of a single DOM node, a jQuery 9273 * collection of nodes or a jQuery selector which will identify the node(s). 9274 * @param {integer} column the column that a click on this node will sort on 9275 * @param {function} [callback] callback function when sort is run 9276 * @returns {DataTables.Api} this 9277 */ 9278 _api_register( 'order.listener()', function ( node, column, callback ) { 9279 return this.iterator( 'table', function ( settings ) { 9280 _fnSortAttachListener(settings, node, {}, column, callback); 9281 } ); 9282 } ); 9283 9284 9285 _api_register( 'order.fixed()', function ( set ) { 9286 if ( ! set ) { 9287 var ctx = this.context; 9288 var fixed = ctx.length ? 9289 ctx[0].aaSortingFixed : 9290 undefined; 9291 9292 return Array.isArray( fixed ) ? 9293 { pre: fixed } : 9294 fixed; 9295 } 9296 9297 return this.iterator( 'table', function ( settings ) { 9298 settings.aaSortingFixed = $.extend( true, {}, set ); 9299 } ); 9300 } ); 9301 9302 9303 // Order by the selected column(s) 9304 _api_register( [ 9305 'columns().order()', 9306 'column().order()' 9307 ], function ( dir ) { 9308 var that = this; 9309 9310 if ( ! dir ) { 9311 return this.iterator( 'column', function ( settings, idx ) { 9312 var sort = _fnSortFlatten( settings ); 9313 9314 for ( var i=0, ien=sort.length ; i<ien ; i++ ) { 9315 if ( sort[i].col === idx ) { 9316 return sort[i].dir; 9317 } 9318 } 9319 9320 return null; 9321 }, 1 ); 9322 } 9323 else { 9324 return this.iterator( 'table', function ( settings, i ) { 9325 settings.aaSorting = that[i].map( function (col) { 9326 return [ col, dir ]; 9327 } ); 9328 } ); 9329 } 9330 } ); 9331 9332 _api_registerPlural('columns().orderable()', 'column().orderable()', function ( directions ) { 9333 return this.iterator( 'column', function ( settings, idx ) { 9334 var col = settings.aoColumns[idx]; 9335 9336 return directions ? 9337 col.asSorting : 9338 col.bSortable; 9339 }, 1 ); 9340 } ); 9341 9342 9343 _api_register( 'processing()', function ( show ) { 9344 return this.iterator( 'table', function ( ctx ) { 9345 _fnProcessingDisplay( ctx, show ); 9346 } ); 9347 } ); 9348 9349 9350 _api_register( 'search()', function ( input, regex, smart, caseInsen ) { 9351 var ctx = this.context; 9352 9353 if ( input === undefined ) { 9354 // get 9355 return ctx.length !== 0 ? 9356 ctx[0].oPreviousSearch.search : 9357 undefined; 9358 } 9359 9360 // set 9361 return this.iterator( 'table', function ( settings ) { 9362 if ( ! settings.oFeatures.bFilter ) { 9363 return; 9364 } 9365 9366 if (typeof regex === 'object') { 9367 // New style options to pass to the search builder 9368 _fnFilterComplete( settings, $.extend( settings.oPreviousSearch, regex, { 9369 search: input 9370 } ) ); 9371 } 9372 else { 9373 // Compat for the old options 9374 _fnFilterComplete( settings, $.extend( settings.oPreviousSearch, { 9375 search: input, 9376 regex: regex === null ? false : regex,
9377 smart: smart === null ? true : smart, 9378 caseInsensitive: caseInsen === null ? true : caseInsen 9379 } ) ); 9380 } 9381 } ); 9382 } ); 9383 9384 _api_register( 'search.fixed()', function ( name, search ) { 9385 var ret = this.iterator( true, 'table', function ( settings ) { 9386 var fixed = settings.searchFixed; 9387 9388 if (! name) { 9389 return Object.keys(fixed) 9390 } 9391 else if (search === undefined) { 9392 return fixed[name]; 9393 } 9394 else if (search === null) { 9395 delete fixed[name]; 9396 } 9397 else { 9398 fixed[name] = search; 9399 } 9400 9401 return this; 9402 } ); 9403 9404 return name !== undefined && search === undefined 9405 ? ret[0] 9406 : ret; 9407 } ); 9408 9409 _api_registerPlural( 9410 'columns().search()', 9411 'column().search()', 9412 function ( input, regex, smart, caseInsen ) { 9413 return this.iterator( 'column', function ( settings, column ) { 9414 var preSearch = settings.aoPreSearchCols; 9415 9416 if ( input === undefined ) { 9417 // get 9418 return preSearch[ column ].search; 9419 } 9420 9421 // set 9422 if ( ! settings.oFeatures.bFilter ) { 9423 return; 9424 } 9425 9426 if (typeof regex === 'object') { 9427 // New style options to pass to the search builder 9428 $.extend( preSearch[ column ], regex, { 9429 search: input 9430 } ); 9431 } 9432 else { 9433 // Old style (with not all options available) 9434 $.extend( preSearch[ column ], { 9435 search: input, 9436 regex: regex === null ? false : regex, 9437 smart: smart === null ? true : smart, 9438 caseInsensitive: caseInsen === null ? true : caseInsen 9439 } ); 9440 } 9441 9442 _fnFilterComplete( settings, settings.oPreviousSearch ); 9443 } ); 9444 } 9445 ); 9446 9447 _api_register([ 9448 'columns().search.fixed()', 9449 'column().search.fixed()' 9450 ], 9451 function ( name, search ) { 9452 var ret = this.iterator( true, 'column', function ( settings, colIdx ) { 9453 var fixed = settings.aoColumns[colIdx].searchFixed; 9454 9455 if (! name) { 9456 return Object.keys(fixed) 9457 } 9458 else if (search === undefined) { 9459 return fixed[name]; 9460 } 9461 else if (search === null) { 9462 delete fixed[name]; 9463 } 9464 else { 9465 fixed[name] = search; 9466 } 9467 9468 return this; 9469 } ); 9470 9471 return name !== undefined && search === undefined 9472 ? ret[0] 9473 : ret; 9474 } 9475 ); 9476 /* 9477 * State API methods 9478 */ 9479 9480 _api_register( 'state()', function ( set, ignoreTime ) { 9481 // getter 9482 if ( ! set ) { 9483 return this.context.length ? 9484 this.context[0].oSavedState : 9485 null; 9486 } 9487 9488 var setMutate = $.extend( true, {}, set ); 9489 9490 // setter 9491 return this.iterator( 'table', function ( settings ) { 9492 if ( ignoreTime !== false ) { 9493 setMutate.time = +new Date() + 100; 9494 } 9495 9496 _fnImplementState( settings, setMutate, function(){} ); 9497 } ); 9498 } ); 9499 9500 9501 _api_register( 'state.clear()', function () { 9502 return this.iterator( 'table', function ( settings ) { 9503 // Save an empty object 9504 settings.fnStateSaveCallback.call( settings.oInstance, settings, {} ); 9505 } ); 9506 } ); 9507 9508 9509 _api_register( 'state.loaded()', function () { 9510 return this.context.length ? 9511 this.context[0].oLoadedState : 9512 null; 9513 } ); 9514 9515 9516 _api_register( 'state.save()', function () { 9517 return this.iterator( 'table', function ( settings ) { 9518 _fnSaveState( settings ); 9519 } ); 9520 } ); 9521 9522 // Can be assigned in DateTable.use() - note luxon and moment vars are in helpers.js 9523 var __bootstrap; 9524 var __foundation; 9525 9526 /** 9527 * Set the libraries that DataTables uses, or the global objects. 9528 * Note that the arguments can be either way around (legacy support) 9529 * and the second is optional. See docs. 9530 */ 9531 DataTable.use = function (arg1, arg2) { 9532 // Reverse arguments for legacy support 9533 var module = typeof arg1 === 'string' 9534 ? arg2 9535 : arg1; 9536 var type = typeof arg2 === 'string' 9537 ? arg2 9538 : arg1; 9539 9540 // Getter 9541 if (module === undefined && typeof type === 'string') { 9542 switch (type) { 9543 case 'lib': 9544 case 'jq': 9545 return $; 9546 9547 case 'win': 9548 return window; 9549 9550 case 'datetime': 9551 return DataTable.DateTime; 9552 9553 case 'luxon': 9554 return __luxon; 9555 9556 case 'moment': 9557 return __moment; 9558 9559 case 'bootstrap': 9560 // Use local if set, otherwise try window, which could be undefined 9561 return __bootstrap || window.bootstrap; 9562 9563 case 'foundation': 9564 // Ditto 9565 return __foundation || window.Foundation; 9566 9567 default: 9568 return null; 9569 } 9570 } 9571 9572 // Setter 9573 if (type === 'lib' || type === 'jq' || (module && module.fn && module.fn.jquery)) { 9574 $ = module; 9575 } 9576 else if (type === 'win' || (module && module.document)) { 9577 window = module; 9578 document = module.document; 9579 } 9580 else if (type === 'datetime' || (module && module.type === 'DateTime')) { 9581 DataTable.DateTime = module; 9582 } 9583 else if (type === 'luxon' || (module && module.FixedOffsetZone)) { 9584 __luxon = module; 9585 } 9586 else if (type === 'moment' || (module && module.isMoment)) { 9587 __moment = module; 9588 } 9589 else if (type === 'bootstrap' || (module && module.Modal && module.Modal.NAME === 'modal')) 9590 { 9591 // This is currently for BS5 only. BS3/4 attach to jQuery, so no need to use `.use()` 9592 __bootstrap = module; 9593 } 9594 else if (type === 'foundation' || (module && module.Reveal)) { 9595 __foundation = module; 9596 } 9597 } 9598 9599 /** 9600 * CommonJS factory function pass through. This will check if the arguments 9601 * given are a window object or a jQuery object. If so they are set 9602 * accordingly. 9603 * @param {*} root Window 9604 * @param {*} jq jQUery 9605 * @returns {boolean} Indicator 9606 */ 9607 DataTable.factory = function (root, jq) { 9608 var is = false;
9609 9610 // Test if the first parameter is a window object 9611 if (root && root.document) { 9612 window = root; 9613 document = root.document; 9614 } 9615 9616 // Test if the second parameter is a jQuery object 9617 if (jq && jq.fn && jq.fn.jquery) { 9618 $ = jq; 9619 is = true; 9620 } 9621 9622 return is; 9623 } 9624 9625 /** 9626 * Provide a common method for plug-ins to check the version of DataTables being 9627 * used, in order to ensure compatibility. 9628 * 9629 * @param {string} version Version string to check for, in the format "X.Y.Z". 9630 * Note that the formats "X" and "X.Y" are also acceptable. 9631 * @param {string} [version2=current DataTables version] As above, but optional. 9632 * If not given the current DataTables version will be used. 9633 * @returns {boolean} true if this version of DataTables is greater or equal to 9634 * the required version, or false if this version of DataTales is not 9635 * suitable 9636 * @static 9637 * @dtopt API-Static 9638 * 9639 * @example 9640 * alert( $.fn.dataTable.versionCheck( '1.9.0' ) ); 9641 */ 9642 DataTable.versionCheck = function( version, version2 ) 9643 { 9644 var aThis = version2 ? 9645 version2.split('.') : 9646 DataTable.version.split('.'); 9647 var aThat = version.split('.'); 9648 var iThis, iThat; 9649 9650 for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) { 9651 iThis = parseInt( aThis[i], 10 ) || 0; 9652 iThat = parseInt( aThat[i], 10 ) || 0; 9653 9654 // Parts are the same, keep comparing 9655 if (iThis === iThat) { 9656 continue; 9657 } 9658 9659 // Parts are different, return immediately 9660 return iThis > iThat; 9661 } 9662 9663 return true; 9664 }; 9665 9666 9667 /** 9668 * Check if a `<table>` node is a DataTable table already or not. 9669 * 9670 * @param {node|jquery|string} table Table node, jQuery object or jQuery 9671 * selector for the table to test. Note that if more than more than one 9672 * table is passed on, only the first will be checked 9673 * @returns {boolean} true the table given is a DataTable, or false otherwise 9674 * @static 9675 * @dtopt API-Static 9676 * 9677 * @example 9678 * if ( ! $.fn.DataTable.isDataTable( '#example' ) ) { 9679 * $('#example').dataTable(); 9680 * } 9681 */ 9682 DataTable.isDataTable = function ( table ) 9683 { 9684 var t = $(table).get(0); 9685 var is = false; 9686 9687 if ( table instanceof DataTable.Api ) { 9688 return true; 9689 } 9690 9691 $.each( DataTable.settings, function (i, o) { 9692 var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null; 9693 var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null; 9694 9695 if ( o.nTable === t || head === t || foot === t ) { 9696 is = true; 9697 } 9698 } ); 9699 9700 return is; 9701 }; 9702 9703 9704 /** 9705 * Get all DataTable tables that have been initialised - optionally you can 9706 * select to get only currently visible tables. 9707 * 9708 * @param {boolean} [visible=false] Flag to indicate if you want all (default) 9709 * or visible tables only. 9710 * @returns {array} Array of `table` nodes (not DataTable instances) which are 9711 * DataTables 9712 * @static 9713 * @dtopt API-Static 9714 * 9715 * @example 9716 * $.each( $.fn.dataTable.tables(true), function () { 9717 * $(table).DataTable().columns.adjust(); 9718 * } ); 9719 */ 9720 DataTable.tables = function ( visible ) 9721 { 9722 var api = false; 9723 9724 if ( $.isPlainObject( visible ) ) { 9725 api = visible.api; 9726 visible = visible.visible; 9727 } 9728 9729 var a = DataTable.settings 9730 .filter( function (o) { 9731 return !visible || (visible && $(o.nTable).is(':visible')) 9732 ? true 9733 : false; 9734 } ) 9735 .map( function (o) { 9736 return o.nTable; 9737 }); 9738 9739 return api ? 9740 new _Api( a ) : 9741 a; 9742 }; 9743 9744 9745 /** 9746 * Convert from camel case parameters to Hungarian notation. This is made public 9747 * for the extensions to provide the same ability as DataTables core to accept 9748 * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase 9749 * parameters. 9750 * 9751 * @param {object} src The model object which holds all parameters that can be 9752 * mapped. 9753 * @param {object} user The object to convert from camel case to Hungarian. 9754 * @param {boolean} force When set to `true`, properties which already have a 9755 * Hungarian value in the `user` object will be overwritten. Otherwise they 9756 * won't be. 9757 */ 9758 DataTable.camelToHungarian = _fnCamelToHungarian; 9759 9760 9761 9762 /** 9763 * 9764 */ 9765 _api_register( '$()', function ( selector, opts ) { 9766 var 9767 rows = this.rows( opts ).nodes(), // Get all rows 9768 jqRows = $(rows); 9769 9770 return $( [].concat( 9771 jqRows.filter( selector ).toArray(), 9772 jqRows.find( selector ).toArray() 9773 ) ); 9774 } ); 9775 9776 9777 // jQuery functions to operate on the tables 9778 $.each( [ 'on', 'one', 'off' ], function (i, key) { 9779 _api_register( key+'()', function ( /* event, handler */ ) { 9780 var args = Array.prototype.slice.call(arguments); 9781 9782 // Add the `dt` namespace automatically if it isn't already present 9783 args[0] = args[0].split( /\s/ ).map( function ( e ) { 9784 return ! e.match(/\.dt\b/) ? 9785 e+'.dt' : 9786 e; 9787 } ).join( ' ' ); 9788 9789 var inst = $( this.tables().nodes() ); 9790 inst[key].apply( inst, args ); 9791 return this; 9792 } ); 9793 } ); 9794 9795 9796 _api_register( 'clear()', function () { 9797 return this.iterator( 'table', function ( settings ) { 9798 _fnClearTable( settings ); 9799 } ); 9800 } ); 9801 9802 9803 _api_register( 'error()', function (msg) { 9804 return this.iterator( 'table', function ( settings ) { 9805 _fnLog( settings, 0, msg ); 9806 } ); 9807 } ); 9808 9809 9810 _api_register( 'settings()', function () { 9811 return new _Api( this.context, this.context ); 9812 } ); 9813 9814 9815 _api_register( 'init()', function () { 9816 var ctx = this.context; 9817 return ctx.length ? ctx[0].oInit : null; 9818 } ); 9819 9820 9821 _api_register( 'data()', function () { 9822 return this.iterator( 'table', function ( settings ) { 9823 return _pluck( settings.aoData, '_aData' ); 9824 } ).flatten(); 9825 } ); 9826 9827 9828 _api_register( 'trigger()', function ( name, args, bubbles ) { 9829 return this.iterator( 'table', function ( settings ) { 9830 return _fnCallbackFire( settings, null, name, args, bubbles ); 9831 } ).flatten(); 9832 } ); 9833 9834 9835 _api_register( 'ready()', function ( fn ) { 9836 var ctx = this.context; 9837 9838 // Get status of first table 9839 if (! fn) { 9840 return ctx.length 9841 ? (ctx[0]._bInitComplete || false) 9842 : null; 9843 } 9844 9845 // Function to run either once the table becomes ready or 9846 // immediately if it is already ready. 9847 return this.tables().every(function () { 9848 var api = this; 9849 9850 if (this.context[0]._bInitComplete) { 9851 fn.call(api); 9852 } 9853 else { 9854 this.on('init.dt.DT', function () { 9855 fn.call(api); 9856 }); 9857 } 9858 } ); 9859 } ); 9860 9861 9862 _api_register( 'destroy()', function ( remove ) { 9863 remove = remove || false; 9864 9865 return this.iterator( 'table', function ( settings ) { 9866 var classes = settings.oClasses; 9867 var table = settings.nTable; 9868 var tbody = settings.nTBody; 9869 var thead = settings.nTHead; 9870 var tfoot = settings.nTFoot; 9871 var jqTable = $(table); 9872 var jqTbody = $(tbody); 9873 var jqWrapper = $(settings.nTableWrapper); 9874 var rows = settings.aoData.map( function (r) { return r ? r.nTr : null; } ); 9875 var orderClasses = classes.order;
9876 9877 // Flag to note that the table is currently being destroyed - no action 9878 // should be taken 9879 settings.bDestroying = true; 9880 9881 // Fire off the destroy callbacks for plug-ins etc 9882 _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings], true ); 9883 9884 // If not being removed from the document, make all columns visible 9885 if ( ! remove ) { 9886 new _Api( settings ).columns().visible( true ); 9887 } 9888 9889 // Container width change listener 9890 if (settings.resizeObserver) { 9891 settings.resizeObserver.disconnect(); 9892 } 9893 9894 // Blitz all `DT` namespaced events (these are internal events, the 9895 // lowercase, `dt` events are user subscribed and they are responsible 9896 // for removing them 9897 jqWrapper.off('.DT').find(':not(tbody *)').off('.DT'); 9898 $(window).off('.DT-'+settings.sInstance); 9899 9900 // When scrolling we had to break the table up - restore it 9901 if ( table != thead.parentNode ) { 9902 jqTable.children('thead').detach(); 9903 jqTable.append( thead ); 9904 } 9905 9906 if ( tfoot && table != tfoot.parentNode ) { 9907 jqTable.children('tfoot').detach(); 9908 jqTable.append( tfoot ); 9909 } 9910 9911 // Clean up the header 9912 $(thead).find('span.dt-column-order').remove(); 9913 $(thead).find('span.dt-column-title').each(function () { 9914 var title = $(this).html(); 9915 $(this).parent().append(title); 9916 $(this).remove(); 9917 }); 9918 9919 settings.colgroup.remove(); 9920 9921 settings.aaSorting = []; 9922 settings.aaSortingFixed = []; 9923 _fnSortingClasses( settings ); 9924 9925 $(jqTable).find('th, td').removeClass( 9926 $.map(DataTable.ext.type.className, function (v) { 9927 return v; 9928 }).join(' ') 9929 ); 9930 9931 $('th, td', thead) 9932 .removeClass( 9933 orderClasses.none + ' ' + 9934 orderClasses.canAsc + ' ' + 9935 orderClasses.canDesc + ' ' + 9936 orderClasses.isAsc + ' ' + 9937 orderClasses.isDesc 9938 ) 9939 .css('width', '') 9940 .removeAttr('data-dt-column') 9941 .removeAttr('aria-sort'); 9942 9943 // Add the TR elements back into the table in their original order 9944 jqTbody.children().detach(); 9945 jqTbody.append( rows ); 9946 9947 var orig = settings.nTableWrapper.parentNode; 9948 var insertBefore = settings.nTableWrapper.nextSibling; 9949 9950 // Remove the DataTables generated nodes, events and classes 9951 var removedMethod = remove ? 'remove' : 'detach'; 9952 jqTable[ removedMethod ](); 9953 jqWrapper[ removedMethod ](); 9954 9955 // If we need to reattach the table to the document 9956 if ( ! remove && orig ) { 9957 // insertBefore acts like appendChild if !arg[1] 9958 orig.insertBefore( table, insertBefore ); 9959 9960 // Restore the width of the original table - was read from the style property, 9961 // so we can restore directly to that 9962 jqTable 9963 .css( 'width', settings.sDestroyWidth ) 9964 .removeClass( classes.table ); 9965 } 9966 9967 /* Remove the settings object from the settings array */ 9968 var idx = DataTable.settings.indexOf(settings); 9969 if ( idx !== -1 ) { 9970 DataTable.settings.splice( idx, 1 ); 9971 } 9972 } ); 9973 } ); 9974 9975 9976 // Add the `every()` method for rows, columns and cells in a compact form 9977 $.each( [ 'column', 'row', 'cell' ], function ( i, type ) { 9978 _api_register( type+'s().every()', function ( fn ) { 9979 var opts = this.selector.opts; 9980 var api = this; 9981 var inst; 9982 var counter = 0; 9983 9984 return this.iterator( 'every', function ( settings, selectedIdx, tableIdx ) { 9985 inst = api[ type ](selectedIdx, opts); 9986 9987 if (type === 'cell') { 9988 fn.call(inst, inst[0][0].row, inst[0][0].column, tableIdx, counter); 9989 } 9990 else { 9991 fn.call(inst, selectedIdx, tableIdx, counter); 9992 } 9993 9994 counter++; 9995 } ); 9996 } ); 9997 } ); 9998 9999 10000 // i18n method for extensions to be able to use the language object from the 10001 // DataTable 10002 _api_register( 'i18n()', function ( token, def, plural ) { 10003 var ctx = this.context[0]; 10004 var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage ); 10005 10006 if ( resolved === undefined ) { 10007 resolved = def; 10008 } 10009 10010 if ( $.isPlainObject( resolved ) ) { 10011 resolved = plural !== undefined && resolved[ plural ] !== undefined ? 10012 resolved[ plural ] : 10013 resolved._; 10014 } 10015 10016 return typeof resolved === 'string' 10017 ? resolved.replace( '%d', plural ) // nb: plural might be undefined, 10018 : resolved; 10019 } ); 10020 10021 /**
10022 * Version string for plug-ins to check compatibility. Allowed format is 10023 * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used 10024 * only for non-release builds. See https://semver.org/ for more information. 10025 * @member 10026 * @type string 10027 * @default Version number 10028 */ 10029 DataTable.version = "2.2.2"; 10030 10031 /** 10032 * Private data store, containing all of the settings objects that are 10033 * created for the tables on a given page. 10034 * 10035 * Note that the `DataTable.settings` object is aliased to 10036 * `jQuery.fn.dataTableExt` through which it may be accessed and 10037 * manipulated, or `jQuery.fn.dataTable.settings`. 10038 * @member 10039 * @type array 10040 * @default [] 10041 * @private 10042 */ 10043 DataTable.settings = []; 10044 10045 /** 10046 * Object models container, for the various models that DataTables has 10047 * available to it. These models define the objects that are used to hold 10048 * the active state and configuration of the table. 10049 * @namespace 10050 */ 10051 DataTable.models = {}; 10052 10053 10054 10055 /** 10056 * Template object for the way in which DataTables holds information about 10057 * search information for the global filter and individual column filters. 10058 * @namespace 10059 */ 10060 DataTable.models.oSearch = { 10061 /** 10062 * Flag to indicate if the filtering should be case insensitive or not 10063 */ 10064 "caseInsensitive": true, 10065 10066 /** 10067 * Applied search term 10068 */ 10069 "search": "", 10070 10071 /** 10072 * Flag to indicate if the search term should be interpreted as a 10073 * regular expression (true) or not (false) and therefore and special 10074 * regex characters escaped. 10075 */ 10076 "regex": false, 10077 10078 /** 10079 * Flag to indicate if DataTables is to use its smart filtering or not. 10080 */ 10081 "smart": true, 10082 10083 /** 10084 * Flag to indicate if DataTables should only trigger a search when 10085 * the return key is pressed. 10086 */ 10087 "return": false 10088 }; 10089 10090 10091 10092 10093 /** 10094 * Template object for the way in which DataTables holds information about 10095 * each individual row. This is the object format used for the settings 10096 * aoData array. 10097 * @namespace 10098 */ 10099 DataTable.models.oRow = { 10100 /** 10101 * TR element for the row 10102 */ 10103 "nTr": null, 10104 10105 /** 10106 * Array of TD elements for each row. This is null until the row has been 10107 * created. 10108 */ 10109 "anCells": null, 10110 10111 /** 10112 * Data object from the original data source for the row. This is either 10113 * an array if using the traditional form of DataTables, or an object if 10114 * using mData options. The exact type will depend on the passed in 10115 * data from the data source, or will be an array if using DOM a data 10116 * source. 10117 */ 10118 "_aData": [], 10119 10120 /** 10121 * Sorting data cache - this array is ostensibly the same length as the 10122 * number of columns (although each index is generated only as it is 10123 * needed), and holds the data that is used for sorting each column in the 10124 * row. We do this cache generation at the start of the sort in order that 10125 * the formatting of the sort data need be done only once for each cell 10126 * per sort. This array should not be read from or written to by anything 10127 * other than the master sorting methods. 10128 */ 10129 "_aSortData": null, 10130 10131 /** 10132 * Per cell filtering data cache. As per the sort data cache, used to 10133 * increase the performance of the filtering in DataTables 10134 */ 10135 "_aFilterData": null, 10136 10137 /** 10138 * Filtering data cache. This is the same as the cell filtering cache, but 10139 * in this case a string rather than an array. This is easily computed with 10140 * a join on `_aFilterData`, but is provided as a cache so the join isn't 10141 * needed on every search (memory traded for performance) 10142 */ 10143 "_sFilterRow": null, 10144 10145 /** 10146 * Denote if the original data source was from the DOM, or the data source 10147 * object. This is used for invalidating data, so DataTables can 10148 * automatically read data from the original source, unless uninstructed 10149 * otherwise. 10150 */ 10151 "src": null, 10152 10153 /** 10154 * Index in the aoData array. This saves an indexOf lookup when we have the 10155 * object, but want to know the index 10156 */ 10157 "idx": -1, 10158 10159 /** 10160 * Cached display value 10161 */ 10162 displayData: null 10163 }; 10164 10165 10166 /** 10167 * Template object for the column information object in DataTables. This object 10168 * is held in the settings aoColumns array and contains all the information that 10169 * DataTables needs about each individual column. 10170 * 10171 * Note that this object is related to {@link DataTable.defaults.column} 10172 * but this one is the internal data store for DataTables's cache of columns. 10173 * It should NOT be manipulated outside of DataTables. Any configuration should 10174 * be done through the initialisation options. 10175 * @namespace 10176 */ 10177 DataTable.models.oColumn = { 10178 /** 10179 * Column index. 10180 */ 10181 "idx": null, 10182 10183 /** 10184 * A list of the columns that sorting should occur on when this column 10185 * is sorted. That this property is an array allows multi-column s
10185orting 10186 * to be defined for a column (for example first name / last name columns 10187 * would benefit from this). The values are integers pointing to the 10188 * columns to be sorted on (typically it will be a single integer pointing 10189 * at itself, but that doesn't need to be the case). 10190 */ 10191 "aDataSort": null, 10192 10193 /** 10194 * Define the sorting directions that are applied to the column, in sequence 10195 * as the column is repeatedly sorted upon - i.e. the first value is used 10196 * as the sorting direction when the column if first sorted (clicked on). 10197 * Sort it again (click again) and it will move on to the next index. 10198 * Repeat until loop. 10199 */ 10200 "asSorting": null, 10201 10202 /** 10203 * Flag to indicate if the column is searchable, and thus should be included 10204 * in the filtering or not. 10205 */ 10206 "bSearchable": null, 10207 10208 /** 10209 * Flag to indicate if the column is sortable or not. 10210 */ 10211 "bSortable": null, 10212 10213 /** 10214 * Flag to indicate if the column is currently visible in the table or not 10215 */ 10216 "bVisible": null, 10217 10218 /** 10219 * Store for manual type assignment using the `column.type` option. This 10220 * is held in store so we can manipulate the column's `sType` property. 10221 */ 10222 "_sManualType": null, 10223 10224 /** 10225 * Flag to indicate if HTML5 data attributes should be used as the data 10226 * source for filtering or sorting. True is either are. 10227 */ 10228 "_bAttrSrc": false, 10229 10230 /** 10231 * Developer definable function that is called whenever a cell is created (Ajax source, 10232 * etc) or processed for input (DOM source). This can be used as a compliment to mRender 10233 * allowing you to modify the DOM element (add background colour for example) when the 10234 * element is available. 10235 */ 10236 "fnCreatedCell": null, 10237 10238 /** 10239 * Function to get data from a cell in a column. You should <b>never</b> 10240 * access data directly through _aData internally in DataTables - always use 10241 * the method attached to this property. It allows mData to function as 10242 * required. This function is automatically assigned by the column 10243 * initialisation method 10244 */ 10245 "fnGetData": null, 10246 10247 /** 10248 * Function to set data for a cell in the column. You should <b>never</b> 10249 * set the data directly to _aData internally in DataTables - always use 10250 * this method. It allows mData to function as required. This function 10251 * is automatically assigned by the column initialisation method 10252 */ 10253 "fnSetData": null, 10254 10255 /** 10256 * Property to read the value for the cells in the column from the data 10257 * source array / object. If null, then the default content is used, if a 10258 * function is given then the return from the function is used. 10259 */ 10260 "mData": null, 10261 10262 /** 10263 * Partner property to mData which is used (only when defined) to get 10264 * the data - i.e. it is basically the same as mData, but without the 10265 * 'set' option, and also the data fed to it is the result from mData. 10266 * This is the rendering method to match the data method of mData. 10267 */ 10268 "mRender": null, 10269 10270 /** 10271 * The class to apply to all TD elements in the table's TBODY for the column 10272 */ 10273 "sClass": null, 10274 10275 /** 10276 * When DataTables calculates the column widths to assign to each column, 10277 * it finds the longest string in each column and then constructs a 10278 * temporary table and reads the widths from that. The problem with this 10279 * is that "mmm" is much wider then "iiii", but the latter is a longer 10280 * string - thus the calculation can go wrong (doing it properly and putting 10281 * it into an DOM object and measuring that is horribly(!) slow). Thus as 10282 * a "work around" we provide this option. It will append its value to the 10283 * text that is found to be the longest string for the column - i.e. padding. 10284 */ 10285 "sContentPadding": null, 10286 10287 /** 10288 * Allows a default value to be given for a column's data, and will be used 10289 * whenever a null data source is encountered (this can be because mData 10290 * is set to null, or because the data source itself is null). 10291 */ 10292 "sDefaultContent": null, 10293 10294 /** 10295 * Name for the column, allowing reference to the column by name as well as 10296 * by index (needs a lookup to work by name). 10297 */ 10298 "sName": null, 10299 10300 /** 10301 * Custom sorting data type - defines which of the available plug-ins in 10302 * afnSortData the custom sorting will use - if any is defined. 10303 */ 10304 "sSortDataType": 'std', 10305 10306 /** 10307 * Class to be applied to the header element when sorting on this column 10308 */ 10309 "sSortingClass": null, 10310 10311 /** 10312 * Title of the column - what is seen in the TH element (nTh). 10313 */ 10314 "sTitle": null, 10315 10316 /** 10317 * Column sorting and filtering type 10318 */ 10319 "sType": null, 10320 10321 /** 10322 * Width of the column 10323 */ 10324 "sWidth": null, 10325 10326 /** 10327 * Width of the column when it was first "encountered" 10328 */ 10329 "sWidthOrig": null, 10330 10331 /** Cached string which is the longest in the column */ 10332 maxLenString: null, 10333 10334 /** 10335 * Store for named searches 10336 */ 10337 searchFixed: null 10338 }; 10339 10340 10341 /* 10342 * Developer note: The properties of the object below are given in Hungarian 10343 * notation, that was used as the interface for DataTables prior to v1.10, however 10344 * from v1.10 onwards the primary interface is camel case. In order to avoid 10345 * breaking backwards compatibility utterly with this change, the Hungarian
10346 * version is still, internally the primary interface, but is is not documented 10347 * - hence the @name tags in each doc comment. This allows a Javascript function 10348 * to create a map from Hungarian notation to camel case (going the other direction 10349 * would require each property to be listed, which would add around 3K to the size 10350 * of DataTables, while this method is about a 0.5K hit). 10351 * 10352 * Ultimately this does pave the way for Hungarian notation to be dropped 10353 * completely, but that is a massive amount of work and will break current 10354 * installs (therefore is on-hold until v2). 10355 */ 10356 10357 /** 10358 * Initialisation options that can be given to DataTables at initialisation 10359 * time. 10360 * @namespace 10361 */ 10362 DataTable.defaults = { 10363 /** 10364 * An array of data to use for the table, passed in at initialisation which 10365 * will be used in preference to any data which is already in the DOM. This is 10366 * particularly useful for constructing tables purely in Javascript, for 10367 * example with a custom Ajax call. 10368 */ 10369 "aaData": null, 10370 10371 10372 /** 10373 * If ordering is enabled, then DataTables will perform a first pass sort on 10374 * initialisation. You can define which column(s) the sort is performed 10375 * upon, and the sorting direction, with this variable. The `sorting` array 10376 * should contain an array for each column to be sorted initially containing 10377 * the column's index and a direction string ('asc' or 'desc'). 10378 */ 10379 "aaSorting": [[0,'asc']], 10380 10381 10382 /** 10383 * This parameter is basically identical to the `sorting` parameter, but 10384 * cannot be overridden by user interaction with the table. What this means 10385 * is that you could have a column (visible or hidden) which the sorting 10386 * will always be forced on first - any sorting after that (from the user) 10387 * will then be performed as required. This can be useful for grouping rows 10388 * together. 10389 */ 10390 "aaSortingFixed": [], 10391 10392 10393 /** 10394 * DataTables can be instructed to load data to display in the table from a 10395 * Ajax source. This option defines how that Ajax call is made and where to. 10396 * 10397 * The `ajax` property has three different modes of operation, depending on 10398 * how it is defined. These are: 10399 * 10400 * * `string` - Set the URL from where the data should be loaded from. 10401 * * `object` - Define properties for `jQuery.ajax`. 10402 * * `function` - Custom data get function 10403 * 10404 * `string` 10405 * -------- 10406 * 10407 * As a string, the `ajax` property simply defines the URL from which 10408 * DataTables will load data. 10409 * 10410 * `object` 10411 * -------- 10412 * 10413 * As an object, the parameters in the object are passed to 10414 * [jQuery.ajax](https://api.jquery.com/jQuery.ajax/) allowing fine control 10415 * of the Ajax request. DataTables has a number of default parameters which 10416 * you can override using this option. Please refer to the jQuery 10417 * documentation for a full description of the options available, although 10418 * the following parameters provide additional options in DataTables or 10419 * require special consideration: 10420 * 10421 * * `data` - As with jQuery, `data` can be provided as an object, but it 10422 * can also be used as a function to manipulate the data DataTables sends 10423 * to the server. The function takes a single parameter, an object of 10424 * parameters with the values that DataTables has readied for sending. An 10425 * object may be returned which will be merged into the DataTables 10426 * defaults, or you can add the items to the object that was passed in and 10427 * not return anything from the function. This supersedes `fnServerParams` 10428 * from DataTables 1.9-. 10429 * 10430 * * `dataSrc` - By default DataTables will look for the property `data` (or 10431 * `aaData` for compatibility with DataTables 1.9-) when obtaining data 10432 * from an Ajax source or for server-side processing - this parameter 10433 * allows that property to be changed. You can use Javascript dotted 10434 * object notation to get a data source for multiple levels of nesting, or 10435 * it my be used as a function. As a function it takes a single parameter, 10436 * the JSON returned from the server, which can be manipulated as 10437 * required, with the returned value being that used by DataTables as the 10438 * data source for the table. 10439 * 10440 * * `success` - Should not be overridden it is used internally in 10441 * DataTables. To manipulate / transform the data returned by the server 10442 * use `ajax.dataSrc`, or use `ajax` as a function (see below). 10443 * 10444 * `function` 10445 * ---------- 10446 * 10447 * As a function, making the Ajax call is left up to yourself allowing 10448 * complete control of the Ajax request. Indeed, if desired, a method other 10449 * than Ajax could be used to obtain the required data, such as Web storage 10450 * or an AIR database. 10451 * 10452 * The function is given four parameters and no return is required. The 10453 * parameters are: 10454 * 10455 * 1. _object_ - Data to send to the server 10456 * 2. _function_ - Callback function that must be executed when the required 10457 * data has been obtained. That data should be passed into the callback 10458 * as the only parameter 10459 * 3. _object_ - DataTables settings object for the table 10460 */ 10461 "ajax": null, 10462 10463 10464 /** 10465 * This parameter allows you to readily specify the entries in the length drop 10466 * down menu that DataTables shows when pagination is enabled. It can be 10467 * either a 1D array of options which will be used for both the displayed 10468 * option and the value, or a 2D array which will use the array in the first 10469 * position as the value, and the array in the second position as the 10470 * displayed options (useful for language strings such as 'All'). 10471 * 10472 * Note that the `pageLength` property will be automatically set to the 10473 * first value given in this array, unless `pageLength` is also provided. 10474 */ 10475 "aLengthMenu": [ 10, 25, 50, 100 ], 10476 10477 10478 /** 10479 * The `columns` option in the initialisation parameter allows you to define 10480 * details about the way individual columns behave. For a full list of 10481 * column options that can be set, please see 10482 * {@link DataTable.defaults.column}. Note that if you use `columns` to 10483 * define your columns, you must have an entry in the array for every single 10484 * column that you have in your table (these can be null if you don't which 10485 * to specify any options). 10486 */ 10487 "aoColumns": null, 10488 10489 /**
10490 * Very similar to `columns`, `columnDefs` allows you to target a specific 10491 * column, multiple columns, or all columns, using the `targets` property of 10492 * each object in the array. This allows great flexibility when creating 10493 * tables, as the `columnDefs` arrays can be of any length, targeting the 10494 * columns you specifically want. `columnDefs` may use any of the column 10495 * options available: {@link DataTable.defaults.column}, but it _must_ 10496 * have `targets` defined in each object in the array. Values in the `targets` 10497 * array may be: 10498 * <ul> 10499 * <li>a string - class name will be matched on the TH for the column</li> 10500 * <li>0 or a positive integer - column index counting from the left</li> 10501 * <li>a negative integer - column index counting from the right</li> 10502 * <li>the string "_all" - all columns (i.e. assign a default)</li> 10503 * </ul> 10504 */ 10505 "aoColumnDefs": null, 10506 10507 10508 /** 10509 * Basically the same as `search`, this parameter defines the individual column 10510 * filtering state at initialisation time. The array must be of the same size 10511 * as the number of columns, and each element be an object with the parameters 10512 * `search` and `escapeRegex` (the latter is optional). 'null' is also 10513 * accepted and the default will be used. 10514 */ 10515 "aoSearchCols": [], 10516 10517 10518 /** 10519 * Enable or disable automatic column width calculation. This can be disabled 10520 * as an optimisation (it takes some time to calculate the widths) if the 10521 * tables widths are passed in using `columns`. 10522 */ 10523 "bAutoWidth": true, 10524 10525 10526 /** 10527 * Deferred rendering can provide DataTables with a huge speed boost when you 10528 * are using an Ajax or JS data source for the table. This option, when set to 10529 * true, will cause DataTables to defer the creation of the table elements for 10530 * each row until they are needed for a draw - saving a significant amount of 10531 * time. 10532 */ 10533 "bDeferRender": true, 10534 10535 10536 /** 10537 * Replace a DataTable which matches the given selector and replace it with 10538 * one which has the properties of the new initialisation object passed. If no 10539 * table matches the selector, then the new DataTable will be constructed as 10540 * per normal. 10541 */ 10542 "bDestroy": false, 10543 10544 10545 /** 10546 * Enable or disable filtering of data. Filtering in DataTables is "smart" in 10547 * that it allows the end user to input multiple words (space separated) and 10548 * will match a row containing those words, even if not in the order that was 10549 * specified (this allow matching across multiple columns). Note that if you 10550 * wish to use filtering in DataTables this must remain 'true' - to remove the 10551 * default filtering input box and retain filtering abilities, please use 10552 * {@link DataTable.defaults.dom}. 10553 */ 10554 "bFilter": true, 10555 10556 /** 10557 * Used only for compatiblity with DT1 10558 * @deprecated 10559 */ 10560 "bInfo": true, 10561 10562 /** 10563 * Used only for compatiblity with DT1 10564 * @deprecated 10565 */ 10566 "bLengthChange": true, 10567 10568 /** 10569 * Enable or disable pagination. 10570 */ 10571 "bPaginate": true, 10572 10573 10574 /** 10575 * Enable or disable the display of a 'processing' indicator when the table is 10576 * being processed (e.g. a sort). This is particularly useful for tables with 10577 * large amounts of data where it can take a noticeable amount of time to sort 10578 * the entries. 10579 */ 10580 "bProcessing": false, 10581 10582 10583 /** 10584 * Retrieve the DataTables object for the given selector. Note that if the 10585 * table has already been initialised, this parameter will cause DataTables 10586 * to simply return the object that has already been set up - it will not take 10587 * account of any changes you might have made to the initialisation object 10588 * passed to DataTables (setting this parameter to true is an acknowledgement 10589 * that you understand this). `destroy` can be used to reinitialise a table if 10590 * you need. 10591 */ 10592 "bRetrieve": false, 10593 10594 10595 /** 10596 * When vertical (y) scrolling is enabled, DataTables will force the height of 10597 * the table's viewport to the given height at all times (useful for layout). 10598 * However, this can look odd when filtering data down to a small data set, 10599 * and the footer is left "floating" further down. This parameter (when 10600 * enabled) will cause DataTables to collapse the table's viewport down when 10601 * the result set will fit within the given Y height. 10602 */
10603 "bScrollCollapse": false, 10604 10605 10606 /** 10607 * Configure DataTables to use server-side processing. Note that the 10608 * `ajax` parameter must also be given in order to give DataTables a 10609 * source to obtain the required data for each draw. 10610 */ 10611 "bServerSide": false, 10612 10613 10614 /** 10615 * Enable or disable sorting of columns. Sorting of individual columns can be 10616 * disabled by the `sortable` option for each column. 10617 */ 10618 "bSort": true, 10619 10620 10621 /** 10622 * Enable or display DataTables' ability to sort multiple columns at the 10623 * same time (activated by shift-click by the user). 10624 */ 10625 "bSortMulti": true, 10626 10627 10628 /** 10629 * Allows control over whether DataTables should use the top (true) unique 10630 * cell that is found for a single column, or the bottom (false - default). 10631 * This is useful when using complex headers. 10632 */ 10633 "bSortCellsTop": null, 10634 10635 10636 /** 10637 * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and 10638 * `sorting\_3` to the columns which are currently being sorted on. This is 10639 * presented as a feature switch as it can increase processing time (while 10640 * classes are removed and added) so for large data sets you might want to 10641 * turn this off. 10642 */ 10643 "bSortClasses": true, 10644 10645 10646 /** 10647 * Enable or disable state saving. When enabled HTML5 `localStorage` will be 10648 * used to save table display information such as pagination information, 10649 * display length, filtering and sorting. As such when the end user reloads 10650 * the page the display display will match what thy had previously set up. 10651 */ 10652 "bStateSave": false, 10653 10654 10655 /** 10656 * This function is called when a TR element is created (and all TD child 10657 * elements have been inserted), or registered if using a DOM source, allowing 10658 * manipulation of the TR element (adding classes etc). 10659 */ 10660 "fnCreatedRow": null, 10661 10662 10663 /** 10664 * This function is called on every 'draw' event, and allows you to 10665 * dynamically modify any aspect you want about the created DOM. 10666 */ 10667 "fnDrawCallback": null, 10668 10669 10670 /** 10671 * Identical to fnHeaderCallback() but for the table footer this function 10672 * allows you to modify the table footer on every 'draw' event. 10673 */ 10674 "fnFooterCallback": null, 10675 10676 10677 /** 10678 * When rendering large numbers in the information element for the table 10679 * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers 10680 * to have a comma separator for the 'thousands' units (e.g. 1 million is 10681 * rendered as "1,000,000") to help readability for the end user. This 10682 * function will override the default method DataTables uses. 10683 */ 10684 "fnFormatNumber": function ( toFormat ) { 10685 return toFormat.toString().replace( 10686 /\B(?=(\d{3})+(?!\d))/g, 10687 this.oLanguage.sThousands 10688 ); 10689 }, 10690 10691 10692 /** 10693 * This function is called on every 'draw' event, and allows you to 10694 * dynamically modify the header row. This can be used to calculate and 10695 * display useful information about the table. 10696 */ 10697 "fnHeaderCallback": null, 10698 10699 10700 /** 10701 * The information element can be used to convey information about the current 10702 * state of the table. Although the internationalisation options presented by 10703 * DataTables are quite capable of dealing with most customisations, there may 10704 * be times where you wish to customise the string further. This callback 10705 * allows you to do exactly that. 10706 */ 10707 "fnInfoCallback": null, 10708 10709 10710 /** 10711 * Called when the table has been initialised. Normally DataTables will 10712 * initialise sequentially and there will be no need for this function, 10713 * however, this does not hold true when using external language information 10714 * since that is obtained using an async XHR call. 10715 */ 10716 "fnInitComplete": null, 10717 10718 10719 /** 10720 * Called at the very start of each table draw and can be used to cancel the 10721 * draw by returning false, any other return (including undefined) results in 10722 * the full draw occurring). 10723 */ 10724 "fnPreDrawCallback": null, 10725 10726 10727 /** 10728 * This function allows you to 'post process' each row after it have been 10729 * generated for each table draw, but before it is rendered on screen. This 10730 * function might be used for setting the row class name etc. 10731 */ 10732 "fnRowCallback": null, 10733 10734 10735 /** 10736 * Load the table state. With this function you can define from where, and how, the 10737 * state of a table is loaded. By default DataTables will load from `localStorage` 10738 * but you might wish to use a server-side database or cookies. 10739 */ 10740 "fnStateLoadCallback": function ( settings ) { 10741 try { 10742 return JSON.parse(
10743 (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem( 10744 'DataTables_'+settings.sInstance+'_'+location.pathname 10745 ) 10746 ); 10747 } catch (e) { 10748 return {}; 10749 } 10750 }, 10751 10752 10753 /** 10754 * Callback which allows modification of the saved state prior to loading that state. 10755 * This callback is called when the table is loading state from the stored data, but 10756 * prior to the settings object being modified by the saved state. Note that for 10757 * plug-in authors, you should use the `stateLoadParams` event to load parameters for 10758 * a plug-in. 10759 */ 10760 "fnStateLoadParams": null, 10761 10762 10763 /** 10764 * Callback that is called when the state has been loaded from the state saving method 10765 * and the DataTables settings object has been modified as a result of the loaded state. 10766 */ 10767 "fnStateLoaded": null, 10768 10769 10770 /** 10771 * Save the table state. This function allows you to define where and how the state 10772 * information for the table is stored By default DataTables will use `localStorage` 10773 * but you might wish to use a server-side database or cookies. 10774 */ 10775 "fnStateSaveCallback": function ( settings, data ) { 10776 try { 10777 (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem( 10778 'DataTables_'+settings.sInstance+'_'+location.pathname, 10779 JSON.stringify( data ) 10780 ); 10781 } catch (e) { 10782 // noop 10783 } 10784 }, 10785 10786 10787 /** 10788 * Callback which allows modification of the state to be saved. Called when the table 10789 * has changed state a new state save is required. This method allows modification of 10790 * the state saving object prior to actually doing the save, including addition or 10791 * other state properties or modification. Note that for plug-in authors, you should 10792 * use the `stateSaveParams` event to save parameters for a plug-in. 10793 */ 10794 "fnStateSaveParams": null, 10795 10796 10797 /** 10798 * Duration for which the saved state information is considered valid. After this period 10799 * has elapsed the state will be returned to the default. 10800 * Value is given in seconds. 10801 */ 10802 "iStateDuration": 7200, 10803 10804 10805 /** 10806 * Number of rows to display on a single page when using pagination. If 10807 * feature enabled (`lengthChange`) then the end user will be able to override 10808 * this to a custom setting using a pop-up menu. 10809 */ 10810 "iDisplayLength": 10, 10811 10812 10813 /** 10814 * Define the starting point for data display when using DataTables with 10815 * pagination. Note that this parameter is the number of records, rather than 10816 * the page number, so if you have 10 records per page and want to start on 10817 * the third page, it should be "20". 10818 */ 10819 "iDisplayStart": 0, 10820 10821 10822 /** 10823 * By default DataTables allows keyboard navigation of the table (sorting, paging, 10824 * and filtering) by adding a `tabindex` attribute to the required elements. This 10825 * allows you to tab through the controls and press the enter key to activate them. 10826 * The tabindex is default 0, meaning that the tab follows the flow of the document. 10827 * You can overrule this using this parameter if you wish. Use a value of -1 to 10828 * disable built-in keyboard navigation. 10829 */ 10830 "iTabIndex": 0, 10831 10832 10833 /** 10834 * Classes that DataTables assigns to the various components and features 10835 * that it adds to the HTML table. This allows classes to be configured 10836 * during initialisation in addition to through the static 10837 * {@link DataTable.ext.oStdClasses} object). 10838 */ 10839 "oClasses": {}, 10840 10841 10842 /** 10843 * All strings that DataTables uses in the user interface that it creates 10844 * are defined in this object, allowing you to modified them individually or 10845 * completely replace them all as required. 10846 */ 10847 "oLanguage": { 10848 /** 10849 * Strings that are used for WAI-ARIA labels and controls only (these are not 10850 * actually visible on the page, but will be read by screenreaders, and thus 10851 * must be internationalised as well). 10852 */ 10853 "oAria": { 10854 /** 10855 * ARIA label that is added to the table headers when the column may be sorted 10856 */ 10857 "orderable": ": Activate to sort", 10858 10859 /** 10860 * ARIA label that is added to the table headers when the column is currently being sorted 10861 */ 10862 "orderableReverse": ": Activate to invert sorting", 10863 10864 /** 10865 * ARIA label that is added to the table headers when the column is currently being 10866 * sorted and next step is to remove sorting 10867 */ 10868 "orderableRemove": ": Activate to remove sorting", 10869 10870 paginate: { 10871 first: 'First', 10872 last: 'Last', 10873 next: 'Next', 10874 previous: 'Previous', 10875 number: '' 10876 } 10877 }, 10878 10879 /** 10880 * Pagination string used by DataTables for the built-in pagination 10881 * control types. 10882 */ 10883 "oPaginate": { 10884 /** 10885 * Label and character for first page button («) 10886 */ 10887 "sFirst": "\u00AB", 10888 10889 /** 10890 * Last page button (») 10891 */ 10892 "sLast": "\u00BB", 10893 10894 /** 10895 * Next page button (âº) 10896 */ 10897 "sNext": "\u203A", 10898 10899 /** 10900 * Previous page button (â¹) 10901 */ 10902 "sPrevious": "\u2039", 10903 }, 10904 10905 /** 10906 * Plural object for the data type the table is showing 10907 */ 10908 entries: { 10909 _: "entries", 10910 1: "entry" 10911 }, 10912 10913 /** 10914 * This string is shown in preference to `zeroRecords` when the table is 10915 * empty of data (regardless of filtering). Note that this is an optional 10916 * parameter - if it is not given, the value of `zeroRecords` will be used 10917 * instead (either the default or given value). 10918 */ 10919 "sEmptyTable": "No data available in table", 10920 10921 10922 /** 10923 * This string gives information to the end user about the information 10924 * that is current on display on the page. The following tokens can be 10925 * used in the string and will be dynamically replaced as the table 10926 * display updates. This tokens can be placed anywhere in the string, or 10927 * removed as needed by the language requires: 10928 * 10929 * * `\_START\_` - Display index of the first record on the current page 10930 * * `\_END\_` - Display index of the last record on the current page 10931 * * `\_TOTAL\_` - Number of records in the table after filtering 10932 * * `\_MAX\_` - Number of records in the table without filtering 10933 * * `\_PAGE\_` - Current page number 10934 * * `\_PAGES\_` - Total number of pages of data in the table 10935 */ 10936 "sInfo": "Showing _START_ to _END_ of _TOTAL_ _ENTRIES-TOTAL_", 10937 10938 10939 /**
10940 * Display information string for when the table is empty. Typically the 10941 * format of this string should match `info`. 10942 */ 10943 "sInfoEmpty": "Showing 0 to 0 of 0 _ENTRIES-TOTAL_", 10944 10945 10946 /** 10947 * When a user filters the information in a table, this string is appended 10948 * to the information (`info`) to give an idea of how strong the filtering 10949 * is. The variable _MAX_ is dynamically updated. 10950 */ 10951 "sInfoFiltered": "(filtered from _MAX_ total _ENTRIES-MAX_)", 10952 10953 10954 /** 10955 * If can be useful to append extra information to the info string at times, 10956 * and this variable does exactly that. This information will be appended to 10957 * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are 10958 * being used) at all times. 10959 */ 10960 "sInfoPostFix": "", 10961 10962 10963 /** 10964 * This decimal place operator is a little different from the other 10965 * language options since DataTables doesn't output floating point 10966 * numbers, so it won't ever use this for display of a number. Rather, 10967 * what this parameter does is modify the sort methods of the table so 10968 * that numbers which are in a format which has a character other than 10969 * a period (`.`) as a decimal place will be sorted numerically. 10970 * 10971 * Note that numbers with different decimal places cannot be shown in 10972 * the same table and still be sortable, the table must be consistent. 10973 * However, multiple different tables on the page can use different 10974 * decimal place characters. 10975 */ 10976 "sDecimal": "", 10977 10978 10979 /** 10980 * DataTables has a build in number formatter (`formatNumber`) which is 10981 * used to format large numbers that are used in the table information. 10982 * By default a comma is used, but this can be trivially changed to any 10983 * character you wish with this parameter. 10984 */ 10985 "sThousands": ",", 10986 10987 10988 /** 10989 * Detail the action that will be taken when the drop down menu for the 10990 * pagination length option is changed. The '_MENU_' variable is replaced 10991 * with a default select list of 10, 25, 50 and 100, and can be replaced 10992 * with a custom select box if required. 10993 */ 10994 "sLengthMenu": "_MENU_ _ENTRIES_ per page", 10995 10996 10997 /** 10998 * When using Ajax sourced data and during the first draw when DataTables is 10999 * gathering the data, this message is shown in an empty row in the table to 11000 * indicate to the end user the the data is being loaded. Note that this 11001 * parameter is not used when loading data by server-side processing, just 11002 * Ajax sourced data with client-side processing. 11003 */ 11004 "sLoadingRecords": "Loading...", 11005 11006 11007 /** 11008 * Text which is displayed when the table is processing a user action 11009 * (usually a sort command or similar). 11010 */ 11011 "sProcessing": "", 11012 11013 11014 /** 11015 * Details the actions that will be taken when the user types into the 11016 * filtering input text box. The variable "_INPUT_", if used in the string, 11017 * is replaced with the HTML text box for the filtering input allowing 11018 * control over where it appears in the string. If "_INPUT_" is not given 11019 * then the input box is appended to the string automatically. 11020 */ 11021 "sSearch": "Search:", 11022 11023 11024 /** 11025 * Assign a `placeholder` attribute to the search `input` element 11026 * @type string 11027 * @default 11028 * 11029 * @dtopt Language 11030 * @name DataTable.defaults.language.searchPlaceholder 11031 */ 11032 "sSearchPlaceholder": "", 11033 11034 11035 /** 11036 * All of the language information can be stored in a file on the 11037 * server-side, which DataTables will look up if this parameter is passed. 11038 * It must store the URL of the language file, which is in a JSON format, 11039 * and the object has the same properties as the oLanguage object in the 11040 * initialiser object (i.e. the above parameters). Please refer to one of 11041 * the example language files to see how this works in action. 11042 */ 11043 "sUrl": "", 11044 11045 11046 /** 11047 * Text shown inside the table records when the is no information to be 11048 * displayed after filtering. `emptyTable` is shown when there is simply no 11049 * information in the table at all (regardless of filtering). 11050 */
11051 "sZeroRecords": "No matching records found" 11052 }, 11053 11054 11055 /** The initial data order is reversed when `desc` ordering */ 11056 orderDescReverse: true, 11057 11058 11059 /** 11060 * This parameter allows you to have define the global filtering state at 11061 * initialisation time. As an object the `search` parameter must be 11062 * defined, but all other parameters are optional. When `regex` is true, 11063 * the search string will be treated as a regular expression, when false 11064 * (default) it will be treated as a straight string. When `smart` 11065 * DataTables will use it's smart filtering methods (to word match at 11066 * any point in the data), when false this will not be done. 11067 */ 11068 "oSearch": $.extend( {}, DataTable.models.oSearch ), 11069 11070 11071 /** 11072 * Table and control layout. This replaces the legacy `dom` option. 11073 */ 11074 layout: { 11075 topStart: 'pageLength', 11076 topEnd: 'search', 11077 bottomStart: 'info', 11078 bottomEnd: 'paging' 11079 }, 11080 11081 11082 /** 11083 * Legacy DOM layout option 11084 */ 11085 "sDom": null, 11086 11087 11088 /** 11089 * Search delay option. This will throttle full table searches that use the 11090 * DataTables provided search input element (it does not effect calls to 11091 * `dt-api search()`, providing a delay before the search is made. 11092 */ 11093 "searchDelay": null, 11094 11095 11096 /** 11097 * DataTables features six different built-in options for the buttons to 11098 * display for pagination control: 11099 * 11100 * * `numbers` - Page number buttons only 11101 * * `simple` - 'Previous' and 'Next' buttons only 11102 * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers 11103 * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons 11104 * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers 11105 * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers 11106 */ 11107 "sPaginationType": "", 11108 11109 11110 /** 11111 * Enable horizontal scrolling. When a table is too wide to fit into a 11112 * certain layout, or you have a large number of columns in the table, you 11113 * can enable x-scrolling to show the table in a viewport, which can be 11114 * scrolled. This property can be `true` which will allow the table to 11115 * scroll horizontally when needed, or any CSS unit, or a number (in which 11116 * case it will be treated as a pixel measurement). Setting as simply `true` 11117 * is recommended. 11118 */ 11119 "sScrollX": "", 11120 11121 11122 /** 11123 * This property can be used to force a DataTable to use more width than it 11124 * might otherwise do when x-scrolling is enabled. For example if you have a 11125 * table which requires to be well spaced, this parameter is useful for 11126 * "over-sizing" the table, and thus forcing scrolling. This property can by 11127 * any CSS unit, or a number (in which case it will be treated as a pixel 11128 * measurement). 11129 */ 11130 "sScrollXInner": "", 11131 11132 11133 /** 11134 * Enable vertical scrolling. Vertical scrolling will constrain the DataTable 11135 * to the given height, and enable scrolling for any data which overflows the 11136 * current viewport. This can be used as an alternative to paging to display 11137 * a lot of data in a small area (although paging and scrolling can both be 11138 * enabled at the same time). This property can be any CSS unit, or a number 11139 * (in which case it will be treated as a pixel measurement). 11140 */ 11141 "sScrollY": "", 11142 11143 11144 /** 11145 * __Deprecated__ The functionality provided by this parameter has now been 11146 * superseded by that provided through `ajax`, which should be used instead. 11147 * 11148 * Set the HTTP method that is used to make the Ajax call for server-side 11149 * processing or Ajax sourced data. 11150 */ 11151 "sServerMethod": "GET", 11152 11153 11154 /** 11155 * DataTables makes use of renderers when displaying HTML elements for 11156 * a table. These renderers can be added or modified by plug-ins to 11157 * generate suitable mark-up for a site. For example the Bootstrap 11158 * integration plug-in for DataTables uses a paging button renderer to 11159 * display pagination buttons in the mark-up required by Bootstrap. 11160 * 11161 * For further information about the renderers available see 11162 * DataTable.ext.renderer 11163 */ 11164 "renderer": null, 11165 11166 11167 /** 11168 * Set the data property name that DataTables should use to get a row's id 11169 * to set as the `id` property in the node. 11170 */ 11171 "rowId": "DT_RowId", 11172 11173 11174 /** 11175 * Caption value 11176 */ 11177 "caption": null, 11178 11179 11180 /** 11181 * For server-side processing - use the data from the DOM for the first draw 11182 */ 11183 iDeferLoading: null 11184 }; 11185 11186 _fnHungarianMap( DataTable.defaults ); 11187 11188 11189 11190 /* 11191 * Developer note - See note in model.defaults.js about the use of Hungarian 11192 * notation and camel case. 11193 */ 11194 11195 /** 11196 * Column options that can be given to DataTables at initialisation time. 11197 * @namespace 11198 */ 11199 DataTable.defaults.column = { 11200 /** 11201 * Define which column(s) an order will occur on for this column. This 11202 * allows a column's ordering to take multiple columns into account when
11203 * doing a sort or use the data from a different column. For example first 11204 * name / last name columns make sense to do a multi-column sort over the 11205 * two columns. 11206 */ 11207 "aDataSort": null, 11208 "iDataSort": -1, 11209 11210 ariaTitle: '', 11211 11212 11213 /** 11214 * You can control the default ordering direction, and even alter the 11215 * behaviour of the sort handler (i.e. only allow ascending ordering etc) 11216 * using this parameter. 11217 */ 11218 "asSorting": [ 'asc', 'desc', '' ], 11219 11220 11221 /** 11222 * Enable or disable filtering on the data in this column. 11223 */ 11224 "bSearchable": true, 11225 11226 11227 /** 11228 * Enable or disable ordering on this column. 11229 */ 11230 "bSortable": true, 11231 11232 11233 /** 11234 * Enable or disable the display of this column. 11235 */ 11236 "bVisible": true, 11237 11238 11239 /** 11240 * Developer definable function that is called whenever a cell is created (Ajax source, 11241 * etc) or processed for input (DOM source). This can be used as a compliment to mRender 11242 * allowing you to modify the DOM element (add background colour for example) when the 11243 * element is available. 11244 */ 11245 "fnCreatedCell": null, 11246 11247 11248 /** 11249 * This property can be used to read data from any data source property, 11250 * including deeply nested objects / properties. `data` can be given in a 11251 * number of different ways which effect its behaviour: 11252 * 11253 * * `integer` - treated as an array index for the data source. This is the 11254 * default that DataTables uses (incrementally increased for each column). 11255 * * `string` - read an object property from the data source. There are 11256 * three 'special' options that can be used in the string to alter how 11257 * DataTables reads the data from the source object: 11258 * * `.` - Dotted Javascript notation. Just as you use a `.` in 11259 * Javascript to read from nested objects, so to can the options 11260 * specified in `data`. For example: `browser.version` or 11261 * `browser.name`. If your object parameter name contains a period, use 11262 * `\\` to escape it - i.e. `first\\.name`. 11263 * * `[]` - Array notation. DataTables can automatically combine data 11264 * from and array source, joining the data with the characters provided 11265 * between the two brackets. For example: `name[, ]` would provide a 11266 * comma-space separated list from the source array. If no characters 11267 * are provided between the brackets, the original array source is 11268 * returned. 11269 * * `()` - Function notation. Adding `()` to the end of a parameter will 11270 * execute a function of the name given. For example: `browser()` for a 11271 * simple function on the data source, `browser.version()` for a 11272 * function in a nested property or even `browser().version` to get an 11273 * object property if the function called returns an object. Note that 11274 * function notation is recommended for use in `render` rather than 11275 * `data` as it is much simpler to use as a renderer. 11276 * * `null` - use the original data source for the row rather than plucking 11277 * data directly from it. This action has effects on two other 11278 * initialisation options: 11279 * * `defaultContent` - When null is given as the `data` option and 11280 * `defaultContent` is specified for the column, the value defined by 11281 * `defaultContent` will be used for the cell. 11282 * * `render` - When null is used for the `data` option and the `render` 11283 * option is specified for the column, the whole data source for the 11284 * row is used for the renderer. 11285 * * `function` - the function given will be executed whenever DataTables 11286 * needs to set or get the data for a cell in the column. The function 11287 * takes three parameters: 11288 * * Parameters: 11289 * * `{array|object}` The data source for the row 11290 * * `{string}` The type call data requested - this will be 'set' when 11291 * setting data or 'filter', 'display', 'type', 'sort' or undefined 11292 * when gathering data. Note that when `undefined` is given for the 11293 * type DataTables expects to get the raw data for the object back< 11294 * * `{*}` Data to set when the second parameter is 'set'. 11295 * * Return: 11296 * * The return value from the function is not required when '
11296set' is 11297 * the type of call, but otherwise the return is what will be used 11298 * for the data requested. 11299 * 11300 * Note that `data` is a getter and setter option. If you just require 11301 * formatting of data for output, you will likely want to use `render` which 11302 * is simply a getter and thus simpler to use. 11303 * 11304 * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The 11305 * name change reflects the flexibility of this property and is consistent 11306 * with the naming of mRender. If 'mDataProp' is given, then it will still 11307 * be used by DataTables, as it automatically maps the old name to the new 11308 * if required. 11309 */ 11310 "mData": null, 11311 11312 11313 /** 11314 * This property is the rendering partner to `data` and it is suggested that 11315 * when you want to manipulate data for display (including filtering, 11316 * sorting etc) without altering the underlying data for the table, use this 11317 * property. `render` can be considered to be the the read only companion to 11318 * `data` which is read / write (then as such more complex). Like `data` 11319 * this option can be given in a number of different ways to effect its 11320 * behaviour: 11321 * 11322 * * `integer` - treated as an array index for the data source. This is the 11323 * default that DataTables uses (incrementally increased for each column). 11324 * * `string` - read an object property from the data source. There are 11325 * three 'special' options that can be used in the string to alter how 11326 * DataTables reads the data from the source object: 11327 * * `.` - Dotted Javascript notation. Just as you use a `.` in 11328 * Javascript to read from nested objects, so to can the options 11329 * specified in `data`. For example: `browser.version` or 11330 * `browser.name`. If your object parameter name contains a period, use 11331 * `\\` to escape it - i.e. `first\\.name`. 11332 * * `[]` - Array notation. DataTables can automatically combine data 11333 * from and array source, joining the data with the characters provided 11334 * between the two brackets. For example: `name[, ]` would provide a 11335 * comma-space separated list from the source array. If no characters 11336 * are provided between the brackets, the original array source is 11337 * returned. 11338 * * `()` - Function notation. Adding `()` to the end of a parameter will 11339 * execute a function of the name given. For example: `browser()` for a 11340 * simple function on the data source, `browser.version()` for a 11341 * function in a nested property or even `browser().version` to get an 11342 * object property if the function called returns an object. 11343 * * `object` - use different data for the different data types requested by 11344 * DataTables ('filter', 'display', 'type' or 'sort'). The property names 11345 * of the object is the data type the property refers to and the value can 11346 * defined using an integer, string or function using the same rules as 11347 * `render` normally does. Note that an `_` option _must_ be specified. 11348 * This is the default value to use if you haven't specified a value for 11349 * the data type requested by DataTables. 11350 * * `function` - the function given will be executed whenever DataTables 11351 * needs to set or get the data for a cell in the column. The function 11352 * takes three parameters: 11353 * * Parameters: 11354 * * {array|object} The data source for the row (based on `data`) 11355 * * {string} The type call data requested - this will be 'filter', 11356 * 'display', 'type' or 'sort'. 11357 * * {array|object} The full data source for the row (not based on 11358 * `data`) 11359 * * Return: 11360 * * The return value from the function is what will be used for the 11361 * data requested. 11362 */ 11363 "mRender": null, 11364 11365 11366 /** 11367 * Change the cell type created for the column - either TD cells or TH cells. This 11368 * can be useful as TH cells have semantic meaning in the table body, allowing them 11369 * to act as a header for a row (you may wish to add scope='row' to the TH elements). 11370 */ 11371 "sCellType": "td", 11372 11373 11374 /** 11375 * Class to give to each cell in this column. 11376 */ 11377 "sClass": "", 11378 11379 /** 11380 * When DataTables calculates the column widths to assign to each column, 11381 * it finds the longest string in each column and then constructs a 11382 * temporary table and reads the widths from that. The problem with this 11383 * is that "mmm" is much wider then "iiii", but the latter is a longer 11384 * string - thus the calculation can go wrong (doing it properly and putting 11385 * it into an DOM object and measuring that is horribly(!) slow). Thus as 11386 * a "work around" we provide this option. It will append its value to the 11387 * text that is found to be the longest string for the column - i.e. padding. 11388 * Generally you shouldn't need this! 11389 */ 11390 "sContentPadding": "", 11391 11392 11393 /** 11394 * Allows a default value to be given for a column's data, and will be used 11395 * whenever a null data source is encountered (this can be because `data` 11396 * is set to null, or because the data source itself is null). 11397 */ 11398 "sDefaultContent": null, 11399 11400 11401 /** 11402 * This parameter is only used in DataTables' server-side processing. It can 11403 * be exceptionally useful to know what columns are being displayed on the 11404 * client side, and to map these to database fields. When defined, the names 11405 * also allow DataTables to reorder information from the server if it comes 11406 * back in an unexpected order (i.e. if you switch your columns around on the 11407 * client-side, your server-side code does not also need updating). 11408 */ 11409 "sName": "", 11410 11411 11412 /** 11413 * Defines a data source type for the ordering which can be used to read 11414 * real-time information from the table (updating the internally cached 11415 * version) prior to ordering. This allows ordering to occur on user 11416 * editable elements such as form inputs. 11417 */ 11418 "sSortDataType": "std", 11419 11420 11421 /** 11422 * The title of this column. 11423 */ 11424 "sTitle": null, 11425 11426 11427 /** 11428 * The type allows you to specify how the data for this column will be
11429 * ordered. Four types (string, numeric, date and html (which will strip 11430 * HTML tags before ordering)) are currently available. Note that only date 11431 * formats understood by Javascript's Date() object will be accepted as type 11432 * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string', 11433 * 'numeric', 'date' or 'html' (by default). Further types can be adding 11434 * through plug-ins. 11435 */ 11436 "sType": null, 11437 11438 11439 /** 11440 * Defining the width of the column, this parameter may take any CSS value 11441 * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not 11442 * been given a specific width through this interface ensuring that the table 11443 * remains readable. 11444 */ 11445 "sWidth": null 11446 }; 11447 11448 _fnHungarianMap( DataTable.defaults.column ); 11449 11450 11451 11452 /** 11453 * DataTables settings object - this holds all the information needed for a 11454 * given table, including configuration, data and current application of the 11455 * table options. DataTables does not have a single instance for each DataTable 11456 * with the settings attached to that instance, but rather instances of the 11457 * DataTable "class" are created on-the-fly as needed (typically by a 11458 * $().dataTable() call) and the settings object is then applied to that 11459 * instance. 11460 * 11461 * Note that this object is related to {@link DataTable.defaults} but this 11462 * one is the internal data store for DataTables's cache of columns. It should 11463 * NOT be manipulated outside of DataTables. Any configuration should be done 11464 * through the initialisation options. 11465 */ 11466 DataTable.models.oSettings = { 11467 /** 11468 * Primary features of DataTables and their enablement state. 11469 */ 11470 "oFeatures": { 11471 11472 /** 11473 * Flag to say if DataTables should automatically try to calculate the 11474 * optimum table and columns widths (true) or not (false). 11475 * Note that this parameter will be set by the initialisation routine. To 11476 * set a default use {@link DataTable.defaults}. 11477 */ 11478 "bAutoWidth": null, 11479 11480 /** 11481 * Delay the creation of TR and TD elements until they are actually 11482 * needed by a driven page draw. This can give a significant speed 11483 * increase for Ajax source and Javascript source data, but makes no 11484 * difference at all for DOM and server-side processing tables. 11485 * Note that this parameter will be set by the initialisation routine. To 11486 * set a default use {@link DataTable.defaults}. 11487 */ 11488 "bDeferRender": null, 11489 11490 /** 11491 * Enable filtering on the table or not. Note that if this is disabled 11492 * then there is no filtering at all on the table, including fnFilter. 11493 * To just remove the filtering input use sDom and remove the 'f' option. 11494 * Note that this parameter will be set by the initialisation routine. To 11495 * set a default use {@link DataTable.defaults}. 11496 */ 11497 "bFilter": null, 11498 11499 /** 11500 * Used only for compatiblity with DT1 11501 * @deprecated 11502 */ 11503 "bInfo": true, 11504 11505 /** 11506 * Used only for compatiblity with DT1 11507 * @deprecated 11508 */ 11509 "bLengthChange": true, 11510 11511 /** 11512 * Pagination enabled or not. Note that if this is disabled then length 11513 * changing must also be disabled. 11514 * Note that this parameter will be set by the initialisation routine. To 11515 * set a default use {@link DataTable.defaults}. 11516 */ 11517 "bPaginate": null, 11518 11519 /** 11520 * Processing indicator enable flag whenever DataTables is enacting a 11521 * user request - typically an Ajax request for server-side processing. 11522 * Note that this parameter will be set by the initialisation routine. To 11523 * set a default use {@link DataTable.defaults}. 11524 */ 11525 "bProcessing": null, 11526 11527 /** 11528 * Server-side processing enabled flag - when enabled DataTables will 11529 * get all data from the server for every draw - there is no filtering, 11530 * sorting or paging done on the client-side. 11531 * Note that this parameter will be set by the initialisation routine. To 11532 * set a default use {@link DataTable.defaults}. 11533 */ 11534 "bServerSide": null, 11535 11536 /** 11537 * Sorting enablement flag. 11538 * Note that this parameter will be set by the initialisation routine. To 11539 * set a default use {@link DataTable.defaults}. 11540 */ 11541 "bSort": null, 11542 11543 /**
11544 * Multi-column sorting 11545 * Note that this parameter will be set by the initialisation routine. To 11546 * set a default use {@link DataTable.defaults}. 11547 */ 11548 "bSortMulti": null, 11549 11550 /** 11551 * Apply a class to the columns which are being sorted to provide a 11552 * visual highlight or not. This can slow things down when enabled since 11553 * there is a lot of DOM interaction. 11554 * Note that this parameter will be set by the initialisation routine. To 11555 * set a default use {@link DataTable.defaults}. 11556 */ 11557 "bSortClasses": null, 11558 11559 /** 11560 * State saving enablement flag. 11561 * Note that this parameter will be set by the initialisation routine. To 11562 * set a default use {@link DataTable.defaults}. 11563 */ 11564 "bStateSave": null 11565 }, 11566 11567 11568 /** 11569 * Scrolling settings for a table. 11570 */ 11571 "oScroll": { 11572 /** 11573 * When the table is shorter in height than sScrollY, collapse the 11574 * table container down to the height of the table (when true). 11575 * Note that this parameter will be set by the initialisation routine. To 11576 * set a default use {@link DataTable.defaults}. 11577 */ 11578 "bCollapse": null, 11579 11580 /** 11581 * Width of the scrollbar for the web-browser's platform. Calculated 11582 * during table initialisation. 11583 */ 11584 "iBarWidth": 0, 11585 11586 /** 11587 * Viewport width for horizontal scrolling. Horizontal scrolling is 11588 * disabled if an empty string. 11589 * Note that this parameter will be set by the initialisation routine. To 11590 * set a default use {@link DataTable.defaults}. 11591 */ 11592 "sX": null, 11593 11594 /** 11595 * Width to expand the table to when using x-scrolling. Typically you 11596 * should not need to use this. 11597 * Note that this parameter will be set by the initialisation routine. To 11598 * set a default use {@link DataTable.defaults}. 11599 * @deprecated 11600 */ 11601 "sXInner": null, 11602 11603 /** 11604 * Viewport height for vertical scrolling. Vertical scrolling is disabled 11605 * if an empty string. 11606 * Note that this parameter will be set by the initialisation routine. To 11607 * set a default use {@link DataTable.defaults}. 11608 */ 11609 "sY": null 11610 }, 11611 11612 /** 11613 * Language information for the table. 11614 */ 11615 "oLanguage": { 11616 /** 11617 * Information callback function. See 11618 * {@link DataTable.defaults.fnInfoCallback} 11619 */ 11620 "fnInfoCallback": null 11621 }, 11622 11623 /** 11624 * Browser support parameters 11625 */ 11626 "oBrowser": { 11627 /** 11628 * Determine if the vertical scrollbar is on the right or left of the 11629 * scrolling container - needed for rtl language layout, although not 11630 * all browsers move the scrollbar (Safari). 11631 */ 11632 "bScrollbarLeft": false, 11633 11634 /** 11635 * Browser scrollbar width 11636 */ 11637 "barWidth": 0 11638 }, 11639 11640 11641 "ajax": null, 11642 11643 11644 /** 11645 * Array referencing the nodes which are used for the features. The 11646 * parameters of this object match what is allowed by sDom - i.e. 11647 * <ul> 11648 * <li>'l' - Length changing</li> 11649 * <li>'f' - Filtering input</li> 11650 * <li>'t' - The table!</li> 11651 * <li>'i' - Information</li> 11652 * <li>'p' - Pagination</li> 11653 * <li>'r' - pRocessing</li> 11654 * </ul> 11655 */ 11656 "aanFeatures": [], 11657 11658 /** 11659 * Store data information - see {@link DataTable.models.oRow} for detailed 11660 * information. 11661 */ 11662 "aoData": [], 11663 11664 /** 11665 * Array of indexes which are in the current display (after filtering etc) 11666 */ 11667 "aiDisplay": [], 11668 11669 /** 11670 * Array of indexes for display - no filtering 11671 */ 11672 "aiDisplayMaster": [], 11673 11674 /** 11675 * Map of row ids to data indexes 11676 */ 11677 "aIds": {}, 11678 11679 /** 11680 * Store information about each column that is in use 11681 */ 11682 "aoColumns": [], 11683 11684 /** 11685 * Store information about the table's header 11686 */ 11687 "aoHeader": [], 11688 11689 /** 11690 * Store information about the table's footer 11691 */ 11692 "aoFooter": [], 11693 11694 /** 11695 * Store the applied global search information in case we want to force a 11696 * research or compare the old search to a new one. 11697 * Note that this parameter will be set by the initialisation routine. To 11698 * set a default use {@link DataTable.defaults}. 11699 */ 11700 "oPreviousSearch": {}, 11701 11702 /** 11703 * Store for named searches 11704 */ 11705 searchFixed: {}, 11706 11707 /** 11708 * Store the applied search for each column - see 11709 * {@link DataTable.models.oSearch} for the format that is used for the 11710 * filtering information for each column. 11711 */ 11712 "aoPreSearchCols": [], 11713 11714 /** 11715 * Sorting that is applied to the table. Note that the inner arrays are 11716 * used in the following manner: 11717 * <ul> 11718 * <li>Index 0 - column number</li> 11719 * <li>Index 1 - current sorting direction</li> 11720 * </ul> 11721 * Note that this parameter will be set by the initialisation routine. To 11722 * set a default use {@link DataTable.defaults}. 11723 */ 11724 "aaSorting": null, 11725 11726 /** 11727 * Sorting that is always applied to the table (i.e. prefixed in front of 11728 * aaSorting). 11729 * Note that this parameter will be set by the initialisation routine. To 11730 * set a default use {@link DataTable.defaults}. 11731 */ 11732 "aaSortingFixed": [], 11733 11734 /** 11735 * If restoring a table - we should restore its width 11736 */ 11737 "sDestroyWidth": 0, 11738 11739 /** 11740 * Callback functions array for every time a row is inserted (i.e. on a draw). 11741 */ 11742 "aoRowCallback": [], 11743 11744 /** 11745 * Callback functions for the header on each draw. 11746 */ 11747 "aoHeaderCallback": [], 11748 11749 /** 11750 * Callback function for the footer on each draw. 11751 */ 11752 "aoFooterCallback": [], 11753 11754 /** 11755 * Array of callback functions for draw callback functions 11756 */ 11757 "aoDrawCallback": [], 11758 11759 /** 11760 * Array of callback functions for row created function 11761 */ 11762 "aoRowCreatedCallback": [], 11763 11764 /** 11765 * Callback functions for just before the table is redrawn. A return of 11766 * false will be used to cancel the draw. 11767 */ 11768 "aoPreDrawCallback": [], 11769 11770 /** 11771 * Callback functions for when the table has been initialised. 11772 */ 11773 "aoInitComplete": [], 11774 11775 11776 /** 11777 * Callbacks for modifying the settings to be stored for state saving, prior to 11778 * saving state. 11779 */ 11780 "aoStateSaveParams": [], 11781 11782 /** 11783 * Callbacks for modifying the settings that have been stored for state saving 11784 * prior to using the stored values to restore the state. 11785 */ 11786 "aoStateLoadParams": [], 11787 11788 /** 11789 * Callbacks for operating on the settings object once the saved state has been 11790 * loaded 11791 */ 11792 "aoStateLoaded": [], 11793 11794 /** 11795 * Cache the table ID for quick access 11796 */ 11797 "sTableId": "", 11798 11799 /** 11800 * The TABLE node for the main table 11801 */ 11802 "nTable": null, 11803 11804 /** 11805 * Permanent ref to the thead element 11806 */ 11807 "nTHead": null, 11808 11809 /** 11810 * Permanent ref to the tfoot element - if it exists 11811 */ 11812 "nTFoot": null, 11813 11814 /** 11815 * Permanent ref to the tbody element 11816 */ 11817 "nTBody": null, 11818 11819 /** 11820 * Cache the wrapper node (contains all DataTables controlled elements) 11821 */ 11822 "nTableWrapper": null, 11823 11824 /** 11825 * Indicate if all required information has been read in 11826 */ 11827 "bInitialised": false, 11828 11829 /** 11830 * Information about open rows. Each object in the array has the parameters 11831 * 'nTr' and 'nParent' 11832 */ 11833 "aoOpenRows": [], 11834 11835 /**
11836 * Dictate the positioning of DataTables' control elements - see 11837 * {@link DataTable.model.oInit.sDom}. 11838 * Note that this parameter will be set by the initialisation routine. To 11839 * set a default use {@link DataTable.defaults}. 11840 */ 11841 "sDom": null, 11842 11843 /** 11844 * Search delay (in mS) 11845 */ 11846 "searchDelay": null, 11847 11848 /** 11849 * Which type of pagination should be used. 11850 * Note that this parameter will be set by the initialisation routine. To 11851 * set a default use {@link DataTable.defaults}. 11852 */ 11853 "sPaginationType": "two_button", 11854 11855 /** 11856 * Number of paging controls on the page. Only used for backwards compatibility 11857 */ 11858 pagingControls: 0, 11859 11860 /** 11861 * The state duration (for `stateSave`) in seconds. 11862 * Note that this parameter will be set by the initialisation routine. To 11863 * set a default use {@link DataTable.defaults}. 11864 */ 11865 "iStateDuration": 0, 11866 11867 /** 11868 * Array of callback functions for state saving. Each array element is an 11869 * object with the following parameters: 11870 * <ul> 11871 * <li>function:fn - function to call. Takes two parameters, oSettings 11872 * and the JSON string to save that has been thus far created. Returns 11873 * a JSON string to be inserted into a json object 11874 * (i.e. '"param": [ 0, 1, 2]')</li> 11875 * <li>string:sName - name of callback</li> 11876 * </ul> 11877 */ 11878 "aoStateSave": [], 11879 11880 /** 11881 * Array of callback functions for state loading. Each array element is an 11882 * object with the following parameters: 11883 * <ul> 11884 * <li>function:fn - function to call. Takes two parameters, oSettings 11885 * and the object stored. May return false to cancel state loading</li> 11886 * <li>string:sName - name of callback</li> 11887 * </ul> 11888 */ 11889 "aoStateLoad": [], 11890 11891 /** 11892 * State that was saved. Useful for back reference 11893 */ 11894 "oSavedState": null, 11895 11896 /** 11897 * State that was loaded. Useful for back reference 11898 */ 11899 "oLoadedState": null, 11900 11901 /** 11902 * Note if draw should be blocked while getting data 11903 */ 11904 "bAjaxDataGet": true, 11905 11906 /** 11907 * The last jQuery XHR object that was used for server-side data gathering. 11908 * This can be used for working with the XHR information in one of the 11909 * callbacks 11910 */ 11911 "jqXHR": null, 11912 11913 /** 11914 * JSON returned from the server in the last Ajax request 11915 */ 11916 "json": undefined, 11917 11918 /** 11919 * Data submitted as part of the last Ajax request 11920 */ 11921 "oAjaxData": undefined, 11922 11923 /** 11924 * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if 11925 * required). 11926 * Note that this parameter will be set by the initialisation routine. To 11927 * set a default use {@link DataTable.defaults}. 11928 */ 11929 "sServerMethod": null, 11930 11931 /** 11932 * Format numbers for display. 11933 * Note that this parameter will be set by the initialisation routine. To 11934 * set a default use {@link DataTable.defaults}. 11935 */ 11936 "fnFormatNumber": null, 11937 11938 /** 11939 * List of options that can be used for the user selectable length menu. 11940 * Note that this parameter will be set by the initialisation routine. To 11941 * set a default use {@link DataTable.defaults}. 11942 */ 11943 "aLengthMenu": null, 11944 11945 /** 11946 * Counter for the draws that the table does. Also used as a tracker for 11947 * server-side processing 11948 */ 11949 "iDraw": 0, 11950 11951 /** 11952 * Indicate if a redraw is being done - useful for Ajax 11953 */ 11954 "bDrawing": false, 11955 11956 /** 11957 * Draw index (iDraw) of the last error when parsing the returned data 11958 */ 11959 "iDrawError": -1, 11960 11961 /** 11962 * Paging display length 11963 */ 11964 "_iDisplayLength": 10, 11965 11966 /** 11967 * Paging start point - aiDisplay index 11968 */ 11969 "_iDisplayStart": 0, 11970 11971 /** 11972 * Server-side processing - number of records in the result set 11973 * (i.e. before filtering), Use fnRecordsTotal rather than 11974 * this property to get the value of the number of records, regardless of 11975 * the server-side processing setting. 11976 */ 11977 "_iRecordsTotal": 0, 11978 11979 /** 11980 * Server-side processing - number of records in the current display set 11981 * (i.e. after filtering). Use fnRecordsDisplay rather than 11982 * this property to get the value of the number of records, regardless of 11983 * the server-side processing setting. 11984 */ 11985 "_iRecordsDisplay": 0, 11986 11987 /** 11988 * The classes to use for the table 11989 */
11990 "oClasses": {}, 11991 11992 /** 11993 * Flag attached to the settings object so you can check in the draw 11994 * callback if filtering has been done in the draw. Deprecated in favour of 11995 * events. 11996 * @deprecated 11997 */ 11998 "bFiltered": false, 11999 12000 /** 12001 * Flag attached to the settings object so you can check in the draw 12002 * callback if sorting has been done in the draw. Deprecated in favour of 12003 * events. 12004 * @deprecated 12005 */ 12006 "bSorted": false, 12007 12008 /** 12009 * Indicate that if multiple rows are in the header and there is more than 12010 * one unique cell per column, if the top one (true) or bottom one (false) 12011 * should be used for sorting / title by DataTables. 12012 * Note that this parameter will be set by the initialisation routine. To 12013 * set a default use {@link DataTable.defaults}. 12014 */ 12015 "bSortCellsTop": null, 12016 12017 /** 12018 * Initialisation object that is used for the table 12019 */ 12020 "oInit": null, 12021 12022 /** 12023 * Destroy callback functions - for plug-ins to attach themselves to the 12024 * destroy so they can clean up markup and events. 12025 */ 12026 "aoDestroyCallback": [], 12027 12028 12029 /** 12030 * Get the number of records in the current record set, before filtering 12031 */ 12032 "fnRecordsTotal": function () 12033 { 12034 return _fnDataSource( this ) == 'ssp' ? 12035 this._iRecordsTotal * 1 : 12036 this.aiDisplayMaster.length; 12037 }, 12038 12039 /** 12040 * Get the number of records in the current record set, after filtering 12041 */ 12042 "fnRecordsDisplay": function () 12043 { 12044 return _fnDataSource( this ) == 'ssp' ? 12045 this._iRecordsDisplay * 1 : 12046 this.aiDisplay.length; 12047 }, 12048 12049 /** 12050 * Get the display end point - aiDisplay index 12051 */ 12052 "fnDisplayEnd": function () 12053 { 12054 var 12055 len = this._iDisplayLength, 12056 start = this._iDisplayStart, 12057 calc = start + len, 12058 records = this.aiDisplay.length, 12059 features = this.oFeatures, 12060 paginate = features.bPaginate; 12061 12062 if ( features.bServerSide ) { 12063 return paginate === false || len === -1 ? 12064 start + records : 12065 Math.min( start+len, this._iRecordsDisplay ); 12066 } 12067 else { 12068 return ! paginate || calc>records || len===-1 ? 12069 records : 12070 calc; 12071 } 12072 }, 12073 12074 /** 12075 * The DataTables object for this table 12076 */ 12077 "oInstance": null, 12078 12079 /** 12080 * Unique identifier for each instance of the DataTables object. If there 12081 * is an ID on the table node, then it takes that value, otherwise an 12082 * incrementing internal counter is used. 12083 */ 12084 "sInstance": null, 12085 12086 /** 12087 * tabindex attribute value that is added to DataTables control elements, allowing 12088 * keyboard navigation of the table and its controls. 12089 */ 12090 "iTabIndex": 0, 12091 12092 /** 12093 * DIV container for the footer scrolling table if scrolling 12094 */ 12095 "nScrollHead": null, 12096 12097 /** 12098 * DIV container for the footer scrolling table if scrolling 12099 */ 12100 "nScrollFoot": null, 12101 12102 /** 12103 * Last applied sort 12104 */ 12105 "aLastSort": [], 12106 12107 /** 12108 * Stored plug-in instances 12109 */ 12110 "oPlugins": {}, 12111 12112 /** 12113 * Function used to get a row's id from the row's data 12114 */ 12115 "rowIdFn": null, 12116 12117 /** 12118 * Data location where to store a row's id 12119 */ 12120 "rowId": null, 12121 12122 caption: '', 12123 12124 captionNode: null, 12125 12126 colgroup: null, 12127 12128 /** Delay loading of data */ 12129 deferLoading: null, 12130 12131 /** Allow auto type detection */ 12132 typeDetect: true, 12133 12134 /** ResizeObserver for the container div */ 12135 resizeObserver: null, 12136 12137 /** Keep a record of the last size of the container, so we can skip duplicates */ 12138 containerWidth: -1 12139 }; 12140 12141 /** 12142 * Extension object for DataTables that is used to provide all extension 12143 * options. 12144 * 12145 * Note that the `DataTable.ext` object is available through 12146 * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is 12147 * also aliased to `jQuery.fn.dataTableExt` for historic reasons. 12148 * @namespace 12149 * @extends DataTable.models.ext 12150 */ 12151 12152 12153 var extPagination = DataTable.ext.pager; 12154 12155 // Paging buttons configuration 12156 $.extend( extPagination, { 12157 simple: function () { 12158 return [ 'previous', 'next' ]; 12159 }, 12160 12161 full: function () { 12162 return [ 'first', 'previous', 'next', 'last' ]; 12163 }, 12164 12165 numbers: function () { 12166 return [ 'numbers' ]; 12167 }, 12168 12169 simple_numbers: function () { 12170 return [ 'previous', 'numbers', 'next' ]; 12171 }, 12172 12173 full_numbers: function () { 12174 return [ 'first', 'previous', 'numbers', 'next', 'last' ]; 12175 }, 12176 12177 first_last: function () { 12178 return ['first', 'last']; 12179 }, 12180 12181 first_last_numbers: function () { 12182 return ['first', 'numbers', 'last']; 12183 }, 12184 12185 // For testing and plug-ins to use 12186 _numbers: _pagingNumbers, 12187 12188 // Number of number buttons - legacy, use `numbers` option for paging feature 12189 numbers_length: 7 12190 } ); 12191 12192 12193 $.extend( true, DataTable.ext.renderer, { 12194 pagingButton: { 12195 _: function (settings, buttonType, content, active, disabled) { 12196 var classes = settings.oClasses.paging; 12197 var btnClasses = [classes.button]; 12198 var btn; 12199 12200 if (active) { 12201 btnClasses.push(classes.active); 12202 } 12203 12204 if (disabled) { 12205 btnClasses.push(classes.disabled) 12206 } 12207 12208 if (buttonType === 'ellipsis') { 12209 btn = $('<span class="ellipsis"></span>').html(content)[0]; 12210 } 12211 else { 12212 btn = $('<button>', { 12213 class: btnClasses.join(' '), 12214 role: 'link', 12215 type: 'button' 12216 }).html(content); 12217 } 12218 12219 return { 12220 display: btn, 12221 clicker: btn 12222 } 12223 } 12224 }, 12225 12226 pagingContainer: { 12227 _: function (settings, buttons) { 12228 // No wrapping element - just append directly to the host 12229 return buttons; 12230 } 12231 } 12232 } ); 12233 12234 // Common function to remove new lines, strip HTML and diacritic control 12235 var _filterString = function (stripHtml, normalize) { 12236 return function (str) { 12237 if (_empty(str) || typeof str !== 'string') { 12238 return str; 12239 } 12240 12241 str = str.replace( _re_new_lines, " " ); 12242 12243 if (stripHtml) { 12244 str = _stripHtml(str); 12245 } 12246 12247 if (normalize) { 12248 str = _normalize(str, false); 12249 } 12250 12251 return str; 12252 }; 12253 } 12254 12255 /* 12256 * Public helper functions. These aren't used internally by DataTables, or 12257 * called by any of the options passed into DataTables, but they can be used 12258 * externally by developers working with DataTables. They are helper functions 12259 * to make working with DataTables a little bit easier. 12260 */ 12261 12262 /** 12263 * Common logic for moment, luxon or a date action. 12264 * 12265 * Happens after __mldObj, so don't need to call `resolveWindowsLibs` again 12266 */ 12267 function __mld( dtLib, momentFn, luxonFn, dateFn, arg1 ) { 12268 if (__moment) { 12269 return dtLib[momentFn]( arg1 ); 12270 } 12271 else if (__luxon) { 12272 return dtLib[luxonFn]( arg1 ); 12273 } 12274 12275 return dateFn ? dtLib[dateFn]( arg1 ) : dtLib; 12276 } 12277 12278 12279 var __mlWarning = false;
12280 var __luxon; // Can be assigned in DateTable.use() 12281 var __moment; // Can be assigned in DateTable.use() 12282 12283 /** 12284 * 12285 */ 12286 function resolveWindowLibs() { 12287 if (window.luxon && ! __luxon) { 12288 __luxon = window.luxon; 12289 } 12290 12291 if (window.moment && ! __moment) { 12292 __moment = window.moment; 12293 } 12294 } 12295 12296 function __mldObj (d, format, locale) { 12297 var dt; 12298 12299 resolveWindowLibs(); 12300 12301 if (__moment) { 12302 dt = __moment.utc( d, format, locale, true ); 12303 12304 if (! dt.isValid()) { 12305 return null; 12306 } 12307 } 12308 else if (__luxon) { 12309 dt = format && typeof d === 'string' 12310 ? __luxon.DateTime.fromFormat( d, format ) 12311 : __luxon.DateTime.fromISO( d ); 12312 12313 if (! dt.isValid) { 12314 return null; 12315 } 12316 12317 dt = dt.setLocale(locale); 12318 } 12319 else if (! format) { 12320 // No format given, must be ISO 12321 dt = new Date(d); 12322 } 12323 else { 12324 if (! __mlWarning) { 12325 alert('DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17'); 12326 } 12327 12328 __mlWarning = true; 12329 } 12330 12331 return dt; 12332 } 12333 12334 // Wrapper for date, datetime and time which all operate the same way with the exception of 12335 // the output string for auto locale support 12336 function __mlHelper (localeString) { 12337 return function ( from, to, locale, def ) { 12338 // Luxon and Moment support 12339 // Argument shifting 12340 if ( arguments.length === 0 ) { 12341 locale = 'en'; 12342 to = null; // means toLocaleString 12343 from = null; // means iso8601 12344 } 12345 else if ( arguments.length === 1 ) { 12346 locale = 'en'; 12347 to = from; 12348 from = null; 12349 } 12350 else if ( arguments.length === 2 ) { 12351 locale = to; 12352 to = from; 12353 from = null; 12354 } 12355 12356 var typeName = 'datetime' + (to ? '-' + to : ''); 12357 12358 // Add type detection and sorting specific to this date format - we need to be able to identify 12359 // date type columns as such, rather than as numbers in extensions. Hence the need for this. 12360 if (! DataTable.ext.type.order[typeName + '-pre']) { 12361 DataTable.type(typeName, { 12362 detect: function (d) { 12363 // The renderer will give the value to type detect as the type! 12364 return d === typeName ? typeName : false; 12365 }, 12366 order: { 12367 pre: function (d) { 12368 // The renderer gives us Moment, Luxon or Date obects for the sorting, all of which have a 12369 // `valueOf` which gives milliseconds epoch 12370 return d.valueOf(); 12371 } 12372 }, 12373 className: 'dt-right' 12374 }); 12375 } 12376 12377 return function ( d, type ) { 12378 // Allow for a default value 12379 if (d === null || d === undefined) { 12380 if (def === '--now') { 12381 // We treat everything as UTC further down, so no changes are 12382 // made, as such need to get the local date / time as if it were 12383 // UTC 12384 var local = new Date(); 12385 d = new Date( Date.UTC( 12386 local.getFullYear(), local.getMonth(), local.getDate(), 12387 local.getHours(), local.getMinutes(), local.getSeconds() 12388 ) ); 12389 } 12390 else { 12391 d = ''; 12392 } 12393 } 12394 12395 if (type === 'type') { 12396 // Typing uses the type name for fast matching 12397 return typeName; 12398 } 12399 12400 if (d === '') { 12401 return type !== 'sort' 12402 ? '' 12403 : __mldObj('0000-01-01 00:00:00', null, locale); 12404 } 12405 12406 // Shortcut. If `from` and `to` are the same, we are using the renderer to 12407 // format for ordering, not display - its already in the display format. 12408 if ( to !== null && from === to && type !== 'sort' && type !== 'type' && ! (d instanceof Date) ) { 12409 return d; 12410 } 12411 12412 var dt = __mldObj(d, from, locale); 12413 12414 if (dt === null) { 12415 return d; 12416 } 12417 12418 if (type === 'sort') { 12419 return dt; 12420 } 12421 12422 var formatted = to === null 12423 ? __mld(dt, 'toDate', 'toJSDate', '')[localeString]() 12424 : __mld(dt, 'format', 'toFormat', 'toISOString', to); 12425 12426 // XSS protection 12427 return type === 'display' ? 12428 _escapeHtml( formatted ) : 12429 formatted; 12430 }; 12431 } 12432 } 12433 12434 // Based on locale, determine standard number formatting 12435 // Fallback for legacy browsers is US English 12436 var __thousands = ','; 12437 var __decimal = '.'; 12438 12439 if (window.Intl !== undefined) { 12440 try { 12441 var num = new Intl.NumberFormat().formatToParts(100000.1); 12442 12443 for (var i=0 ; i<num.length ; i++) { 12444 if (num[i].type === 'group') { 12445 __thousands = num[i].value; 12446 } 12447 else if (num[i].type === 'decimal') { 12448 __decimal = num[i].value; 12449 } 12450 } 12451 } 12452 catch (e) { 12453 // noop 12454 } 12455 } 12456 12457 // Formatted date time detection - use by declaring the formats you are going to use 12458 DataTable.datetime = function ( format, locale ) { 12459 var typeName = 'datetime-' + format; 12460 12461 if (! locale) { 12462 locale = 'en'; 12463 } 12464 12465 if (! DataTable.ext.type.order[typeName]) { 12466 DataTable.type(typeName, { 12467 detect: function (d) { 12468 var dt = __mldObj(d, format, locale); 12469 return d === '' || dt ? typeName : false;
12470 }, 12471 order: { 12472 pre: function (d) { 12473 return __mldObj(d, format, locale) || 0; 12474 } 12475 }, 12476 className: 'dt-right' 12477 }); 12478 } 12479 } 12480 12481 /** 12482 * Helpers for `columns.render`. 12483 * 12484 * The options defined here can be used with the `columns.render` initialisation 12485 * option to provide a display renderer. The following functions are defined: 12486 * 12487 * * `moment` - Uses the MomentJS library to convert from a given format into another. 12488 * This renderer has three overloads: 12489 * * 1 parameter: 12490 * * `string` - Format to convert to (assumes input is ISO8601 and locale is `en`) 12491 * * 2 parameters: 12492 * * `string` - Format to convert from 12493 * * `string` - Format to convert to. Assumes `en` locale 12494 * * 3 parameters: 12495 * * `string` - Format to convert from 12496 * * `string` - Format to convert to 12497 * * `string` - Locale 12498 * * `number` - Will format numeric data (defined by `columns.data`) for 12499 * display, retaining the original unformatted data for sorting and filtering. 12500 * It takes 5 parameters: 12501 * * `string` - Thousands grouping separator 12502 * * `string` - Decimal point indicator 12503 * * `integer` - Number of decimal points to show 12504 * * `string` (optional) - Prefix. 12505 * * `string` (optional) - Postfix (/suffix). 12506 * * `text` - Escape HTML to help prevent XSS attacks. It has no optional 12507 * parameters. 12508 * 12509 * @example 12510 * // Column definition using the number renderer 12511 * { 12512 * data: "salary", 12513 * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' ) 12514 * } 12515 * 12516 * @namespace 12517 */ 12518 DataTable.render = { 12519 date: __mlHelper('toLocaleDateString'), 12520 datetime: __mlHelper('toLocaleString'), 12521 time: __mlHelper('toLocaleTimeString'), 12522 number: function ( thousands, decimal, precision, prefix, postfix ) { 12523 // Auto locale detection 12524 if (thousands === null || thousands === undefined) { 12525 thousands = __thousands; 12526 } 12527 12528 if (decimal === null || decimal === undefined) { 12529 decimal = __decimal; 12530 } 12531 12532 return { 12533 display: function ( d ) { 12534 if ( typeof d !== 'number' && typeof d !== 'string' ) { 12535 return d; 12536 } 12537 12538 if (d === '' || d === null) { 12539 return d; 12540 } 12541 12542 var negative = d < 0 ? '-' : ''; 12543 var flo = parseFloat( d ); 12544 var abs = Math.abs(flo); 12545 12546 // Scientific notation for large and small numbers 12547 if (abs >= 100000000000 || (abs < 0.0001 && abs !== 0) ) { 12548 var exp = flo.toExponential(precision).split(/e\+?/); 12549 return exp[0] + ' x 10<sup>' + exp[1] + '</sup>'; 12550 } 12551 12552 // If NaN then there isn't much formatting that we can do - just 12553 // return immediately, escaping any HTML (this was supposed to 12554 // be a number after all) 12555 if ( isNaN( flo ) ) { 12556 return _escapeHtml( d ); 12557 } 12558 12559 flo = flo.toFixed( precision ); 12560 d = Math.abs( flo ); 12561 12562 var intPart = parseInt( d, 10 ); 12563 var floatPart = precision ? 12564 decimal+(d - intPart).toFixed( precision ).substring( 2 ): 12565 ''; 12566 12567 // If zero, then can't have a negative prefix 12568 if (intPart === 0 && parseFloat(floatPart) === 0) { 12569 negative = ''; 12570 } 12571 12572 return negative + (prefix||'') + 12573 intPart.toString().replace( 12574 /\B(?=(\d{3})+(?!\d))/g, thousands 12575 ) + 12576 floatPart + 12577 (postfix||''); 12578 } 12579 }; 12580 }, 12581 12582 text: function () { 12583 return { 12584 display: _escapeHtml, 12585 filter: _escapeHtml 12586 }; 12587 } 12588 }; 12589 12590 12591 var _extTypes = DataTable.ext.type; 12592 12593 // Get / set type 12594 DataTable.type = function (name, prop, val) { 12595 if (! prop) { 12596 return { 12597 className: _extTypes.className[name], 12598 detect: _extTypes.detect.find(function (fn) { 12599 return fn._name === name; 12600 }), 12601 order: { 12602 pre: _extTypes.order[name + '-pre'], 12603 asc: _extTypes.order[name + '-asc'], 12604 desc: _extTypes.order[name + '-desc'] 12605 }, 12606 render: _extTypes.render[name], 12607 search: _extTypes.search[name] 12608 }; 12609 } 12610 12611 var setProp = function(prop, propVal) { 12612 _extTypes[prop][name] = propVal; 12613 }; 12614 var setDetect = function (detect) { 12615 // `detect` can be a function or an object - we set a name 12616 // property for either - that is used for the detection 12617 Object.defineProperty(detect, "_name", {value: name}); 12618 12619 var idx = _extTypes.detect.findIndex(function (item) { 12620 return item._name === name; 12621 }); 12622 12623 if (idx === -1) { 12624 _extTypes.detect.unshift(detect); 12625 } 12626 else { 12627 _extTypes.detect.splice(idx, 1, detect); 12628 } 12629 }; 12630 var setOrder = function (obj) { 12631 _extTypes.order[name + '-pre'] = obj.pre; // can be undefined 12632 _extTypes.order[name + '-asc'] = obj.asc; // can be undefined 12633 _extTypes.order[name + '-desc'] = obj.desc; // can be undefined 12634 }; 12635 12636 // prop is optional 12637 if (val === undefined) { 12638 val = prop; 12639 prop = null; 12640 } 12641 12642 if (prop === 'className') { 12643 setProp('className', val); 12644 } 12645 else if (prop === 'detect') { 12646 setDetect(val); 12647 } 12648 else if (prop === 'order') { 12649 setOrder(val); 12650 } 12651 else if (prop === 'render') { 12652 setProp('render', val); 12653 } 12654 else if (prop === 'search') { 12655 setProp('search', val); 12656 } 12657 else if (! prop) { 12658 if (val.className) { 12659 setProp('className', val.className); 12660 } 12661 12662 if (val.detect !== undefined) { 12663 setDetect(val.detect); 12664 } 12665 12666 if (val.order) { 12667 setOrder(val.order); 12668 } 12669 12670 if (val.render !== undefined) { 12671 setProp('render', val.render); 12672 } 12673 12674 if (val.search !== undefined) { 12675 setProp('search', val.search); 12676 } 12677 } 12678 } 12679 12680 // Get a list of types 12681 DataTable.types = function () { 12682 return _extTypes.detect.map(function (fn) { 12683 return fn._name; 12684 }); 12685 }; 12686 12687 var __diacriticSort = function (a, b) { 12688 a = a !== null && a !== undefined ? a.toString().toLowerCase() : ''; 12689 b = b !== null && b !== undefined ? b.toString().toLowerCase() : ''; 12690 12691 // Checked for `navigator.languages` support in `oneOf` so this code can't execute in old 12692 // Safari and thus can disable this check 12693 // eslint-disable-next-line compat/compat 12694 return a.localeCompare(b, navigator.languages[0] || navigator.language, { 12695 numeric: true, 12696 ignorePunctuation: true, 12697 }); 12698 } 12699 12700 var __diacriticHtmlSort = function (a, b) { 12701 a = _stripHtml(a); 12702 b = _stripHtml(b); 12703 12704 return __diacriticSort(a, b); 12705 } 12706 12707 // 12708 // Built in data types 12709 // 12710 12711 DataTable.type('string', { 12712 detect: function () { 12713 return 'string'; 12714 }, 12715 order: { 12716 pre: function ( a ) { 12717 // This is a little complex, but faster than always calling toString, 12718 // http://jsperf.com/tostring-v-check 12719 return _empty(a) && typeof a !== 'boolean' ? 12720 '' : 12721 typeof a === 'string' ? 12722 a.toLowerCase() : 12723 ! a.toString ? 12724 '' : 12725 a.toString(); 12726 } 12727 }, 12728 search: _filterString(false, true) 12729 }); 12730 12731 DataTable.type('string-utf8', { 12732 detect: { 12733 allOf: function ( d ) { 12734 return true; 12735 }, 12736 oneOf: function ( d ) { 12737 // At least one data point must contain a non-ASCII character 12738 // This line will also check if navigator.languages is supported or not. If not (Safari 10.0-) 12739 // this data type won't be supported. 12740 // eslint-disable-next-line compat/compat 12741 return ! _empty( d ) && navigator.languages && typeof d === 'string' && d.match(/[^\x00-\x7F]/); 12742 } 12743 }, 12744 order: { 12745 asc: __diacriticSort, 12746 desc: function (a, b) { 12747 return __diacriticSort(a, b) * -1; 12748 } 12749 }, 12750 search: _filterString(false, true) 12751 }); 12752 12753 12754 DataTable.type('html', { 12755 detect: { 12756 allOf: function ( d ) { 12757 return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1); 12758 }, 12759 oneOf: function ( d ) { 12760 // At least one data point must contain a `<` 12761 return ! _empty( d ) && typeof d === 'string' && d.indexOf('<') !== -1; 12762 } 12763 }, 12764 order: { 12765 pre: function ( a ) { 12766 return _empty(a) ? 12767 '' : 12768 a.replace ?
12769 _stripHtml(a).trim().toLowerCase() : 12770 a+''; 12771 } 12772 }, 12773 search: _filterString(true, true) 12774 }); 12775 12776 12777 DataTable.type('html-utf8', { 12778 detect: { 12779 allOf: function ( d ) { 12780 return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1); 12781 }, 12782 oneOf: function ( d ) { 12783 // At least one data point must contain a `<` and a non-ASCII character 12784 // eslint-disable-next-line compat/compat 12785 return navigator.languages && 12786 ! _empty( d ) && 12787 typeof d === 'string' && 12788 d.indexOf('<') !== -1 && 12789 typeof d === 'string' && d.match(/[^\x00-\x7F]/); 12790 } 12791 }, 12792 order: { 12793 asc: __diacriticHtmlSort, 12794 desc: function (a, b) { 12795 return __diacriticHtmlSort(a, b) * -1; 12796 } 12797 }, 12798 search: _filterString(true, true) 12799 }); 12800 12801 12802 DataTable.type('date', { 12803 className: 'dt-type-date', 12804 detect: { 12805 allOf: function ( d ) { 12806 // V8 tries _very_ hard to make a string passed into `Date.parse()` 12807 // valid, so we need to use a regex to restrict date formats. Use a 12808 // plug-in for anything other than ISO8601 style strings 12809 if ( d && !(d instanceof Date) && ! _re_date.test(d) ) { 12810 return null; 12811 } 12812 var parsed = Date.parse(d); 12813 return (parsed !== null && !isNaN(parsed)) || _empty(d); 12814 }, 12815 oneOf: function ( d ) { 12816 // At least one entry must be a date or a string with a date 12817 return (d instanceof Date) || (typeof d === 'string' && _re_date.test(d)); 12818 } 12819 }, 12820 order: { 12821 pre: function ( d ) { 12822 var ts = Date.parse( d ); 12823 return isNaN(ts) ? -Infinity : ts; 12824 } 12825 } 12826 }); 12827 12828 12829 DataTable.type('html-num-fmt', { 12830 className: 'dt-type-numeric', 12831 detect: { 12832 allOf: function ( d, settings ) { 12833 var decimal = settings.oLanguage.sDecimal; 12834 return _htmlNumeric( d, decimal, true, false ); 12835 }, 12836 oneOf: function (d, settings) { 12837 // At least one data point must contain a numeric value 12838 var decimal = settings.oLanguage.sDecimal; 12839 return _htmlNumeric( d, decimal, true, false ); 12840 } 12841 }, 12842 order: { 12843 pre: function ( d, s ) { 12844 var dp = s.oLanguage.sDecimal; 12845 return __numericReplace( d, dp, _re_html, _re_formatted_numeric ); 12846 } 12847 }, 12848 search: _filterString(true, true) 12849 }); 12850 12851 12852 DataTable.type('html-num', { 12853 className: 'dt-type-numeric', 12854 detect: { 12855 allOf: function ( d, settings ) { 12856 var decimal = settings.oLanguage.sDecimal; 12857 return _htmlNumeric( d, decimal, false, true ); 12858 }, 12859 oneOf: function (d, settings) { 12860 // At least one data point must contain a numeric value 12861 var decimal = settings.oLanguage.sDecimal; 12862 return _htmlNumeric( d, decimal, false, false ); 12863 } 12864 }, 12865 order: { 12866 pre: function ( d, s ) { 12867 var dp = s.oLanguage.sDecimal; 12868 return __numericReplace( d, dp, _re_html ); 12869 } 12870 }, 12871 search: _filterString(true, true) 12872 }); 12873 12874 12875 DataTable.type('num-fmt', { 12876 className: 'dt-type-numeric', 12877 detect: { 12878 allOf: function ( d, settings ) { 12879 var decimal = settings.oLanguage.sDecimal; 12880 return _isNumber( d, decimal, true, true ); 12881 }, 12882 oneOf: function (d, settings) { 12883 // At least one data point must contain a numeric value 12884 var decimal = settings.oLanguage.sDecimal; 12885 return _isNumber( d, decimal, true, false ); 12886 } 12887 }, 12888 order: { 12889 pre: function ( d, s ) { 12890 var dp = s.oLanguage.sDecimal; 12891 return __numericReplace( d, dp, _re_formatted_numeric ); 12892 } 12893 } 12894 }); 12895 12896 12897 DataTable.type('num', { 12898 className: 'dt-type-numeric', 12899 detect: { 12900 allOf: function ( d, settings ) { 12901 var decimal = settings.oLanguage.sDecimal; 12902 return _isNumber( d, decimal, false, true ); 12903 }, 12904 oneOf: function (d, settings) { 12905 // At least one data point must contain a numeric value 12906 var decimal = settings.oLanguage.sDecimal; 12907 return _isNumber( d, decimal, false, false ); 12908 } 12909 }, 12910 order: { 12911 pre: function (d, s) { 12912 var dp = s.oLanguage.sDecimal; 12913 return __numericReplace( d, dp ); 12914 } 12915 } 12916 }); 12917 12918 12919 12920 12921 var __numericReplace = function ( d, decimalPlace, re1, re2 ) { 12922 if ( d !== 0 && (!d || d === '-') ) { 12923 return -Infinity; 12924 } 12925 12926 var type = typeof d; 12927 12928 if (type === 'number' || type === 'bigint') { 12929 return d; 12930 } 12931 12932 // If a decimal place other than `.` is used, it needs to be given to the 12933 // function so we can detect it and replace with a `.` which is the only 12934 // decimal place Javascript recognises - it is not locale aware. 12935 if ( decimalPlace ) { 12936 d = _numToDecimal( d, decimalPlace ); 12937 } 12938 12939 if ( d.replace ) { 12940 if ( re1 ) { 12941 d = d.replace( re1, '' ); 12942 } 12943 12944 if ( re2 ) { 12945 d = d.replace( re2, '' ); 12946 } 12947 } 12948 12949 return d * 1; 12950 }; 12951 12952 12953 $.extend( true, DataTable.ext.renderer, { 12954 footer: { 12955 _: function ( settings, cell, classes ) {
12956 cell.addClass(classes.tfoot.cell); 12957 } 12958 }, 12959 12960 header: { 12961 _: function ( settings, cell, classes ) { 12962 cell.addClass(classes.thead.cell); 12963 12964 if (! settings.oFeatures.bSort) { 12965 cell.addClass(classes.order.none); 12966 } 12967 12968 var legacyTop = settings.bSortCellsTop; 12969 var headerRows = cell.closest('thead').find('tr'); 12970 var rowIdx = cell.parent().index(); 12971 12972 // Conditions to not apply the ordering icons 12973 if ( 12974 // Cells and rows which have the attribute to disable the icons 12975 cell.attr('data-dt-order') === 'disable' || 12976 cell.parent().attr('data-dt-order') === 'disable' || 12977 12978 // Legacy support for `orderCellsTop`. If it is set, then cells 12979 // which are not in the top or bottom row of the header (depending 12980 // on the value) do not get the sorting classes applied to them 12981 (legacyTop === true && rowIdx !== 0) || 12982 (legacyTop === false && rowIdx !== headerRows.length - 1) 12983 ) { 12984 return; 12985 } 12986 12987 // No additional mark-up required 12988 // Attach a sort listener to update on sort - note that using the 12989 // `DT` namespace will allow the event to be removed automatically 12990 // on destroy, while the `dt` namespaced event is the one we are 12991 // listening for 12992 $(settings.nTable).on( 'order.dt.DT column-visibility.dt.DT', function ( e, ctx ) { 12993 if ( settings !== ctx ) { // need to check this this is the host 12994 return; // table, not a nested one 12995 } 12996 12997 var sorting = ctx.sortDetails; 12998
12999 if (! sorting) { 13000 return; 13001 } 13002 13003 var i; 13004 var orderClasses = classes.order; 13005 var columns = ctx.api.columns( cell ); 13006 var col = settings.aoColumns[columns.flatten()[0]]; 13007 var orderable = columns.orderable().includes(true); 13008 var ariaType = ''; 13009 var indexes = columns.indexes(); 13010 var sortDirs = columns.orderable(true).flatten(); 13011 var orderedColumns = _pluck(sorting, 'col'); 13012 var tabIndex = settings.iTabIndex; 13013 13014 cell 13015 .removeClass( 13016 orderClasses.isAsc +' '+ 13017 orderClasses.isDesc 13018 ) 13019 .toggleClass( orderClasses.none, ! orderable ) 13020 .toggleClass( orderClasses.canAsc, orderable && sortDirs.includes('asc') ) 13021 .toggleClass( orderClasses.canDesc, orderable && sortDirs.includes('desc') ); 13022 13023 // Determine if all of the columns that this cell covers are included in the 13024 // current ordering 13025 var isOrdering = true; 13026 13027 for (i=0; i<indexes.length; i++) { 13028 if (! orderedColumns.includes(indexes[i])) { 13029 isOrdering = false; 13030 } 13031 } 13032 13033 if ( isOrdering ) { 13034 // Get the ordering direction for the columns under this cell 13035 // Note that it is possible for a cell to be asc and desc sorting 13036 // (column spanning cells) 13037 var orderDirs = columns.order(); 13038 13039 cell.addClass( 13040 orderDirs.includes('asc') ? orderClasses.isAsc : '' + 13041 orderDirs.includes('desc') ? orderClasses.isDesc : '' 13042 ); 13043 } 13044 13045 // Find the first visible column that has ordering applied to it - it get's 13046 // the aria information, as the ARIA spec says that only one column should 13047 // be marked with aria-sort 13048 var firstVis = -1; // column index 13049 13050 for (i=0; i<orderedColumns.length; i++) { 13051 if (settings.aoColumns[orderedColumns[i]].bVisible) { 13052 firstVis = orderedColumns[i]; 13053 break; 13054 } 13055 } 13056 13057 if (indexes[0] == firstVis) { 13058 var firstSort = sorting[0]; 13059 var sortOrder = col.asSorting; 13060 13061 cell.attr('aria-sort', firstSort.dir === 'asc' ? 'ascending' : 'descending'); 13062 13063 // Determine if the next click will remove sorting or change the sort 13064 ariaType = ! sortOrder[firstSort.index + 1] ? 'Remove' : 'Reverse'; 13065 } 13066 else { 13067 cell.removeAttr('aria-sort'); 13068 } 13069 13070 // Make the headers tab-able for keyboard navigation 13071 if (orderable) { 13072 var orderSpan = cell.find('.dt-column-order'); 13073 13074 orderSpan 13075 .attr('role', 'button') 13076 .attr('aria-label', orderable 13077 ? col.ariaTitle + ctx.api.i18n('oAria.orderable' + ariaType) 13078 : col.ariaTitle 13079 ); 13080 13081 if (tabIndex !== -1) { 13082 orderSpan.attr('tabindex', tabIndex); 13083 } 13084 } 13085 } ); 13086 } 13087 }, 13088 13089 layout: { 13090 _: function ( settings, container, items ) { 13091 var classes = settings.oClasses.layout; 13092 var row = $('<div/>') 13093 .attr('id', items.id || null) 13094 .addClass(items.className || classes.row) 13095 .appendTo( container ); 13096 13097 DataTable.ext.renderer.layout._forLayoutRow(items, function (key, val) { 13098 if (key === 'id' || key === 'className') { 13099 return; 13100 } 13101 13102 var klass = ''; 13103 13104 if (val.table) {
13105 row.addClass(classes.tableRow); 13106 klass += classes.tableCell + ' '; 13107 } 13108 13109 if (key === 'start') { 13110 klass += classes.start; 13111 } 13112 else if (key === 'end') { 13113 klass += classes.end; 13114 } 13115 else { 13116 klass += classes.full; 13117 } 13118 13119 $('<div/>') 13120 .attr({ 13121 id: val.id || null, 13122 "class": val.className 13123 ? val.className 13124 : classes.cell + ' ' + klass 13125 }) 13126 .append( val.contents ) 13127 .appendTo( row ); 13128 }); 13129 }, 13130 13131 // Shared for use by the styling frameworks 13132 _forLayoutRow: function (items, fn) { 13133 // As we are inserting dom elements, we need start / end in a 13134 // specific order, this function is used for sorting the layout 13135 // keys. 13136 var layoutEnum = function (x) { 13137 switch (x) { 13138 case '': return 0; 13139 case 'start': return 1; 13140 case 'end': return 2; 13141 default: return 3; 13142 } 13143 }; 13144 13145 Object 13146 .keys(items) 13147 .sort(function (a, b) { 13148 return layoutEnum(a) - layoutEnum(b); 13149 }) 13150 .forEach(function (key) { 13151 fn(key, items[key]); 13152 }); 13153 } 13154 } 13155 } ); 13156 13157 13158 DataTable.feature = {}; 13159 13160 // Third parameter is internal only! 13161 DataTable.feature.register = function ( name, cb, legacy ) { 13162 DataTable.ext.features[ name ] = cb; 13163 13164 if (legacy) { 13165 _ext.feature.push({ 13166 cFeature: legacy, 13167 fnInit: cb 13168 }); 13169 } 13170 }; 13171 13172 function _divProp(el, prop, val) { 13173 if (val) { 13174 el[prop] = val; 13175 } 13176 } 13177 13178 DataTable.feature.register( 'div', function ( settings, opts ) { 13179 var n = $('<div>')[0]; 13180 13181 if (opts) { 13182 _divProp(n, 'className', opts.className); 13183 _divProp(n, 'id', opts.id); 13184 _divProp(n, 'innerHTML', opts.html); 13185 _divProp(n, 'textContent', opts.text); 13186 } 13187 13188 return n; 13189 } ); 13190 13191 DataTable.feature.register( 'info', function ( settings, opts ) { 13192 // For compatibility with the legacy `info` top level option 13193 if (! settings.oFeatures.bInfo) { 13194 return null; 13195 } 13196 13197 var 13198 lang = settings.oLanguage, 13199 tid = settings.sTableId, 13200 n = $('<div/>', { 13201 'class': settings.oClasses.info.container, 13202 } ); 13203 13204 opts = $.extend({ 13205 callback: lang.fnInfoCallback, 13206 empty: lang.sInfoEmpty, 13207 postfix: lang.sInfoPostFix, 13208 search: lang.sInfoFiltered, 13209 text: lang.sInfo, 13210 }, opts); 13211 13212 13213 // Update display on each draw 13214 settings.aoDrawCallback.push(function (s) { 13215 _fnUpdateInfo(s, opts, n); 13216 }); 13217 13218 // For the first info display in the table, we add a callback and aria information. 13219 if (! settings._infoEl) { 13220 n.attr({ 13221 'aria-live': 'polite', 13222 id: tid+'_info', 13223 role: 'status' 13224 }); 13225 13226 // Table is described by our info div 13227 $(settings.nTable).attr( 'aria-describedby', tid+'_info' ); 13228 13229 settings._infoEl = n; 13230 } 13231 13232 return n; 13233 }, 'i' ); 13234 13235 /** 13236 * Update the information elements in the display 13237 * @param {object} settings dataTables settings object 13238 * @memberof DataTable#oApi 13239 */ 13240 function _fnUpdateInfo ( settings, opts, node ) 13241 { 13242 var 13243 start = settings._iDisplayStart+1, 13244 end = settings.fnDisplayEnd(), 13245 max = settings.fnRecordsTotal(), 13246 total = settings.fnRecordsDisplay(), 13247 out = total 13248 ? opts.text 13249 : opts.empty; 13250 13251 if ( total !== max ) { 13252 // Record set after filtering 13253 out += ' ' + opts.search; 13254 } 13255 13256 // Convert the macros 13257 out += opts.postfix; 13258 out = _fnMacros( settings, out ); 13259 13260 if ( opts.callback ) { 13261 out = opts.callback.call( settings.oInstance, 13262 settings, start, end, max, total, out 13263 ); 13264 } 13265 13266 node.html( out ); 13267 13268 _fnCallbackFire(settings, null, 'info', [settings, node[0], out]); 13269 } 13270 13271 var __searchCounter = 0; 13272 13273 // opts 13274 // - text 13275 // - placeholder 13276 DataTable.feature.register( 'search', function ( settings, opts ) { 13277 // Don't show the input if filtering isn't available on the table 13278 if (! settings.oFeatures.bFilter) { 13279 return null; 13280 } 13281 13282 var classes = settings.oClasses.search; 13283 var tableId = settings.sTableId; 13284 var language = settings.oLanguage; 13285 var previousSearch = settings.oPreviousSearch; 13286 var input = '<input type="search" class="'+classes.input+'"/>'; 13287 13288 opts = $.extend({ 13289 placeholder: language.sSearchPlaceholder, 13290 processing: false, 13291 text: language.sSearch 13292 }, opts); 13293
13294 // The _INPUT_ is optional - is appended if not present 13295 if (opts.text.indexOf('_INPUT_') === -1) { 13296 opts.text += '_INPUT_'; 13297 } 13298 13299 opts.text = _fnMacros(settings, opts.text); 13300 13301 // We can put the <input> outside of the label if it is at the start or end 13302 // which helps improve accessability (not all screen readers like implicit 13303 // for elements). 13304 var end = opts.text.match(/_INPUT_$/); 13305 var start = opts.text.match(/^_INPUT_/); 13306 var removed = opts.text.replace(/_INPUT_/, ''); 13307 var str = '<label>' + opts.text + '</label>'; 13308 13309 if (start) { 13310 str = '_INPUT_<label>' + removed + '</label>'; 13311 } 13312 else if (end) { 13313 str = '<label>' + removed + '</label>_INPUT_'; 13314 } 13315 13316 var filter = $('<div>') 13317 .addClass(classes.container) 13318 .append(str.replace(/_INPUT_/, input)); 13319 13320 // add for and id to label and input 13321 filter.find('label').attr('for', 'dt-search-' + __searchCounter); 13322 filter.find('input').attr('id', 'dt-search-' + __searchCounter); 13323 __searchCounter++; 13324 13325 var searchFn = function(event) { 13326 var val = this.value; 13327 13328 if(previousSearch.return && event.key !== "Enter") { 13329 return; 13330 } 13331 13332 /* Now do the filter */ 13333 if ( val != previousSearch.search ) { 13334 _fnProcessingRun(settings, opts.processing, function () { 13335 previousSearch.search = val; 13336 13337 _fnFilterComplete( settings, previousSearch ); 13338 13339 // Need to redraw, without resorting 13340 settings._iDisplayStart = 0; 13341 _fnDraw( settings ); 13342 }); 13343 } 13344 }; 13345 13346 var searchDelay = settings.searchDelay !== null ? 13347 settings.searchDelay : 13348 0; 13349 13350 var jqFilter = $('input', filter) 13351 .val( previousSearch.search ) 13352 .attr( 'placeholder', opts.placeholder ) 13353 .on( 13354 'keyup.DT search.DT input.DT paste.DT cut.DT', 13355 searchDelay ? 13356 DataTable.util.debounce( searchFn, searchDelay ) : 13357 searchFn 13358 ) 13359 .on( 'mouseup.DT', function(e) { 13360 // Edge fix! Edge 17 does not trigger anything other than mouse events when clicking 13361 // on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn` 13362 // checks the value to see if it has changed. In other browsers it won't have. 13363 setTimeout( function () { 13364 searchFn.call(jqFilter[0], e); 13365 }, 10); 13366 } ) 13367 .on( 'keypress.DT', function(e) { 13368 /* Prevent form submission */ 13369 if ( e.keyCode == 13 ) { 13370 return false; 13371 } 13372 } ) 13373 .attr('aria-controls', tableId); 13374 13375 // Update the input elements whenever the table is filtered 13376 $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) { 13377 if ( settings === s && jqFilter[0] !== document.activeElement ) { 13378 jqFilter.val( typeof previousSearch.search !== 'function' 13379 ? previousSearch.search 13380 : '' 13381 ); 13382 } 13383 } ); 13384 13385 return filter; 13386 }, 'f' ); 13387 13388 // opts 13389 // - type - button configuration 13390 // - buttons - number of buttons to show - must be odd 13391 DataTable.feature.register( 'paging', function ( settings, opts ) { 13392 // Don't show the paging input if the table doesn't have paging enabled 13393 if (! settings.oFeatures.bPaginate) { 13394 return null; 13395 } 13396 13397 opts = $.extend({ 13398 buttons: DataTable.ext.pager.numbers_length, 13399 type: settings.sPaginationType, 13400 boundaryNumbers: true, 13401 firstLast: true, 13402 previousNext: true, 13403 numbers: true 13404 }, opts); 13405 13406 var host = $('<div/>') 13407 .addClass(settings.oClasses.paging.container + (opts.type ? ' paging_' + opts.type : '')) 13408 .append( 13409 $('<nav>') 13410 .attr('aria-label', 'pagination') 13411 .addClass(settings.oClasses.paging.nav) 13412 ); 13413 var draw = function () { 13414 _pagingDraw(settings, host.children(), opts); 13415 }; 13416 13417 settings.aoDrawCallback.push(draw); 13418 13419 // Responsive redraw of paging control 13420 $(settings.nTable).on('column-sizing.dt.DT', draw); 13421 13422 return host; 13423 }, 'p' ); 13424 13425 /** 13426 * Dynamically create the button type array based on the configuration options. 13427 * This will only happen if the paging type is not defined. 13428 */ 13429 function _pagingDynamic(opts) { 13430 var out = []; 13431 13432 if (opts.numbers) { 13433 out.push('numbers'); 13434 } 13435 13436 if (opts.previousNext) { 13437 out.unshift('previous'); 13438 out.push('next'); 13439 } 13440 13441 if (opts.firstLast) { 13442 out.unshift('first'); 13443 out.push('last'); 13444 } 13445 13446 return out; 13447 } 13448 13449 function _pagingDraw(settings, host, opts) { 13450 if (! settings._bInitComplete) { 13451 return; 13452 } 13453 13454 var 13455 plugin = opts.type 13456 ? DataTable.ext.pager[ opts.type ] 13457 : _pagingDynamic, 13458 aria = settings.oLanguage.oAria.paginate || {}, 13459 start = settings._iDisplayStart, 13460 len = settings._iDisplayLength, 13461 visRecords = settings.fnRecordsDisplay(), 13462 all = len === -1, 13463 page = all ? 0 : Math.ceil( start / len ), 13464 pages = all ? 1 : Math.ceil( visRecords / len ), 13465 buttons = [], 13466 buttonEls = [], 13467 buttonsNested = plugin(opts) 13468 .map(function (val) { 13469 return val === 'numbers' 13470 ? _pagingNumbers(page, pages, opts.buttons, opts.boundaryNumbers) 13471 : val; 13472 }); 13473 13474 // .flat() would be better, but not supported in old Safari 13475 buttons = buttons.concat.apply(buttons, buttonsNested); 13476 13477 for (var i=0 ; i<buttons.length ; i++) { 13478 var button = buttons[i]; 13479 13480 var btnInfo = _pagingButtonInfo(settings, button, page, pages); 13481 var btn = _fnRenderer( settings, 'pagingButton' )( 13482 settings, 13483 button, 13484 btnInfo.display, 13485 btnInfo.active, 13486 btnInfo.disabled 13487 ); 13488 13489 var ariaLabel = typeof button === 'string' 13490 ? aria[ button ] 13491 : aria.number 13492 ? aria.number + (button+1) 13493 : null; 13494 13495 // Common attributes 13496 $(btn.clicker).attr({ 13497 'aria-controls': settings.sTableId, 13498 'aria-disabled': btnInfo.disabled ? 'true' : null, 13499 'aria-current': btnInfo.active ? 'page' : null, 13500 'aria-label': ariaLabel, 13501 'data-dt-idx': button, 13502 'tabIndex': btnInfo.disabled 13503 ? -1 13504 : settings.iTabIndex && btn.clicker[0].nodeName.toLowerCase() !== 'span' 13505 ? settings.iTabIndex 13506 : null, // `0` doesn't need a tabIndex since it is the default 13507 }); 13508 13509 if (typeof button !== 'number') { 13510 $(btn.clicker).addClass(button); 13511 } 13512 13513 _fnBindAction( 13514 btn.clicker, {action: button}, function(e) { 13515 e.preventDefault(); 13516 13517 _fnPageChange( settings, e.data.action, true ); 13518 } 13519 ); 13520 13521 buttonEls.push(btn.display); 13522 } 13523 13524 var wrapped = _fnRenderer(settings, 'pagingContainer')( 13525 settings, buttonEls 13526 ); 13527 13528 var activeEl = host.find(document.activeElement).data('dt-idx'); 13529 13530 host.empty().append(wrapped); 13531 13532 if ( activeEl !== undefined ) { 13533 host.find( '[data-dt-idx='+activeEl+']' ).trigger('focus'); 13534 } 13535 13536 // Responsive - check if the buttons are over two lines based on the 13537 // height of the buttons and the container. 13538 if (buttonEls.length) { 13539 var outerHeight = $(buttonEls[0]).outerHeight(); 13540 13541 if ( 13542 opts.buttons > 1 && // prevent infinite 13543 outerHeight > 0 && // will be 0 if hidden 13544 $(host).height() >= (outerHeight * 2) - 10 13545 ) { 13546 _pagingDraw(settings, host, $.extend({}, opts, { buttons: opts.buttons - 2 })); 13547 } 13548 } 13549 } 13550 13551 /** 13552 * Get properties for a button based on the current paging state of the table 13553 * 13554 * @param {*} settings DT settings object 13555 * @param {*} button The button type in question 13556 * @param {*} page Table's current page 13557 * @param {*} pages Number of pages 13558 * @returns Info object 13559 */ 13560 function _pagingButtonInfo(settings, button, page, pages) { 13561 var lang = settings.oLanguage.oPaginate; 13562 var o = { 13563 display: '', 13564 active: false, 13565 disabled: false 13566 }; 13567 13568 switch ( button ) { 13569 case 'ellipsis': 13570 o.display = '…'; 13571 break; 13572 13573 case 'first': 13574 o.display = lang.sFirst; 13575 13576 if (page === 0) { 13577 o.disabled = true; 13578 } 13579 break; 13580 13581 case 'previous': 13582 o.display = lang.sPrevious; 13583 13584 if ( page === 0 ) { 13585 o.disabled = true; 13586 } 13587 break; 13588 13589 case 'next': 13590 o.display = lang.sNext; 13591 13592 if ( pages === 0 || page === pages-1 ) { 13593 o.disabled = true; 13594 } 13595 break; 13596 13597 case 'last': 13598 o.display = lang.sLast; 13599 13600 if ( pages === 0 || page === pages-1 ) { 13601 o.disabled = true; 13602 } 13603 break; 13604 13605 default: 13606 if ( typeof button === 'number' ) { 13607 o.display = settings.fnFormatNumber( button + 1 ); 13608 13609 if (page === button) { 13610 o.active = true; 13611 } 13612 } 13613 break; 13614 } 13615 13616 return o; 13617 } 13618 13619 /** 13620 * Compute what number buttons to show in the paging control 13621 * 13622 * @param {*} page Current page 13623 * @param {*} pages Total number of pages 13624 * @param {*} buttons Target number of number buttons 13625 * @param {boolean} addFirstLast Indicate if page 1 and end should be included 13626 * @returns Buttons to show 13627 */ 13628 function _pagingNumbers ( page, pages, buttons, addFirstLast ) { 13629 var 13630 numbers = [], 13631 half = Math.floor(buttons / 2), 13632 before = addFirstLast ? 2 : 1, 13633 after = addFirstLast ? 1 : 0; 13634 13635 if ( pages <= buttons ) { 13636 numbers = _range(0, pages); 13637 } 13638 else if (buttons === 1) { 13639 // Single button - current page only 13640 numbers = [page]; 13641 } 13642 else if (buttons === 3) { 13643 // Special logic for just three buttons 13644 if (page <= 1) { 13645 numbers = [0, 1, 'ellipsis']; 13646 } 13647 else if (page >= pages - 2) { 13648 numbers = _range(pages-2, pages); 13649 numbers.unshift('ellipsis'); 13650 } 13651 else { 13652 numbers = ['ellipsis', page, 'ellipsis']; 13653 } 13654 } 13655 else if ( page <= half ) { 13656 numbers = _range(0, buttons-before); 13657 numbers.push('ellipsis'); 13658 13659 if (addFirstLast) { 13660 numbers.push(pages-1); 13661 } 13662 } 13663 else if ( page >= pages - 1 - half ) { 13664 numbers = _range(pages-(buttons-before), pages); 13665 numbers.unshift('ellipsis'); 13666 13667 if (addFirstLast) { 13668 numbers.unshift(0); 13669 } 13670 } 13671 else { 13672 numbers = _range(page-half+before, page+half-after); 13673 numbers.push('ellipsis'); 13674 numbers.unshift('ellipsis'); 13675 13676 if (addFirstLast) { 13677 numbers.push(pages-1); 13678 numbers.unshift(0); 13679 } 13680 } 13681 13682 return numbers; 13683 } 13684 13685 var __lengthCounter = 0; 13686 13687 // opts 13688 // - menu 13689 // - text 13690 DataTable.feature.register( 'pageLength', function ( settings, opts ) { 13691 var features = settings.oFeatures; 13692 13693 // For compatibility with the legacy `pageLength` top level option 13694 if (! features.bPaginate || ! features.bLengthChange) { 13695 return null; 13696 } 13697 13698 opts = $.extend({ 13699 menu: settings.aLengthMenu, 13700 text: settings.oLanguage.sLengthMenu 13701 }, opts); 13702 13703 var 13704 classes = settings.oClasses.length, 13705 tableId = settings.sTableId, 13706 menu = opts.menu, 13707 lengths = [], 13708 language = [], 13709 i; 13710 13711 // Options can be given in a number of ways 13712 if (Array.isArray( menu[0] )) { 13713 // Old 1.x style - 2D array 13714 lengths = menu[0]; 13715 language = menu[1]; 13716 } 13717 else { 13718 for ( i=0 ; i<menu.length ; i++ ) { 13719 // An object with different label and value 13720 if ($.isPlainObject(menu[i])) { 13721 lengths.push(menu[i].value); 13722 language.push(menu[i].label); 13723 } 13724 else { 13725 // Or just a number to display and use 13726 lengths.push(menu[i]); 13727 language.push(menu[i]); 13728 } 13729 } 13730 } 13731 13732 // We can put the <select> outside of the label if it is at the start or 13733 // end which helps improve accessability (not all screen readers like 13734 // implicit for elements). 13735 var end = opts.text.match(/_MENU_$/); 13736 var start = opts.text.match(/^_MENU_/); 13737 var removed = opts.text.replace(/_MENU_/, ''); 13738 var str = '<label>' + opts.text + '</label>'; 13739 13740 if (start) { 13741 str = '_MENU_<label>' + removed + '</label>'; 13742 } 13743 else if (end) { 13744 str = '<label>' + removed + '</label>_MENU_'; 13745 } 13746 13747 // Wrapper element - use a span as a holder for where the select will go 13748 var tmpId = 'tmp-' + (+new Date())
13749 var div = $('<div/>') 13750 .addClass( classes.container ) 13751 .append( 13752 str.replace( '_MENU_', '<span id="'+tmpId+'"></span>' ) 13753 ); 13754 13755 // Save text node content for macro updating 13756 var textNodes = []; 13757 Array.prototype.slice.call(div.find('label')[0].childNodes).forEach(function (el) { 13758 if (el.nodeType === Node.TEXT_NODE) { 13759 textNodes.push({ 13760 el: el, 13761 text: el.textContent 13762 }); 13763 } 13764 }); 13765 13766 // Update the label text in case it has an entries value 13767 var updateEntries = function (len) { 13768 textNodes.forEach(function (node) { 13769 node.el.textContent = _fnMacros(settings, node.text, len); 13770 }); 13771 } 13772 13773 // Next, the select itself, along with the options 13774 var select = $('<select/>', { 13775 'aria-controls': tableId, 13776 'class': classes.select 13777 } ); 13778 13779 for ( i=0 ; i<lengths.length ; i++ ) { 13780 select[0][ i ] = new Option( 13781 typeof language[i] === 'number' ? 13782 settings.fnFormatNumber( language[i] ) : 13783 language[i], 13784 lengths[i] 13785 ); 13786 } 13787 13788 // add for and id to label and input 13789 div.find('label').attr('for', 'dt-length-' + __lengthCounter); 13790 select.attr('id', 'dt-length-' + __lengthCounter); 13791 __lengthCounter++; 13792 13793 // Swap in the select list 13794 div.find('#' + tmpId).replaceWith(select); 13795 13796 // Can't use `select` variable as user might provide their own and the 13797 // reference is broken by the use of outerHTML 13798 $('select', div) 13799 .val( settings._iDisplayLength ) 13800 .on( 'change.DT', function() { 13801 _fnLengthChange( settings, $(this).val() ); 13802 _fnDraw( settings ); 13803 } ); 13804 13805 // Update node value whenever anything changes the table's length 13806 $(settings.nTable).on( 'length.dt.DT', function (e, s, len) { 13807 if ( settings === s ) { 13808 $('select', div).val( len ); 13809 13810 // Resolve plurals in the text for the new length 13811 updateEntries(len); 13812 } 13813 } ); 13814 13815 updateEntries(settings._iDisplayLength); 13816 13817 return div; 13818 }, 'l' ); 13819 13820 // jQuery access 13821 $.fn.dataTable = DataTable; 13822 13823 // Provide access to the host jQuery object (circular reference) 13824 DataTable.$ = $; 13825 13826 // Legacy aliases 13827 $.fn.dataTableSettings = DataTable.settings; 13828 $.fn.dataTableExt = DataTable.ext; 13829 13830 // With a capital `D` we return a DataTables API instance rather than a 13831 // jQuery object 13832 $.fn.DataTable = function ( opts ) { 13833 return $(this).dataTable( opts ).api(); 13834 }; 13835 13836 // All properties that are available to $.fn.dataTable should also be 13837 // available on $.fn.DataTable 13838 $.each( DataTable, function ( prop, val ) { 13839 $.fn.DataTable[ prop ] = val; 13840 } ); 13841 13842 return DataTable; 13843}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.