1/* 2 * This combined file was created by the DataTables downloader builder: 3 * https://datatables.net/download 4 * 5 * To rebuild or modify this file with the latest versions of the included 6 * software please visit: 7 * https://datatables.net/download/#dt/dt-2.2.2 8 * 9 * Included libraries: 10 * DataTables 2.2.2 11 */ 12 13/*! DataTables 2.2.2 14 * © SpryMedia Ltd - datatables.net/license 15 */ 16 17(function( factory ) { 18 "use strict"; 19 20 if ( typeof define === 'function' && define.amd ) { 21 // AMD 22 define( ['jquery'], function ( $ ) { 23 return factory( $, window, document ); 24 } ); 25 } 26 else if ( typeof exports === 'object' ) { 27 // CommonJS 28 // jQuery's factory checks for a global window - if it isn't present then it 29 // returns a factory function that expects the window object 30 var jq = require('jquery'); 31 32 if (typeof window === 'undefined') { 33 module.exports = function (root, $) { 34 if ( ! root ) { 35 // CommonJS environments without a window global must pass a 36 // root. This will give an error otherwise 37 root = window; 38 } 39 40 if ( ! $ ) { 41 $ = jq( root ); 42 } 43 44 return factory( $, root, root.document ); 45 }; 46 } 47 else { 48 module.exports = factory( jq, window, window.document ); 49 } 50 } 51 else { 52 // Browser 53 window.DataTable = factory( jQuery, window, document ); 54 } 55}(function( $, window, document ) { 56 "use strict"; 57 58 59 var DataTable = function ( selector, options ) 60 { 61 // Check if called with a window or jQuery object for DOM less applications 62 // This is for backwards compatibility 63 if (DataTable.factory(selector, options)) { 64 return DataTable; 65 } 66 67 // When creating with `new`, create a new DataTable, returning the API instance 68 if (this instanceof DataTable) { 69 return $(selector).DataTable(options); 70 } 71 else { 72 // Argument switching 73 options = selector; 74 } 75 76 var _that = this; 77 var emptyInit = options === undefined; 78 var len = this.length; 79 80 if ( emptyInit ) { 81 options = {}; 82 } 83 84 // Method to get DT API instance from jQuery object 85 this.api = function () 86 { 87 return new _Api( this ); 88 }; 89 90 this.each(function() { 91 // For each initialisation we want to give it a clean initialisation 92 // object that can be bashed around 93 var o = {}; 94 var oInit = len > 1 ? // optimisation for single table case 95 _fnExtend( o, options, true ) : 96 options; 97 98 99 var i=0, iLen; 100 var sId = this.getAttribute( 'id' ); 101 var defaults = DataTable.defaults; 102 var $this = $(this); 103 104 105 /* Sanity check */ 106 if ( this.nodeName.toLowerCase() != 'table' ) 107 { 108 _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); 109 return; 110 } 111 112 $(this).trigger( 'options.dt', oInit ); 113 114 /* Backwards compatibility for the defaults */ 115 _fnCompatOpts( defaults ); 116 _fnCompatCols( defaults.column ); 117 118 /* Convert the camel-case defaults to Hungarian */ 119 _fnCamelToHungarian( defaults, defaults, true ); 120 _fnCamelToHungarian( defaults.column, defaults.column, true ); 121 122 /* Setting up the initialisation object */ 123 _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true ); 124 125 126 127 /* Check to see if we are re-initialising a table */ 128 var allSettings = DataTable.settings; 129 for ( i=0, iLen=allSettings.length ; i<iLen ; i++ ) 130 { 131 var s = allSettings[i]; 132 133 /* Base check on table node */ 134 if ( 135 s.nTable == this || 136 (s.nTHead && s.nTHead.parentNode == this) || 137 (s.nTFoot && s.nTFoot.parentNode == this) 138 ) { 139 var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve; 140 var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy; 141 142 if ( emptyInit || bRetrieve ) 143 { 144 return s.oInstance; 145 } 146 else if ( bDestroy ) 147 { 148 new DataTable.Api(s).destroy(); 149 break; 150 } 151 else 152 { 153 _fnLog( s, 0, 'Cannot reinitialise DataTable', 3 ); 154 return; 155 } 156 } 157 158 /* If the element we are initialising has the same ID as a table which was previously 159 * initialised, but the table nodes don't match (from before) then we destroy the old 160 * instance by simply deleting it. This is under the assumption that the table has been 161 * destroyed by other methods. Anyone using non-id selectors will need to do this manually 162 */ 163 if ( s.sTableId == this.id ) 164 { 165 allSettings.splice( i, 1 ); 166 break; 167 } 168 } 169 170 /* Ensure the table has an ID - required for accessibility */ 171 if ( sId === null || sId === "" ) 172 { 173 sId = "DataTables_Table_"+(DataTable.ext._unique++); 174 this.id = sId; 175 } 176 177 /* Create the settings object for this table and set some of the default parameters */ 178 var oSettings = $.extend( true, {}, DataTable.models.oSettings, { 179 "sDestroyWidth": $this[0].style.width, 180 "sInstance": sId, 181 "sTableId": sId, 182 colgroup: $('<colgroup>').prependTo(this), 183 fastData: function (row, column, type) {
184 return _fnGetCellData(oSettings, row, column, type); 185 } 186 } ); 187 oSettings.nTable = this; 188 oSettings.oInit = oInit; 189 190 allSettings.push( oSettings ); 191 192 // Make a single API instance available for internal handling 193 oSettings.api = new _Api( oSettings ); 194 195 // Need to add the instance after the instance after the settings object has been added 196 // to the settings array, so we can self reference the table instance if more than one 197 oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable(); 198 199 // Backwards compatibility, before we apply all the defaults 200 _fnCompatOpts( oInit ); 201 202 // If the length menu is given, but the init display length is not, use the length menu 203 if ( oInit.aLengthMenu && ! oInit.iDisplayLength ) 204 { 205 oInit.iDisplayLength = Array.isArray(oInit.aLengthMenu[0]) 206 ? oInit.aLengthMenu[0][0] 207 : $.isPlainObject( oInit.aLengthMenu[0] ) 208 ? oInit.aLengthMenu[0].value 209 : oInit.aLengthMenu[0]; 210 } 211 212 // Apply the defaults and init options to make a single init object will all 213 // options defined from defaults and instance options. 214 oInit = _fnExtend( $.extend( true, {}, defaults ), oInit ); 215 216 217 // Map the initialisation options onto the settings object 218 _fnMap( oSettings.oFeatures, oInit, [ 219 "bPaginate", 220 "bLengthChange", 221 "bFilter", 222 "bSort", 223 "bSortMulti", 224 "bInfo", 225 "bProcessing", 226 "bAutoWidth", 227 "bSortClasses", 228 "bServerSide", 229 "bDeferRender" 230 ] ); 231 _fnMap( oSettings, oInit, [ 232 "ajax", 233 "fnFormatNumber", 234 "sServerMethod", 235 "aaSorting", 236 "aaSortingFixed", 237 "aLengthMenu", 238 "sPaginationType", 239 "iStateDuration", 240 "bSortCellsTop", 241 "iTabIndex", 242 "sDom", 243 "fnStateLoadCallback", 244 "fnStateSaveCallback", 245 "renderer", 246 "searchDelay", 247 "rowId", 248 "caption", 249 "layout", 250 "orderDescReverse", 251 "typeDetect", 252 [ "iCookieDuration", "iStateDuration" ], // backwards compat 253 [ "oSearch", "oPreviousSearch" ], 254 [ "aoSearchCols", "aoPreSearchCols" ], 255 [ "iDisplayLength", "_iDisplayLength" ] 256 ] ); 257 _fnMap( oSettings.oScroll, oInit, [ 258 [ "sScrollX", "sX" ], 259 [ "sScrollXInner", "sXInner" ], 260 [ "sScrollY", "sY" ], 261 [ "bScrollCollapse", "bCollapse" ] 262 ] ); 263 _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" ); 264 265 /* Callback functions which are array driven */ 266 _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback ); 267 _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams ); 268 _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams ); 269 _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded ); 270 _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback ); 271 _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow ); 272 _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback ); 273 _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback ); 274 _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete ); 275 _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback ); 276 277 oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId ); 278 279 /* Browser support detection */ 280 _fnBrowserDetect( oSettings ); 281 282 var oClasses = oSettings.oClasses; 283 284 $.extend( oClasses, DataTable.ext.classes, oInit.oClasses ); 285 $this.addClass( oClasses.table ); 286 287 if (! oSettings.oFeatures.bPaginate) { 288 oInit.iDisplayStart = 0; 289 } 290 291 if ( oSettings.iInitDisplayStart === undefined ) 292 { 293 /* Display start point, taking into account the save saving */ 294 oSettings.iInitDisplayStart = oInit.iDisplayStart; 295 oSettings._iDisplayStart = oInit.iDisplayStart; 296 } 297 298 var defer = oInit.iDeferLoading; 299 if ( defer !== null ) 300 { 301 oSettings.deferLoading = true; 302 303 var tmp = Array.isArray(defer); 304 oSettings._iRecordsDisplay = tmp ? defer[0] : defer; 305 oSettings._iRecordsTotal = tmp ? defer[1] : defer; 306 } 307 308 /* 309 * Columns 310 * See if we should load columns automatically or use defined ones 311 */ 312 var columnsInit = []; 313 var thead = this.getElementsByTagName('thead'); 314 var initHeaderLayout = _fnDetectHeader( oSettings, thead[0] ); 315 316 // If we don't have a columns array, then generate one with nulls 317 if ( oInit.aoColumns ) { 318 columnsInit = oInit.aoColumns; 319 } 320 else if ( initHeaderLayout.length ) { 321 for ( i=0, iLen=initHeaderLayout[0].length ; i<iLen ; i++ ) { 322 columnsInit.push( null ); 323 } 324 } 325 326 // Add the columns 327 for ( i=0, iLen=columnsInit.length ; i<iLen ; i++ ) { 328 _fnAddColumn( oSettings ); 329 } 330 331 // Apply the column definitions 332 _fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, columnsInit, initHeaderLayout, function (iCol, oDef) { 333 _fnColumnOptions( oSettings, iCol, oDef ); 334 } ); 335 336 /* HTML5 attribute detection - build an mData object automatically if the 337 * attributes are found 338 */ 339 var rowOne = $this.children('tbody').find('tr').eq(0); 340 341 if ( rowOne.length ) { 342 var a = function ( cell, name ) { 343 return cell.getAttribute( 'data-'+name ) !== null ? name : null; 344 }; 345 346 $( rowOne[0] ).children('th, td').each( function (i, cell) { 347 var col = oSettings.aoColumns[i]; 348 349 if (! col) { 350 _fnLog( oSettings, 0, 'Incorrect column count', 18 ); 351 } 352 353 if ( col.mData === i ) { 354 var sort = a( cell, 'sort' ) || a( cell, 'order' ); 355 var filter = a( cell, 'filter' ) || a( cell, 'search' ); 356 357 if ( sort !== null || filter !== null ) { 358 col.mData = { 359 _: i+'.display', 360 sort: sort !== null ? i+'.@data-'+sort : undefined, 361 type: sort !== null ? i+'.@data-'+sort : undefined, 362 filter: filter !== null ? i+'.@data-'+filter : undefined 363 }; 364 col._isArrayHost = true; 365 366 _fnColumnOptions( oSettings, i ); 367 } 368 } 369 } ); 370 } 371 372 // Must be done after everything which can be overridden by the state saving! 373 _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState ); 374 375 var features = oSettings.oFeatures; 376 if ( oInit.bStateSave ) 377 { 378 features.bStateSave = true; 379 } 380 381 // If aaSorting is not defined, then we use the first indicator in asSorting 382 // in case that has been altered, so the default sort reflects that option 383 if ( oInit.aaSorting === undefined ) { 384 var sorting = oSettings.aaSorting; 385 for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) { 386 sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0]; 387 } 388 } 389 390 // Do a first pass on the sorting classes (allows any size changes to be taken into 391 // account, and also will apply sorting disabled classes if disabled 392 _fnSortingClasses( oSettings ); 393 394 _fnCallbackReg( oSettings, 'aoDrawCallback', function () { 395 if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) { 396 _fnSortingClasses( oSettings ); 397 } 398 } ); 399 400 401 /* 402 * Table HTML init 403 * Cache the header, body and footer as required, creating them if needed 404 */ 405 var caption = $this.children('caption'); 406 407 if ( oSettings.caption ) { 408 if ( caption.length === 0 ) { 409 caption = $('<caption/>').appendTo( $this ); 410 } 411 412 caption.html( oSettings.caption ); 413 } 414 415 // Store the caption side, so we can remove the element from the document 416 // when creating the element 417 if (caption.length) { 418 caption[0]._captionSide = caption.css('caption-side'); 419 oSettings.captionNode = caption[0]; 420 } 421 422 if ( thead.length === 0 ) { 423 thead = $('<thead/>').appendTo($this); 424 } 425 oSettings.nTHead = thead[0]; 426 427 var tbody = $this.children('tbody'); 428 if ( tbody.length === 0 ) { 429 tbody = $('<tbody/>').insertAfter(thead); 430 } 431 oSettings.nTBody = tbody[0]; 432 433 var tfoot = $this.children('tfoot'); 434 if ( tfoot.length === 0 ) { 435 // If we are a scrolling table, and no footer has been given, then we need to create 436 // a tfoot element for the caption element to be appended to 437 tfoot = $('<tfoot/>').appendTo($this); 438 } 439 oSettings.nTFoot = tfoot[0]; 440 441 // Copy the data index array 442 oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); 443 444 // Initialisation complete - table can be drawn
445 oSettings.bInitialised = true; 446 447 // Language definitions 448 var oLanguage = oSettings.oLanguage; 449 $.extend( true, oLanguage, oInit.oLanguage ); 450 451 if ( oLanguage.sUrl ) { 452 // Get the language definitions from a file 453 $.ajax( { 454 dataType: 'json', 455 url: oLanguage.sUrl, 456 success: function ( json ) { 457 _fnCamelToHungarian( defaults.oLanguage, json ); 458 $.extend( true, oLanguage, json, oSettings.oInit.oLanguage ); 459 460 _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true); 461 _fnInitialise( oSettings ); 462 }, 463 error: function () { 464 // Error occurred loading language file 465 _fnLog( oSettings, 0, 'i18n file loading error', 21 ); 466 467 // Continue on as best we can 468 _fnInitialise( oSettings ); 469 } 470 } ); 471 } 472 else { 473 _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true); 474 _fnInitialise( oSettings ); 475 } 476 } ); 477 _that = null; 478 return this; 479 }; 480 481 482 483 /** 484 * DataTables extensions 485 * 486 * This namespace acts as a collection area for plug-ins that can be used to 487 * extend DataTables capabilities. Indeed many of the build in methods 488 * use this method to provide their own capabilities (sorting methods for 489 * example). 490 * 491 * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy 492 * reasons 493 * 494 * @namespace 495 */ 496 DataTable.ext = _ext = { 497 /** 498 * Buttons. For use with the Buttons extension for DataTables. This is 499 * defined here so other extensions can define buttons regardless of load 500 * order. It is _not_ used by DataTables core. 501 * 502 * @type object 503 * @default {} 504 */ 505 buttons: {}, 506 507 508 /** 509 * Element class names 510 * 511 * @type object 512 * @default {} 513 */ 514 classes: {}, 515 516 517 /** 518 * DataTables build type (expanded by the download builder) 519 * 520 * @type string 521 */ 522 builder: "dt/dt-2.2.2", 523 524 525 /** 526 * Error reporting. 527 * 528 * How should DataTables report an error. Can take the value 'alert', 529 * 'throw', 'none' or a function. 530 * 531 * @type string|function 532 * @default alert 533 */ 534 errMode: "alert", 535 536 537 /** 538 * Legacy so v1 plug-ins don't throw js errors on load 539 */ 540 feature: [], 541 542 /** 543 * Feature plug-ins. 544 * 545 * This is an object of callbacks which provide the features for DataTables 546 * to be initialised via the `layout` option. 547 */ 548 features: {}, 549 550 551 /** 552 * Row searching. 553 * 554 * This method of searching is complimentary to the default type based 555 * searching, and a lot more comprehensive as it allows you complete control 556 * over the searching logic. Each element in this array is a function 557 * (parameters described below) that is called for every row in the table, 558 * and your logic decides if it should be included in the searching data set 559 * or not. 560 * 561 * Searching functions have the following input parameters: 562 * 563 * 1. `{object}` DataTables settings object: see 564 * {@link DataTable.models.oSettings} 565 * 2. `{array|object}` Data for the row to be processed (same as the 566 * original format that was passed in as the data source, or an array 567 * from a DOM data source 568 * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which 569 * can be useful to retrieve the `TR` element if you need DOM interaction. 570 * 571 * And the following return is expected: 572 * 573 * * {boolean} Include the row in the searched result set (true) or not 574 * (false) 575 * 576 * Note that as with the main search ability in DataTables, technically this 577 * is "filtering", since it is subtractive. However, for consistency in 578 * naming we call it searching here. 579 * 580 * @type array 581 * @default [] 582 * 583 * @example 584 * // The following example shows custom search being applied to the 585 * // fourth column (i.e. the data[3] index) based on two input values 586 * // from the end-user, matching the data in a certain range. 587 * $.fn.dataTable.ext.search.push( 588 * function( settings, data, dataIndex ) { 589 * var min = document.getElementById('min').value * 1; 590 * var max = document.getElementById('max').value * 1; 591 * var version = data[3] == "-" ? 0 : data[3]*1; 592 * 593 * if ( min == "" && max == "" ) { 594 * return true; 595 * } 596 * else if ( min == "" && version < max ) { 597 * return true; 598 * } 599 * else if ( min < version && "" == max ) { 600 * return true; 601 * } 602 * else if ( min < version && version < max ) { 603 * return true; 604 * } 605 * return false;
606 * } 607 * ); 608 */ 609 search: [], 610 611 612 /** 613 * Selector extensions 614 * 615 * The `selector` option can be used to extend the options available for the 616 * selector modifier options (`selector-modifier` object data type) that 617 * each of the three built in selector types offer (row, column and cell + 618 * their plural counterparts). For example the Select extension uses this 619 * mechanism to provide an option to select only rows, columns and cells 620 * that have been marked as selected by the end user (`{selected: true}`), 621 * which can be used in conjunction with the existing built in selector 622 * options. 623 * 624 * Each property is an array to which functions can be pushed. The functions 625 * take three attributes: 626 * 627 * * Settings object for the host table 628 * * Options object (`selector-modifier` object type) 629 * * Array of selected item indexes 630 * 631 * The return is an array of the resulting item indexes after the custom 632 * selector has been applied. 633 * 634 * @type object 635 */ 636 selector: { 637 cell: [], 638 column: [], 639 row: [] 640 }, 641 642 643 /** 644 * Legacy configuration options. Enable and disable legacy options that 645 * are available in DataTables. 646 * 647 * @type object 648 */ 649 legacy: { 650 /** 651 * Enable / disable DataTables 1.9 compatible server-side processing 652 * requests 653 * 654 * @type boolean 655 * @default null 656 */ 657 ajax: null 658 }, 659 660 661 /** 662 * Pagination plug-in methods. 663 * 664 * Each entry in this object is a function and defines which buttons should 665 * be shown by the pagination rendering method that is used for the table: 666 * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the 667 * buttons are displayed in the document, while the functions here tell it 668 * what buttons to display. This is done by returning an array of button 669 * descriptions (what each button will do). 670 * 671 * Pagination types (the four built in options and any additional plug-in 672 * options defined here) can be used through the `paginationType` 673 * initialisation parameter. 674 * 675 * The functions defined take two parameters: 676 * 677 * 1. `{int} page` The current page index 678 * 2. `{int} pages` The number of pages in the table 679 * 680 * Each function is expected to return an array where each element of the 681 * array can be one of: 682 * 683 * * `first` - Jump to first page when activated 684 * * `last` - Jump to last page when activated 685 * * `previous` - Show previous page when activated 686 * * `next` - Show next page when activated 687 * * `{int}` - Show page of the index given 688 * * `{array}` - A nested array containing the above elements to add a 689 * containing 'DIV' element (might be useful for styling). 690 * 691 * Note that DataTables v1.9- used this object slightly differently whereby 692 * an object with two functions would be defined for each plug-in. That 693 * ability is still supported by DataTables 1.10+ to provide backwards 694 * compatibility, but this option of use is now decremented and no longer 695 * documented in DataTables 1.10+. 696 * 697 * @type object 698 * @default {} 699 * 700 * @example 701 * // Show previous, next and current page buttons only 702 * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { 703 * return [ 'previous', page, 'next' ]; 704 * }; 705 */ 706 pager: {}, 707 708 709 renderer: { 710 pageButton: {}, 711 header: {} 712 }, 713 714 715 /** 716 * Ordering plug-ins - custom data source 717 * 718 * The extension options for ordering of data available here is complimentary 719 * to the default type based ordering that DataTables typically uses. It 720 * allows much greater control over the the data that is being used to 721 * order a column, but is necessarily therefore more complex. 722 * 723 * This type of ordering is useful if you want to do ordering based on data 724 * live from the DOM (for example the contents of an 'input' element) rather 725 * than just the static string that DataTables knows of. 726 * 727 * The way these plug-ins work is that you create an array of the values you 728 * wish to be ordering for the column in question and then return that 729 * array. The data in the array much be in the index order of the rows in 730 * the table (not the currently ordering order!). Which order data gathering 731 * function is run here depends on the `dt-init columns.orderDataType` 732 * parameter that is used for the column (if any). 733 * 734 * The functions defined take two parameters: 735 * 736 * 1. `{object}` DataTables settings object: see 737 * {@link DataTable.models.oSettings} 738 * 2. `{int}` Target column index 739 * 740 * Each function is expected to return an array: 741 * 742 * * `{array}` Data for the column to be ordering upon 743 * 744 * @type array 745 * 746 * @example 747 * // Ordering using `input` node values 748 * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) 749 * { 750 * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { 751 * return $('input', td).val(); 752 * } ); 753 * } 754 */ 755 order: {}, 756 757 758 /** 759 * Type based plug-ins. 760 * 761 * Each column in DataTables has a type assigned to it, either by automatic 762 * detection or by direct assignment using the `type` option for the column. 763 * The type of a column will effect how it is ordering and search (plug-ins 764 * can also make use of the column type if required). 765 * 766 * @namespace 767 */ 768 type: { 769 /** 770 * Automatic column class assignment 771 */ 772 className: {}, 773 774 /** 775 * Type detection functions. 776 * 777 * The functions defined in this object are used to automatically detect 778 * a column's type, making initialisation of DataTables super easy, even 779 * when complex data is in the table. 780 * 781 * The functions defined take two parameters: 782 * 783 * 1. `{*}` Data from the column cell to be analysed 784 * 2. `{settings}` DataTables settings object. This can be used to 785 * perform context specific type detection - for example detection 786 * based on language settings such as using a comma for a decimal 787 * place. Generally speaking the options from the settings will not 788 * be required 789 *
790 * Each function is expected to return: 791 * 792 * * `{string|null}` Data type detected, or null if unknown (and thus 793 * pass it on to the other type detection functions. 794 * 795 * @type array 796 * 797 * @example 798 * // Currency type detection plug-in: 799 * $.fn.dataTable.ext.type.detect.push( 800 * function ( data, settings ) { 801 * // Check the numeric part 802 * if ( ! data.substring(1).match(/[0-9]/) ) { 803 * return null; 804 * } 805 * 806 * // Check prefixed by currency 807 * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) { 808 * return 'currency'; 809 * } 810 * return null; 811 * } 812 * ); 813 */ 814 detect: [], 815 816 /** 817 * Automatic renderer assignment 818 */ 819 render: {}, 820 821 822 /** 823 * Type based search formatting. 824 * 825 * The type based searching functions can be used to pre-format the 826 * data to be search on. For example, it can be used to strip HTML 827 * tags or to de-format telephone numbers for numeric only searching. 828 * 829 * Note that is a search is not defined for a column of a given type, 830 * no search formatting will be performed. 831 * 832 * Pre-processing of searching data plug-ins - When you assign the sType 833 * for a column (or have it automatically detected for you by DataTables 834 * or a type detection plug-in), you will typically be using this for 835 * custom sorting, but it can also be used to provide custom searching 836 * by allowing you to pre-processing the data and returning the data in 837 * the format that should be searched upon. This is done by adding 838 * functions this object with a parameter name which matches the sType 839 * for that target column. This is the corollary of <i>afnSortData</i> 840 * for searching data. 841 * 842 * The functions defined take a single parameter: 843 * 844 * 1. `{*}` Data from the column cell to be prepared for searching 845 * 846 * Each function is expected to return: 847 * 848 * * `{string|null}` Formatted string that will be used for the searching. 849 * 850 * @type object 851 * @default {} 852 * 853 * @example 854 * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { 855 * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); 856 * } 857 */ 858 search: {}, 859 860 861 /** 862 * Type based ordering. 863 * 864 * The column type tells DataTables what ordering to apply to the table 865 * when a column is sorted upon. The order for each type that is defined, 866 * is defined by the functions available in this object. 867 * 868 * Each ordering option can be described by three properties added to 869 * this object: 870 * 871 * * `{type}-pre` - Pre-formatting function 872 * * `{type}-asc` - Ascending order function 873 * * `{type}-desc` - Descending order function 874 * 875 * All three can be used together, only `{type}-pre` or only 876 * `{type}-asc` and `{type}-desc` together. It is generally recommended 877 * that only `{type}-pre` is used, as this provides the optimal 878 * implementation in terms of speed, although the others are provided 879 * for compatibility with existing Javascript sort functions. 880 * 881 * `{type}-pre`: Functions defined take a single parameter: 882 * 883 * 1. `{*}` Data from the column cell to be prepared for ordering 884 * 885 * And return: 886 * 887 * * `{*}` Data to be sorted upon 888 * 889 * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort 890 * functions, taking two parameters: 891 * 892 * 1. `{*}` Data to compare to the second parameter 893 * 2. `{*}` Data to compare to the first parameter 894 * 895 * And returning: 896 * 897 * * `{*}` Ordering match: <0 if first parameter should be sorted lower 898 * than the second parameter, ===0 if the two parameters are equal and 899 * >0 if the first parameter should be sorted height than the second 900 * parameter. 901 * 902 * @type object 903 * @default {} 904 * 905 * @example 906 * // Numeric ordering of formatted numbers with a pre-formatter 907 * $.extend( $.fn.dataTable.ext.type.order, { 908 * "string-pre": function(x) { 909 * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); 910 * return parseFloat( a ); 911 * } 912 * } ); 913 * 914 * @example 915 * // Case-sensitive string ordering, with no pre-formatting method 916 * $.extend( $.fn.dataTable.ext.order, { 917 * "string-case-asc": function(x,y) { 918 * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); 919 * }, 920 * "string-case-desc": function(x,y) { 921 * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); 922 * } 923 * } ); 924 */ 925 order: {} 926 }, 927 928 /** 929 * Unique DataTables instance counter 930 * 931 * @type int 932 * @private 933 */ 934 _unique: 0, 935 936 937 // 938 // Depreciated 939 // The following properties are retained for backwards compatibility only. 940 // The should not be used in new projects and will be removed in a future 941 // version 942 // 943 944 /** 945 * Version check function. 946 * @type function 947 * @depreciated Since 1.10 948 */ 949 fnVersionCheck: DataTable.fnVersionCheck, 950 951 952 /** 953 * Index for what 'this' index API functions should use 954 * @type int 955 * @deprecated Since v1.10 956 */ 957 iApiIndex: 0, 958 959 960 /** 961 * Software version 962 * @type string 963 * @deprecated Since v1.10 964 */
965 sVersion: DataTable.version 966 }; 967 968 969 // 970 // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts 971 // 972 $.extend( _ext, { 973 afnFiltering: _ext.search, 974 aTypes: _ext.type.detect, 975 ofnSearch: _ext.type.search, 976 oSort: _ext.type.order, 977 afnSortData: _ext.order, 978 aoFeatures: _ext.feature, 979 oStdClasses: _ext.classes, 980 oPagination: _ext.pager 981 } ); 982 983 984 $.extend( DataTable.ext.classes, { 985 container: 'dt-container', 986 empty: { 987 row: 'dt-empty' 988 }, 989 info: { 990 container: 'dt-info' 991 }, 992 layout: { 993 row: 'dt-layout-row', 994 cell: 'dt-layout-cell', 995 tableRow: 'dt-layout-table', 996 tableCell: '', 997 start: 'dt-layout-start', 998 end: 'dt-layout-end', 999 full: 'dt-layout-full' 1000 }, 1001 length: { 1002 container: 'dt-length', 1003 select: 'dt-input' 1004 }, 1005 order: { 1006 canAsc: 'dt-orderable-asc', 1007 canDesc: 'dt-orderable-desc', 1008 isAsc: 'dt-ordering-asc', 1009 isDesc: 'dt-ordering-desc', 1010 none: 'dt-orderable-none', 1011 position: 'sorting_' 1012 }, 1013 processing: { 1014 container: 'dt-processing' 1015 }, 1016 scrolling: { 1017 body: 'dt-scroll-body', 1018 container: 'dt-scroll', 1019 footer: { 1020 self: 'dt-scroll-foot', 1021 inner: 'dt-scroll-footInner' 1022 }, 1023 header: { 1024 self: 'dt-scroll-head', 1025 inner: 'dt-scroll-headInner' 1026 } 1027 }, 1028 search: { 1029 container: 'dt-search', 1030 input: 'dt-input' 1031 }, 1032 table: 'dataTable', 1033 tbody: { 1034 cell: '', 1035 row: '' 1036 }, 1037 thead: { 1038 cell: '', 1039 row: '' 1040 }, 1041 tfoot: { 1042 cell: '', 1043 row: '' 1044 }, 1045 paging: { 1046 active: 'current', 1047 button: 'dt-paging-button', 1048 container: 'dt-paging', 1049 disabled: 'disabled', 1050 nav: '' 1051 } 1052 } ); 1053 1054 1055 /*
1056 * It is useful to have variables which are scoped locally so only the 1057 * DataTables functions can access them and they don't leak into global space. 1058 * At the same time these functions are often useful over multiple files in the 1059 * core and API, so we list, or at least document, all variables which are used 1060 * by DataTables as private variables here. This also ensures that there is no 1061 * clashing of variable names and that they can easily referenced for reuse. 1062 */ 1063 1064 1065 // Defined else where 1066 // _selector_run 1067 // _selector_opts 1068 // _selector_row_indexes 1069 1070 var _ext; // DataTable.ext 1071 var _Api; // DataTable.Api 1072 var _api_register; // DataTable.Api.register 1073 var _api_registerPlural; // DataTable.Api.registerPlural 1074 1075 var _re_dic = {}; 1076 var _re_new_lines = /[\r\n\u2028]/g; 1077 var _re_html = /<([^>]*>)/g; 1078 var _max_str_len = Math.pow(2, 28); 1079 1080 // This is not strict ISO8601 - Date.parse() is quite lax, although 1081 // implementations differ between browsers. 1082 var _re_date = /^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/; 1083 1084 // Escape regular expression special characters 1085 var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); 1086 1087 // https://en.wikipedia.org/wiki/Foreign_exchange_market 1088 // - \u20BD - Russian ruble. 1089 // - \u20a9 - South Korean Won 1090 // - \u20BA - Turkish Lira 1091 // - \u20B9 - Indian Rupee 1092 // - R - Brazil (R$) and South Africa 1093 // - fr - Swiss Franc 1094 // - kr - Swedish krona, Norwegian krone and Danish krone 1095 // - \u2009 is thin space and \u202F is narrow no-break space, both used in many 1096 // - É - Bitcoin 1097 // - Î - Ethereum 1098 // standards as thousands separators. 1099 var _re_formatted_numeric = /['\u00A0,$£â¬Â¥%\u2009\u202F\u20BD\u20a9\u20BArfkÉÎ]/gi; 1100 1101 1102 var _empty = function ( d ) { 1103 return !d || d === true || d === '-' ? true : false; 1104 }; 1105 1106 1107 var _intVal = function ( s ) { 1108 var integer = parseInt( s, 10 ); 1109 return !isNaN(integer) && isFinite(s) ? integer : null; 1110 }; 1111 1112 // Convert from a formatted number with characters other than `.` as the 1113 // decimal place, to a Javascript number 1114 var _numToDecimal = function ( num, decimalPoint ) { 1115 // Cache created regular expressions for speed as this function is called often 1116 if ( ! _re_dic[ decimalPoint ] ) { 1117 _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); 1118 } 1119 return typeof num === 'string' && decimalPoint !== '.' ? 1120 num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : 1121 num; 1122 }; 1123 1124 1125 var _isNumber = function ( d, decimalPoint, formatted, allowEmpty ) { 1126 var type = typeof d; 1127 var strType = type === 'string'; 1128 1129 if ( type === 'number' || type === 'bigint') { 1130 return true; 1131 } 1132 1133 // If empty return immediately so there must be a number if it is a 1134 // formatted string (this stops the string "k", or "kr", etc being detected 1135 // as a formatted number for currency 1136 if ( allowEmpty && _empty( d ) ) { 1137 return true; 1138 } 1139 1140 if ( decimalPoint && strType ) { 1141 d = _numToDecimal( d, decimalPoint ); 1142 } 1143 1144 if ( formatted && strType ) { 1145 d = d.replace( _re_formatted_numeric, '' ); 1146 } 1147 1148 return !isNaN( parseFloat(d) ) && isFinite( d ); 1149 }; 1150 1151 1152 // A string without HTML in it can be considered to be HTML still 1153 var _isHtml = function ( d ) { 1154 return _empty( d ) || typeof d === 'string'; 1155 }; 1156 1157 // Is a string a number surrounded by HTML? 1158 var _htmlNumeric = function ( d, decimalPoint, formatted, allowEmpty ) { 1159 if ( allowEmpty && _empty( d ) ) { 1160 return true; 1161 } 1162 1163 // input and select strings mean that this isn't just a number 1164 if (typeof d === 'string' && d.match(/<(input|select)/i)) { 1165 return null; 1166 } 1167 1168 var html = _isHtml( d ); 1169 return ! html ? 1170 null : 1171 _isNumber( _stripHtml( d ), decimalPoint, formatted, allowEmpty ) ? 1172 true : 1173 null; 1174 }; 1175 1176 1177 var _pluck = function ( a, prop, prop2 ) { 1178 var out = []; 1179 var i=0, ien=a.length; 1180 1181 // Could have the test in the loop for slightly smaller code, but speed 1182 // is essential here 1183 if ( prop2 !== undefined ) { 1184 for ( ; i<ien ; i++ ) { 1185 if ( a[i] && a[i][ prop ] ) { 1186 out.push( a[i][ prop ][ prop2 ] ); 1187 } 1188 } 1189 } 1190 else { 1191 for ( ; i<ien ; i++ ) { 1192 if ( a[i] ) { 1193 out.push( a[i][ prop ] ); 1194 } 1195 } 1196 } 1197 1198 return out; 1199 }; 1200 1201 1202 // Basically the same as _pluck, but rather than looping over `a` we use `order` 1203 // as the indexes to pick from `a` 1204 var _pluck_order = function ( a, order, prop, prop2 ) 1205 { 1206 var out = []; 1207 var i=0, ien=order.length; 1208 1209 // Could have the test in the loop for slightly smaller code, but speed 1210 // is essential here 1211 if ( prop2 !== undefined ) { 1212 for ( ; i<ien ; i++ ) { 1213 if ( a[ order[i] ] && a[ order[i] ][ prop ] ) { 1214 out.push( a[ order[i] ][ prop ][ prop2 ] ); 1215 } 1216 } 1217 } 1218 else { 1219 for ( ; i<ien ; i++ ) { 1220 if ( a[ order[i] ] ) { 1221 out.push( a[ order[i] ][ prop ] ); 1222 } 1223 } 1224 } 1225 1226 return out; 1227 }; 1228 1229 1230 var _range = function ( len, start ) 1231 { 1232 var out = []; 1233 var end; 1234 1235 if ( start === undefined ) { 1236 start = 0; 1237 end = len; 1238 } 1239 else { 1240 end = start; 1241 start = len; 1242 } 1243 1244 for ( var i=start ; i<end ; i++ ) { 1245 out.push( i ); 1246 } 1247 1248 return out; 1249 }; 1250 1251 1252 var _removeEmpty = function ( a ) 1253 { 1254 var out = []; 1255 1256 for ( var i=0, ien=a.length ; i<ien ; i++ ) { 1257 if ( a[i] ) { // careful - will remove all falsy values! 1258 out.push( a[i] ); 1259 } 1260 } 1261 1262 return out; 1263 }; 1264 1265 // Replaceable function in api.util 1266 var _stripHtml = function (input) { 1267 if (! input || typeof input !== 'string') { 1268 return input; 1269 } 1270 1271 // Irrelevant check to workaround CodeQL's false positive on the regex 1272 if (input.length > _max_str_len) { 1273 throw new Error('Exceeded max str len'); 1274 } 1275 1276 var previous; 1277 1278 input = input.replace(_re_html, ''); // Complete tags 1279 1280 // Safety for incomplete script tag - use do / while to ensure that 1281 // we get all instances 1282 do { 1283 previous = input; 1284 input = input.replace(/<script/i, ''); 1285 } while (input !== previous); 1286 1287 return previous; 1288 }; 1289 1290 // Replaceable function in api.util 1291 var _escapeHtml = function ( d ) { 1292 if (Array.isArray(d)) { 1293 d = d.join(','); 1294 } 1295 1296 return typeof d === 'string' ? 1297 d 1298 .replace(/&/g, '&') 1299 .replace(/</g, '<') 1300 .replace(/>/g, '>') 1301 .replace(/"/g, '"') : 1302 d; 1303 }; 1304 1305 // Remove diacritics from a string by decomposing it and then removing 1306 // non-ascii characters 1307 var _normalize = function (str, both) { 1308 if (typeof str !== 'string') { 1309 return str; 1310 } 1311 1312 // It is faster to just run `normalize` than it is to check if 1313 // we need to with a regex! (Check as it isn't available in old 1314 // Safari) 1315 var res = str.normalize 1316 ? str.normalize("NFD") 1317 : str; 1318 1319 // Equally, here we check if a regex is needed or not 1320 return res.length !== str.length 1321 ? (both === true ? str + ' ' : '' ) + res.replace(/[\u0300-\u036f]/g, "") 1322 : res; 1323 } 1324 1325 /** 1326 * Determine if all values in the array are unique. This means we can short 1327 * cut the _unique method at the cost of a single loop. A sorted array is used 1328 * to easily check the values. 1329 * 1330 * @param {array} src Source array 1331 * @return {boolean} true if all unique, false otherwise 1332 * @ignore 1333 */ 1334 var _areAllUnique = function ( src ) { 1335 if ( src.length < 2 ) { 1336 return true; 1337 } 1338 1339 var sorted = src.slice().sort(); 1340 var last = sorted[0]; 1341 1342 for ( var i=1, ien=sorted.length ; i<ien ; i++ ) { 1343 if ( sorted[i] === last ) { 1344 return false;
1345 } 1346 1347 last = sorted[i]; 1348 } 1349 1350 return true; 1351 }; 1352 1353 1354 /** 1355 * Find the unique elements in a source array. 1356 * 1357 * @param {array} src Source array 1358 * @return {array} Array of unique items 1359 * @ignore 1360 */ 1361 var _unique = function ( src ) 1362 { 1363 if (Array.from && Set) { 1364 return Array.from(new Set(src)); 1365 } 1366 1367 if ( _areAllUnique( src ) ) { 1368 return src.slice(); 1369 } 1370 1371 // A faster unique method is to use object keys to identify used values, 1372 // but this doesn't work with arrays or objects, which we must also 1373 // consider. See jsperf.app/compare-array-unique-versions/4 for more 1374 // information. 1375 var 1376 out = [], 1377 val, 1378 i, ien=src.length, 1379 j, k=0; 1380 1381 again: for ( i=0 ; i<ien ; i++ ) { 1382 val = src[i]; 1383 1384 for ( j=0 ; j<k ; j++ ) { 1385 if ( out[j] === val ) { 1386 continue again; 1387 } 1388 } 1389 1390 out.push( val ); 1391 k++; 1392 } 1393 1394 return out; 1395 }; 1396 1397 // Surprisingly this is faster than [].concat.apply 1398 // https://jsperf.com/flatten-an-array-loop-vs-reduce/2 1399 var _flatten = function (out, val) { 1400 if (Array.isArray(val)) { 1401 for (var i=0 ; i<val.length ; i++) { 1402 _flatten(out, val[i]); 1403 } 1404 } 1405 else { 1406 out.push(val); 1407 } 1408 1409 return out; 1410 } 1411 1412 // Similar to jQuery's addClass, but use classList.add 1413 function _addClass(el, name) { 1414 if (name) { 1415 name.split(' ').forEach(function (n) { 1416 if (n) { 1417 // `add` does deduplication, so no need to check `contains` 1418 el.classList.add(n); 1419 } 1420 }); 1421 } 1422 } 1423 1424 /** 1425 * DataTables utility methods 1426 * 1427 * This namespace provides helper methods that DataTables uses internally to 1428 * create a DataTable, but which are not exclusively used only for DataTables. 1429 * These methods can be used by extension authors to save the duplication of 1430 * code. 1431 * 1432 * @namespace 1433 */ 1434 DataTable.util = { 1435 /** 1436 * Return a string with diacritic characters decomposed 1437 * @param {*} mixed Function or string to normalize 1438 * @param {*} both Return original string and the normalized string 1439 * @returns String or undefined 1440 */ 1441 diacritics: function (mixed, both) { 1442 var type = typeof mixed; 1443 1444 if (type !== 'function') { 1445 return _normalize(mixed, both); 1446 } 1447 _normalize = mixed; 1448 }, 1449 1450 /** 1451 * Debounce a function 1452 * 1453 * @param {function} fn Function to be called 1454 * @param {integer} freq Call frequency in mS 1455 * @return {function} Wrapped function 1456 */ 1457 debounce: function ( fn, timeout ) { 1458 var timer; 1459 1460 return function () { 1461 var that = this; 1462 var args = arguments; 1463 1464 clearTimeout(timer); 1465 1466 timer = setTimeout( function () { 1467 fn.apply(that, args); 1468 }, timeout || 250 ); 1469 }; 1470 }, 1471 1472 /** 1473 * Throttle the calls to a function. Arguments and context are maintained 1474 * for the throttled function. 1475 * 1476 * @param {function} fn Function to be called 1477 * @param {integer} freq Call frequency in mS 1478 * @return {function} Wrapped function 1479 */ 1480 throttle: function ( fn, freq ) { 1481 var 1482 frequency = freq !== undefined ? freq : 200, 1483 last, 1484 timer; 1485 1486 return function () { 1487 var 1488 that = this, 1489 now = +new Date(), 1490 args = arguments; 1491 1492 if ( last && now < last + frequency ) { 1493 clearTimeout( timer ); 1494 1495 timer = setTimeout( function () { 1496 last = undefined; 1497 fn.apply( that, args ); 1498 }, frequency ); 1499 } 1500 else { 1501 last = now; 1502 fn.apply( that, args ); 1503 } 1504 }; 1505 }, 1506 1507 /** 1508 * Escape a string such that it can be used in a regular expression 1509 * 1510 * @param {string} val string to escape 1511 * @returns {string} escaped string 1512 */ 1513 escapeRegex: function ( val ) { 1514 return val.replace( _re_escape_regex, '\\$1' ); 1515 }, 1516 1517 /** 1518 * Create a function that will write to a nested object or array 1519 * @param {*} source JSON notation string 1520 * @returns Write function 1521 */ 1522 set: function ( source ) { 1523 if ( $.isPlainObject( source ) ) { 1524 /* Unlike get, only the underscore (global) option is used for for 1525 * setting data since we don't know the type here. This is why an object 1526 * option is not documented for `mData` (which is read/write), but it is 1527 * for `mRender` which is read only. 1528 */ 1529 return DataTable.util.set( source._ ); 1530 } 1531 else if ( source === null ) { 1532 // Nothing to do when the data source is null 1533 return function () {}; 1534 } 1535 else if ( typeof source === 'function' ) { 1536 return function (data, val, meta) { 1537 source( data, 'set', val, meta ); 1538 }; 1539 } 1540 else if ( 1541 typeof source === 'string' && (source.indexOf('.') !== -1 || 1542 source.indexOf('[') !== -1 || source.indexOf('(') !== -1) 1543 ) { 1544 // Like the get, we need to get data from a nested object 1545 var setData = function (data, val, src) { 1546 var a = _fnSplitObjNotation( src ), b; 1547 var aLast = a[a.length-1]; 1548 var arrayNotation, funcNotation, o, innerSrc; 1549 1550 for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ ) { 1551 // Protect against prototype pollution 1552 if (a[i] === '__proto__' || a[i] === 'constructor') { 1553 throw new Error('Cannot set prototype values'); 1554 } 1555 1556 // Check if we are dealing with an array notation request 1557 arrayNotation = a[i].match(__reArray); 1558 funcNotation = a[i].match(__reFn); 1559 1560 if ( arrayNotation ) { 1561 a[i] = a[i].replace(__reArray, ''); 1562 data[ a[i] ] = []; 1563 1564 // Get the remainder of the nested object to set so we can recurse 1565 b = a.slice(); 1566 b.splice( 0, i+1 ); 1567 innerSrc = b.join('.'); 1568 1569 // Traverse each entry in the array setting the properties requested 1570 if ( Array.isArray( val ) ) { 1571 for ( var j=0, jLen=val.length ; j<jLen ; j++ ) { 1572 o = {}; 1573 setData( o, val[j], innerSrc ); 1574 data[ a[i] ].push( o ); 1575 } 1576 } 1577 else { 1578 // We've been asked to save data to an array, but it 1579 // isn't array data to be saved. Best that can be done 1580 // is to just save the value. 1581 data[ a[i] ] = val; 1582 } 1583 1584 // The inner call to setData has already traversed through the remainder 1585 // of the source and has set the data, thus we can exit here 1586 return; 1587 } 1588 else if ( funcNotation ) { 1589 // Function call 1590 a[i] = a[i].replace(__reFn, ''); 1591 data = data[ a[i] ]( val ); 1592 } 1593 1594 // If the nested object doesn't currently exist - since we are 1595 // trying to set the value - create it 1596 if ( data[ a[i] ] === null || data[ a[i] ] === undefined ) { 1597 data[ a[i] ] = {}; 1598 } 1599 data = data[ a[i] ]; 1600 } 1601 1602 // Last item in the input - i.e, the actual set 1603 if ( aLast.match(__reFn ) ) { 1604 // Function call 1605 data = data[ aLast.replace(__reFn, '') ]( val ); 1606 } 1607 else { 1608 // If array notation is used, we just want to strip it and use the property name 1609 // and assign the value. If it isn't used, then we get the result we want anyway 1610 data[ aLast.replace(__reArray, '') ] = val; 1611 } 1612 }; 1613 1614 return function (data, val) { // meta is also passed in, but not used 1615 return setData( data, val, source ); 1616 }; 1617 } 1618 else { 1619 // Array or flat object mapping 1620 return function (data, val) { // meta is also passed in, but not used 1621 data[source] = val; 1622 }; 1623 } 1624 }, 1625 1626 /** 1627 * Create a function that will read nested objects from arrays, based on JSON notation 1628 * @param {*} source JSON notation string 1629 * @returns Value read 1630 */ 1631 get: function ( source ) { 1632 if ( $.isPlainObject( source ) ) { 1633 // Build an object of get functions, and wrap them in a single call 1634 var o = {}; 1635 $.each( source, function (key, val) { 1636 if ( val ) { 1637 o[key] = DataTable.util.get( val ); 1638 } 1639 } ); 1640
1641 return function (data, type, row, meta) { 1642 var t = o[type] || o._; 1643 return t !== undefined ? 1644 t(data, type, row, meta) : 1645 data; 1646 }; 1647 } 1648 else if ( source === null ) { 1649 // Give an empty string for rendering / sorting etc 1650 return function (data) { // type, row and meta also passed, but not used 1651 return data; 1652 }; 1653 } 1654 else if ( typeof source === 'function' ) { 1655 return function (data, type, row, meta) { 1656 return source( data, type, row, meta ); 1657 }; 1658 } 1659 else if ( 1660 typeof source === 'string' && (source.indexOf('.') !== -1 || 1661 source.indexOf('[') !== -1 || source.indexOf('(') !== -1) 1662 ) { 1663 /* If there is a . in the source string then the data source is in a 1664 * nested object so we loop over the data for each level to get the next 1665 * level down. On each loop we test for undefined, and if found immediately 1666 * return. This allows entire objects to be missing and sDefaultContent to 1667 * be used if defined, rather than throwing an error 1668 */ 1669 var fetchData = function (data, type, src) { 1670 var arrayNotation, funcNotation, out, innerSrc; 1671 1672 if ( src !== "" ) { 1673 var a = _fnSplitObjNotation( src ); 1674 1675 for ( var i=0, iLen=a.length ; i<iLen ; i++ ) { 1676 // Check if we are dealing with special notation 1677 arrayNotation = a[i].match(__reArray); 1678 funcNotation = a[i].match(__reFn); 1679 1680 if ( arrayNotation ) { 1681 // Array notation 1682 a[i] = a[i].replace(__reArray, ''); 1683 1684 // Condition allows simply [] to be passed in 1685 if ( a[i] !== "" ) { 1686 data = data[ a[i] ]; 1687 } 1688 out = []; 1689 1690 // Get the remainder of the nested object to get 1691 a.splice( 0, i+1 ); 1692 innerSrc = a.join('.'); 1693 1694 // Traverse each entry in the array getting the properties requested 1695 if ( Array.isArray( data ) ) { 1696 for ( var j=0, jLen=data.length ; j<jLen ; j++ ) { 1697 out.push( fetchData( data[j], type, innerSrc ) ); 1698 } 1699 } 1700 1701 // If a string is given in between the array notation indicators, that 1702 // is used to join the strings together, otherwise an array is returned 1703 var join = arrayNotation[0].substring(1, arrayNotation[0].length-1); 1704 data = (join==="") ? out : out.join(join); 1705 1706 // The inner call to fetchData has already traversed through the remainder 1707 // of the source requested, so we exit from the loop 1708 break; 1709 } 1710 else if ( funcNotation ) { 1711 // Function call 1712 a[i] = a[i].replace(__reFn, ''); 1713 data = data[ a[i] ](); 1714 continue; 1715 } 1716 1717 if (data === null || data[ a[i] ] === null) { 1718 return null; 1719 } 1720 else if ( data === undefined || data[ a[i] ] === undefined ) { 1721 return undefined; 1722 } 1723 1724 data = data[ a[i] ]; 1725 } 1726 } 1727 1728 return data; 1729 }; 1730 1731 return function (data, type) { // row and meta also passed, but not used 1732 return fetchData( data, type, source ); 1733 }; 1734 } 1735 else { 1736 // Array or flat object mapping 1737 return function (data) { // row and meta also passed, but not used 1738 return data[source]; 1739 }; 1740 } 1741 }, 1742 1743 stripHtml: function (mixed) { 1744 var type = typeof mixed; 1745 1746 if (type === 'function') { 1747 _stripHtml = mixed; 1748 return; 1749 } 1750 else if (type === 'string') { 1751 return _stripHtml(mixed); 1752 } 1753 return mixed; 1754 }, 1755 1756 escapeHtml: function (mixed) { 1757 var type = typeof mixed; 1758 1759 if (type === 'function') { 1760 _escapeHtml = mixed; 1761 return; 1762 } 1763 else if (type === 'string' || Array.isArray(mixed)) { 1764 return _escapeHtml(mixed); 1765 } 1766 return mixed; 1767 }, 1768 1769 unique: _unique 1770 }; 1771 1772 1773 1774 /** 1775 * Create a mapping object that allows camel case parameters to be looked up 1776 * for their Hungarian counterparts. The mapping is stored in a private 1777 * parameter called `_hungarianMap` which can be accessed on the source object. 1778 * @param {object} o 1779 * @memberof DataTable#oApi 1780 */ 1781 function _fnHungarianMap ( o ) 1782 { 1783 var 1784 hungarian = 'a aa ai ao as b fn i m o s ', 1785 match, 1786 newKey, 1787 map = {}; 1788 1789 $.each( o, function (key) { 1790 match = key.match(/^([^A-Z]+?)([A-Z])/); 1791 1792 if ( match && hungarian.indexOf(match[1]+' ') !== -1 ) 1793 { 1794 newKey = key.replace( match[0], match[2].toLowerCase() ); 1795 map[ newKey ] = key; 1796 1797 if ( match[1] === 'o' ) 1798 { 1799 _fnHungarianMap( o[key] ); 1800 } 1801 } 1802 } ); 1803 1804 o._hungarianMap = map; 1805 } 1806 1807 1808 /** 1809 * Convert from camel case parameters to Hungarian, based on a Hungarian map 1810 * created by _fnHungarianMap. 1811 * @param {object} src The model object which holds all parameters that can be 1812 * mapped. 1813 * @param {object} user The object to convert from camel case to Hungarian. 1814 * @param {boolean} force When set to `true`, properties which already have a 1815 * Hungarian value in the `user` object will be overwritten. Otherwise they 1816 * won't be. 1817 * @memberof DataTable#oApi 1818 */ 1819 function _fnCamelToHungarian ( src, user, force ) 1820 { 1821 if ( ! src._hungarianMap ) { 1822 _fnHungarianMap( src ); 1823 } 1824 1825 var hungarianKey; 1826 1827 $.each( user, function (key) { 1828 hungarianKey = src._hungarianMap[ key ]; 1829 1830 if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) ) 1831 { 1832 // For objects, we need to buzz down into the object to copy parameters 1833 if ( hungarianKey.charAt(0) === 'o' ) 1834 { 1835 // Copy the camelCase options over to the hungarian 1836 if ( ! user[ hungarianKey ] ) { 1837 user[ hungarianKey ] = {}; 1838 } 1839 $.extend( true, user[hungarianKey], user[key] ); 1840 1841 _fnCamelToHungarian( src[hungarianKey], user[hungarianKey], force ); 1842 } 1843 else { 1844 user[hungarianKey] = user[ key ]; 1845 } 1846 } 1847 } ); 1848 } 1849 1850 /** 1851 * Map one parameter onto another 1852 * @param {object} o Object to map 1853 * @param {*} knew The new parameter name 1854 * @param {*} old The old parameter name 1855 */ 1856 var _fnCompatMap = function ( o, knew, old ) { 1857 if ( o[ knew ] !== undefined ) { 1858 o[ old ] = o[ knew ]; 1859 } 1860 }; 1861 1862 1863 /** 1864 * Provide backwards compatibility for the main DT options. Note that the new 1865 * options are mapped onto the old parameters, so this is an external interface 1866 * change only. 1867 * @param {object} init Object to map 1868 */ 1869 function _fnCompatOpts ( init ) 1870 { 1871 _fnCompatMap( init, 'ordering', 'bSort' ); 1872 _fnCompatMap( init, 'orderMulti', 'bSortMulti' ); 1873 _fnCompatMap( init, 'orderClasses', 'bSortClasses' ); 1874 _fnCompatMap( init, 'orderCellsTop', 'bSortCellsTop' ); 1875 _fnCompatMap( init, 'order', 'aaSorting' ); 1876 _fnCompatMap( init, 'orderFixed', 'aaSortingFixed' ); 1877 _fnCompatMap( init, 'paging', 'bPaginate' ); 1878 _fnCompatMap( init, 'pagingType', 'sPaginationType' ); 1879 _fnCompatMap( init, 'pageLength', 'iDisplayLength' ); 1880 _fnCompatMap( init, 'searching', 'bFilter' ); 1881 1882 // Boolean initialisation of x-scrolling 1883 if ( typeof init.sScrollX === 'boolean' ) { 1884 init.sScrollX = init.sScrollX ? '100%' : ''; 1885 } 1886 if ( typeof init.scrollX === 'boolean' ) { 1887 init.scrollX = init.scrollX ? '100%' : ''; 1888 } 1889 1890 // Column search objects are in an array, so it needs to be converted 1891 // element by element 1892 var searchCols = init.aoSearchCols; 1893 1894 if ( searchCols ) { 1895 for ( var i=0, ien=searchCols.length ; i<ien ; i++ ) { 1896 if ( searchCols[i] ) { 1897 _fnCamelToHungarian( DataTable.models.oSearch, searchCols[i] ); 1898 } 1899 } 1900 } 1901 1902 // Enable search delay if server-side processing is enabled 1903 if (init.serverSide && ! init.searchDelay) { 1904 init.searchDelay = 400; 1905 } 1906 } 1907 1908 1909 /** 1910 * Provide backwards compatibility for column options. Note that the new options 1911 * are mapped onto the old parameters, so this is an external interface change 1912 * only. 1913 * @param {object} init Object to map 1914 */ 1915 function _fnCompatCols ( init ) 1916 { 1917 _fnCompatMap( init, 'orderable', 'bSortable' ); 1918 _fnCompatMap( init, 'orderData', 'aDataSort' ); 1919 _fnCompatMap( init, 'orderSequence', 'asSorting' ); 1920 _fnCompatMap( init, 'orderDataType', 'sortDataType' ); 1921 1922 // orderData can be given as an integer 1923 var dataSort = init.aDataSort; 1924 if ( typeof dataSort === 'number' && ! Array.isArray( dataSort ) ) { 1925 init.aDataSort = [ dataSort ]; 1926 } 1927 } 1928 1929 1930 /** 1931 * Browser feature detection for capabilities, quirks 1932 * @param {object} settings dataTables settings object 1933 * @memberof DataTable#oApi 1934 */ 1935 function _fnBrowserDetect( settings ) 1936 { 1937 // We don't need to do this every time DataTables is constructed, the values 1938 // calculated are specific to the browser and OS configuration which we 1939 // don't expect to change between initialisations 1940 if ( ! DataTable.__browser ) { 1941 var browser = {}; 1942 DataTable.__browser = browser; 1943 1944 // Scrolling feature / quirks detection 1945 var n = $('<div/>') 1946 .css( { 1947 position: 'fixed', 1948 top: 0, 1949 left: -1 * window.pageXOffset, // allow for scrolling 1950 height: 1, 1951 width: 1, 1952 overflow: 'hidden' 1953 } ) 1954 .append( 1955 $('<div/>') 1956 .css( { 1957 position: 'absolute', 1958 top: 1, 1959 left: 1, 1960 width: 100, 1961 overflow: 'scroll' 1962 } ) 1963 .append( 1964 $('<div/>') 1965 .css( { 1966 width: '100%', 1967 height: 10 1968 } ) 1969 ) 1970 ) 1971 .appendTo( 'body' ); 1972 1973 var outer = n.children(); 1974 var inner = outer.children(); 1975 1976 // Get scrollbar width 1977 browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth; 1978 1979 // In rtl text layout, some browsers (most, but not all) will place the 1980 // scrollbar on the left, rather than the right. 1981 browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1; 1982 1983 n.remove(); 1984 } 1985 1986 $.extend( settings.oBrowser, DataTable.__browser ); 1987 settings.oScroll.iBarWidth = DataTable.__browser.barWidth; 1988 } 1989 1990 /** 1991 * Add a column to the list used for the table with default values 1992 * @param {object} oSettings dataTables settings object 1993 * @memberof DataTable#oApi 1994 */ 1995 function _fnAddColumn( oSettings ) 1996 { 1997 // Add column to aoColumns array 1998 var oDefaults = DataTable.defaults.column; 1999 var iCol = oSettings.aoColumns.length; 2000 var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, { 2001 "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], 2002 "mData": oDefaults.mData ? oDefaults.mData : iCol, 2003 idx: iCol, 2004 searchFixed: {},
2005 colEl: $('<col>').attr('data-dt-column', iCol) 2006 } ); 2007 oSettings.aoColumns.push( oCol ); 2008 2009 // Add search object for column specific search. Note that the `searchCols[ iCol ]` 2010 // passed into extend can be undefined. This allows the user to give a default 2011 // with only some of the parameters defined, and also not give a default 2012 var searchCols = oSettings.aoPreSearchCols; 2013 searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] ); 2014 } 2015 2016 2017 /** 2018 * Apply options for a column 2019 * @param {object} oSettings dataTables settings object 2020 * @param {int} iCol column index to consider 2021 * @param {object} oOptions object with sType, bVisible and bSearchable etc 2022 * @memberof DataTable#oApi 2023 */ 2024 function _fnColumnOptions( oSettings, iCol, oOptions ) 2025 { 2026 var oCol = oSettings.aoColumns[ iCol ]; 2027 2028 /* User specified column options */ 2029 if ( oOptions !== undefined && oOptions !== null ) 2030 { 2031 // Backwards compatibility 2032 _fnCompatCols( oOptions ); 2033 2034 // Map camel case parameters to their Hungarian counterparts 2035 _fnCamelToHungarian( DataTable.defaults.column, oOptions, true ); 2036 2037 /* Backwards compatibility for mDataProp */ 2038 if ( oOptions.mDataProp !== undefined && !oOptions.mData ) 2039 { 2040 oOptions.mData = oOptions.mDataProp; 2041 } 2042 2043 if ( oOptions.sType ) 2044 { 2045 oCol._sManualType = oOptions.sType; 2046 } 2047 2048 // `class` is a reserved word in Javascript, so we need to provide 2049 // the ability to use a valid name for the camel case input 2050 if ( oOptions.className && ! oOptions.sClass ) 2051 { 2052 oOptions.sClass = oOptions.className; 2053 } 2054 2055 var origClass = oCol.sClass; 2056 2057 $.extend( oCol, oOptions ); 2058 _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); 2059 2060 // Merge class from previously defined classes with this one, rather than just 2061 // overwriting it in the extend above 2062 if (origClass !== oCol.sClass) { 2063 oCol.sClass = origClass + ' ' + oCol.sClass; 2064 } 2065 2066 /* iDataSort to be applied (backwards compatibility), but aDataSort will take 2067 * priority if defined 2068 */ 2069 if ( oOptions.iDataSort !== undefined ) 2070 { 2071 oCol.aDataSort = [ oOptions.iDataSort ]; 2072 } 2073 _fnMap( oCol, oOptions, "aDataSort" ); 2074 } 2075 2076 /* Cache the data get and set functions for speed */ 2077 var mDataSrc = oCol.mData; 2078 var mData = _fnGetObjectDataFn( mDataSrc ); 2079 2080 // The `render` option can be given as an array to access the helper rendering methods. 2081 // The first element is the rendering method to use, the rest are the parameters to pass 2082 if ( oCol.mRender && Array.isArray( oCol.mRender ) ) { 2083 var copy = oCol.mRender.slice(); 2084 var name = copy.shift(); 2085 2086 oCol.mRender = DataTable.render[name].apply(window, copy); 2087 } 2088 2089 oCol._render = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null; 2090 2091 var attrTest = function( src ) { 2092 return typeof src === 'string' && src.indexOf('@') !== -1; 2093 }; 2094 oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && ( 2095 attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter) 2096 ); 2097 oCol._setter = null; 2098 2099 oCol.fnGetData = function (rowData, type, meta) { 2100 var innerData = mData( rowData, type, undefined, meta ); 2101 2102 return oCol._render && type ? 2103 oCol._render( innerData, type, rowData, meta ) : 2104 innerData; 2105 }; 2106 oCol.fnSetData = function ( rowData, val, meta ) { 2107 return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta ); 2108 }; 2109 2110 // Indicate if DataTables should read DOM data as an object or array 2111 // Used in _fnGetRowElements 2112 if ( typeof mDataSrc !== 'number' && ! oCol._isArrayHost ) { 2113 oSettings._rowReadObject = true; 2114 } 2115 2116 /* Feature sorting overrides column specific when off */ 2117 if ( !oSettings.oFeatures.bSort ) 2118 { 2119 oCol.bSortable = false; 2120 } 2121 } 2122 2123 2124 /** 2125 * Adjust the table column widths for new data. Note: you would probably want to 2126 * do a redraw after calling this function! 2127 * @param {object} settings dataTables settings object 2128 * @memberof DataTable#oApi 2129 */ 2130 function _fnAdjustColumnSizing ( settings ) 2131 { 2132 _fnCalculateColumnWidths( settings ); 2133 _fnColumnSizes( settings ); 2134 2135 var scroll = settings.oScroll;
2136 if ( scroll.sY !== '' || scroll.sX !== '') { 2137 _fnScrollDraw( settings ); 2138 } 2139 2140 _fnCallbackFire( settings, null, 'column-sizing', [settings] ); 2141 } 2142 2143 /** 2144 * Apply column sizes 2145 * 2146 * @param {*} settings DataTables settings object 2147 */ 2148 function _fnColumnSizes ( settings ) 2149 { 2150 var cols = settings.aoColumns; 2151 2152 for (var i=0 ; i<cols.length ; i++) { 2153 var width = _fnColumnsSumWidth(settings, [i], false, false); 2154 2155 cols[i].colEl.css('width', width); 2156 2157 if (settings.oScroll.sX) { 2158 cols[i].colEl.css('min-width', width); 2159 } 2160 } 2161 } 2162 2163 2164 /** 2165 * Convert the index of a visible column to the index in the data array (take account 2166 * of hidden columns) 2167 * @param {object} oSettings dataTables settings object 2168 * @param {int} iMatch Visible column index to lookup 2169 * @returns {int} i the data index 2170 * @memberof DataTable#oApi 2171 */ 2172 function _fnVisibleToColumnIndex( oSettings, iMatch ) 2173 { 2174 var aiVis = _fnGetColumns( oSettings, 'bVisible' ); 2175 2176 return typeof aiVis[iMatch] === 'number' ? 2177 aiVis[iMatch] : 2178 null; 2179 } 2180 2181 2182 /** 2183 * Convert the index of an index in the data array and convert it to the visible 2184 * column index (take account of hidden columns) 2185 * @param {int} iMatch Column index to lookup 2186 * @param {object} oSettings dataTables settings object 2187 * @returns {int} i the data index 2188 * @memberof DataTable#oApi 2189 */ 2190 function _fnColumnIndexToVisible( oSettings, iMatch ) 2191 { 2192 var aiVis = _fnGetColumns( oSettings, 'bVisible' ); 2193 var iPos = aiVis.indexOf(iMatch); 2194 2195 return iPos !== -1 ? iPos : null; 2196 } 2197 2198 2199 /** 2200 * Get the number of visible columns 2201 * @param {object} oSettings dataTables settings object 2202 * @returns {int} i the number of visible columns 2203 * @memberof DataTable#oApi 2204 */ 2205 function _fnVisbleColumns( settings ) 2206 { 2207 var layout = settings.aoHeader; 2208 var columns = settings.aoColumns; 2209 var vis = 0; 2210 2211 if ( layout.length ) { 2212 for ( var i=0, ien=layout[0].length ; i<ien ; i++ ) { 2213 if ( columns[i].bVisible && $(layout[0][i].cell).css('display') !== 'none' ) { 2214 vis++; 2215 } 2216 } 2217 } 2218 2219 return vis; 2220 } 2221 2222 2223 /** 2224 * Get an array of column indexes that match a given property 2225 * @param {object} oSettings dataTables settings object 2226 * @param {string} sParam Parameter in aoColumns to look for - typically 2227 * bVisible or bSearchable 2228 * @returns {array} Array of indexes with matched properties 2229 * @memberof DataTable#oApi 2230 */ 2231 function _fnGetColumns( oSettings, sParam ) 2232 { 2233 var a = []; 2234 2235 oSettings.aoColumns.map( function(val, i) { 2236 if ( val[sParam] ) { 2237 a.push( i ); 2238 } 2239 } ); 2240 2241 return a; 2242 } 2243 2244 /** 2245 * Allow the result from a type detection function to be `true` while 2246 * translating that into a string. Old type detection functions will 2247 * return the type name if it passes. An obect store would be better, 2248 * but not backwards compatible. 2249 * 2250 * @param {*} typeDetect Object or function for type detection 2251 * @param {*} res Result from the type detection function 2252 * @returns Type name or false 2253 */ 2254 function _typeResult (typeDetect, res) { 2255 return res === true 2256 ? typeDetect._name 2257 : res; 2258 } 2259 2260 /** 2261 * Calculate the 'type' of a column 2262 * @param {object} settings dataTables settings object 2263 * @memberof DataTable#oApi 2264 */ 2265 function _fnColumnTypes ( settings ) 2266 { 2267 var columns = settings.aoColumns; 2268 var data = settings.aoData; 2269 var types = DataTable.ext.type.detect; 2270 var i, ien, j, jen, k, ken; 2271 var col, detectedType, cache; 2272 2273 // For each column, spin over the data type detection functions, seeing if one matches 2274 for ( i=0, ien=columns.length ; i<ien ; i++ ) { 2275 col = columns[i]; 2276 cache = []; 2277 2278 if ( ! col.sType && col._sManualType ) { 2279 col.sType = col._sManualType; 2280 } 2281 else if ( ! col.sType ) { 2282 // With SSP type detection can be unreliable and error prone, so we provide a way 2283 // to turn it off. 2284 if (! settings.typeDetect) { 2285 return; 2286 } 2287 2288 for ( j=0, jen=types.length ; j<jen ; j++ ) { 2289 var typeDetect = types[j]; 2290 2291 // There can be either one, or three type detection functions 2292 var oneOf = typeDetect.oneOf; 2293 var allOf = typeDetect.allOf || typeDetect; 2294 var init = typeDetect.init; 2295 var one = false;
2296 2297 detectedType = null; 2298 2299 // Fast detect based on column assignment 2300 if (init) { 2301 detectedType = _typeResult(typeDetect, init(settings, col, i)); 2302 2303 if (detectedType) { 2304 col.sType = detectedType; 2305 break; 2306 } 2307 } 2308 2309 for ( k=0, ken=data.length ; k<ken ; k++ ) { 2310 if (! data[k]) { 2311 continue; 2312 } 2313 2314 // Use a cache array so we only need to get the type data 2315 // from the formatter once (when using multiple detectors) 2316 if ( cache[k] === undefined ) { 2317 cache[k] = _fnGetCellData( settings, k, i, 'type' ); 2318 } 2319 2320 // Only one data point in the column needs to match this function 2321 if (oneOf && ! one) { 2322 one = _typeResult(typeDetect, oneOf( cache[k], settings )); 2323 } 2324 2325 // All data points need to match this function 2326 detectedType = _typeResult(typeDetect, allOf( cache[k], settings )); 2327 2328 // If null, then this type can't apply to this column, so 2329 // rather than testing all cells, break out. There is an 2330 // exception for the last type which is `html`. We need to 2331 // scan all rows since it is possible to mix string and HTML 2332 // types 2333 if ( ! detectedType && j !== types.length-3 ) { 2334 break; 2335 } 2336 2337 // Only a single match is needed for html type since it is 2338 // bottom of the pile and very similar to string - but it 2339 // must not be empty 2340 if ( detectedType === 'html' && ! _empty(cache[k]) ) { 2341 break; 2342 } 2343 } 2344 2345 // Type is valid for all data points in the column - use this 2346 // type 2347 if ( (oneOf && one && detectedType) || (!oneOf && detectedType) ) { 2348 col.sType = detectedType; 2349 break; 2350 } 2351 } 2352 2353 // Fall back - if no type was detected, always use string 2354 if ( ! col.sType ) { 2355 col.sType = 'string'; 2356 } 2357 } 2358 2359 // Set class names for header / footer for auto type classes 2360 var autoClass = _ext.type.className[col.sType]; 2361 2362 if (autoClass) { 2363 _columnAutoClass(settings.aoHeader, i, autoClass); 2364 _columnAutoClass(settings.aoFooter, i, autoClass); 2365 } 2366 2367 var renderer = _ext.type.render[col.sType]; 2368 2369 // This can only happen once! There is no way to remove 2370 // a renderer. After the first time the renderer has 2371 // already been set so createTr will run the renderer itself. 2372 if (renderer && ! col._render) { 2373 col._render = DataTable.util.get(renderer); 2374 2375 _columnAutoRender(settings, i); 2376 } 2377 } 2378 } 2379 2380 /** 2381 * Apply an auto detected renderer to data which doesn't yet have 2382 * a renderer 2383 */ 2384 function _columnAutoRender(settings, colIdx) { 2385 var data = settings.aoData; 2386 2387 for (var i=0 ; i<data.length ; i++) { 2388 if (data[i].nTr) { 2389 // We have to update the display here since there is no 2390 // invalidation check for the data 2391 var display = _fnGetCellData( settings, i, colIdx, 'display' ); 2392 2393 data[i].displayData[colIdx] = display; 2394 _fnWriteCell(data[i].anCells[colIdx], display); 2395 2396 // No need to update sort / filter data since it has 2397 // been invalidated and will be re-read with the 2398 // renderer now applied 2399 } 2400 } 2401 } 2402 2403 /** 2404 * Apply a class name to a column's header cells 2405 */ 2406 function _columnAutoClass(container, colIdx, className) { 2407 container.forEach(function (row) { 2408 if (row[colIdx] && row[colIdx].unique) { 2409 _addClass(row[colIdx].cell, className); 2410 } 2411 }); 2412 } 2413 2414 /** 2415 * Take the column definitions and static columns arrays and calculate how 2416 * they relate to column indexes. The callback function will then apply the 2417 * definition found for a column to a suitable configuration object. 2418 * @param {object} oSettings dataTables settings object 2419 * @param {array} aoColDefs The aoColumnDefs array that is to be applied 2420 * @param {array} aoCols The aoColumns array that defines columns individually 2421 * @param {array} headerLayout Layout for header as it was loaded 2422 * @param {function} fn Callback function - takes two parameters, the calculated 2423 * column index and the definition for that column. 2424 * @memberof DataTable#oApi 2425 */ 2426 function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, headerLayout, fn ) 2427 { 2428 var i, iLen, j, jLen, k, kLen, def; 2429 var columns = oSettings.aoColumns; 2430 2431 if ( aoCols ) { 2432 for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) { 2433 if (aoCols[i] && aoCols[i].name) { 2434 columns[i].sName = aoCols[i].name; 2435 } 2436 } 2437 } 2438 2439 // Column definitions with aTargets 2440 if ( aoColDefs ) 2441 { 2442 /* Loop over the definitions array - loop in reverse so first instance has priority */ 2443 for ( i=aoColDefs.length-1 ; i>=0 ; i-- ) 2444 { 2445 def = aoColDefs[i]; 2446 2447 /* Each definition can target multiple columns, as it is an array */ 2448 var aTargets = def.target !== undefined 2449 ? def.target 2450 : def.targets !== undefined 2451 ? def.targets 2452 : def.aTargets; 2453 2454 if ( ! Array.isArray( aTargets ) ) 2455 { 2456 aTargets = [ aTargets ]; 2457 } 2458 2459 for ( j=0, jLen=aTargets.length ; j<jLen ; j++ ) 2460 { 2461 var target = aTargets[j]; 2462 2463 if ( typeof target === 'number' && target >= 0 ) 2464 { 2465 /* Add columns that we don't yet know about */ 2466 while( columns.length <= target ) 2467 { 2468 _fnAddColumn( oSettings ); 2469 } 2470
2471 /* Integer, basic index */ 2472 fn( target, def ); 2473 } 2474 else if ( typeof target === 'number' && target < 0 ) 2475 { 2476 /* Negative integer, right to left column counting */ 2477 fn( columns.length+target, def ); 2478 } 2479 else if ( typeof target === 'string' ) 2480 { 2481 for ( k=0, kLen=columns.length ; k<kLen ; k++ ) { 2482 if (target === '_all') { 2483 // Apply to all columns 2484 fn( k, def ); 2485 } 2486 else if (target.indexOf(':name') !== -1) { 2487 // Column selector 2488 if (columns[k].sName === target.replace(':name', '')) { 2489 fn( k, def ); 2490 } 2491 } 2492 else { 2493 // Cell selector 2494 headerLayout.forEach(function (row) { 2495 if (row[k]) { 2496 var cell = $(row[k].cell); 2497 2498 // Legacy support. Note that it means that we don't support 2499 // an element name selector only, since they are treated as 2500 // class names for 1.x compat. 2501 if (target.match(/^[a-z][\w-]*$/i)) { 2502 target = '.' + target; 2503 } 2504 2505 if (cell.is( target )) { 2506 fn( k, def ); 2507 } 2508 } 2509 }); 2510 } 2511 } 2512 } 2513 } 2514 } 2515 } 2516 2517 // Statically defined columns array 2518 if ( aoCols ) { 2519 for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) { 2520 fn( i, aoCols[i] ); 2521 } 2522 } 2523 } 2524 2525 2526 /** 2527 * Get the width for a given set of columns 2528 * 2529 * @param {*} settings DataTables settings object 2530 * @param {*} targets Columns - comma separated string or array of numbers 2531 * @param {*} original Use the original width (true) or calculated (false) 2532 * @param {*} incVisible Include visible columns (true) or not (false) 2533 * @returns Combined CSS value 2534 */ 2535 function _fnColumnsSumWidth( settings, targets, original, incVisible ) { 2536 if ( ! Array.isArray( targets ) ) { 2537 targets = _fnColumnsFromHeader( targets ); 2538 } 2539 2540 var sum = 0; 2541 var unit; 2542 var columns = settings.aoColumns; 2543 2544 for ( var i=0, ien=targets.length ; i<ien ; i++ ) { 2545 var column = columns[ targets[i] ]; 2546 var definedWidth = original ? 2547 column.sWidthOrig : 2548 column.sWidth; 2549 2550 if ( ! incVisible && column.bVisible === false ) { 2551 continue; 2552 } 2553 2554 if ( definedWidth === null || definedWidth === undefined ) { 2555 return null; // can't determine a defined width - browser defined 2556 } 2557 else if ( typeof definedWidth === 'number' ) { 2558 unit = 'px'; 2559 sum += definedWidth; 2560 } 2561 else { 2562 var matched = definedWidth.match(/([\d\.]+)([^\d]*)/); 2563 2564 if ( matched ) { 2565 sum += matched[1] * 1; 2566 unit = matched.length === 3 ? 2567 matched[2] : 2568 'px'; 2569 } 2570 } 2571 } 2572 2573 return sum + unit; 2574 } 2575 2576 function _fnColumnsFromHeader( cell ) 2577 { 2578 var attr = $(cell).closest('[data-dt-column]').attr('data-dt-column'); 2579 2580 if ( ! attr ) { 2581 return []; 2582 } 2583 2584 return attr.split(',').map( function (val) { 2585 return val * 1; 2586 } ); 2587 } 2588 /** 2589 * Add a data array to the table, creating DOM node etc. This is the parallel to 2590 * _fnGatherData, but for adding rows from a Javascript source, rather than a 2591 * DOM source. 2592 * @param {object} settings dataTables settings object 2593 * @param {array} data data array to be added 2594 * @param {node} [tr] TR element to add to the table - optional. If not given, 2595 * DataTables will create a row automatically 2596 * @param {array} [tds] Array of TD|TH elements for the row - must be given 2597 * if nTr is. 2598 * @returns {int} >=0 if successful (index of new aoData entry), -1 if failed 2599 * @memberof DataTable#oApi 2600 */ 2601 function _fnAddData ( settings, dataIn, tr, tds ) 2602 { 2603 /* Create the object for storing information about this new row */ 2604 var rowIdx = settings.aoData.length; 2605 var rowModel = $.extend( true, {}, DataTable.models.oRow, { 2606 src: tr ? 'dom' : 'data', 2607 idx: rowIdx 2608 } ); 2609 2610 rowModel._aData = dataIn; 2611 settings.aoData.push( rowModel ); 2612 2613 var columns = settings.aoColumns; 2614 2615 for ( var i=0, iLen=columns.length ; i<iLen ; i++ ) 2616 { 2617 // Invalidate the column types as the new data needs to be revalidated 2618 columns[i].sType = null; 2619 } 2620 2621 /* Add to the display array */ 2622 settings.aiDisplayMaster.push( rowIdx ); 2623 2624 var id = settings.rowIdFn( dataIn ); 2625 if ( id !== undefined ) { 2626 settings.aIds[ id ] = rowModel; 2627 } 2628 2629 /* Create the DOM information, or register it if already present */ 2630 if ( tr || ! settings.oFeatures.bDeferRender ) 2631 { 2632 _fnCreateTr( settings, rowIdx, tr, tds ); 2633 } 2634 2635 return rowIdx; 2636 } 2637 2638 2639 /** 2640 * Add one or more TR elements to the table. Generally we'd expect to 2641 * use this for reading data from a DOM sourced table, but it could be 2642 * used for an TR element. Note that if a TR is given, it is used (i.e.
2643 * it is not cloned). 2644 * @param {object} settings dataTables settings object 2645 * @param {array|node|jQuery} trs The TR element(s) to add to the table 2646 * @returns {array} Array of indexes for the added rows 2647 * @memberof DataTable#oApi 2648 */ 2649 function _fnAddTr( settings, trs ) 2650 { 2651 var row; 2652 2653 // Allow an individual node to be passed in 2654 if ( ! (trs instanceof $) ) { 2655 trs = $(trs); 2656 } 2657 2658 return trs.map( function (i, el) { 2659 row = _fnGetRowElements( settings, el ); 2660 return _fnAddData( settings, row.data, el, row.cells ); 2661 } ); 2662 } 2663 2664 2665 /** 2666 * Get the data for a given cell from the internal cache, taking into account data mapping 2667 * @param {object} settings dataTables settings object 2668 * @param {int} rowIdx aoData row id 2669 * @param {int} colIdx Column index 2670 * @param {string} type data get type ('display', 'type' 'filter|search' 'sort|order') 2671 * @returns {*} Cell data 2672 * @memberof DataTable#oApi 2673 */ 2674 function _fnGetCellData( settings, rowIdx, colIdx, type ) 2675 { 2676 if (type === 'search') { 2677 type = 'filter'; 2678 } 2679 else if (type === 'order') { 2680 type = 'sort'; 2681 } 2682 2683 var row = settings.aoData[rowIdx]; 2684 2685 if (! row) { 2686 return undefined; 2687 } 2688 2689 var draw = settings.iDraw; 2690 var col = settings.aoColumns[colIdx]; 2691 var rowData = row._aData; 2692 var defaultContent = col.sDefaultContent; 2693 var cellData = col.fnGetData( rowData, type, { 2694 settings: settings, 2695 row: rowIdx, 2696 col: colIdx 2697 } ); 2698 2699 // Allow for a node being returned for non-display types 2700 if (type !== 'display' && cellData && typeof cellData === 'object' && cellData.nodeName) { 2701 cellData = cellData.innerHTML; 2702 } 2703 2704 if ( cellData === undefined ) { 2705 if ( settings.iDrawError != draw && defaultContent === null ) { 2706 _fnLog( settings, 0, "Requested unknown parameter "+ 2707 (typeof col.mData=='function' ? '{function}' : "'"+col.mData+"'")+ 2708 " for row "+rowIdx+", column "+colIdx, 4 ); 2709 settings.iDrawError = draw; 2710 } 2711 return defaultContent; 2712 } 2713 2714 // When the data source is null and a specific data type is requested (i.e. 2715 // not the original data), we can use default column data 2716 if ( (cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined ) { 2717 cellData = defaultContent; 2718 } 2719 else if ( typeof cellData === 'function' ) { 2720 // If the data source is a function, then we run it and use the return, 2721 // executing in the scope of the data object (for instances) 2722 return cellData.call( rowData ); 2723 } 2724 2725 if ( cellData === null && type === 'display' ) { 2726 return ''; 2727 } 2728 2729 if ( type === 'filter' ) { 2730 var fomatters = DataTable.ext.type.search; 2731 2732 if ( fomatters[ col.sType ] ) { 2733 cellData = fomatters[ col.sType ]( cellData ); 2734 } 2735 } 2736 2737 return cellData; 2738 } 2739 2740 2741 /** 2742 * Set the value for a specific cell, into the internal data cache 2743 * @param {object} settings dataTables settings object 2744 * @param {int} rowIdx aoData row id 2745 * @param {int} colIdx Column index 2746 * @param {*} val Value to set 2747 * @memberof DataTable#oApi 2748 */ 2749 function _fnSetCellData( settings, rowIdx, colIdx, val ) 2750 { 2751 var col = settings.aoColumns[colIdx]; 2752 var rowData = settings.aoData[rowIdx]._aData; 2753 2754 col.fnSetData( rowData, val, { 2755 settings: settings, 2756 row: rowIdx, 2757 col: colIdx 2758 } ); 2759 } 2760 2761 /** 2762 * Write a value to a cell 2763 * @param {*} td Cell 2764 * @param {*} val Value 2765 */ 2766 function _fnWriteCell(td, val) 2767 { 2768 if (val && typeof val === 'object' && val.nodeName) { 2769 $(td) 2770 .empty() 2771 .append(val); 2772 } 2773 else { 2774 td.innerHTML = val; 2775 } 2776 } 2777 2778 2779 // Private variable that is used to match action syntax in the data property object 2780 var __reArray = /\[.*?\]$/; 2781 var __reFn = /\(\)$/; 2782 2783 /** 2784 * Split string on periods, taking into account escaped periods 2785 * @param {string} str String to split 2786 * @return {array} Split string 2787 */ 2788 function _fnSplitObjNotation( str ) 2789 { 2790 var parts = str.match(/(\\.|[^.])+/g) || ['']; 2791 2792 return parts.map( function ( s ) { 2793 return s.replace(/\\\./g, '.'); 2794 } ); 2795 } 2796 2797 2798 /** 2799 * Return a function that can be used to get data from a source object, taking 2800 * into account the ability to use nested objects as a source 2801 * @param {string|int|function} mSource The data source for the object 2802 * @returns {function} Data get function 2803 * @memberof DataTable#oApi 2804 */ 2805 var _fnGetObjectDataFn = DataTable.util.get; 2806 2807 2808 /** 2809 * Return a function that can be used to set data from a source object, taking 2810 * into account the ability to use nested objects as a source 2811 * @param {string|int|function} mSource The data source for the object 2812 * @returns {function} Data set function 2813 * @memberof DataTable#oApi 2814 */ 2815 var _fnSetObjectDataFn = DataTable.util.set; 2816 2817 2818 /** 2819 * Return an array with the full table data 2820 * @param {object} oSettings dataTables settings object 2821 * @returns array {array} aData Master data array 2822 * @memberof DataTable#oApi 2823 */ 2824 function _fnGetDataMaster ( settings ) 2825 { 2826 return _pluck( settings.aoData, '_aData' ); 2827 } 2828 2829 2830 /** 2831 * Nuke the table 2832 * @param {object} oSettings dataTables settings object 2833 * @memberof DataTable#oApi 2834 */ 2835 function _fnClearTable( settings ) 2836 { 2837 settings.aoData.length = 0; 2838 settings.aiDisplayMaster.length = 0; 2839 settings.aiDisplay.length = 0; 2840 settings.aIds = {}; 2841 } 2842 2843 2844 /**
2845 * Mark cached data as invalid such that a re-read of the data will occur when 2846 * the cached data is next requested. Also update from the data source object. 2847 * 2848 * @param {object} settings DataTables settings object 2849 * @param {int} rowIdx Row index to invalidate 2850 * @param {string} [src] Source to invalidate from: undefined, 'auto', 'dom' 2851 * or 'data' 2852 * @param {int} [colIdx] Column index to invalidate. If undefined the whole 2853 * row will be invalidated 2854 * @memberof DataTable#oApi 2855 * 2856 * @todo For the modularisation of v1.11 this will need to become a callback, so 2857 * the sort and filter methods can subscribe to it. That will required 2858 * initialisation options for sorting, which is why it is not already baked in 2859 */ 2860 function _fnInvalidate( settings, rowIdx, src, colIdx ) 2861 { 2862 var row = settings.aoData[ rowIdx ]; 2863 var i, ien; 2864 2865 // Remove the cached data for the row 2866 row._aSortData = null; 2867 row._aFilterData = null; 2868 row.displayData = null; 2869 2870 // Are we reading last data from DOM or the data object? 2871 if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) { 2872 // Read the data from the DOM 2873 row._aData = _fnGetRowElements( 2874 settings, row, colIdx, colIdx === undefined ? undefined : row._aData 2875 ) 2876 .data; 2877 } 2878 else { 2879 // Reading from data object, update the DOM 2880 var cells = row.anCells; 2881 var display = _fnGetRowDisplay(settings, rowIdx); 2882 2883 if ( cells ) { 2884 if ( colIdx !== undefined ) { 2885 _fnWriteCell(cells[colIdx], display[colIdx]); 2886 } 2887 else { 2888 for ( i=0, ien=cells.length ; i<ien ; i++ ) { 2889 _fnWriteCell(cells[i], display[i]); 2890 } 2891 } 2892 } 2893 } 2894 2895 // Column specific invalidation 2896 var cols = settings.aoColumns; 2897 if ( colIdx !== undefined ) { 2898 // Type - the data might have changed 2899 cols[ colIdx ].sType = null; 2900 2901 // Max length string. Its a fairly cheep recalculation, so not worth 2902 // something more complicated 2903 cols[ colIdx ].maxLenString = null; 2904 } 2905 else { 2906 for ( i=0, ien=cols.length ; i<ien ; i++ ) { 2907 cols[i].sType = null; 2908 cols[i].maxLenString = null; 2909 } 2910 2911 // Update DataTables special `DT_*` attributes for the row 2912 _fnRowAttributes( settings, row ); 2913 } 2914 } 2915 2916 2917 /** 2918 * Build a data source object from an HTML row, reading the contents of the 2919 * cells that are in the row. 2920 * 2921 * @param {object} settings DataTables settings object 2922 * @param {node|object} TR element from which to read data or existing row 2923 * object from which to re-read the data from the cells 2924 * @param {int} [colIdx] Optional column index 2925 * @param {array|object} [d] Data source object. If `colIdx` is given then this 2926 * parameter should also be given and will be used to write the data into. 2927 * Only the column in question will be written 2928 * @returns {object} Object with two parameters: `data` the data read, in 2929 * document order, and `cells` and array of nodes (they can be useful to the 2930 * caller, so rather than needing a second traversal to get them, just return 2931 * them from here). 2932 * @memberof DataTable#oApi 2933 */ 2934 function _fnGetRowElements( settings, row, colIdx, d ) 2935 { 2936 var 2937 tds = [], 2938 td = row.firstChild, 2939 name, col, i=0, contents, 2940 columns = settings.aoColumns, 2941 objectRead = settings._rowReadObject; 2942 2943 // Allow the data object to be passed in, or construct 2944 d = d !== undefined ? 2945 d : 2946 objectRead ? 2947 {} : 2948 []; 2949 2950 var attr = function ( str, td ) { 2951 if ( typeof str === 'string' ) { 2952 var idx = str.indexOf('@'); 2953 2954 if ( idx !== -1 ) { 2955 var attr = str.substring( idx+1 ); 2956 var setter = _fnSetObjectDataFn( str ); 2957 setter( d, td.getAttribute( attr ) ); 2958 } 2959 } 2960 }; 2961 2962 // Read data from a cell and store into the data object 2963 var cellProcess = function ( cell ) { 2964 if ( colIdx === undefined || colIdx === i ) { 2965 col = columns[i]; 2966 contents = (cell.innerHTML).trim(); 2967 2968 if ( col && col._bAttrSrc ) { 2969 var setter = _fnSetObjectDataFn( col.mData._ ); 2970 setter( d, contents ); 2971 2972 attr( col.mData.sort, cell ); 2973 attr( col.mData.type, cell ); 2974 attr( col.mData.filter, cell ); 2975 } 2976 else { 2977 // Depending on the `data` option for the columns the data can 2978 // be read to either an object or an array.
2979 if ( objectRead ) { 2980 if ( ! col._setter ) { 2981 // Cache the setter function 2982 col._setter = _fnSetObjectDataFn( col.mData ); 2983 } 2984 col._setter( d, contents ); 2985 } 2986 else { 2987 d[i] = contents; 2988 } 2989 } 2990 } 2991 2992 i++; 2993 }; 2994 2995 if ( td ) { 2996 // `tr` element was passed in 2997 while ( td ) { 2998 name = td.nodeName.toUpperCase(); 2999 3000 if ( name == "TD" || name == "TH" ) { 3001 cellProcess( td ); 3002 tds.push( td ); 3003 } 3004 3005 td = td.nextSibling; 3006 } 3007 } 3008 else { 3009 // Existing row object passed in 3010 tds = row.anCells; 3011 3012 for ( var j=0, jen=tds.length ; j<jen ; j++ ) { 3013 cellProcess( tds[j] ); 3014 } 3015 } 3016 3017 // Read the ID from the DOM if present 3018 var rowNode = row.firstChild ? row : row.nTr; 3019 3020 if ( rowNode ) { 3021 var id = rowNode.getAttribute( 'id' ); 3022 3023 if ( id ) { 3024 _fnSetObjectDataFn( settings.rowId )( d, id ); 3025 } 3026 } 3027 3028 return { 3029 data: d, 3030 cells: tds 3031 }; 3032 } 3033 3034 /** 3035 * Render and cache a row's display data for the columns, if required 3036 * @returns 3037 */ 3038 function _fnGetRowDisplay (settings, rowIdx) { 3039 var rowModal = settings.aoData[rowIdx]; 3040 var columns = settings.aoColumns; 3041 3042 if (! rowModal.displayData) { 3043 // Need to render and cache 3044 rowModal.displayData = []; 3045 3046 for ( var colIdx=0, len=columns.length ; colIdx<len ; colIdx++ ) { 3047 rowModal.displayData.push( 3048 _fnGetCellData( settings, rowIdx, colIdx, 'display' ) 3049 ); 3050 } 3051 } 3052 3053 return rowModal.displayData; 3054 } 3055 3056 /** 3057 * Create a new TR element (and it's TD children) for a row 3058 * @param {object} oSettings dataTables settings object 3059 * @param {int} iRow Row to consider 3060 * @param {node} [nTrIn] TR element to add to the table - optional. If not given, 3061 * DataTables will create a row automatically 3062 * @param {array} [anTds] Array of TD|TH elements for the row - must be given 3063 * if nTr is. 3064 * @memberof DataTable#oApi 3065 */ 3066 function _fnCreateTr ( oSettings, iRow, nTrIn, anTds ) 3067 { 3068 var 3069 row = oSettings.aoData[iRow], 3070 rowData = row._aData, 3071 cells = [], 3072 nTr, nTd, oCol, 3073 i, iLen, create, 3074 trClass = oSettings.oClasses.tbody.row; 3075 3076 if ( row.nTr === null ) 3077 { 3078 nTr = nTrIn || document.createElement('tr'); 3079 3080 row.nTr = nTr; 3081 row.anCells = cells; 3082 3083 _addClass(nTr, trClass); 3084 3085 /* Use a private property on the node to allow reserve mapping from the node 3086 * to the aoData array for fast look up 3087 */ 3088 nTr._DT_RowIndex = iRow; 3089 3090 /* Special parameters can be given by the data source to be used on the row */ 3091 _fnRowAttributes( oSettings, row ); 3092 3093 /* Process each column */ 3094 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) 3095 { 3096 oCol = oSettings.aoColumns[i]; 3097 create = nTrIn && anTds[i] ? false : true; 3098 3099 nTd = create ? document.createElement( oCol.sCellType ) : anTds[i]; 3100 3101 if (! nTd) { 3102 _fnLog( oSettings, 0, 'Incorrect column count', 18 ); 3103 } 3104 3105 nTd._DT_CellIndex = { 3106 row: iRow, 3107 column: i 3108 }; 3109 3110 cells.push( nTd ); 3111 3112 var display = _fnGetRowDisplay(oSettings, iRow); 3113 3114 // Need to create the HTML if new, or if a rendering function is defined 3115 if ( 3116 create || 3117 ( 3118 (oCol.mRender || oCol.mData !== i) && 3119 (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display') 3120 ) 3121 ) { 3122 _fnWriteCell(nTd, display[i]); 3123 } 3124 3125 // column class 3126 _addClass(nTd, oCol.sClass); 3127 3128 // Visibility - add or remove as required 3129 if ( oCol.bVisible && create ) 3130 { 3131 nTr.appendChild( nTd ); 3132 } 3133 else if ( ! oCol.bVisible && ! create ) 3134 { 3135 nTd.parentNode.removeChild( nTd ); 3136 } 3137 3138 if ( oCol.fnCreatedCell ) 3139 { 3140 oCol.fnCreatedCell.call( oSettings.oInstance, 3141 nTd, _fnGetCellData( oSettings, iRow, i ), rowData, iRow, i 3142 ); 3143 } 3144 } 3145 3146 _fnCallbackFire( oSettings, 'aoRowCreatedCallback', 'row-created', [nTr, rowData, iRow, cells] ); 3147 } 3148 else { 3149 _addClass(row.nTr, trClass); 3150 } 3151 } 3152 3153 3154 /** 3155 * Add attributes to a row based on the special `DT_*` parameters in a data 3156 * source object. 3157 * @param {object} settings DataTables settings object 3158 * @param {object} DataTables row object for the row to be modified 3159 * @memberof DataTable#oApi 3160 */ 3161 function _fnRowAttributes( settings, row ) 3162 { 3163 var tr = row.nTr; 3164 var data = row._aData; 3165 3166 if ( tr ) { 3167 var id = settings.rowIdFn( data ); 3168 3169 if ( id ) { 3170 tr.id = id; 3171 } 3172 3173 if ( data.DT_RowClass ) { 3174 // Remove any classes added by DT_RowClass before 3175 var a = data.DT_RowClass.split(' '); 3176 row.__rowc = row.__rowc ? 3177 _unique( row.__rowc.concat( a ) ) : 3178 a; 3179 3180 $(tr) 3181 .removeClass( row.__rowc.join(' ') ) 3182 .addClass( data.DT_RowClass ); 3183 } 3184 3185 if ( data.DT_RowAttr ) { 3186 $(tr).attr( data.DT_RowAttr ); 3187 } 3188 3189 if ( data.DT_RowData ) { 3190 $(tr).data( data.DT_RowData ); 3191 } 3192 } 3193 } 3194 3195 3196 /** 3197 * Create the HTML header for the table 3198 * @param {object} oSettings dataTables settings object 3199 * @memberof DataTable#oApi 3200 */ 3201 function _fnBuildHead( settings, side ) 3202 { 3203 var classes = settings.oClasses; 3204 var columns = settings.aoColumns; 3205 var i, ien, row; 3206 var target = side === 'header' 3207 ? settings.nTHead 3208 : settings.nTFoot; 3209 var titleProp = side === 'header' ? 'sTitle' : side; 3210 3211 // Footer might be defined 3212 if (! target) { 3213 return; 3214 } 3215 3216 // If no cells yet and we have content for them, then create 3217 if (side === 'header' || _pluck(settings.aoColumns, titleProp).join('')) { 3218 row = $('tr', target); 3219 3220 // Add a row if needed 3221 if (! row.length) { 3222 row = $('<tr/>').appendTo(target) 3223 } 3224 3225 // Add the number of cells needed to make up to the number of columns 3226 if (row.length === 1) { 3227 var cellCount = 0; 3228 3229 $('td, th', row).each(function () { 3230 cellCount += this.colSpan; 3231 }); 3232 3233 for ( i=cellCount, ien=columns.length ; i<ien ; i++ ) { 3234 $('<th/>') 3235 .html( columns[i][titleProp] || '' ) 3236 .appendTo( row ); 3237 } 3238 } 3239 } 3240 3241 var detected = _fnDetectHeader( settings, target, true ); 3242 3243 if (side === 'header') { 3244 settings.aoHeader = detected;
3245 $('tr', target).addClass(classes.thead.row); 3246 } 3247 else { 3248 settings.aoFooter = detected; 3249 $('tr', target).addClass(classes.tfoot.row); 3250 } 3251 3252 // Every cell needs to be passed through the renderer 3253 $(target).children('tr').children('th, td') 3254 .each( function () { 3255 _fnRenderer( settings, side )( 3256 settings, $(this), classes 3257 ); 3258 } ); 3259 } 3260 3261 /** 3262 * Build a layout structure for a header or footer 3263 * 3264 * @param {*} settings DataTables settings 3265 * @param {*} source Source layout array 3266 * @param {*} incColumns What columns should be included 3267 * @returns Layout array 3268 */ 3269 function _fnHeaderLayout( settings, source, incColumns ) 3270 { 3271 var row, column, cell; 3272 var local = []; 3273 var structure = []; 3274 var columns = settings.aoColumns; 3275 var columnCount = columns.length; 3276 var rowspan, colspan; 3277 3278 if ( ! source ) { 3279 return; 3280 } 3281 3282 // Default is to work on only visible columns 3283 if ( ! incColumns ) { 3284 incColumns = _range(columnCount) 3285 .filter(function (idx) { 3286 return columns[idx].bVisible; 3287 }); 3288 } 3289 3290 // Make a copy of the master layout array, but with only the columns we want 3291 for ( row=0 ; row<source.length ; row++ ) { 3292 // Remove any columns we haven't selected 3293 local[row] = source[row].slice().filter(function (cell, i) { 3294 return incColumns.includes(i); 3295 }); 3296 3297 // Prep the structure array - it needs an element for each row 3298 structure.push( [] ); 3299 } 3300 3301 for ( row=0 ; row<local.length ; row++ ) { 3302 for ( column=0 ; column<local[row].length ; column++ ) { 3303 rowspan = 1; 3304 colspan = 1; 3305 3306 // Check to see if there is already a cell (row/colspan) covering our target 3307 // insert point. If there is, then there is nothing to do. 3308 if ( structure[row][column] === undefined ) { 3309 cell = local[row][column].cell; 3310 3311 // Expand for rowspan 3312 while ( 3313 local[row+rowspan] !== undefined && 3314 local[row][column].cell == local[row+rowspan][column].cell 3315 ) { 3316 structure[row+rowspan][column] = null; 3317 rowspan++; 3318 } 3319 3320 // And for colspan 3321 while ( 3322 local[row][column+colspan] !== undefined && 3323 local[row][column].cell == local[row][column+colspan].cell 3324 ) { 3325 // Which also needs to go over rows 3326 for ( var k=0 ; k<rowspan ; k++ ) { 3327 structure[row+k][column+colspan] = null; 3328 } 3329 3330 colspan++; 3331 } 3332 3333 var titleSpan = $('span.dt-column-title', cell); 3334 3335 structure[row][column] = { 3336 cell: cell, 3337 colspan: colspan, 3338 rowspan: rowspan, 3339 title: titleSpan.length 3340 ? titleSpan.html() 3341 : $(cell).html() 3342 }; 3343 } 3344 } 3345 } 3346 3347 return structure; 3348 } 3349 3350 3351 /** 3352 * Draw the header (or footer) element based on the column visibility states. 3353 * 3354 * @param object oSettings dataTables settings object 3355 * @param array aoSource Layout array from _fnDetectHeader 3356 * @memberof DataTable#oApi 3357 */ 3358 function _fnDrawHead( settings, source ) 3359 { 3360 var layout = _fnHeaderLayout(settings, source); 3361 var tr, n; 3362 3363 for ( var row=0 ; row<source.length ; row++ ) { 3364 tr = source[row].row; 3365 3366 // All cells are going to be replaced, so empty out the row 3367 // Can't use $().empty() as that kills event handlers 3368 if (tr) { 3369 while( (n = tr.firstChild) ) { 3370 tr.removeChild( n ); 3371 } 3372 } 3373 3374 for ( var column=0 ; column<layout[row].length ; column++ ) { 3375 var point = layout[row][column]; 3376 3377 if (point) { 3378 $(point.cell) 3379 .appendTo(tr) 3380 .attr('rowspan', point.rowspan) 3381 .attr('colspan', point.colspan); 3382 } 3383 } 3384 } 3385 } 3386 3387 3388 /** 3389 * Insert the required TR nodes into the table for display 3390 * @param {object} oSettings dataTables settings object 3391 * @param ajaxComplete true after ajax call to complete rendering 3392 * @memberof DataTable#oApi 3393 */ 3394 function _fnDraw( oSettings, ajaxComplete ) 3395 { 3396 // Allow for state saving and a custom start position 3397 _fnStart( oSettings ); 3398 3399 /* Provide a pre-callback function which can be used to cancel the draw is false is returned */ 3400 var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] ); 3401 if ( aPreDraw.indexOf(false) !== -1 ) 3402 { 3403 _fnProcessingDisplay( oSettings, false ); 3404 return; 3405 } 3406 3407 var anRows = []; 3408 var iRowCount = 0; 3409 var bServerSide = _fnDataSource( oSettings ) == 'ssp'; 3410 var aiDisplay = oSettings.aiDisplay; 3411 var iDisplayStart = oSettings._iDisplayStart; 3412 var iDisplayEnd = oSettings.fnDisplayEnd(); 3413 var columns = oSettings.aoColumns; 3414 var body = $(oSettings.nTBody); 3415 3416 oSettings.bDrawing = true; 3417 3418 /* Server-side processing draw intercept */ 3419 if ( oSettings.deferLoading ) 3420 { 3421 oSettings.deferLoading = false;
3422 oSettings.iDraw++; 3423 _fnProcessingDisplay( oSettings, false ); 3424 } 3425 else if ( !bServerSide ) 3426 { 3427 oSettings.iDraw++; 3428 } 3429 else if ( !oSettings.bDestroying && !ajaxComplete) 3430 { 3431 // Show loading message for server-side processing 3432 if (oSettings.iDraw === 0) { 3433 body.empty().append(_emptyRow(oSettings)); 3434 } 3435 3436 _fnAjaxUpdate( oSettings ); 3437 return; 3438 } 3439 3440 if ( aiDisplay.length !== 0 ) 3441 { 3442 var iStart = bServerSide ? 0 : iDisplayStart; 3443 var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd; 3444 3445 for ( var j=iStart ; j<iEnd ; j++ ) 3446 { 3447 var iDataIndex = aiDisplay[j]; 3448 var aoData = oSettings.aoData[ iDataIndex ]; 3449 if ( aoData.nTr === null ) 3450 { 3451 _fnCreateTr( oSettings, iDataIndex ); 3452 } 3453 3454 var nRow = aoData.nTr; 3455 3456 // Add various classes as needed 3457 for (var i=0 ; i<columns.length ; i++) { 3458 var col = columns[i]; 3459 var td = aoData.anCells[i]; 3460 3461 _addClass(td, _ext.type.className[col.sType]); // auto class 3462 _addClass(td, oSettings.oClasses.tbody.cell); // all cells 3463 } 3464 3465 // Row callback functions - might want to manipulate the row 3466 // iRowCount and j are not currently documented. Are they at all 3467 // useful? 3468 _fnCallbackFire( oSettings, 'aoRowCallback', null, 3469 [nRow, aoData._aData, iRowCount, j, iDataIndex] ); 3470 3471 anRows.push( nRow ); 3472 iRowCount++; 3473 } 3474 } 3475 else 3476 { 3477 anRows[ 0 ] = _emptyRow(oSettings); 3478 } 3479 3480 /* Header and footer callbacks */ 3481 _fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0], 3482 _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] ); 3483 3484 _fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0], 3485 _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] ); 3486 3487 // replaceChildren is faster, but only became widespread in 2020, 3488 // so a fall back in jQuery is provided for older browsers. 3489 if (body[0].replaceChildren) { 3490 body[0].replaceChildren.apply(body[0], anRows); 3491 } 3492 else { 3493 body.children().detach(); 3494 body.append( $(anRows) ); 3495 } 3496 3497 // Empty table needs a specific class 3498 $(oSettings.nTableWrapper).toggleClass('dt-empty-footer', $('tr', oSettings.nTFoot).length === 0); 3499 3500 /* Call all required callback functions for the end of a draw */ 3501 _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings], true ); 3502 3503 /* Draw is complete, sorting and filtering must be as well */ 3504 oSettings.bSorted = false; 3505 oSettings.bFiltered = false; 3506 oSettings.bDrawing = false; 3507 } 3508 3509 3510 /** 3511 * Redraw the table - taking account of the various features which are enabled 3512 * @param {object} oSettings dataTables settings object 3513 * @param {boolean} [holdPosition] Keep the current paging position. By default 3514 * the paging is reset to the first page 3515 * @memberof DataTable#oApi 3516 */ 3517 function _fnReDraw( settings, holdPosition, recompute ) 3518 { 3519 var 3520 features = settings.oFeatures, 3521 sort = features.bSort, 3522 filter = features.bFilter; 3523 3524 if (recompute === undefined || recompute === true) { 3525 // Resolve any column types that are unknown due to addition or invalidation 3526 _fnColumnTypes( settings ); 3527 3528 if ( sort ) { 3529 _fnSort( settings ); 3530 } 3531 3532 if ( filter ) { 3533 _fnFilterComplete( settings, settings.oPreviousSearch ); 3534 } 3535 else { 3536 // No filtering, so we want to just use the display master 3537 settings.aiDisplay = settings.aiDisplayMaster.slice(); 3538 } 3539 } 3540 3541 if ( holdPosition !== true ) { 3542 settings._iDisplayStart = 0; 3543 } 3544 3545 // Let any modules know about the draw hold position state (used by 3546 // scrolling internally) 3547 settings._drawHold = holdPosition; 3548 3549 _fnDraw( settings ); 3550 3551 settings._drawHold = false; 3552 } 3553 3554 3555 /* 3556 * Table is empty - create a row with an empty message in it 3557 */ 3558 function _emptyRow ( settings ) { 3559 var oLang = settings.oLanguage; 3560 var zero = oLang.sZeroRecords; 3561 var dataSrc = _fnDataSource( settings ); 3562 3563 if ( 3564 (settings.iDraw < 1 && dataSrc === 'ssp') || 3565 (settings.iDraw <= 1 && dataSrc === 'ajax') 3566 ) { 3567 zero = oLang.sLoadingRecords; 3568 } 3569 else if ( oLang.sEmptyTable && settings.fnRecordsTotal() === 0 ) 3570 { 3571 zero = oLang.sEmptyTable; 3572 } 3573 3574 return $( '<tr/>' ) 3575 .append( $('<td />', { 3576 'colSpan': _fnVisbleColumns( settings ), 3577 'class': settings.oClasses.empty.row 3578 } ).html( zero ) )[0]; 3579 } 3580 3581 3582 /** 3583 * Expand the layout items into an object for the rendering function 3584 */ 3585 function _layoutItems (row, align, items) { 3586 if ( Array.isArray(items)) { 3587 for (var i=0 ; i<items.length ; i++) { 3588 _layoutItems(row, align, items[i]); 3589 } 3590 3591 return; 3592 } 3593 3594 var rowCell = row[align]; 3595 3596 // If it is an object, then there can be multiple features contained in it 3597 if ( $.isPlainObject( items ) ) { 3598 // A feature plugin cannot be named "features" due to this check 3599 if (items.features) { 3600 if (items.rowId) { 3601 row.id = items.rowId; 3602 } 3603 if (items.rowClass) { 3604 row.className = items.rowClass; 3605 } 3606 3607 rowCell.id = items.id; 3608 rowCell.className = items.className; 3609 3610 _layoutItems(row, align, items.features); 3611 } 3612 else { 3613 Object.keys(items).map(function (key) { 3614 rowCell.contents.push( { 3615 feature: key, 3616 opts: items[key] 3617 }); 3618 }); 3619 } 3620 } 3621 else { 3622 rowCell.contents.push(items); 3623 } 3624 } 3625 3626 /** 3627 * Find, or create a layout row 3628 */ 3629 function _layoutGetRow(rows, rowNum, align) { 3630 var row; 3631 3632 // Find existing rows 3633 for (var i=0; i<rows.length; i++) { 3634 row = rows[i]; 3635 3636 if (row.rowNum === rowNum) { 3637 // full is on its own, but start and end share a row 3638 if ( 3639 (align === 'full' && row.full) || 3640 ((align === 'start' || align === 'end') && (row.start || row.end)) 3641 ) { 3642 if (! row[align]) { 3643 row[align] = { 3644 contents: [] 3645 }; 3646 } 3647 3648 return row; 3649 } 3650 } 3651 } 3652 3653 // If we get this far, then there was no match, create a new row 3654 row = { 3655 rowNum: rowNum 3656 }; 3657 3658 row[align] = { 3659 contents: [] 3660 }; 3661 3662 rows.push(row); 3663 3664 return row; 3665 } 3666 3667 /** 3668 * Convert a `layout` object given by a user to the object structure needed 3669 * for the renderer. This is done twice, once for above and once for below
3670 * the table. Ordering must also be considered. 3671 * 3672 * @param {*} settings DataTables settings object 3673 * @param {*} layout Layout object to convert 3674 * @param {string} side `top` or `bottom` 3675 * @returns Converted array structure - one item for each row. 3676 */ 3677 function _layoutArray ( settings, layout, side ) { 3678 var rows = []; 3679 3680 // Split out into an array 3681 $.each( layout, function ( pos, items ) { 3682 if (items === null) { 3683 return; 3684 } 3685 3686 var parts = pos.match(/^([a-z]+)([0-9]*)([A-Za-z]*)$/); 3687 var rowNum = parts[2] 3688 ? parts[2] * 1 3689 : 0; 3690 var align = parts[3] 3691 ? parts[3].toLowerCase() 3692 : 'full'; 3693 3694 // Filter out the side we aren't interested in 3695 if (parts[1] !== side) { 3696 return; 3697 } 3698 3699 // Get or create the row we should attach to 3700 var row = _layoutGetRow(rows, rowNum, align); 3701 3702 _layoutItems(row, align, items); 3703 }); 3704 3705 // Order by item identifier 3706 rows.sort( function ( a, b ) { 3707 var order1 = a.rowNum; 3708 var order2 = b.rowNum; 3709 3710 // If both in the same row, then the row with `full` comes first 3711 if (order1 === order2) { 3712 var ret = a.full && ! b.full ? -1 : 1; 3713 3714 return side === 'bottom' 3715 ? ret * -1 3716 : ret; 3717 } 3718 3719 return order2 - order1; 3720 } ); 3721 3722 // Invert for below the table 3723 if ( side === 'bottom' ) { 3724 rows.reverse(); 3725 } 3726 3727 for (var row = 0; row<rows.length; row++) { 3728 delete rows[row].rowNum; 3729 3730 _layoutResolve(settings, rows[row]); 3731 } 3732 3733 return rows; 3734 } 3735 3736 3737 /** 3738 * Convert the contents of a row's layout object to nodes that can be inserted 3739 * into the document by a renderer. Execute functions, look up plug-ins, etc. 3740 * 3741 * @param {*} settings DataTables settings object 3742 * @param {*} row Layout object for this row 3743 */ 3744 function _layoutResolve( settings, row ) { 3745 var getFeature = function (feature, opts) { 3746 if ( ! _ext.features[ feature ] ) { 3747 _fnLog( settings, 0, 'Unknown feature: '+ feature ); 3748 } 3749 3750 return _ext.features[ feature ].apply( this, [settings, opts] ); 3751 }; 3752 3753 var resolve = function ( item ) { 3754 if (! row[ item ]) { 3755 return; 3756 } 3757 3758 var line = row[ item ].contents; 3759 3760 for ( var i=0, ien=line.length ; i<ien ; i++ ) { 3761 if ( ! line[i] ) { 3762 continue; 3763 } 3764 else if ( typeof line[i] === 'string' ) { 3765 line[i] = getFeature( line[i], null ); 3766 } 3767 else if ( $.isPlainObject(line[i]) ) { 3768 // If it's an object, it just has feature and opts properties from 3769 // the transform in _layoutArray 3770 line[i] = getFeature(line[i].feature, line[i].opts); 3771 } 3772 else if ( typeof line[i].node === 'function' ) { 3773 line[i] = line[i].node( settings ); 3774 } 3775 else if ( typeof line[i] === 'function' ) { 3776 var inst = line[i]( settings ); 3777 3778 line[i] = typeof inst.node === 'function' ? 3779 inst.node() : 3780 inst; 3781 } 3782 } 3783 }; 3784 3785 resolve('start'); 3786 resolve('end'); 3787 resolve('full'); 3788 } 3789 3790 3791 /** 3792 * Add the options to the page HTML for the table 3793 * @param {object} settings DataTables settings object 3794 * @memberof DataTable#oApi 3795 */ 3796 function _fnAddOptionsHtml ( settings ) 3797 { 3798 var classes = settings.oClasses; 3799 var table = $(settings.nTable); 3800 3801 // Wrapper div around everything DataTables controls 3802 var insert = $('<div/>') 3803 .attr({ 3804 id: settings.sTableId+'_wrapper', 3805 'class': classes.container 3806 }) 3807 .insertBefore(table); 3808 3809 settings.nTableWrapper = insert[0]; 3810 3811 if (settings.sDom) { 3812 // Legacy 3813 _fnLayoutDom(settings, settings.sDom, insert); 3814 } 3815 else { 3816 var top = _layoutArray( settings, settings.layout, 'top' ); 3817 var bottom = _layoutArray( settings, settings.layout, 'bottom' ); 3818 var renderer = _fnRenderer( settings, 'layout' ); 3819 3820 // Everything above - the renderer will actually insert the contents into the document 3821 top.forEach(function (item) { 3822 renderer( settings, insert, item ); 3823 }); 3824 3825 // The table - always the center of attention 3826 renderer( settings, insert, { 3827 full: { 3828 table: true, 3829 contents: [ _fnFeatureHtmlTable(settings) ] 3830 } 3831 } ); 3832 3833 // Everything below 3834 bottom.forEach(function (item) { 3835 renderer( settings, insert, item ); 3836 }); 3837 } 3838 3839 // Processing floats on top, so it isn't an inserted feature 3840 _processingHtml( settings ); 3841 } 3842 3843 /** 3844 * Draw the table with the legacy DOM property 3845 * @param {*} settings DT settings object 3846 * @param {*} dom DOM string 3847 * @param {*} insert Insert point 3848 */ 3849 function _fnLayoutDom( settings, dom, insert ) 3850 { 3851 var parts = dom.match(/(".*?")|('.*?')|./g); 3852 var featureNode, option, newNode, next, attr; 3853 3854 for ( var i=0 ; i<parts.length ; i++ ) { 3855 featureNode = null; 3856 option = parts[i]; 3857 3858 if ( option == '<' ) { 3859 // New container div
3860 newNode = $('<div/>'); 3861 3862 // Check to see if we should append an id and/or a class name to the container 3863 next = parts[i+1]; 3864 3865 if ( next[0] == "'" || next[0] == '"' ) { 3866 attr = next.replace(/['"]/g, ''); 3867 3868 var id = '', className; 3869 3870 /* The attribute can be in the format of "#id.class", "#id" or "class" This logic 3871 * breaks the string into parts and applies them as needed 3872 */ 3873 if ( attr.indexOf('.') != -1 ) { 3874 var split = attr.split('.'); 3875 3876 id = split[0]; 3877 className = split[1]; 3878 } 3879 else if ( attr[0] == "#" ) { 3880 id = attr; 3881 } 3882 else { 3883 className = attr; 3884 } 3885 3886 newNode 3887 .attr('id', id.substring(1)) 3888 .addClass(className); 3889 3890 i++; // Move along the position array 3891 } 3892 3893 insert.append( newNode ); 3894 insert = newNode; 3895 } 3896 else if ( option == '>' ) { 3897 // End container div 3898 insert = insert.parent(); 3899 } 3900 else if ( option == 't' ) { 3901 // Table 3902 featureNode = _fnFeatureHtmlTable( settings ); 3903 } 3904 else 3905 { 3906 DataTable.ext.feature.forEach(function(feature) { 3907 if ( option == feature.cFeature ) { 3908 featureNode = feature.fnInit( settings ); 3909 } 3910 }); 3911 } 3912 3913 // Add to the display 3914 if ( featureNode ) { 3915 insert.append( featureNode ); 3916 } 3917 } 3918 } 3919 3920 3921 /** 3922 * Use the DOM source to create up an array of header cells. The idea here is to 3923 * create a layout grid (array) of rows x columns, which contains a reference 3924 * to the cell that that point in the grid (regardless of col/rowspan), such that 3925 * any column / row could be removed and the new grid constructed 3926 * @param {node} thead The header/footer element for the table 3927 * @returns {array} Calculated layout array 3928 * @memberof DataTable#oApi 3929 */ 3930 function _fnDetectHeader ( settings, thead, write ) 3931 { 3932 var columns = settings.aoColumns; 3933 var rows = $(thead).children('tr'); 3934 var row, cell; 3935 var i, k, l, iLen, shifted, column, colspan, rowspan; 3936 var isHeader = thead && thead.nodeName.toLowerCase() === 'thead'; 3937 var layout = []; 3938 var unique; 3939 var shift = function ( a, i, j ) { 3940 var k = a[i]; 3941 while ( k[j] ) { 3942 j++; 3943 } 3944 return j; 3945 }; 3946 3947 // We know how many rows there are in the layout - so prep it 3948 for ( i=0, iLen=rows.length ; i<iLen ; i++ ) { 3949 layout.push( [] ); 3950 } 3951 3952 for ( i=0, iLen=rows.length ; i<iLen ; i++ ) { 3953 row = rows[i]; 3954 column = 0; 3955 3956 // For every cell in the row.. 3957 cell = row.firstChild; 3958 while ( cell ) { 3959 if ( 3960 cell.nodeName.toUpperCase() == 'TD' || 3961 cell.nodeName.toUpperCase() == 'TH' 3962 ) { 3963 var cols = []; 3964 3965 // Get the col and rowspan attributes from the DOM and sanitise them 3966 colspan = cell.getAttribute('colspan') * 1; 3967 rowspan = cell.getAttribute('rowspan') * 1; 3968 colspan = (!colspan || colspan===0 || colspan===1) ? 1 : colspan; 3969 rowspan = (!rowspan || rowspan===0 || rowspan===1) ? 1 : rowspan; 3970 3971 // There might be colspan cells already in this row, so shift our target 3972 // accordingly 3973 shifted = shift( layout, i, column ); 3974 3975 // Cache calculation for unique columns 3976 unique = colspan === 1 ? 3977 true : 3978 false; 3979 3980 // Perform header setup 3981 if ( write ) { 3982 if (unique) { 3983 // Allow column options to be set from HTML attributes 3984 _fnColumnOptions( settings, shifted, $(cell).data() ); 3985 3986 // Get the width for the column. This can be defined from the 3987 // width attribute, style attribute or `columns.width` option 3988 var columnDef = columns[shifted]; 3989 var width = cell.getAttribute('width') || null; 3990 var t = cell.style.width.match(/width:\s*(\d+[pxem%]+)/); 3991 if ( t ) { 3992 width = t[1]; 3993 } 3994 3995 columnDef.sWidthOrig = columnDef.sWidth || width; 3996 3997 if (isHeader) { 3998 // Column title handling - can be user set, or read from the DOM 3999 // This happens before the render, so the original is still in place 4000 if ( columnDef.sTitle !== null && ! columnDef.autoTitle ) { 4001 cell.innerHTML = columnDef.sTitle; 4002 } 4003 4004 if (! columnDef.sTitle && unique) { 4005 columnDef.sTitle = _stripHtml(cell.innerHTML); 4006 columnDef.autoTitle = true; 4007 } 4008 } 4009 else { 4010 // Footer specific operations 4011 if (columnDef.footer) { 4012 cell.innerHTML = columnDef.footer; 4013 } 4014 } 4015 4016 // Fall back to the aria-label attribute on the table header if no ariaTitle is 4017 // provided. 4018 if (! columnDef.ariaTitle) { 4019 columnDef.ariaTitle = $(cell).attr("aria-label") || columnDef.sTitle; 4020 } 4021 4022 // Column specific class names 4023 if ( columnDef.className ) { 4024 $(cell).addClass( columnDef.className ); 4025 } 4026 } 4027 4028 // Wrap the column title so we can write to it in future 4029 if ( $('span.dt-column-title', cell).length === 0) { 4030 $('<span>') 4031 .addClass('dt-column-title') 4032 .append(cell.childNodes) 4033 .appendTo(cell); 4034 } 4035 4036 if ( isHeader && $('span.dt-column-order', cell).length === 0) { 4037 $('<span>') 4038 .addClass('dt-column-order') 4039 .appendTo(cell); 4040 } 4041 } 4042 4043 // If there is col / rowspan, copy the information into the layout grid 4044 for ( l=0 ; l<colspan ; l++ ) { 4045 for ( k=0 ; k<rowspan ; k++ ) { 4046 layout[i+k][shifted+l] = { 4047 cell: cell, 4048 unique: unique 4049 }; 4050
4051 layout[i+k].row = row; 4052 } 4053 4054 cols.push( shifted+l ); 4055 } 4056 4057 // Assign an attribute so spanning cells can still be identified 4058 // as belonging to a column 4059 cell.setAttribute('data-dt-column', _unique(cols).join(',')); 4060 } 4061 4062 cell = cell.nextSibling; 4063 } 4064 } 4065 4066 return layout; 4067 } 4068 4069 /** 4070 * Set the start position for draw 4071 * @param {object} oSettings dataTables settings object 4072 */ 4073 function _fnStart( oSettings ) 4074 { 4075 var bServerSide = _fnDataSource( oSettings ) == 'ssp'; 4076 var iInitDisplayStart = oSettings.iInitDisplayStart; 4077 4078 // Check and see if we have an initial draw position from state saving 4079 if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 ) 4080 { 4081 oSettings._iDisplayStart = bServerSide ? 4082 iInitDisplayStart : 4083 iInitDisplayStart >= oSettings.fnRecordsDisplay() ? 4084 0 : 4085 iInitDisplayStart; 4086 4087 oSettings.iInitDisplayStart = -1; 4088 } 4089 } 4090 4091 /** 4092 * Create an Ajax call based on the table's settings, taking into account that 4093 * parameters can have multiple forms, and backwards compatibility. 4094 * 4095 * @param {object} oSettings dataTables settings object 4096 * @param {array} data Data to send to the server, required by 4097 * DataTables - may be augmented by developer callbacks 4098 * @param {function} fn Callback function to run when data is obtained 4099 */ 4100 function _fnBuildAjax( oSettings, data, fn ) 4101 { 4102 var ajaxData; 4103 var ajax = oSettings.ajax; 4104 var instance = oSettings.oInstance; 4105 var callback = function ( json ) { 4106 var status = oSettings.jqXHR 4107 ? oSettings.jqXHR.status 4108 : null; 4109 4110 if ( json === null || (typeof status === 'number' && status == 204 ) ) { 4111 json = {}; 4112 _fnAjaxDataSrc( oSettings, json, [] ); 4113 } 4114 4115 var error = json.error || json.sError; 4116 if ( error ) { 4117 _fnLog( oSettings, 0, error ); 4118 } 4119 4120 // Microsoft often wrap JSON as a string in another JSON object 4121 // Let's handle that automatically 4122 if (json.d && typeof json.d === 'string') { 4123 try { 4124 json = JSON.parse(json.d); 4125 } 4126 catch (e) { 4127 // noop 4128 } 4129 } 4130 4131 oSettings.json = json; 4132 4133 _fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR], true ); 4134 fn( json ); 4135 }; 4136 4137 if ( $.isPlainObject( ajax ) && ajax.data ) 4138 { 4139 ajaxData = ajax.data; 4140 4141 var newData = typeof ajaxData === 'function' ? 4142 ajaxData( data, oSettings ) : // fn can manipulate data or return 4143 ajaxData; // an object object or array to merge 4144 4145 // If the function returned something, use that alone 4146 data = typeof ajaxData === 'function' && newData ? 4147 newData : 4148 $.extend( true, data, newData ); 4149 4150 // Remove the data property as we've resolved it already and don't want 4151 // jQuery to do it again (it is restored at the end of the function) 4152 delete ajax.data; 4153 } 4154 4155 var baseAjax = { 4156 "url": typeof ajax === 'string' ? 4157 ajax : 4158 '', 4159 "data": data, 4160 "success": callback, 4161 "dataType": "json", 4162 "cache": false, 4163 "type": oSettings.sServerMethod, 4164 "error": function (xhr, error) { 4165 var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR], true ); 4166 4167 if ( ret.indexOf(true) === -1 ) { 4168 if ( error == "parsererror" ) { 4169 _fnLog( oSettings, 0, 'Invalid JSON response', 1 ); 4170 } 4171 else if ( xhr.readyState === 4 ) { 4172 _fnLog( oSettings, 0, 'Ajax error', 7 ); 4173 } 4174 } 4175 4176 _fnProcessingDisplay( oSettings, false ); 4177 } 4178 }; 4179 4180 // If `ajax` option is an object, extend and override our default base 4181 if ( $.isPlainObject( ajax ) ) { 4182 $.extend( baseAjax, ajax ) 4183 } 4184 4185 // Store the data submitted for the API 4186 oSettings.oAjaxData = data; 4187 4188 // Allow plug-ins and external processes to modify the data 4189 _fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data, baseAjax], true ); 4190 4191 if ( typeof ajax === 'function' ) 4192 { 4193 // Is a function - let the caller define what needs to be done 4194 oSettings.jqXHR = ajax.call( instance, data, callback, oSettings ); 4195 } 4196 else if (ajax.url === '') { 4197 // No url, so don't load any data. Just apply an empty data array 4198 // to the object for the callback. 4199 var empty = {}; 4200 4201 DataTable.util.set(ajax.dataSrc)(empty, []); 4202 callback(empty); 4203 } 4204 else { 4205 // Object to extend the base settings 4206 oSettings.jqXHR = $.ajax( baseAjax ); 4207 } 4208 4209 // Restore for next time around 4210 if ( ajaxData ) { 4211 ajax.data = ajaxData; 4212 } 4213 } 4214 4215 4216 /** 4217 * Update the table using an Ajax call 4218 * @param {object} settings dataTables settings object 4219 * @returns {boolean} Block the table drawing or not 4220 * @memberof DataTable#oApi 4221 */ 4222 function _fnAjaxUpdate( settings ) 4223 { 4224 settings.iDraw++; 4225 _fnProcessingDisplay( settings, true ); 4226 4227 _fnBuildAjax( 4228 settings, 4229 _fnAjaxParameters( settings ), 4230 function(json) { 4231 _fnAjaxUpdateDraw( settings, json ); 4232 } 4233 ); 4234 } 4235 4236 4237 /** 4238 * Build up the parameters in an object needed for a server-side processing 4239 * request. 4240 * @param {object} oSettings dataTables settings object 4241 * @returns {bool} block the table drawing or not 4242 * @memberof DataTable#oApi 4243 */ 4244 function _fnAjaxParameters( settings ) 4245 { 4246 var 4247 columns = settings.aoColumns, 4248 features = settings.oFeatures, 4249 preSearch = settings.oPreviousSearch, 4250 preColSearch = settings.aoPreSearchCols, 4251 colData = function ( idx, prop ) { 4252 return typeof columns[idx][prop] === 'function' ? 4253 'function' : 4254 columns[idx][prop]; 4255 }; 4256 4257 return { 4258 draw: settings.iDraw,
4259 columns: columns.map( function ( column, i ) { 4260 return { 4261 data: colData(i, 'mData'), 4262 name: column.sName, 4263 searchable: column.bSearchable, 4264 orderable: column.bSortable, 4265 search: { 4266 value: preColSearch[i].search, 4267 regex: preColSearch[i].regex, 4268 fixed: Object.keys(column.searchFixed).map( function(name) { 4269 return { 4270 name: name, 4271 term: column.searchFixed[name].toString() 4272 } 4273 }) 4274 } 4275 }; 4276 } ), 4277 order: _fnSortFlatten( settings ).map( function ( val ) { 4278 return { 4279 column: val.col, 4280 dir: val.dir, 4281 name: colData(val.col, 'sName') 4282 }; 4283 } ), 4284 start: settings._iDisplayStart, 4285 length: features.bPaginate ? 4286 settings._iDisplayLength : 4287 -1, 4288 search: { 4289 value: preSearch.search, 4290 regex: preSearch.regex, 4291 fixed: Object.keys(settings.searchFixed).map( function(name) { 4292 return { 4293 name: name, 4294 term: settings.searchFixed[name].toString() 4295 } 4296 }) 4297 } 4298 }; 4299 } 4300 4301 4302 /** 4303 * Data the data from the server (nuking the old) and redraw the table 4304 * @param {object} oSettings dataTables settings object 4305 * @param {object} json json data return from the server. 4306 * @param {string} json.sEcho Tracking flag for DataTables to match requests 4307 * @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering 4308 * @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering 4309 * @param {array} json.aaData The data to display on this page 4310 * @param {string} [json.sColumns] Column ordering (sName, comma separated) 4311 * @memberof DataTable#oApi 4312 */ 4313 function _fnAjaxUpdateDraw ( settings, json ) 4314 { 4315 var data = _fnAjaxDataSrc(settings, json); 4316 var draw = _fnAjaxDataSrcParam(settings, 'draw', json); 4317 var recordsTotal = _fnAjaxDataSrcParam(settings, 'recordsTotal', json); 4318 var recordsFiltered = _fnAjaxDataSrcParam(settings, 'recordsFiltered', json); 4319 4320 if ( draw !== undefined ) { 4321 // Protect against out of sequence returns 4322 if ( draw*1 < settings.iDraw ) { 4323 return; 4324 } 4325 settings.iDraw = draw * 1; 4326 } 4327 4328 // No data in returned object, so rather than an array, we show an empty table 4329 if ( ! data ) { 4330 data = []; 4331 } 4332 4333 _fnClearTable( settings ); 4334 settings._iRecordsTotal = parseInt(recordsTotal, 10); 4335 settings._iRecordsDisplay = parseInt(recordsFiltered, 10); 4336 4337 for ( var i=0, ien=data.length ; i<ien ; i++ ) { 4338 _fnAddData( settings, data[i] ); 4339 } 4340 settings.aiDisplay = settings.aiDisplayMaster.slice(); 4341 4342 _fnColumnTypes(settings); 4343 _fnDraw( settings, true ); 4344 _fnInitComplete( settings ); 4345 _fnProcessingDisplay( settings, false ); 4346 } 4347 4348 4349 /** 4350 * Get the data from the JSON data source to use for drawing a table. Using 4351 * `_fnGetObjectDataFn` allows the data to be sourced from a property of the 4352 * source object, or from a processing function. 4353 * @param {object} settings dataTables settings object 4354 * @param {object} json Data source object / array from the server 4355 * @return {array} Array of data to use 4356 */ 4357 function _fnAjaxDataSrc ( settings, json, write ) 4358 { 4359 var dataProp = 'data'; 4360 4361 if ($.isPlainObject( settings.ajax ) && settings.ajax.dataSrc !== undefined) { 4362 // Could in inside a `dataSrc` object, or not! 4363 var dataSrc = settings.ajax.dataSrc; 4364 4365 // string, function and object are valid types 4366 if (typeof dataSrc === 'string' || typeof dataSrc === 'function') { 4367 dataProp = dataSrc; 4368 } 4369 else if (dataSrc.data !== undefined) { 4370 dataProp = dataSrc.data; 4371 } 4372 } 4373 4374 if ( ! write ) { 4375 if ( dataProp === 'data' ) { 4376 // If the default, then we still want to support the old style, and safely ignore 4377 // it if possible 4378 return json.aaData || json[dataProp]; 4379 } 4380 4381 return dataProp !== "" ? 4382 _fnGetObjectDataFn( dataProp )( json ) : 4383 json; 4384 } 4385 4386 // set 4387 _fnSetObjectDataFn( dataProp )( json, write ); 4388 } 4389 4390 /** 4391 * Very similar to _fnAjaxDataSrc, but for the other SSP properties 4392 * @param {*} settings DataTables settings object 4393 * @param {*} param Target parameter 4394 * @param {*} json JSON data 4395 * @returns Resolved value 4396 */ 4397 function _fnAjaxDataSrcParam (settings, param, json) { 4398 var dataSrc = $.isPlainObject( settings.ajax ) 4399 ? settings.ajax.dataSrc 4400 : null; 4401 4402 if (dataSrc && dataSrc[param]) { 4403 // Get from custom location 4404 return _fnGetObjectDataFn( dataSrc[param] )( json ); 4405 } 4406 4407 // else - Default behaviour 4408 var old = ''; 4409 4410 // Legacy support 4411 if (param === 'draw') { 4412 old = 'sEcho'; 4413 } 4414 else if (param === 'recordsTotal') { 4415 old = 'iTotalRecords'; 4416 } 4417 else if (param === 'recordsFiltered') { 4418 old = 'iTotalDisplayRecords'; 4419 } 4420 4421 return json[old] !== undefined 4422 ? json[old] 4423 : json[param]; 4424 } 4425 4426 4427 /** 4428 * Filter the table using both the global filter and column based filtering 4429 * @param {object} settings dataTables settings object 4430 * @param {object} input search information 4431 * @memberof DataTable#oApi 4432 */ 4433 function _fnFilterComplete ( settings, input ) 4434 { 4435 var columnsSearch = settings.aoPreSearchCols; 4436 4437 // In server-side processing all filtering is done by the server, so no point hanging around here 4438 if ( _fnDataSource( settings ) != 'ssp' ) 4439 { 4440 // Check if any of the rows were invalidated 4441 _fnFilterData( settings ); 4442 4443 // Start from the full data set 4444 settings.aiDisplay = settings.aiDisplayMaster.slice(); 4445 4446 // Global filter first 4447 _fnFilter( settings.aiDisplay, settings, input.search, input ); 4448 4449 $.each(settings.searchFixed, function (name, term) { 4450 _fnFilter(settings.aiDisplay, settings, term, {}); 4451 }); 4452 4453 // Then individual column filters 4454 for ( var i=0 ; i<columnsSearch.length ; i++ ) 4455 { 4456 var col = columnsSearch[i]; 4457 4458 _fnFilter( 4459 settings.aiDisplay, 4460 settings, 4461 col.search, 4462 col, 4463 i 4464 ); 4465 4466 $.each(settings.aoColumns[i].searchFixed, function (name, term) { 4467 _fnFilter(settings.aiDisplay, settings, term, {}, i); 4468 }); 4469 } 4470 4471 // And finally global filtering 4472 _fnFilterCustom( settings ); 4473 } 4474 4475 // Tell the draw function we have been filtering 4476 settings.bFiltered = true; 4477 4478 _fnCallbackFire( settings, null, 'search', [settings] ); 4479 } 4480 4481 4482 /** 4483 * Apply custom filtering functions 4484 * 4485 * This is legacy now that we have named functions, but it is widely used 4486 * from 1.x, so it is not yet deprecated. 4487 * @param {object} oSettings dataTables settings object 4488 * @memberof DataTable#oApi 4489 */ 4490 function _fnFilterCustom( settings ) 4491 { 4492 var filters = DataTable.ext.search; 4493 var displayRows = settings.aiDisplay; 4494 var row, rowIdx; 4495 4496 for ( var i=0, ien=filters.length ; i<ien ; i++ ) { 4497 var rows = []; 4498 4499 // Loop over each row and see if it should be included 4500 for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) { 4501 rowIdx = displayRows[ j ]; 4502 row = settings.aoData[ rowIdx ]; 4503 4504 if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) { 4505 rows.push( rowIdx ); 4506 } 4507 } 4508 4509 // So the array reference doesn't break set the results into the 4510 // existing array 4511 displayRows.length = 0; 4512 _fnArrayApply(displayRows, rows); 4513 } 4514 } 4515 4516 4517 /** 4518 * Filter the data table based on user input and draw the table 4519 */ 4520 function _fnFilter( searchRows, settings, input, options, column ) 4521 { 4522 if ( input === '' ) { 4523 return; 4524 } 4525 4526 var i = 0; 4527 var matched = []; 4528 4529 // Search term can be a function, regex or string - if a string we apply our 4530 // smart filtering regex (assuming the options require that) 4531 var searchFunc = typeof input === 'function' ? input : null; 4532 var rpSearch = input instanceof RegExp 4533 ? input 4534 : searchFunc 4535 ? null 4536 : _fnFilterCreateSearch( input, options ); 4537 4538 // Then for each row, does the test pass. If not, lop the row from the array 4539 for (i=0 ; i<searchRows.length ; i++) { 4540 var row = settings.aoData[ searchRows[i] ]; 4541 var data = column === undefined 4542 ? row._sFilterRow 4543 : row._aFilterData[ column ]; 4544 4545 if ( (searchFunc && searchFunc(data, row._aData, searchRows[i], column)) || (rpSearch && rpSearch.test(data)) ) { 4546 matched.push(searchRows[i]); 4547 } 4548 } 4549 4550 // Mutate the searchRows array 4551 searchRows.length = matched.length; 4552 4553 for (i=0 ; i<matched.length ; i++) { 4554 searchRows[i] = matched[i]; 4555 } 4556 } 4557 4558 4559 /** 4560 * Build a regular expression object suitable for searching a table 4561 * @param {string} sSearch string to search for 4562 * @param {bool} bRegex treat as a regular expression or not 4563 * @param {bool} bSmart perform smart filtering or not 4564 * @param {bool} bCaseInsensitive Do case insensitive matching or not 4565 * @returns {RegExp} constructed object 4566 * @memberof DataTable#oApi 4567 */ 4568 function _fnFilterCreateSearch( search, inOpts ) 4569 { 4570 var not = []; 4571 var options = $.extend({}, { 4572 boundary: false, 4573 caseInsensitive: true, 4574 exact: false, 4575 regex: false, 4576 smart: true 4577 }, inOpts); 4578 4579 if (typeof search !== 'string') { 4580 search = search.toString(); 4581 } 4582 4583 // Remove diacritics if normalize is set up to do so 4584 search = _normalize(search); 4585 4586 if (options.exact) { 4587 return new RegExp( 4588 '^'+_fnEscapeRegex(search)+'$', 4589 options.caseInsensitive ? 'i' : '' 4590 ); 4591 } 4592 4593 search = options.regex ? 4594 search : 4595 _fnEscapeRegex( search ); 4596 4597 if ( options.smart ) { 4598 /* For smart filtering we want to allow the search to work regardless of 4599 * word order. We also want double quoted text to be preserved, so word 4600 * order is important - a la google. And a negative look around for 4601 * finding rows which don't contain a given string. 4602 * 4603 * So this is the sort of thing we want to generate: 4604 * 4605 * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$ 4606 */ 4607 var parts = search.match( /!?["\u201C][^"\u201D]+["\u201D]|[^ ]+/g ) || ['']; 4608 var a = parts.map( function ( word ) { 4609 var negative = false;
4610 var m; 4611 4612 // Determine if it is a "does not include" 4613 if ( word.charAt(0) === '!' ) { 4614 negative = true; 4615 word = word.substring(1); 4616 } 4617 4618 // Strip the quotes from around matched phrases 4619 if ( word.charAt(0) === '"' ) { 4620 m = word.match( /^"(.*)"$/ ); 4621 word = m ? m[1] : word; 4622 } 4623 else if ( word.charAt(0) === '\u201C' ) { 4624 // Smart quote match (iPhone users) 4625 m = word.match( /^\u201C(.*)\u201D$/ ); 4626 word = m ? m[1] : word; 4627 } 4628 4629 // For our "not" case, we need to modify the string that is 4630 // allowed to match at the end of the expression. 4631 if (negative) { 4632 if (word.length > 1) { 4633 not.push('(?!'+word+')'); 4634 } 4635 4636 word = ''; 4637 } 4638 4639 return word.replace(/"/g, ''); 4640 } ); 4641 4642 var match = not.length 4643 ? not.join('') 4644 : ''; 4645 4646 var boundary = options.boundary 4647 ? '\\b' 4648 : ''; 4649 4650 search = '^(?=.*?'+boundary+a.join( ')(?=.*?'+boundary )+')('+match+'.)*$'; 4651 } 4652 4653 return new RegExp( search, options.caseInsensitive ? 'i' : '' ); 4654 } 4655 4656 4657 /** 4658 * Escape a string such that it can be used in a regular expression 4659 * @param {string} sVal string to escape 4660 * @returns {string} escaped string 4661 * @memberof DataTable#oApi 4662 */ 4663 var _fnEscapeRegex = DataTable.util.escapeRegex; 4664 4665 var __filter_div = $('<div>')[0]; 4666 var __filter_div_textContent = __filter_div.textContent !== undefined; 4667 4668 // Update the filtering data for each row if needed (by invalidation or first run) 4669 function _fnFilterData ( settings ) 4670 { 4671 var columns = settings.aoColumns; 4672 var data = settings.aoData; 4673 var column; 4674 var j, jen, filterData, cellData, row; 4675 var wasInvalidated = false; 4676 4677 for ( var rowIdx=0 ; rowIdx<data.length ; rowIdx++ ) { 4678 if (! data[rowIdx]) { 4679 continue; 4680 } 4681 4682 row = data[rowIdx]; 4683 4684 if ( ! row._aFilterData ) { 4685 filterData = []; 4686 4687 for ( j=0, jen=columns.length ; j<jen ; j++ ) { 4688 column = columns[j]; 4689 4690 if ( column.bSearchable ) { 4691 cellData = _fnGetCellData( settings, rowIdx, j, 'filter' ); 4692 4693 // Search in DataTables is string based 4694 if ( cellData === null ) { 4695 cellData = ''; 4696 } 4697 4698 if ( typeof cellData !== 'string' && cellData.toString ) { 4699 cellData = cellData.toString(); 4700 } 4701 } 4702 else { 4703 cellData = ''; 4704 } 4705 4706 // If it looks like there is an HTML entity in the string, 4707 // attempt to decode it so sorting works as expected. Note that 4708 // we could use a single line of jQuery to do this, but the DOM 4709 // method used here is much faster https://jsperf.com/html-decode 4710 if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) { 4711 __filter_div.innerHTML = cellData; 4712 cellData = __filter_div_textContent ? 4713 __filter_div.textContent : 4714 __filter_div.innerText; 4715 } 4716 4717 if ( cellData.replace ) { 4718 cellData = cellData.replace(/[\r\n\u2028]/g, ''); 4719 } 4720 4721 filterData.push( cellData ); 4722 } 4723 4724 row._aFilterData = filterData; 4725 row._sFilterRow = filterData.join(' '); 4726 wasInvalidated = true; 4727 } 4728 } 4729 4730 return wasInvalidated; 4731 } 4732 4733 4734 /** 4735 * Draw the table for the first time, adding all required features 4736 * @param {object} settings dataTables settings object 4737 * @memberof DataTable#oApi 4738 */ 4739 function _fnInitialise ( settings ) 4740 { 4741 var i; 4742 var init = settings.oInit; 4743 var deferLoading = settings.deferLoading; 4744 var dataSrc = _fnDataSource( settings ); 4745 4746 // Ensure that the table data is fully initialised 4747 if ( ! settings.bInitialised ) { 4748 setTimeout( function(){ _fnInitialise( settings ); }, 200 ); 4749 return; 4750 } 4751 4752 // Build the header / footer for the table 4753 _fnBuildHead( settings, 'header' ); 4754 _fnBuildHead( settings, 'footer' ); 4755 4756 // Load the table's state (if needed) and then render around it and draw 4757 _fnLoadState( settings, init, function () { 4758 // Then draw the header / footer 4759 _fnDrawHead( settings, settings.aoHeader ); 4760 _fnDrawHead( settings, settings.aoFooter ); 4761 4762 // Cache the paging start point, as the first redraw will reset it 4763 var iAjaxStart = settings.iInitDisplayStart 4764 4765 // Local data load 4766 // Check if there is data passing into the constructor 4767 if ( init.aaData ) { 4768 for ( i=0 ; i<init.aaData.length ; i++ ) { 4769 _fnAddData( settings, init.aaData[ i ] ); 4770 } 4771 } 4772 else if ( deferLoading || dataSrc == 'dom' ) { 4773 // Grab the data from the page 4774 _fnAddTr( settings, $(settings.nTBody).children('tr') ); 4775 } 4776 4777 // Filter not yet applied - copy the display master 4778 settings.aiDisplay = settings.aiDisplayMaster.slice(); 4779 4780 // Enable features 4781 _fnAddOptionsHtml( settings ); 4782 _fnSortInit( settings ); 4783 4784 _colGroup( settings ); 4785 4786 /* Okay to show that something is going on now */ 4787 _fnProcessingDisplay( settings, true ); 4788 4789 _fnCallbackFire( settings, null, 'preInit', [settings], true ); 4790 4791 // If there is default sorting required - let's do it. The sort function 4792 // will do the drawing for us. Otherwise we draw the table regardless of the 4793 // Ajax source - this allows the table to look initialised for Ajax sourcing 4794 // data (show 'loading' message possibly) 4795 _fnReDraw( settings ); 4796 4797 // Server-side processing init complete is done by _fnAjaxUpdateDraw 4798 if ( dataSrc != 'ssp' || deferLoading ) { 4799 // if there is an ajax source load the data 4800 if ( dataSrc == 'ajax' ) { 4801 _fnBuildAjax( settings, {}, function(json) { 4802 var aData = _fnAjaxDataSrc( settings, json ); 4803 4804 // Got the data - add it to the table 4805 for ( i=0 ; i<aData.length ; i++ ) { 4806 _fnAddData( settings, aData[i] ); 4807 } 4808
4809 // Reset the init display for cookie saving. We've already done 4810 // a filter, and therefore cleared it before. So we need to make 4811 // it appear 'fresh' 4812 settings.iInitDisplayStart = iAjaxStart; 4813 4814 _fnReDraw( settings ); 4815 _fnProcessingDisplay( settings, false ); 4816 _fnInitComplete( settings ); 4817 }, settings ); 4818 } 4819 else { 4820 _fnInitComplete( settings ); 4821 _fnProcessingDisplay( settings, false ); 4822 } 4823 } 4824 } ); 4825 } 4826 4827 4828 /** 4829 * Draw the table for the first time, adding all required features 4830 * @param {object} settings dataTables settings object 4831 * @memberof DataTable#oApi 4832 */ 4833 function _fnInitComplete ( settings ) 4834 { 4835 if (settings._bInitComplete) { 4836 return; 4837 } 4838 4839 var args = [settings, settings.json]; 4840 4841 settings._bInitComplete = true; 4842 4843 // Table is fully set up and we have data, so calculate the 4844 // column widths 4845 _fnAdjustColumnSizing( settings ); 4846 4847 _fnCallbackFire( settings, null, 'plugin-init', args, true ); 4848 _fnCallbackFire( settings, 'aoInitComplete', 'init', args, true ); 4849 } 4850 4851 function _fnLengthChange ( settings, val ) 4852 { 4853 var len = parseInt( val, 10 ); 4854 settings._iDisplayLength = len; 4855 4856 _fnLengthOverflow( settings ); 4857 4858 // Fire length change event 4859 _fnCallbackFire( settings, null, 'length', [settings, len] ); 4860 } 4861 4862 /** 4863 * Alter the display settings to change the page 4864 * @param {object} settings DataTables settings object 4865 * @param {string|int} action Paging action to take: "first", "previous", 4866 * "next" or "last" or page number to jump to (integer) 4867 * @param [bool] redraw Automatically draw the update or not 4868 * @returns {bool} true page has changed, false - no change 4869 * @memberof DataTable#oApi 4870 */ 4871 function _fnPageChange ( settings, action, redraw ) 4872 { 4873 var 4874 start = settings._iDisplayStart, 4875 len = settings._iDisplayLength, 4876 records = settings.fnRecordsDisplay(); 4877 4878 if ( records === 0 || len === -1 ) 4879 { 4880 start = 0; 4881 } 4882 else if ( typeof action === "number" ) 4883 { 4884 start = action * len; 4885 4886 if ( start > records ) 4887 { 4888 start = 0; 4889 } 4890 } 4891 else if ( action == "first" ) 4892 { 4893 start = 0; 4894 } 4895 else if ( action == "previous" ) 4896 { 4897 start = len >= 0 ? 4898 start - len : 4899 0; 4900 4901 if ( start < 0 ) 4902 { 4903 start = 0; 4904 } 4905 } 4906 else if ( action == "next" ) 4907 { 4908 if ( start + len < records ) 4909 { 4910 start += len; 4911 } 4912 } 4913 else if ( action == "last" ) 4914 { 4915 start = Math.floor( (records-1) / len) * len; 4916 } 4917 else if ( action === 'ellipsis' ) 4918 { 4919 return; 4920 } 4921 else 4922 { 4923 _fnLog( settings, 0, "Unknown paging action: "+action, 5 ); 4924 } 4925 4926 var changed = settings._iDisplayStart !== start; 4927 settings._iDisplayStart = start; 4928 4929 _fnCallbackFire( settings, null, changed ? 'page' : 'page-nc', [settings] ); 4930 4931 if ( changed && redraw ) { 4932 _fnDraw( settings ); 4933 } 4934 4935 return changed; 4936 } 4937 4938 4939 /** 4940 * Generate the node required for the processing node 4941 * @param {object} settings DataTables settings object 4942 */ 4943 function _processingHtml ( settings ) 4944 { 4945 var table = settings.nTable; 4946 var scrolling = settings.oScroll.sX !== '' || settings.oScroll.sY !== ''; 4947 4948 if ( settings.oFeatures.bProcessing ) { 4949 var n = $('<div/>', { 4950 'id': settings.sTableId + '_processing', 4951 'class': settings.oClasses.processing.container, 4952 'role': 'status' 4953 } ) 4954 .html( settings.oLanguage.sProcessing ) 4955 .append('<div><div></div><div></div><div></div><div></div></div>'); 4956 4957 // Different positioning depending on if scrolling is enabled or not 4958 if (scrolling) { 4959 n.prependTo( $('div.dt-scroll', settings.nTableWrapper) ); 4960 } 4961 else { 4962 n.insertBefore( table ); 4963 } 4964 4965 $(table).on( 'processing.dt.DT', function (e, s, show) { 4966 n.css( 'display', show ? 'block' : 'none' ); 4967 } ); 4968 } 4969 } 4970 4971 4972 /** 4973 * Display or hide the processing indicator 4974 * @param {object} settings DataTables settings object 4975 * @param {bool} show Show the processing indicator (true) or not (false) 4976 */ 4977 function _fnProcessingDisplay ( settings, show ) 4978 { 4979 // Ignore cases when we are still redrawing 4980 if (settings.bDrawing && show === false) { 4981 return; 4982 } 4983 4984 _fnCallbackFire( settings, null, 'processing', [settings, show] ); 4985 } 4986 4987 /** 4988 * Show the processing element if an action takes longer than a given time 4989 * 4990 * @param {*} settings DataTables settings object 4991 * @param {*} enable Do (true) or not (false) async processing (local feature enablement) 4992 * @param {*} run Function to run 4993 */ 4994 function _fnProcessingRun( settings, enable, run ) { 4995 if (! enable) { 4996 // Immediate execution, synchronous 4997 run(); 4998 } 4999 else { 5000 _fnProcessingDisplay(settings, true); 5001 5002 // Allow the processing display to show if needed 5003 setTimeout(function () { 5004 run(); 5005 5006 _fnProcessingDisplay(settings, false); 5007 }, 0); 5008 } 5009 } 5010 /** 5011 * Add any control elements for the table - specifically scrolling 5012 * @param {object} settings dataTables settings object 5013 * @returns {node} Node to add to the DOM 5014 * @memberof DataTable#oApi 5015 */ 5016 function _fnFeatureHtmlTable ( settings ) 5017 { 5018 var table = $(settings.nTable); 5019 5020 // Scrolling from here on in 5021 var scroll = settings.oScroll; 5022
5023 if ( scroll.sX === '' && scroll.sY === '' ) { 5024 return settings.nTable; 5025 } 5026 5027 var scrollX = scroll.sX; 5028 var scrollY = scroll.sY; 5029 var classes = settings.oClasses.scrolling; 5030 var caption = settings.captionNode; 5031 var captionSide = caption ? caption._captionSide : null; 5032 var headerClone = $( table[0].cloneNode(false) ); 5033 var footerClone = $( table[0].cloneNode(false) ); 5034 var footer = table.children('tfoot'); 5035 var _div = '<div/>'; 5036 var size = function ( s ) { 5037 return !s ? null : _fnStringToCss( s ); 5038 }; 5039 5040 if ( ! footer.length ) { 5041 footer = null; 5042 } 5043 5044 /* 5045 * The HTML structure that we want to generate in this function is: 5046 * div - scroller 5047 * div - scroll head 5048 * div - scroll head inner 5049 * table - scroll head table 5050 * thead - thead 5051 * div - scroll body 5052 * table - table (master table) 5053 * thead - thead clone for sizing 5054 * tbody - tbody 5055 * div - scroll foot 5056 * div - scroll foot inner 5057 * table - scroll foot table 5058 * tfoot - tfoot 5059 */ 5060 var scroller = $( _div, { 'class': classes.container } ) 5061 .append( 5062 $(_div, { 'class': classes.header.self } ) 5063 .css( { 5064 overflow: 'hidden', 5065 position: 'relative', 5066 border: 0, 5067 width: scrollX ? size(scrollX) : '100%' 5068 } ) 5069 .append( 5070 $(_div, { 'class': classes.header.inner } ) 5071 .css( { 5072 'box-sizing': 'content-box', 5073 width: scroll.sXInner || '100%' 5074 } ) 5075 .append( 5076 headerClone 5077 .removeAttr('id') 5078 .css( 'margin-left', 0 ) 5079 .append( captionSide === 'top' ? caption : null ) 5080 .append( 5081 table.children('thead') 5082 ) 5083 ) 5084 ) 5085 ) 5086 .append( 5087 $(_div, { 'class': classes.body } ) 5088 .css( { 5089 position: 'relative', 5090 overflow: 'auto', 5091 width: size( scrollX ) 5092 } ) 5093 .append( table ) 5094 ); 5095 5096 if ( footer ) { 5097 scroller.append( 5098 $(_div, { 'class': classes.footer.self } ) 5099 .css( { 5100 overflow: 'hidden', 5101 border: 0, 5102 width: scrollX ? size(scrollX) : '100%' 5103 } ) 5104 .append( 5105 $(_div, { 'class': classes.footer.inner } ) 5106 .append( 5107 footerClone 5108 .removeAttr('id') 5109 .css( 'margin-left', 0 ) 5110 .append( captionSide === 'bottom' ? caption : null ) 5111 .append( 5112 table.children('tfoot') 5113 ) 5114 ) 5115 ) 5116 ); 5117 } 5118 5119 var children = scroller.children(); 5120 var scrollHead = children[0]; 5121 var scrollBody = children[1]; 5122 var scrollFoot = footer ? children[2] : null; 5123 5124 // When the body is scrolled, then we also want to scroll the headers 5125 $(scrollBody).on( 'scroll.DT', function () { 5126 var scrollLeft = this.scrollLeft; 5127 5128 scrollHead.scrollLeft = scrollLeft; 5129 5130 if ( footer ) { 5131 scrollFoot.scrollLeft = scrollLeft; 5132 } 5133 } ); 5134 5135 // When focus is put on the header cells, we might need to scroll the body 5136 $('th, td', scrollHead).on('focus', function () { 5137 var scrollLeft = scrollHead.scrollLeft; 5138 5139 scrollBody.scrollLeft = scrollLeft; 5140 5141 if ( footer ) { 5142 scrollBody.scrollLeft = scrollLeft; 5143 } 5144 }); 5145 5146 $(scrollBody).css('max-height', scrollY); 5147 if (! scroll.bCollapse) { 5148 $(scrollBody).css('height', scrollY); 5149 } 5150 5151 settings.nScrollHead = scrollHead; 5152 settings.nScrollBody = scrollBody; 5153 settings.nScrollFoot = scrollFoot; 5154 5155 // On redraw - align columns 5156 settings.aoDrawCallback.push(_fnScrollDraw); 5157 5158 return scroller[0]; 5159 } 5160 5161 5162 5163 /** 5164 * Update the header, footer and body tables for resizing - i.e. column 5165 * alignment. 5166 * 5167 * Welcome to the most horrible function DataTables. The process that this 5168 * function follows is basically: 5169 * 1. Re-create the table inside the scrolling div 5170 * 2. Correct colgroup > col values if needed 5171 * 3. Copy colgroup > col over to header and footer 5172 * 4. Clean up 5173 * 5174 * @param {object} settings dataTables settings object 5175 * @memberof DataTable#oApi 5176 */ 5177 function _fnScrollDraw ( settings ) 5178 { 5179 // Given that this is such a monster function, a lot of variables are use 5180 // to try and keep the minimised size as small as possible 5181 var 5182 scroll = settings.oScroll, 5183 barWidth = scroll.iBarWidth, 5184 divHeader = $(settings.nScrollHead), 5185 divHeaderInner = divHeader.children('div'), 5186 divHeaderTable = divHeaderInner.children('table'), 5187 divBodyEl = settings.nScrollBody,
5188 divBody = $(divBodyEl), 5189 divFooter = $(settings.nScrollFoot), 5190 divFooterInner = divFooter.children('div'), 5191 divFooterTable = divFooterInner.children('table'), 5192 header = $(settings.nTHead), 5193 table = $(settings.nTable), 5194 footer = settings.nTFoot && $('th, td', settings.nTFoot).length ? $(settings.nTFoot) : null, 5195 browser = settings.oBrowser, 5196 headerCopy, footerCopy; 5197 5198 // If the scrollbar visibility has changed from the last draw, we need to 5199 // adjust the column sizes as the table width will have changed to account 5200 // for the scrollbar 5201 var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight; 5202 5203 if ( settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined ) { 5204 settings.scrollBarVis = scrollBarVis; 5205 _fnAdjustColumnSizing( settings ); 5206 return; // adjust column sizing will call this function again 5207 } 5208 else { 5209 settings.scrollBarVis = scrollBarVis; 5210 } 5211 5212 // 1. Re-create the table inside the scrolling div 5213 // Remove the old minimised thead and tfoot elements in the inner table 5214 table.children('thead, tfoot').remove(); 5215 5216 // Clone the current header and footer elements and then place it into the inner table 5217 headerCopy = header.clone().prependTo( table ); 5218 headerCopy.find('th, td').removeAttr('tabindex'); 5219 headerCopy.find('[id]').removeAttr('id'); 5220 5221 if ( footer ) { 5222 footerCopy = footer.clone().prependTo( table ); 5223 footerCopy.find('[id]').removeAttr('id'); 5224 } 5225 5226 // 2. Correct colgroup > col values if needed 5227 // It is possible that the cell sizes are smaller than the content, so we need to 5228 // correct colgroup>col for such cases. This can happen if the auto width detection 5229 // uses a cell which has a longer string, but isn't the widest! For example 5230 // "Chief Executive Officer (CEO)" is the longest string in the demo, but 5231 // "Systems Administrator" is actually the widest string since it doesn't collapse. 5232 // Note the use of translating into a column index to get the `col` element. This 5233 // is because of Responsive which might remove `col` elements, knocking the alignment 5234 // of the indexes out. 5235 if (settings.aiDisplay.length) { 5236 // Get the column sizes from the first row in the table. This should really be a 5237 // [].find, but it wasn't supported in Chrome until Sept 2015, and DT has 10 year 5238 // browser support 5239 var firstTr = null; 5240 var start = _fnDataSource( settings ) !== 'ssp' 5241 ? settings._iDisplayStart 5242 : 0; 5243 5244 for (i=start ; i<start + settings.aiDisplay.length ; i++) { 5245 var idx = settings.aiDisplay[i]; 5246 var tr = settings.aoData[idx].nTr; 5247 5248 if (tr) { 5249 firstTr = tr; 5250 break; 5251 } 5252 } 5253 5254 if (firstTr) { 5255 var colSizes = $(firstTr).children('th, td').map(function (vis) { 5256 return { 5257 idx: _fnVisibleToColumnIndex(settings, vis), 5258 width: $(this).outerWidth() 5259 }; 5260 }); 5261 5262 // Check against what the colgroup > col is set to and correct if needed 5263 for (var i=0 ; i<colSizes.length ; i++) { 5264 var colEl = settings.aoColumns[ colSizes[i].idx ].colEl[0]; 5265 var colWidth = colEl.style.width.replace('px', ''); 5266 5267 if (colWidth !== colSizes[i].width) { 5268 colEl.style.width = colSizes[i].width + 'px'; 5269 5270 if (scroll.sX) { 5271 colEl.style.minWidth = colSizes[i].width + 'px'; 5272 } 5273 } 5274 } 5275 } 5276 } 5277 5278 // 3. Copy the colgroup over to the header and footer 5279 divHeaderTable 5280 .find('colgroup') 5281 .remove(); 5282 5283 divHeaderTable.append(settings.colgroup.clone()); 5284 5285 if ( footer ) { 5286 divFooterTable 5287 .find('colgroup') 5288 .remove(); 5289 5290 divFooterTable.append(settings.colgroup.clone()); 5291 } 5292 5293 // "Hide" the header and footer that we used for the sizing. We need to keep 5294 // the content of the cell so that the width applied to the header and body 5295 // both match, but we want to hide it completely. 5296 $('th, td', headerCopy).each(function () { 5297 $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">'); 5298 }); 5299 5300 if ( footer ) { 5301 $('th, td', footerCopy).each(function () { 5302 $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">'); 5303 }); 5304 } 5305 5306 // 4. Clean up 5307 // Figure out if there are scrollbar present - if so then we need a the header and footer to 5308 // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar) 5309 var isScrolling = Math.floor(table.height()) > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll"; 5310 var paddingSide = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' ); 5311 5312 // Set the width's of the header and footer tables 5313 var outerWidth = table.outerWidth(); 5314 5315 divHeaderTable.css('width', _fnStringToCss( outerWidth )); 5316 divHeaderInner 5317 .css('width', _fnStringToCss( outerWidth )) 5318 .css(paddingSide, isScrolling ? barWidth+"px" : "0px"); 5319 5320 if ( footer ) { 5321 divFooterTable.css('width', _fnStringToCss( outerWidth )); 5322 divFooterInner 5323 .css('width', _fnStringToCss( outerWidth )) 5324 .css(paddingSide, isScrolling ? barWidth+"px" : "0px"); 5325 } 5326 5327 // Correct DOM ordering for colgroup - comes before the thead 5328 table.children('colgroup').prependTo(table); 5329 5330 // Adjust the position of the header in case we loose the y-scrollbar 5331 divBody.trigger('scroll'); 5332 5333 // If sorting or filtering has occurred, jump the scrolling back to the top 5334 // only if we aren't holding the position 5335 if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) { 5336 divBodyEl.scrollTop = 0; 5337 } 5338 } 5339 5340 /** 5341 * Calculate the width of columns for the table 5342 * @param {object} settings dataTables settings object 5343 * @memberof DataTable#oApi 5344 */ 5345 function _fnCalculateColumnWidths ( settings ) 5346 { 5347 // Not interested in doing column width calculation if auto-width is disabled 5348 if (! settings.oFeatures.bAutoWidth) { 5349 return; 5350 } 5351 5352 var 5353 table = settings.nTable, 5354 columns = settings.aoColumns, 5355 scroll = settings.oScroll, 5356 scrollY = scroll.sY, 5357 scrollX = scroll.sX, 5358 scrollXInner = scroll.sXInner, 5359 visibleColumns = _fnGetColumns( settings, 'bVisible' ), 5360 tableWidthAttr = table.getAttribute('width'), // from DOM element 5361 tableContainer = table.parentNode, 5362 i, column, columnIdx; 5363 5364 var styleWidth = table.style.width; 5365 var containerWidth = _fnWrapperWidth(settings); 5366 5367 // Don't re-run for the same width as the last time 5368 if (containerWidth === settings.containerWidth) { 5369 return false;
5370 } 5371 5372 settings.containerWidth = containerWidth; 5373 5374 // If there is no width applied as a CSS style or as an attribute, we assume that 5375 // the width is intended to be 100%, which is usually is in CSS, but it is very 5376 // difficult to correctly parse the rules to get the final result. 5377 if ( ! styleWidth && ! tableWidthAttr) { 5378 table.style.width = '100%'; 5379 styleWidth = '100%'; 5380 } 5381 5382 if ( styleWidth && styleWidth.indexOf('%') !== -1 ) { 5383 tableWidthAttr = styleWidth; 5384 } 5385 5386 // Let plug-ins know that we are doing a recalc, in case they have changed any of the 5387 // visible columns their own way (e.g. Responsive uses display:none). 5388 _fnCallbackFire( 5389 settings, 5390 null, 5391 'column-calc', 5392 {visible: visibleColumns}, 5393 false 5394 ); 5395 5396 // Construct a single row, worst case, table with the widest 5397 // node in the data, assign any user defined widths, then insert it into 5398 // the DOM and allow the browser to do all the hard work of calculating 5399 // table widths 5400 var tmpTable = $(table.cloneNode()) 5401 .css( 'visibility', 'hidden' ) 5402 .removeAttr( 'id' ); 5403 5404 // Clean up the table body 5405 tmpTable.append('<tbody>') 5406 var tr = $('<tr/>').appendTo( tmpTable.find('tbody') ); 5407 5408 // Clone the table header and footer - we can't use the header / footer 5409 // from the cloned table, since if scrolling is active, the table's 5410 // real header and footer are contained in different table tags 5411 tmpTable 5412 .append( $(settings.nTHead).clone() ) 5413 .append( $(settings.nTFoot).clone() ); 5414 5415 // Remove any assigned widths from the footer (from scrolling) 5416 tmpTable.find('tfoot th, tfoot td').css('width', ''); 5417 5418 // Apply custom sizing to the cloned header 5419 tmpTable.find('thead th, thead td').each( function () { 5420 // Get the `width` from the header layout 5421 var width = _fnColumnsSumWidth( settings, this, true, false ); 5422 5423 if ( width ) { 5424 this.style.width = width; 5425 5426 // For scrollX we need to force the column width otherwise the 5427 // browser will collapse it. If this width is smaller than the 5428 // width the column requires, then it will have no effect 5429 if ( scrollX ) { 5430 this.style.minWidth = width; 5431 5432 $( this ).append( $('<div/>').css( { 5433 width: width, 5434 margin: 0, 5435 padding: 0, 5436 border: 0, 5437 height: 1 5438 } ) ); 5439 } 5440 } 5441 else { 5442 this.style.width = ''; 5443 } 5444 } ); 5445 5446 // Find the widest piece of data for each column and put it into the table 5447 for ( i=0 ; i<visibleColumns.length ; i++ ) { 5448 columnIdx = visibleColumns[i]; 5449 column = columns[ columnIdx ]; 5450 5451 var longest = _fnGetMaxLenString(settings, columnIdx); 5452 var autoClass = _ext.type.className[column.sType]; 5453 var text = longest + column.sContentPadding; 5454 var insert = longest.indexOf('<') === -1 5455 ? document.createTextNode(text) 5456 : text 5457 5458 $('<td/>') 5459 .addClass(autoClass) 5460 .addClass(column.sClass) 5461 .append(insert) 5462 .appendTo(tr); 5463 } 5464 5465 // Tidy the temporary table - remove name attributes so there aren't 5466 // duplicated in the dom (radio elements for example) 5467 $('[name]', tmpTable).removeAttr('name'); 5468 5469 // Table has been built, attach to the document so we can work with it. 5470 // A holding element is used, positioned at the top of the container 5471 // with minimal height, so it has no effect on if the container scrolls 5472 // or not. Otherwise it might trigger scrolling when it actually isn't 5473 // needed 5474 var holder = $('<div/>').css( scrollX || scrollY ? 5475 { 5476 position: 'absolute', 5477 top: 0, 5478 left: 0, 5479 height: 1, 5480 right: 0, 5481 overflow: 'hidden' 5482 } : 5483 {} 5484 ) 5485 .append( tmpTable ) 5486 .appendTo( tableContainer ); 5487 5488 // When scrolling (X or Y) we want to set the width of the table as 5489 // appropriate. However, when not scrolling leave the table width as it 5490 // is. This results in slightly different, but I think correct behaviour 5491 if ( scrollX && scrollXInner ) { 5492 tmpTable.width( scrollXInner ); 5493 } 5494 else if ( scrollX ) { 5495 tmpTable.css( 'width', 'auto' ); 5496 tmpTable.removeAttr('width'); 5497 5498 // If there is no width attribute or style, then allow the table to 5499 // collapse 5500 if ( tmpTable.outerWidth() < tableContainer.clientWidth && tableWidthAttr ) { 5501 tmpTable.outerWidth( tableContainer.clientWidth ); 5502 } 5503 } 5504 else if ( scrollY ) { 5505 tmpTable.outerWidth( tableContainer.clientWidth ); 5506 } 5507 else if ( tableWidthAttr ) { 5508 tmpTable.outerWidth( tableWidthAttr ); 5509 } 5510 5511 // Get the width of each column in the constructed table 5512 var total = 0; 5513 var bodyCells = tmpTable.find('tbody tr').eq(0).children(); 5514 5515 for ( i=0 ; i<visibleColumns.length ; i++ ) { 5516 // Use getBounding for sub-pixel accuracy, which we then want to round up! 5517 var bounding = bodyCells[i].getBoundingClientRect().width; 5518 5519 // Total is tracked to remove any sub-pixel errors as the outerWidth 5520 // of the table might not equal the total given here 5521 total += bounding; 5522 5523 // Width for each column to use 5524 columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding ); 5525 } 5526 5527 table.style.width = _fnStringToCss( total ); 5528
5529 // Finished with the table - ditch it 5530 holder.remove(); 5531 5532 // If there is a width attr, we want to attach an event listener which 5533 // allows the table sizing to automatically adjust when the window is 5534 // resized. Use the width attr rather than CSS, since we can't know if the 5535 // CSS is a relative value or absolute - DOM read is always px. 5536 if ( tableWidthAttr ) { 5537 table.style.width = _fnStringToCss( tableWidthAttr ); 5538 } 5539 5540 if ( (tableWidthAttr || scrollX) && ! settings._reszEvt ) { 5541 var resize = DataTable.util.throttle( function () { 5542 var newWidth = _fnWrapperWidth(settings); 5543 5544 // Don't do it if destroying or the container width is 0 5545 if (! settings.bDestroying && newWidth !== 0) { 5546 _fnAdjustColumnSizing( settings ); 5547 } 5548 } ); 5549 5550 // For browsers that support it (~2020 onwards for wide support) we can watch for the 5551 // container changing width. 5552 if (window.ResizeObserver) { 5553 // This is a tricky beast - if the element is visible when `.observe()` is called, 5554 // then the callback is immediately run. Which we don't want. If the element isn't 5555 // visible, then it isn't run, but we want it to run when it is then made visible. 5556 // This flag allows the above to be satisfied. 5557 var first = $(settings.nTableWrapper).is(':visible'); 5558 5559 // Use an empty div to attach the observer so it isn't impacted by height changes 5560 var resizer = $('<div>') 5561 .css({ 5562 width: '100%', 5563 height: 0 5564 }) 5565 .addClass('dt-autosize') 5566 .appendTo(settings.nTableWrapper); 5567 5568 settings.resizeObserver = new ResizeObserver(function (e) { 5569 if (first) { 5570 first = false; 5571 } 5572 else { 5573 resize(); 5574 } 5575 }); 5576 5577 settings.resizeObserver.observe(resizer[0]); 5578 } 5579 else { 5580 // For old browsers, the best we can do is listen for a window resize 5581 $(window).on('resize.DT-'+settings.sInstance, resize); 5582 } 5583 5584 settings._reszEvt = true; 5585 } 5586 } 5587 5588 /** 5589 * Get the width of the DataTables wrapper element 5590 * 5591 * @param {*} settings DataTables settings object 5592 * @returns Width 5593 */ 5594 function _fnWrapperWidth(settings) { 5595 return $(settings.nTableWrapper).is(':visible') 5596 ? $(settings.nTableWrapper).width() 5597 : 0; 5598 } 5599 5600 /** 5601 * Get the maximum strlen for each data column 5602 * @param {object} settings dataTables settings object 5603 * @param {int} colIdx column of interest 5604 * @returns {string} string of the max length 5605 * @memberof DataTable#oApi 5606 */ 5607 function _fnGetMaxLenString( settings, colIdx ) 5608 { 5609 var column = settings.aoColumns[colIdx]; 5610 5611 if (! column.maxLenString) { 5612 var s, max='', maxLen = -1; 5613 5614 for ( var i=0, ien=settings.aiDisplayMaster.length ; i<ien ; i++ ) { 5615 var rowIdx = settings.aiDisplayMaster[i]; 5616 var data = _fnGetRowDisplay(settings, rowIdx)[colIdx]; 5617 5618 var cellString = data && typeof data === 'object' && data.nodeType 5619 ? data.innerHTML 5620 : data+''; 5621 5622 // Remove id / name attributes from elements so they 5623 // don't interfere with existing elements 5624 cellString = cellString 5625 .replace(/id=".*?"/g, '') 5626 .replace(/name=".*?"/g, ''); 5627 5628 s = _stripHtml(cellString) 5629 .replace( / /g, ' ' ); 5630 5631 if ( s.length > maxLen ) { 5632 // We want the HTML in the string, but the length that 5633 // is important is the stripped string 5634 max = cellString; 5635 maxLen = s.length; 5636 } 5637 } 5638 5639 column.maxLenString = max; 5640 } 5641 5642 return column.maxLenString; 5643 } 5644 5645 5646 /** 5647 * Append a CSS unit (only if required) to a string 5648 * @param {string} value to css-ify 5649 * @returns {string} value with css unit 5650 * @memberof DataTable#oApi 5651 */ 5652 function _fnStringToCss( s ) 5653 { 5654 if ( s === null ) { 5655 return '0px'; 5656 } 5657 5658 if ( typeof s == 'number' ) { 5659 return s < 0 ? 5660 '0px' : 5661 s+'px'; 5662 } 5663 5664 // Check it has a unit character already 5665 return s.match(/\d$/) ? 5666 s+'px' : 5667 s; 5668 } 5669 5670 /** 5671 * Re-insert the `col` elements for current visibility 5672 * 5673 * @param {*} settings DT settings 5674 */ 5675 function _colGroup( settings ) { 5676 var cols = settings.aoColumns; 5677 5678 settings.colgroup.empty(); 5679 5680 for (i=0 ; i<cols.length ; i++) { 5681 if (cols[i].bVisible) { 5682 settings.colgroup.append(cols[i].colEl); 5683 } 5684 } 5685 } 5686 5687 5688 function _fnSortInit( settings ) { 5689 var target = settings.nTHead; 5690 var headerRows = target.querySelectorAll('tr'); 5691 var legacyTop = settings.bSortCellsTop; 5692 var notSelector = ':not([data-dt-order="disable"]):not([data-dt-order="icon-only"])'; 5693 5694 // Legacy support for `orderCellsTop` 5695 if (legacyTop === true) { 5696 target = headerRows[0]; 5697 } 5698 else if (legacyTop === false) { 5699 target = headerRows[ headerRows.length - 1 ]; 5700 } 5701 5702 _fnSortAttachListener( 5703 settings, 5704 target, 5705 target === settings.nTHead 5706 ? 'tr'+notSelector+' th'+notSelector+', tr'+notSelector+' td'+notSelector 5707 : 'th'+notSelector+', td'+notSelector 5708 ); 5709 5710 // Need to resolve the user input array into our internal structure 5711 var order = []; 5712 _fnSortResolve( settings, order, settings.aaSorting ); 5713 5714 settings.aaSorting = order; 5715 } 5716 5717 5718 function _fnSortAttachListener(settings, node, selector, column, callback) { 5719 _fnBindAction( node, selector, function (e) { 5720 var run = false;
5721 var columns = column === undefined 5722 ? _fnColumnsFromHeader( e.target ) 5723 : [column]; 5724 5725 if ( columns.length ) { 5726 for ( var i=0, ien=columns.length ; i<ien ; i++ ) { 5727 var ret = _fnSortAdd( settings, columns[i], i, e.shiftKey ); 5728 5729 if (ret !== false) { 5730 run = true; 5731 } 5732 5733 // If the first entry is no sort, then subsequent 5734 // sort columns are ignored 5735 if (settings.aaSorting.length === 1 && settings.aaSorting[0][1] === '') { 5736 break; 5737 } 5738 } 5739 5740 if (run) { 5741 _fnProcessingRun(settings, true, function () { 5742 _fnSort( settings ); 5743 _fnSortDisplay( settings, settings.aiDisplay ); 5744 5745 _fnReDraw( settings, false, false ); 5746 5747 if (callback) { 5748 callback(); 5749 } 5750 }); 5751 } 5752 } 5753 } ); 5754 } 5755 5756 /** 5757 * Sort the display array to match the master's order 5758 * @param {*} settings 5759 */ 5760 function _fnSortDisplay(settings, display) { 5761 if (display.length < 2) { 5762 return; 5763 } 5764 5765 var master = settings.aiDisplayMaster; 5766 var masterMap = {}; 5767 var map = {}; 5768 var i; 5769 5770 // Rather than needing an `indexOf` on master array, we can create a map 5771 for (i=0 ; i<master.length ; i++) { 5772 masterMap[master[i]] = i; 5773 } 5774 5775 // And then cache what would be the indexOf fom the display 5776 for (i=0 ; i<display.length ; i++) { 5777 map[display[i]] = masterMap[display[i]]; 5778 } 5779 5780 display.sort(function(a, b){ 5781 // Short version of this function is simply `master.indexOf(a) - master.indexOf(b);` 5782 return map[a] - map[b]; 5783 }); 5784 } 5785 5786 5787 function _fnSortResolve (settings, nestedSort, sort) { 5788 var push = function ( a ) { 5789 if ($.isPlainObject(a)) { 5790 if (a.idx !== undefined) { 5791 // Index based ordering 5792 nestedSort.push([a.idx, a.dir]); 5793 } 5794 else if (a.name) { 5795 // Name based ordering 5796 var cols = _pluck( settings.aoColumns, 'sName'); 5797 var idx = cols.indexOf(a.name); 5798 5799 if (idx !== -1) { 5800 nestedSort.push([idx, a.dir]); 5801 } 5802 } 5803 } 5804 else { 5805 // Plain column index and direction pair 5806 nestedSort.push(a); 5807 } 5808 }; 5809 5810 if ( $.isPlainObject(sort) ) { 5811 // Object 5812 push(sort); 5813 } 5814 else if ( sort.length && typeof sort[0] === 'number' ) { 5815 // 1D array 5816 push(sort); 5817 } 5818 else if ( sort.length ) { 5819 // 2D array 5820 for (var z=0; z<sort.length; z++) { 5821 push(sort[z]); // Object or array 5822 } 5823 } 5824 } 5825 5826 5827 function _fnSortFlatten ( settings ) 5828 { 5829 var 5830 i, k, kLen, 5831 aSort = [], 5832 extSort = DataTable.ext.type.order, 5833 aoColumns = settings.aoColumns, 5834 aDataSort, iCol, sType, srcCol, 5835 fixed = settings.aaSortingFixed, 5836 fixedObj = $.isPlainObject( fixed ), 5837 nestedSort = []; 5838 5839 if ( ! settings.oFeatures.bSort ) { 5840 return aSort; 5841 } 5842 5843 // Build the sort array, with pre-fix and post-fix options if they have been 5844 // specified 5845 if ( Array.isArray( fixed ) ) { 5846 _fnSortResolve( settings, nestedSort, fixed ); 5847 } 5848 5849 if ( fixedObj && fixed.pre ) { 5850 _fnSortResolve( settings, nestedSort, fixed.pre ); 5851 } 5852 5853 _fnSortResolve( settings, nestedSort, settings.aaSorting ); 5854 5855 if (fixedObj && fixed.post ) { 5856 _fnSortResolve( settings, nestedSort, fixed.post ); 5857 } 5858 5859 for ( i=0 ; i<nestedSort.length ; i++ ) 5860 { 5861 srcCol = nestedSort[i][0]; 5862 5863 if ( aoColumns[ srcCol ] ) { 5864 aDataSort = aoColumns[ srcCol ].aDataSort; 5865 5866 for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ ) 5867 { 5868 iCol = aDataSort[k]; 5869 sType = aoColumns[ iCol ].sType || 'string'; 5870 5871 if ( nestedSort[i]._idx === undefined ) { 5872 nestedSort[i]._idx = aoColumns[iCol].asSorting.indexOf(nestedSort[i][1]); 5873 } 5874 5875 if ( nestedSort[i][1] ) { 5876 aSort.push( { 5877 src: srcCol, 5878 col: iCol, 5879 dir: nestedSort[i][1], 5880 index: nestedSort[i]._idx, 5881 type: sType, 5882 formatter: extSort[ sType+"-pre" ], 5883 sorter: extSort[ sType+"-"+nestedSort[i][1] ] 5884 } ); 5885 } 5886 } 5887 } 5888 } 5889 5890 return aSort; 5891 } 5892 5893 /** 5894 * Change the order of the table 5895 * @param {object} oSettings dataTables settings object 5896 * @memberof DataTable#oApi 5897 */ 5898 function _fnSort ( oSettings, col, dir ) 5899 { 5900 var 5901 i, ien, iLen, 5902 aiOrig = [], 5903 extSort = DataTable.ext.type.order, 5904 aoData = oSettings.aoData, 5905 sortCol, 5906 displayMaster = oSettings.aiDisplayMaster, 5907 aSort; 5908 5909 // Make sure the columns all have types defined 5910 _fnColumnTypes(oSettings); 5911 5912 // Allow a specific column to be sorted, which will _not_ alter the display 5913 // master 5914 if (col !== undefined) { 5915 var srcCol = oSettings.aoColumns[col]; 5916 5917 aSort = [{ 5918 src: col, 5919 col: col, 5920 dir: dir, 5921 index: 0, 5922 type: srcCol.sType, 5923 formatter: extSort[ srcCol.sType+"-pre" ], 5924 sorter: extSort[ srcCol.sType+"-"+dir ] 5925 }]; 5926 displayMaster = displayMaster.slice(); 5927 } 5928 else { 5929 aSort = _fnSortFlatten( oSettings ); 5930 } 5931 5932 for ( i=0, ien=aSort.length ; i<ien ; i++ ) { 5933 sortCol = aSort[i]; 5934 5935 // Load the data needed for the sort, for each cell 5936 _fnSortData( oSettings, sortCol.col ); 5937 } 5938 5939 /* No sorting required if server-side or no sorting array */ 5940 if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 ) 5941 { 5942 // Reset the initial positions on each pass so we get a stable sort 5943 for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) { 5944 aiOrig[ i ] = i; 5945 } 5946 5947 // If the first sort is desc, then reverse the array to preserve original 5948 // order, just in reverse 5949 if (aSort.length && aSort[0].dir === 'desc' && oSettings.orderDescReverse) { 5950 aiOrig.reverse(); 5951 } 5952
5953 /* Do the sort - here we want multi-column sorting based on a given data source (column) 5954 * and sorting function (from oSort) in a certain direction. It's reasonably complex to 5955 * follow on it's own, but this is what we want (example two column sorting): 5956 * fnLocalSorting = function(a,b){ 5957 * var test; 5958 * test = oSort['string-asc']('data11', 'data12'); 5959 * if (test !== 0) 5960 * return test; 5961 * test = oSort['numeric-desc']('data21', 'data22'); 5962 * if (test !== 0) 5963 * return test; 5964 * return oSort['numeric-asc']( aiOrig[a], aiOrig[b] ); 5965 * } 5966 * Basically we have a test for each sorting column, if the data in that column is equal, 5967 * test the next column. If all columns match, then we use a numeric sort on the row 5968 * positions in the original data array to provide a stable sort. 5969 */ 5970 displayMaster.sort( function ( a, b ) { 5971 var 5972 x, y, k, test, sort, 5973 len=aSort.length, 5974 dataA = aoData[a]._aSortData, 5975 dataB = aoData[b]._aSortData; 5976 5977 for ( k=0 ; k<len ; k++ ) { 5978 sort = aSort[k]; 5979 5980 // Data, which may have already been through a `-pre` function 5981 x = dataA[ sort.col ]; 5982 y = dataB[ sort.col ]; 5983 5984 if (sort.sorter) { 5985 // If there is a custom sorter (`-asc` or `-desc`) for this 5986 // data type, use it 5987 test = sort.sorter(x, y); 5988 5989 if ( test !== 0 ) { 5990 return test; 5991 } 5992 } 5993 else { 5994 // Otherwise, use generic sorting 5995 test = x<y ? -1 : x>y ? 1 : 0; 5996 5997 if ( test !== 0 ) { 5998 return sort.dir === 'asc' ? test : -test; 5999 } 6000 } 6001 } 6002 6003 x = aiOrig[a]; 6004 y = aiOrig[b]; 6005 6006 return x<y ? -1 : x>y ? 1 : 0; 6007 } ); 6008 } 6009 else if ( aSort.length === 0 ) { 6010 // Apply index order 6011 displayMaster.sort(function (x, y) { 6012 return x<y ? -1 : x>y ? 1 : 0; 6013 }); 6014 } 6015 6016 if (col === undefined) { 6017 // Tell the draw function that we have sorted the data 6018 oSettings.bSorted = true; 6019 oSettings.sortDetails = aSort; 6020 6021 _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort] ); 6022 } 6023
6024 return displayMaster; 6025 } 6026 6027 6028 /** 6029 * Function to run on user sort request 6030 * @param {object} settings dataTables settings object 6031 * @param {node} attachTo node to attach the handler to 6032 * @param {int} colIdx column sorting index 6033 * @param {int} addIndex Counter 6034 * @param {boolean} [shift=false] Shift click add 6035 * @param {function} [callback] callback function 6036 * @memberof DataTable#oApi 6037 */ 6038 function _fnSortAdd ( settings, colIdx, addIndex, shift ) 6039 { 6040 var col = settings.aoColumns[ colIdx ]; 6041 var sorting = settings.aaSorting; 6042 var asSorting = col.asSorting; 6043 var nextSortIdx; 6044 var next = function ( a, overflow ) { 6045 var idx = a._idx; 6046 if ( idx === undefined ) { 6047 idx = asSorting.indexOf(a[1]); 6048 } 6049 6050 return idx+1 < asSorting.length ? 6051 idx+1 : 6052 overflow ? 6053 null : 6054 0; 6055 }; 6056 6057 if ( ! col.bSortable ) { 6058 return false; 6059 } 6060 6061 // Convert to 2D array if needed 6062 if ( typeof sorting[0] === 'number' ) { 6063 sorting = settings.aaSorting = [ sorting ]; 6064 } 6065 6066 // If appending the sort then we are multi-column sorting 6067 if ( (shift || addIndex) && settings.oFeatures.bSortMulti ) { 6068 // Are we already doing some kind of sort on this column? 6069 var sortIdx = _pluck(sorting, '0').indexOf(colIdx); 6070 6071 if ( sortIdx !== -1 ) { 6072 // Yes, modify the sort 6073 nextSortIdx = next( sorting[sortIdx], true ); 6074 6075 if ( nextSortIdx === null && sorting.length === 1 ) { 6076 nextSortIdx = 0; // can't remove sorting completely 6077 } 6078 6079 if ( nextSortIdx === null ) { 6080 sorting.splice( sortIdx, 1 ); 6081 } 6082 else { 6083 sorting[sortIdx][1] = asSorting[ nextSortIdx ]; 6084 sorting[sortIdx]._idx = nextSortIdx; 6085 } 6086 } 6087 else if (shift) { 6088 // No sort on this column yet, being added by shift click 6089 // add it as itself 6090 sorting.push( [ colIdx, asSorting[0], 0 ] ); 6091 sorting[sorting.length-1]._idx = 0; 6092 } 6093 else { 6094 // No sort on this column yet, being added from a colspan 6095 // so add with same direction as first column 6096 sorting.push( [ colIdx, sorting[0][1], 0 ] ); 6097 sorting[sorting.length-1]._idx = 0; 6098 } 6099 } 6100 else if ( sorting.length && sorting[0][0] == colIdx ) { 6101 // Single column - already sorting on this column, modify the sort 6102 nextSortIdx = next( sorting[0] ); 6103 6104 sorting.length = 1; 6105 sorting[0][1] = asSorting[ nextSortIdx ]; 6106 sorting[0]._idx = nextSortIdx; 6107 } 6108 else { 6109 // Single column - sort only on this column 6110 sorting.length = 0; 6111 sorting.push( [ colIdx, asSorting[0] ] ); 6112 sorting[0]._idx = 0; 6113 } 6114 } 6115 6116 6117 /** 6118 * Set the sorting classes on table's body, Note: it is safe to call this function 6119 * when bSort and bSortClasses are false 6120 * @param {object} oSettings dataTables settings object 6121 * @memberof DataTable#oApi 6122 */ 6123 function _fnSortingClasses( settings ) 6124 { 6125 var oldSort = settings.aLastSort; 6126 var sortClass = settings.oClasses.order.position; 6127 var sort = _fnSortFlatten( settings ); 6128 var features = settings.oFeatures; 6129 var i, ien, colIdx; 6130 6131 if ( features.bSort && features.bSortClasses ) { 6132 // Remove old sorting classes 6133 for ( i=0, ien=oldSort.length ; i<ien ; i++ ) { 6134 colIdx = oldSort[i].src; 6135 6136 // Remove column sorting 6137 $( _pluck( settings.aoData, 'anCells', colIdx ) ) 6138 .removeClass( sortClass + (i<2 ? i+1 : 3) ); 6139 } 6140 6141 // Add new column sorting 6142 for ( i=0, ien=sort.length ; i<ien ; i++ ) { 6143 colIdx = sort[i].src; 6144 6145 $( _pluck( settings.aoData, 'anCells', colIdx ) ) 6146 .addClass( sortClass + (i<2 ? i+1 : 3) ); 6147 } 6148 } 6149 6150 settings.aLastSort = sort; 6151 } 6152 6153 6154 // Get the data to sort a column, be it from cache, fresh (populating the 6155 // cache), or from a sort formatter 6156 function _fnSortData( settings, colIdx ) 6157 { 6158 // Custom sorting function - provided by the sort data type 6159 var column = settings.aoColumns[ colIdx ]; 6160 var customSort = DataTable.ext.order[ column.sSortDataType ]; 6161 var customData; 6162 6163 if ( customSort ) { 6164 customData = customSort.call( settings.oInstance, settings, colIdx, 6165 _fnColumnIndexToVisible( settings, colIdx ) 6166 ); 6167 } 6168 6169 // Use / populate cache 6170 var row, cellData; 6171 var formatter = DataTable.ext.type.order[ column.sType+"-pre" ]; 6172 var data = settings.aoData; 6173 6174 for ( var rowIdx=0 ; rowIdx<data.length ; rowIdx++ ) { 6175 // Sparse array 6176 if (! data[rowIdx]) { 6177 continue; 6178 } 6179 6180 row = data[rowIdx]; 6181 6182 if ( ! row._aSortData ) { 6183 row._aSortData = []; 6184 } 6185 6186 if ( ! row._aSortData[colIdx] || customSort ) { 6187 cellData = customSort ? 6188 customData[rowIdx] : // If there was a custom sort function, use data from there 6189 _fnGetCellData( settings, rowIdx, colIdx, 'sort' ); 6190 6191 row._aSortData[ colIdx ] = formatter ? 6192 formatter( cellData, settings ) : 6193 cellData; 6194 } 6195 } 6196 } 6197 6198 6199 /** 6200 * State information for a table 6201 * 6202 * @param {*} settings 6203 * @returns State object 6204 */ 6205 function _fnSaveState ( settings ) 6206 { 6207 if (settings._bLoadingState) { 6208 return; 6209 } 6210 6211 // Sort state saving uses [[idx, order]] structure. 6212 var sorting = []; 6213 _fnSortResolve(settings, sorting, settings.aaSorting ); 6214 6215 /* Store the interesting variables */ 6216 var columns = settings.aoColumns; 6217 var state = { 6218 time: +new Date(), 6219 start: settings._iDisplayStart, 6220 length: settings._iDisplayLength, 6221 order: sorting.map(function (sort) { 6222 // If a column name is available, use it 6223 return columns[sort[0]] && columns[sort[0]].sName 6224 ? [ columns[sort[0]].sName, sort[1] ] 6225 : sort.slice(); 6226 } ), 6227 search: $.extend({}, settings.oPreviousSearch), 6228 columns: settings.aoColumns.map( function ( col, i ) { 6229 return { 6230 name: col.sName, 6231 visible: col.bVisible, 6232 search: $.extend({}, settings.aoPreSearchCols[i]) 6233 }; 6234 } ) 6235 }; 6236 6237 settings.oSavedState = state; 6238 _fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] ); 6239
6240 if ( settings.oFeatures.bStateSave && !settings.bDestroying ) 6241 { 6242 settings.fnStateSaveCallback.call( settings.oInstance, settings, state ); 6243 } 6244 } 6245 6246 6247 /** 6248 * Attempt to load a saved table state 6249 * @param {object} oSettings dataTables settings object 6250 * @param {object} oInit DataTables init object so we can override settings 6251 * @param {function} callback Callback to execute when the state has been loaded 6252 * @memberof DataTable#oApi 6253 */ 6254 function _fnLoadState ( settings, init, callback ) 6255 { 6256 if ( ! settings.oFeatures.bStateSave ) { 6257 callback(); 6258 return; 6259 } 6260 6261 var loaded = function(state) { 6262 _fnImplementState(settings, state, callback); 6263 } 6264 6265 var state = settings.fnStateLoadCallback.call( settings.oInstance, settings, loaded ); 6266 6267 if ( state !== undefined ) { 6268 _fnImplementState( settings, state, callback ); 6269 } 6270 // otherwise, wait for the loaded callback to be executed 6271 6272 return true; 6273 } 6274 6275 function _fnImplementState ( settings, s, callback) { 6276 var i, ien; 6277 var columns = settings.aoColumns; 6278 var currentNames = _pluck(settings.aoColumns, 'sName'); 6279 6280 settings._bLoadingState = true; 6281 6282 // When StateRestore was introduced the state could now be implemented at any time 6283 // Not just initialisation. To do this an api instance is required in some places 6284 var api = settings._bInitComplete ? new DataTable.Api(settings) : null; 6285 6286 if ( ! s || ! s.time ) { 6287 settings._bLoadingState = false; 6288 callback(); 6289 return; 6290 } 6291 6292 // Reject old data 6293 var duration = settings.iStateDuration; 6294 if ( duration > 0 && s.time < +new Date() - (duration*1000) ) { 6295 settings._bLoadingState = false; 6296 callback(); 6297 return; 6298 } 6299 6300 // Allow custom and plug-in manipulation functions to alter the saved data set and 6301 // cancelling of loading by returning false 6302 var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, s] ); 6303 if ( abStateLoad.indexOf(false) !== -1 ) { 6304 settings._bLoadingState = false; 6305 callback(); 6306 return; 6307 } 6308 6309 // Store the saved state so it might be accessed at any time 6310 settings.oLoadedState = $.extend( true, {}, s ); 6311 6312 // This is needed for ColReorder, which has to happen first to allow all 6313 // the stored indexes to be usable. It is not publicly documented. 6314 _fnCallbackFire( settings, null, 'stateLoadInit', [settings, s], true ); 6315 6316 // Page Length 6317 if ( s.length !== undefined ) { 6318 // If already initialised just set the value directly so that the select element is also updated 6319 if (api) { 6320 api.page.len(s.length) 6321 } 6322 else { 6323 settings._iDisplayLength = s.length; 6324 } 6325 } 6326 6327 // Restore key features 6328 if ( s.start !== undefined ) { 6329 if(api === null) { 6330 settings._iDisplayStart = s.start; 6331 settings.iInitDisplayStart = s.start; 6332 } 6333 else { 6334 _fnPageChange(settings, s.start/settings._iDisplayLength); 6335 } 6336 } 6337 6338 // Order 6339 if ( s.order !== undefined ) { 6340 settings.aaSorting = []; 6341 $.each( s.order, function ( i, col ) { 6342 var set = [ col[0], col[1] ]; 6343 6344 // A column name was stored and should be used for restore 6345 if (typeof col[0] === 'string') { 6346 var idx = currentNames.indexOf(col[0]); 6347 6348 // Find the name from the current list of column names, or fallback to index 0 6349 set[0] = idx >= 0 6350 ? idx 6351 : 0; 6352 } 6353 else if (set[0] >= columns.length) { 6354 // If a column name, but it is out of bounds, set to 0 6355 set[0] = 0; 6356 } 6357 6358 settings.aaSorting.push(set); 6359 } ); 6360 } 6361 6362 // Search 6363 if ( s.search !== undefined ) { 6364 $.extend( settings.oPreviousSearch, s.search ); 6365 } 6366 6367 // Columns 6368 if ( s.columns ) { 6369 var set = s.columns; 6370 var incoming = _pluck(s.columns, 'name'); 6371 6372 // Check if it is a 2.2 style state object with a `name` property for the columns, and if 6373 // the name was defined. If so, then create a new array that will map the state object 6374 // given, to the current columns (don't bother if they are already matching tho). 6375 if (incoming.join('').length && incoming.join('') !== currentNames.join('')) { 6376 set = []; 6377 6378 // For each column, try to find the name in the incoming array 6379 for (i=0 ; i<currentNames.length ; i++) { 6380 if (currentNames[i] != '') { 6381 var idx = incoming.indexOf(currentNames[i]); 6382 6383 if (idx >= 0) { 6384 set.push(s.columns[idx]); 6385 } 6386 else { 6387 // No matching column name in the state's columns, so this might be a new 6388 // column and thus can't have a state already. 6389 set.push({}); 6390 } 6391 } 6392 else { 6393 // If no name, but other columns did have a name, then there is no knowing
6394 // where this one came from originally so it can't be restored. 6395 set.push({}); 6396 } 6397 } 6398 } 6399 6400 // If the number of columns to restore is different from current, then all bets are off. 6401 if (set.length === columns.length) { 6402 for ( i=0, ien=set.length ; i<ien ; i++ ) { 6403 var col = set[i]; 6404 6405 // Visibility 6406 if ( col.visible !== undefined ) { 6407 // If the api is defined, the table has been initialised so we need to use it rather than internal settings 6408 if (api) { 6409 // Don't redraw the columns on every iteration of this loop, we will do this at the end instead 6410 api.column(i).visible(col.visible, false); 6411 } 6412 else { 6413 columns[i].bVisible = col.visible; 6414 } 6415 } 6416 6417 // Search 6418 if ( col.search !== undefined ) { 6419 $.extend( settings.aoPreSearchCols[i], col.search ); 6420 } 6421 } 6422 6423 // If the api is defined then we need to adjust the columns once the visibility has been changed 6424 if (api) { 6425 api.columns.adjust(); 6426 } 6427 } 6428 } 6429 6430 settings._bLoadingState = false; 6431 _fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, s] ); 6432 callback(); 6433 } 6434 6435 /** 6436 * Log an error message 6437 * @param {object} settings dataTables settings object 6438 * @param {int} level log error messages, or display them to the user 6439 * @param {string} msg error message 6440 * @param {int} tn Technical note id to get more information about the error. 6441 * @memberof DataTable#oApi 6442 */ 6443 function _fnLog( settings, level, msg, tn ) 6444 { 6445 msg = 'DataTables warning: '+ 6446 (settings ? 'table id='+settings.sTableId+' - ' : '')+msg; 6447 6448 if ( tn ) { 6449 msg += '. For more information about this error, please see '+ 6450 'https://datatables.net/tn/'+tn; 6451 } 6452 6453 if ( ! level ) { 6454 // Backwards compatibility pre 1.10 6455 var ext = DataTable.ext; 6456 var type = ext.sErrMode || ext.errMode; 6457 6458 if ( settings ) { 6459 _fnCallbackFire( settings, null, 'dt-error', [ settings, tn, msg ], true ); 6460 } 6461 6462 if ( type == 'alert' ) { 6463 alert( msg ); 6464 } 6465 else if ( type == 'throw' ) { 6466 throw new Error(msg); 6467 } 6468 else if ( typeof type == 'function' ) { 6469 type( settings, tn, msg ); 6470 } 6471 } 6472 else if ( window.console && console.log ) { 6473 console.log( msg ); 6474 } 6475 } 6476 6477 6478 /** 6479 * See if a property is defined on one object, if so assign it to the other object 6480 * @param {object} ret target object 6481 * @param {object} src source object 6482 * @param {string} name property 6483 * @param {string} [mappedName] name to map too - optional, name used if not given 6484 * @memberof DataTable#oApi 6485 */ 6486 function _fnMap( ret, src, name, mappedName ) 6487 { 6488 if ( Array.isArray( name ) ) { 6489 $.each( name, function (i, val) { 6490 if ( Array.isArray( val ) ) { 6491 _fnMap( ret, src, val[0], val[1] ); 6492 } 6493 else { 6494 _fnMap( ret, src, val ); 6495 } 6496 } ); 6497 6498 return; 6499 } 6500 6501 if ( mappedName === undefined ) { 6502 mappedName = name; 6503 } 6504 6505 if ( src[name] !== undefined ) { 6506 ret[mappedName] = src[name]; 6507 } 6508 } 6509 6510 6511 /** 6512 * Extend objects - very similar to jQuery.extend, but deep copy objects, and 6513 * shallow copy arrays. The reason we need to do this, is that we don't want to 6514 * deep copy array init values (such as aaSorting) since the dev wouldn't be 6515 * able to override them, but we do want to deep copy arrays. 6516 * @param {object} out Object to extend 6517 * @param {object} extender Object from which the properties will be applied to 6518 * out 6519 * @param {boolean} breakRefs If true, then arrays will be sliced to take an 6520 * independent copy with the exception of the `data` or `aaData` parameters 6521 * if they are present. This is so you can pass in a collection to 6522 * DataTables and have that used as your data source without breaking the 6523 * references 6524 * @returns {object} out Reference, just for convenience - out === the return. 6525 * @memberof DataTable#oApi 6526 * @todo This doesn't take account of arrays inside the deep copied objects. 6527 */ 6528 function _fnExtend( out, extender, breakRefs ) 6529 { 6530 var val; 6531 6532 for ( var prop in extender ) { 6533 if ( Object.prototype.hasOwnProperty.call(extender, prop) ) { 6534 val = extender[prop]; 6535 6536 if ( $.isPlainObject( val ) ) { 6537 if ( ! $.isPlainObject( out[prop] ) ) { 6538 out[prop] = {}; 6539 } 6540 $.extend( true, out[prop], val ); 6541 } 6542 else if ( breakRefs && prop !== 'data' && prop !== 'aaData' && Array.isArray(val) ) { 6543 out[prop] = val.slice(); 6544 } 6545 else { 6546 out[prop] = val; 6547 } 6548 } 6549 } 6550 6551 return out; 6552 } 6553 6554 6555 /** 6556 * Bind an event handers to allow a click or return key to activate the callback. 6557 * This is good for accessibility since a return on the keyboard will have the 6558 * same effect as a click, if the element has focus. 6559 * @param {element} n Element to bind the action to 6560 * @param {object|string} selector Selector (for delegated events) or data object 6561 * to pass to the triggered function 6562 * @param {function} fn Callback function for when the event is triggered 6563 * @memberof DataTable#oApi 6564 */ 6565 function _fnBindAction( n, selector, fn ) 6566 { 6567 $(n) 6568 .on( 'click.DT', selector, function (e) { 6569 fn(e); 6570 } ) 6571 .on( 'keypress.DT', selector, function (e){ 6572 if ( e.which === 13 ) { 6573 e.preventDefault(); 6574 fn(e); 6575 } 6576 } ) 6577 .on( 'selectstart.DT', selector, function () { 6578 // Don't want a double click resulting in text selection 6579 return false;
6580 } ); 6581 } 6582 6583 6584 /** 6585 * Register a callback function. Easily allows a callback function to be added to 6586 * an array store of callback functions that can then all be called together. 6587 * @param {object} settings dataTables settings object 6588 * @param {string} store Name of the array storage for the callbacks in oSettings 6589 * @param {function} fn Function to be called back 6590 * @memberof DataTable#oApi 6591 */ 6592 function _fnCallbackReg( settings, store, fn ) 6593 { 6594 if ( fn ) { 6595 settings[store].push(fn); 6596 } 6597 } 6598 6599 6600 /** 6601 * Fire callback functions and trigger events. Note that the loop over the 6602 * callback array store is done backwards! Further note that you do not want to 6603 * fire off triggers in time sensitive applications (for example cell creation) 6604 * as its slow. 6605 * @param {object} settings dataTables settings object 6606 * @param {string} callbackArr Name of the array storage for the callbacks in 6607 * oSettings 6608 * @param {string} eventName Name of the jQuery custom event to trigger. If 6609 * null no trigger is fired 6610 * @param {array} args Array of arguments to pass to the callback function / 6611 * trigger 6612 * @param {boolean} [bubbles] True if the event should bubble 6613 * @memberof DataTable#oApi 6614 */ 6615 function _fnCallbackFire( settings, callbackArr, eventName, args, bubbles ) 6616 { 6617 var ret = []; 6618 6619 if ( callbackArr ) { 6620 ret = settings[callbackArr].slice().reverse().map( function (val) { 6621 return val.apply( settings.oInstance, args ); 6622 } ); 6623 } 6624 6625 if ( eventName !== null) { 6626 var e = $.Event( eventName+'.dt' ); 6627 var table = $(settings.nTable); 6628 6629 // Expose the DataTables API on the event object for easy access 6630 e.dt = settings.api; 6631 6632 table[bubbles ? 'trigger' : 'triggerHandler']( e, args ); 6633 6634 // If not yet attached to the document, trigger the event 6635 // on the body directly to sort of simulate the bubble 6636 if (bubbles && table.parents('body').length === 0) { 6637 $('body').trigger( e, args ); 6638 } 6639 6640 ret.push( e.result ); 6641 } 6642 6643 return ret; 6644 } 6645 6646 6647 function _fnLengthOverflow ( settings ) 6648 { 6649 var 6650 start = settings._iDisplayStart, 6651 end = settings.fnDisplayEnd(), 6652 len = settings._iDisplayLength; 6653 6654 /* If we have space to show extra rows (backing up from the end point - then do so */ 6655 if ( start >= end ) 6656 { 6657 start = end - len; 6658 } 6659 6660 // Keep the start record on the current page 6661 start -= (start % len); 6662 6663 if ( len === -1 || start < 0 ) 6664 { 6665 start = 0; 6666 } 6667 6668 settings._iDisplayStart = start; 6669 } 6670 6671 6672 function _fnRenderer( settings, type ) 6673 { 6674 var renderer = settings.renderer; 6675 var host = DataTable.ext.renderer[type]; 6676 6677 if ( $.isPlainObject( renderer ) && renderer[type] ) { 6678 // Specific renderer for this type. If available use it, otherwise use 6679 // the default. 6680 return host[renderer[type]] || host._; 6681 } 6682 else if ( typeof renderer === 'string' ) { 6683 // Common renderer - if there is one available for this type use it, 6684 // otherwise use the default 6685 return host[renderer] || host._; 6686 } 6687 6688 // Use the default 6689 return host._; 6690 } 6691 6692 6693 /** 6694 * Detect the data source being used for the table. Used to simplify the code 6695 * a little (ajax) and to make it compress a little smaller. 6696 * 6697 * @param {object} settings dataTables settings object 6698 * @returns {string} Data source 6699 * @memberof DataTable#oApi 6700 */ 6701 function _fnDataSource ( settings ) 6702 { 6703 if ( settings.oFeatures.bServerSide ) { 6704 return 'ssp'; 6705 } 6706 else if ( settings.ajax ) { 6707 return 'ajax'; 6708 } 6709 return 'dom'; 6710 } 6711 6712 /** 6713 * Common replacement for language strings 6714 * 6715 * @param {*} settings DT settings object 6716 * @param {*} str String with values to replace 6717 * @param {*} entries Plural number for _ENTRIES_ - can be undefined 6718 * @returns String 6719 */ 6720 function _fnMacros ( settings, str, entries ) 6721 { 6722 // When infinite scrolling, we are always starting at 1. _iDisplayStart is 6723 // used only internally 6724 var 6725 formatter = settings.fnFormatNumber, 6726 start = settings._iDisplayStart+1, 6727 len = settings._iDisplayLength, 6728 vis = settings.fnRecordsDisplay(), 6729 max = settings.fnRecordsTotal(), 6730 all = len === -1; 6731 6732 return str. 6733 replace(/_START_/g, formatter.call( settings, start ) ). 6734 replace(/_END_/g, formatter.call( settings, settings.fnDisplayEnd() ) ). 6735 replace(/_MAX_/g, formatter.call( settings, max ) ). 6736 replace(/_TOTAL_/g, formatter.call( settings, vis ) ). 6737 replace(/_PAGE_/g, formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ). 6738 replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) ). 6739 replace(/_ENTRIES_/g, settings.api.i18n('entries', '', entries) ). 6740 replace(/_ENTRIES-MAX_/g, settings.api.i18n('entries', '', max) ). 6741 replace(/_ENTRIES-TOTAL_/g, settings.api.i18n('entries', '', vis) ); 6742 } 6743 6744 /** 6745 * Add elements to an array as quickly as possible, but stack stafe. 6746 * 6747 * @param {*} arr Array to add the data to 6748 * @param {*} data Data array that is to be added 6749 * @returns 6750 */ 6751 function _fnArrayApply(arr, data) { 6752 if (! data) { 6753 return; 6754 } 6755 6756 // Chrome can throw a max stack error if apply is called with 6757 // too large an array, but apply is faster. 6758 if (data.length < 10000) { 6759 arr.push.apply(arr, data); 6760 } 6761 else { 6762 for (i=0 ; i<data.length ; i++) { 6763 arr.push(data[i]); 6764 } 6765 } 6766 } 6767 6768 6769 6770 /** 6771 * Computed structure of the DataTables API, defined by the options passed to 6772 * `DataTable.Api.register()` when building the API. 6773 * 6774 * The structure is built in order to speed creation and extension of the Api 6775 * objects since the extensions are effectively pre-parsed. 6776 * 6777 * The array is an array of objects with the following structure, where this 6778 * base array represents the Api prototype base: 6779 * 6780 * [ 6781 * { 6782 * name: 'data' -- string - Property name 6783 * val: function () {}, -- function - Api method (or undefined if just an object 6784 * methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result 6785 * propExt: [ ... ] -- array - Array of Api object definitions to extend the property 6786 * }, 6787 * { 6788 * name: 'row' 6789 * val: {}, 6790 * methodExt: [ ... ], 6791 * propExt: [ 6792 * { 6793 * name: 'data' 6794 * val: function () {}, 6795 * methodExt: [ ... ], 6796 * propExt: [ ... ] 6797 * }, 6798 * ... 6799 * ] 6800 * } 6801 * ] 6802 * 6803 * @type {Array} 6804 * @ignore 6805 */ 6806 var __apiStruct = []; 6807 6808 6809 /** 6810 * `Array.prototype` reference. 6811 * 6812 * @type object 6813 * @ignore 6814 */ 6815 var __arrayProto = Array.prototype; 6816 6817 6818 /** 6819 * Abstraction for `context` parameter of the `Api` constructor to allow it to 6820 * take several different forms for ease of use. 6821 * 6822 * Each of the input parameter types will be converted to a DataTables settings 6823 * object where possible. 6824 * 6825 * @param {string|node|jQuery|object} mixed DataTable identifier. Can be one 6826 * of: 6827 * 6828 * * `string` - jQuery selector. Any DataTables' matching the given selector 6829 * with be found and used. 6830 * * `node` - `TABLE` node which has already been formed into a DataTable. 6831 * * `jQuery` - A jQuery object of `TABLE` nodes. 6832 * * `object` - DataTables settings object 6833 * * `DataTables.Api` - API instance 6834 * @return {array|null} Matching DataTables settings objects. `null` or 6835 * `undefined` is returned if no matching DataTable is found. 6836 * @ignore 6837 */ 6838 var _toSettings = function ( mixed ) 6839 { 6840 var idx, jq; 6841 var settings = DataTable.settings; 6842 var tables = _pluck(settings, 'nTable'); 6843 6844 if ( ! mixed ) { 6845 return []; 6846 } 6847 else if ( mixed.nTable && mixed.oFeatures ) { 6848 // DataTables settings object 6849 return [ mixed ]; 6850 } 6851 else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) { 6852 // Table node 6853 idx = tables.indexOf(mixed); 6854 return idx !== -1 ? [ settings[idx] ] : null; 6855 } 6856 else if ( mixed && typeof mixed.settings === 'function' ) { 6857 return mixed.settings().toArray(); 6858 } 6859 else if ( typeof mixed === 'string' ) { 6860 // jQuery selector 6861 jq = $(mixed).get(); 6862 } 6863 else if ( mixed instanceof $ ) { 6864 // jQuery object (also DataTables instance) 6865 jq = mixed.get(); 6866 } 6867 6868 if ( jq ) { 6869 return settings.filter(function (v, idx) { 6870 return jq.includes(tables[idx]); 6871 }); 6872 } 6873 }; 6874 6875 6876 /** 6877 * DataTables API class - used to control and interface with one or more 6878 * DataTables enhanced tables. 6879 * 6880 * The API class is heavily based on jQuery, presenting a chainable interface 6881 * that you can use to interact with tables. Each instance of the API class has 6882 * a "context" - i.e. the tables that it will operate on. This could be a single 6883 * table, all tables on a page or a sub-set thereof. 6884 * 6885 * Additionally the API is designed to allow you to easily work with the data in 6886 * the tables, retrieving and manipulating it as required. This is done by 6887 * presenting the API class as an array like interface. The contents of the 6888 * array depend upon the actions requested by each method (for example 6889 * `rows().nodes()` will return an array of nodes, while `rows().data()` will
6890 * return an array of objects or arrays depending upon your table's 6891 * configuration). The API object has a number of array like methods (`push`, 6892 * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`, 6893 * `unique` etc) to assist your working with the data held in a table. 6894 * 6895 * Most methods (those which return an Api instance) are chainable, which means 6896 * the return from a method call also has all of the methods available that the 6897 * top level object had. For example, these two calls are equivalent: 6898 * 6899 * // Not chained 6900 * api.row.add( {...} ); 6901 * api.draw(); 6902 * 6903 * // Chained 6904 * api.row.add( {...} ).draw(); 6905 * 6906 * @class DataTable.Api 6907 * @param {array|object|string|jQuery} context DataTable identifier. This is 6908 * used to define which DataTables enhanced tables this API will operate on. 6909 * Can be one of: 6910 * 6911 * * `string` - jQuery selector. Any DataTables' matching the given selector 6912 * with be found and used. 6913 * * `node` - `TABLE` node which has already been formed into a DataTable. 6914 * * `jQuery` - A jQuery object of `TABLE` nodes. 6915 * * `object` - DataTables settings object 6916 * @param {array} [data] Data to initialise the Api instance with. 6917 * 6918 * @example 6919 * // Direct initialisation during DataTables construction 6920 * var api = $('#example').DataTable(); 6921 * 6922 * @example 6923 * // Initialisation using a DataTables jQuery object 6924 * var api = $('#example').dataTable().api(); 6925 * 6926 * @example 6927 * // Initialisation as a constructor 6928 * var api = new DataTable.Api( 'table.dataTable' ); 6929 */ 6930 _Api = function ( context, data ) 6931 { 6932 if ( ! (this instanceof _Api) ) { 6933 return new _Api( context, data ); 6934 } 6935 6936 var i; 6937 var settings = []; 6938 var ctxSettings = function ( o ) { 6939 var a = _toSettings( o ); 6940 if ( a ) { 6941 settings.push.apply( settings, a ); 6942 } 6943 }; 6944 6945 if ( Array.isArray( context ) ) { 6946 for ( i=0 ; i<context.length ; i++ ) { 6947 ctxSettings( context[i] ); 6948 } 6949 } 6950 else { 6951 ctxSettings( context ); 6952 } 6953 6954 // Remove duplicates 6955 this.context = settings.length > 1 6956 ? _unique( settings ) 6957 : settings; 6958 6959 // Initial data 6960 _fnArrayApply(this, data); 6961 6962 // selector 6963 this.selector = { 6964 rows: null, 6965 cols: null, 6966 opts: null 6967 }; 6968 6969 _Api.extend( this, this, __apiStruct ); 6970 }; 6971 6972 DataTable.Api = _Api; 6973 6974 // Don't destroy the existing prototype, just extend it. Required for jQuery 2's 6975 // isPlainObject. 6976 $.extend( _Api.prototype, { 6977 any: function () 6978 { 6979 return this.count() !== 0; 6980 }, 6981 6982 context: [], // array of table settings objects 6983 6984 count: function () 6985 { 6986 return this.flatten().length; 6987 }, 6988 6989 each: function ( fn ) 6990 { 6991 for ( var i=0, ien=this.length ; i<ien; i++ ) { 6992 fn.call( this, this[i], i, this ); 6993 } 6994 6995 return this; 6996 }, 6997 6998 eq: function ( idx ) 6999 { 7000 var ctx = this.context; 7001 7002 return ctx.length > idx ? 7003 new _Api( ctx[idx], this[idx] ) : 7004 null; 7005 }, 7006 7007 filter: function ( fn ) 7008 { 7009 var a = __arrayProto.filter.call( this, fn, this ); 7010 7011 return new _Api( this.context, a ); 7012 }, 7013 7014 flatten: function () 7015 { 7016 var a = []; 7017 7018 return new _Api( this.context, a.concat.apply( a, this.toArray() ) ); 7019 }, 7020 7021 get: function ( idx ) 7022 { 7023 return this[ idx ]; 7024 }, 7025 7026 join: __arrayProto.join, 7027 7028 includes: function ( find ) { 7029 return this.indexOf( find ) === -1 ? false : true; 7030 }, 7031 7032 indexOf: __arrayProto.indexOf, 7033 7034 iterator: function ( flatten, type, fn, alwaysNew ) { 7035 var 7036 a = [], ret, 7037 i, ien, j, jen, 7038 context = this.context, 7039 rows, items, item, 7040 selector = this.selector; 7041 7042 // Argument shifting 7043 if ( typeof flatten === 'string' ) { 7044 alwaysNew = fn; 7045 fn = type; 7046 type = flatten; 7047 flatten = false; 7048 } 7049 7050 for ( i=0, ien=context.length ; i<ien ; i++ ) { 7051 var apiInst = new _Api( context[i] ); 7052 7053 if ( type === 'table' ) { 7054 ret = fn.call( apiInst, context[i], i ); 7055 7056 if ( ret !== undefined ) { 7057 a.push( ret ); 7058 } 7059 } 7060 else if ( type === 'columns' || type === 'rows' ) { 7061 // this has same length as context - one entry for each table 7062 ret = fn.call( apiInst, context[i], this[i], i ); 7063 7064 if ( ret !== undefined ) { 7065 a.push( ret ); 7066 } 7067 } 7068 else if ( type === 'every' || type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) { 7069 // columns and rows share the same structure. 7070 // 'this' is an array of column indexes for each context 7071 items = this[i]; 7072 7073 if ( type === 'column-rows' ) { 7074 rows = _selector_row_indexes( context[i], selector.opts ); 7075 } 7076 7077 for ( j=0, jen=items.length ; j<jen ; j++ ) { 7078 item = items[j]; 7079 7080 if ( type === 'cell' ) { 7081 ret = fn.call( apiInst, context[i], item.row, item.column, i, j ); 7082 } 7083 else { 7084 ret = fn.call( apiInst, context[i], item, i, j, rows ); 7085 } 7086 7087 if ( ret !== undefined ) { 7088 a.push( ret ); 7089 } 7090 } 7091 } 7092 } 7093 7094 if ( a.length || alwaysNew ) { 7095 var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a ); 7096 var apiSelector = api.selector; 7097 apiSelector.rows = selector.rows; 7098 apiSelector.cols = selector.cols; 7099 apiSelector.opts = selector.opts; 7100 return api; 7101 } 7102 return this; 7103 }, 7104 7105 lastIndexOf: __arrayProto.lastIndexOf, 7106 7107 length: 0, 7108 7109 map: function ( fn ) 7110 { 7111 var a = __arrayProto.map.call( this, fn, this ); 7112 7113 return new _Api( this.context, a ); 7114 }, 7115 7116 pluck: function ( prop ) 7117 { 7118 var fn = DataTable.util.get(prop); 7119 7120 return this.map( function ( el ) { 7121 return fn(el); 7122 } ); 7123 }, 7124 7125 pop: __arrayProto.pop, 7126 7127 push: __arrayProto.push, 7128 7129 reduce: __arrayProto.reduce, 7130 7131 reduceRight: __arrayProto.reduceRight, 7132 7133 reverse: __arrayProto.reverse, 7134 7135 // Object with rows, columns and opts 7136 selector: null, 7137 7138 shift: __arrayProto.shift, 7139 7140 slice: function () { 7141 return new _Api( this.context, this ); 7142 }, 7143 7144 sort: __arrayProto.sort, 7145 7146 splice: __arrayProto.splice, 7147 7148 toArray: function () 7149 { 7150 return __arrayProto.slice.call( this ); 7151 }, 7152 7153 to$: function () 7154 { 7155 return $( this ); 7156 }, 7157 7158 toJQuery: function () 7159 { 7160 return $( this ); 7161 }, 7162 7163 unique: function () 7164 { 7165 return new _Api( this.context, _unique(this.toArray()) ); 7166 }, 7167 7168 unshift: __arrayProto.unshift 7169 } ); 7170 7171 7172 function _api_scope( scope, fn, struc ) { 7173 return function () { 7174 var ret = fn.apply( scope || this, arguments ); 7175 7176 // Method extension 7177 _Api.extend( ret, ret, struc.methodExt ); 7178 return ret; 7179 }; 7180 } 7181 7182 function _api_find( src, name ) { 7183 for ( var i=0, ien=src.length ; i<ien ; i++ ) { 7184 if ( src[i].name === name ) { 7185 return src[i]; 7186 } 7187 } 7188 return null; 7189 } 7190 7191 window.__apiStruct = __apiStruct; 7192 7193 _Api.extend = function ( scope, obj, ext ) 7194 { 7195 // Only extend API instances and static properties of the API 7196 if ( ! ext.length || ! obj || ( ! (obj instanceof _Api) && ! obj.__dt_wrapper ) ) { 7197 return; 7198 } 7199 7200 var 7201 i, ien, 7202 struct; 7203 7204 for ( i=0, ien=ext.length ; i<ien ; i++ ) { 7205 struct = ext[i]; 7206 7207 if (struct.name === '__proto__') { 7208 continue; 7209 } 7210 7211 // Value 7212 obj[ struct.name ] = struct.type === 'function' ? 7213 _api_scope( scope, struct.val, struct ) : 7214 struct.type === 'object' ? 7215 {} : 7216 struct.val; 7217 7218 obj[ struct.name ].__dt_wrapper = true; 7219 7220 // Property extension 7221 _Api.extend( scope, obj[ struct.name ], struct.propExt ); 7222 } 7223 }; 7224 7225 // [ 7226 // { 7227 // name: 'data' -- string - Property name 7228 // val: function () {}, -- function - Api method (or undefined if just an object 7229 // methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result 7230 // propExt: [ ... ] -- array - Array of Api object definitions to extend the property 7231 // }, 7232 // { 7233 // name: 'row' 7234 // val: {}, 7235 // methodExt: [ ... ], 7236 // propExt: [ 7237 // { 7238 // name: 'data' 7239 // val: function () {}, 7240 // methodExt: [ ... ], 7241 // propExt: [ ... ] 7242 // }, 7243 // ... 7244 // ] 7245 // } 7246 // ] 7247 7248 7249 _Api.register = _api_register = function ( name, val ) 7250 { 7251 if ( Array.isArray( name ) ) { 7252 for ( var j=0, jen=name.length ; j<jen ; j++ ) { 7253 _Api.register( name[j], val ); 7254 } 7255 return; 7256 } 7257 7258 var 7259 i, ien, 7260 heir = name.split('.'), 7261 struct = __apiStruct, 7262 key, method; 7263 7264 for ( i=0, ien=heir.length ; i<ien ; i++ ) { 7265 method = heir[i].indexOf('()') !== -1; 7266 key = method ?
7267 heir[i].replace('()', '') : 7268 heir[i]; 7269 7270 var src = _api_find( struct, key ); 7271 if ( ! src ) { 7272 src = { 7273 name: key, 7274 val: {}, 7275 methodExt: [], 7276 propExt: [], 7277 type: 'object' 7278 }; 7279 struct.push( src ); 7280 } 7281 7282 if ( i === ien-1 ) { 7283 src.val = val; 7284 src.type = typeof val === 'function' ? 7285 'function' : 7286 $.isPlainObject( val ) ? 7287 'object' : 7288 'other'; 7289 } 7290 else { 7291 struct = method ? 7292 src.methodExt : 7293 src.propExt; 7294 } 7295 } 7296 }; 7297 7298 _Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) { 7299 _Api.register( pluralName, val ); 7300 7301 _Api.register( singularName, function () { 7302 var ret = val.apply( this, arguments ); 7303 7304 if ( ret === this ) { 7305 // Returned item is the API instance that was passed in, return it 7306 return this; 7307 } 7308 else if ( ret instanceof _Api ) { 7309 // New API instance returned, want the value from the first item 7310 // in the returned array for the singular result. 7311 return ret.length ? 7312 Array.isArray( ret[0] ) ? 7313 new _Api( ret.context, ret[0] ) : // Array results are 'enhanced' 7314 ret[0] : 7315 undefined; 7316 } 7317 7318 // Non-API return - just fire it back 7319 return ret; 7320 } ); 7321 }; 7322 7323 7324 /** 7325 * Selector for HTML tables. Apply the given selector to the give array of 7326 * DataTables settings objects. 7327 * 7328 * @param {string|integer} [selector] jQuery selector string or integer 7329 * @param {array} Array of DataTables settings objects to be filtered 7330 * @return {array} 7331 * @ignore 7332 */ 7333 var __table_selector = function ( selector, a ) 7334 { 7335 if ( Array.isArray(selector) ) { 7336 var result = []; 7337 7338 selector.forEach(function (sel) { 7339 var inner = __table_selector(sel, a); 7340 7341 _fnArrayApply(result, inner); 7342 }); 7343 7344 return result.filter( function (item) { 7345 return item; 7346 }); 7347 } 7348 7349 // Integer is used to pick out a table by index 7350 if ( typeof selector === 'number' ) { 7351 return [ a[ selector ] ]; 7352 } 7353 7354 // Perform a jQuery selector on the table nodes 7355 var nodes = a.map( function (el) { 7356 return el.nTable; 7357 } ); 7358 7359 return $(nodes) 7360 .filter( selector ) 7361 .map( function () { 7362 // Need to translate back from the table node to the settings 7363 var idx = nodes.indexOf(this); 7364 return a[ idx ]; 7365 } ) 7366 .toArray(); 7367 }; 7368 7369 7370 7371 /** 7372 * Context selector for the API's context (i.e. the tables the API instance 7373 * refers to. 7374 * 7375 * @name DataTable.Api#tables 7376 * @param {string|integer} [selector] Selector to pick which tables the iterator 7377 * should operate on. If not given, all tables in the current context are 7378 * used. This can be given as a jQuery selector (for example `':gt(0)'`) to 7379 * select multiple tables or as an integer to select a single table. 7380 * @returns {DataTable.Api} Returns a new API instance if a selector is given. 7381 */ 7382 _api_register( 'tables()', function ( selector ) { 7383 // A new instance is created if there was a selector specified 7384 return selector !== undefined && selector !== null ? 7385 new _Api( __table_selector( selector, this.context ) ) : 7386 this; 7387 } ); 7388 7389 7390 _api_register( 'table()', function ( selector ) { 7391 var tables = this.tables( selector ); 7392 var ctx = tables.context; 7393 7394 // Truncate to the first matched table 7395 return ctx.length ? 7396 new _Api( ctx[0] ) : 7397 tables; 7398 } ); 7399 7400 // Common methods, combined to reduce size 7401 [ 7402 ['nodes', 'node', 'nTable'], 7403 ['body', 'body', 'nTBody'], 7404 ['header', 'header', 'nTHead'], 7405 ['footer', 'footer', 'nTFoot'], 7406 ].forEach(function (item) { 7407 _api_registerPlural( 7408 'tables().' + item[0] + '()', 7409 'table().' + item[1] + '()' , 7410 function () { 7411 return this.iterator( 'table', function ( ctx ) { 7412 return ctx[item[2]]; 7413 }, 1 ); 7414 } 7415 ); 7416 }); 7417 7418 // Structure methods 7419 [ 7420 ['header', 'aoHeader'], 7421 ['footer', 'aoFooter'], 7422 ].forEach(function (item) { 7423 _api_register( 'table().' + item[0] + '.structure()' , function (selector) { 7424 var indexes = this.columns(selector).indexes().flatten(); 7425 var ctx = this.context[0]; 7426 7427 return _fnHeaderLayout(ctx, ctx[item[1]], indexes); 7428 } ); 7429 }) 7430 7431 7432 _api_registerPlural( 'tables().containers()', 'table().container()' , function () { 7433 return this.iterator( 'table', function ( ctx ) { 7434 return ctx.nTableWrapper; 7435 }, 1 ); 7436 } ); 7437 7438 _api_register( 'tables().every()', function ( fn ) { 7439 var that = this; 7440 7441 return this.iterator('table', function (s, i) { 7442 fn.call(that.table(i), i); 7443 }); 7444 }); 7445 7446 _api_register( 'caption()', function ( value, side ) { 7447 var context = this.context; 7448 7449 // Getter - return existing node's content 7450 if ( value === undefined ) { 7451 var caption = context[0].captionNode; 7452 7453 return caption && context.length ? 7454 caption.innerHTML : 7455 null; 7456 } 7457 7458 return this.iterator( 'table', function ( ctx ) { 7459 var table = $(ctx.nTable); 7460 var caption = $(ctx.captionNode); 7461 var container = $(ctx.nTableWrapper); 7462 7463 // Create the node if it doesn't exist yet 7464 if ( ! caption.length ) { 7465 caption = $('<caption/>').html( value ); 7466 ctx.captionNode = caption[0]; 7467 7468 // If side isn't set, we need to insert into the document to let the 7469 // CSS decide so we can read it back, otherwise there is no way to 7470 // know if the CSS would put it top or bottom for scrolling
7471 if (! side) { 7472 table.prepend(caption); 7473 7474 side = caption.css('caption-side'); 7475 } 7476 } 7477 7478 caption.html( value ); 7479 7480 if ( side ) { 7481 caption.css( 'caption-side', side ); 7482 caption[0]._captionSide = side; 7483 } 7484 7485 if (container.find('div.dataTables_scroll').length) { 7486 var selector = (side === 'top' ? 'Head' : 'Foot'); 7487 7488 container.find('div.dataTables_scroll'+ selector +' table').prepend(caption); 7489 } 7490 else { 7491 table.prepend(caption); 7492 } 7493 }, 1 ); 7494 } ); 7495 7496 _api_register( 'caption.node()', function () { 7497 var ctx = this.context; 7498 7499 return ctx.length ? ctx[0].captionNode : null; 7500 } ); 7501 7502 7503 /** 7504 * Redraw the tables in the current context. 7505 */ 7506 _api_register( 'draw()', function ( paging ) { 7507 return this.iterator( 'table', function ( settings ) { 7508 if ( paging === 'page' ) { 7509 _fnDraw( settings ); 7510 } 7511 else { 7512 if ( typeof paging === 'string' ) { 7513 paging = paging === 'full-hold' ? 7514 false : 7515 true; 7516 } 7517 7518 _fnReDraw( settings, paging===false ); 7519 } 7520 } ); 7521 } ); 7522 7523 7524 7525 /** 7526 * Get the current page index. 7527 * 7528 * @return {integer} Current page index (zero based) 7529 *//** 7530 * Set the current page. 7531 * 7532 * Note that if you attempt to show a page which does not exist, DataTables will 7533 * not throw an error, but rather reset the paging. 7534 * 7535 * @param {integer|string} action The paging action to take. This can be one of: 7536 * * `integer` - The page index to jump to 7537 * * `string` - An action to take: 7538 * * `first` - Jump to first page. 7539 * * `next` - Jump to the next page 7540 * * `previous` - Jump to previous page 7541 * * `last` - Jump to the last page. 7542 * @returns {DataTables.Api} this 7543 */ 7544 _api_register( 'page()', function ( action ) { 7545 if ( action === undefined ) { 7546 return this.page.info().page; // not an expensive call 7547 } 7548 7549 // else, have an action to take on all tables 7550 return this.iterator( 'table', function ( settings ) { 7551 _fnPageChange( settings, action ); 7552 } ); 7553 } ); 7554 7555 7556 /** 7557 * Paging information for the first table in the current context. 7558 * 7559 * If you require paging information for another table, use the `table()` method 7560 * with a suitable selector. 7561 * 7562 * @return {object} Object with the following properties set: 7563 * * `page` - Current page index (zero based - i.e. the first page is `0`) 7564 * * `pages` - Total number of pages 7565 * * `start` - Display index for the first record shown on the current page 7566 * * `end` - Display index for the last record shown on the current page 7567 * * `length` - Display length (number of records). Note that generally `start 7568 * + length = end`, but this is not always true, for example if there are 7569 * only 2 records to show on the final page, with a length of 10. 7570 * * `recordsTotal` - Full data set length 7571 * * `recordsDisplay` - Data set length once the current filtering criterion 7572 * are applied. 7573 */ 7574 _api_register( 'page.info()', function () { 7575 if ( this.context.length === 0 ) { 7576 return undefined; 7577 } 7578 7579 var 7580 settings = this.context[0], 7581 start = settings._iDisplayStart, 7582 len = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1, 7583 visRecords = settings.fnRecordsDisplay(), 7584 all = len === -1; 7585 7586 return { 7587 "page": all ? 0 : Math.floor( start / len ), 7588 "pages": all ? 1 : Math.ceil( visRecords / len ), 7589 "start": start, 7590 "end": settings.fnDisplayEnd(), 7591 "length": len, 7592 "recordsTotal": settings.fnRecordsTotal(), 7593 "recordsDisplay": visRecords, 7594 "serverSide": _fnDataSource( settings ) === 'ssp' 7595 }; 7596 } ); 7597 7598 7599 /** 7600 * Get the current page length. 7601 * 7602 * @return {integer} Current page length. Note `-1` indicates that all records 7603 * are to be shown. 7604 *//** 7605 * Set the current page length. 7606 * 7607 * @param {integer} Page length to set. Use `-1` to show all records. 7608 * @returns {DataTables.Api} this 7609 */ 7610 _api_register( 'page.len()', function ( len ) { 7611 // Note that we can't call this function 'length()' because `length` 7612 // is a Javascript property of functions which defines how many arguments 7613 // the function expects. 7614 if ( len === undefined ) { 7615 return this.context.length !== 0 ? 7616 this.context[0]._iDisplayLength : 7617 undefined; 7618 } 7619 7620 // else, set the page length 7621 return this.iterator( 'table', function ( settings ) { 7622 _fnLengthChange( settings, len ); 7623 } ); 7624 } ); 7625 7626 7627 7628 var __reload = function ( settings, holdPosition, callback ) { 7629 // Use the draw event to trigger a callback 7630 if ( callback ) { 7631 var api = new _Api( settings ); 7632 7633 api.one( 'draw', function () { 7634 callback( api.ajax.json() ); 7635 } ); 7636 } 7637 7638 if ( _fnDataSource( settings ) == 'ssp' ) { 7639 _fnReDraw( settings, holdPosition ); 7640 } 7641 else { 7642 _fnProcessingDisplay( settings, true ); 7643 7644 // Cancel an existing request 7645 var xhr = settings.jqXHR; 7646 if ( xhr && xhr.readyState !== 4 ) { 7647 xhr.abort(); 7648 } 7649 7650 // Trigger xhr 7651 _fnBuildAjax( settings, {}, function( json ) { 7652 _fnClearTable( settings ); 7653 7654 var data = _fnAjaxDataSrc( settings, json ); 7655 for ( var i=0, ien=data.length ; i<ien ; i++ ) { 7656 _fnAddData( settings, data[i] ); 7657 } 7658 7659 _fnReDraw( settings, holdPosition ); 7660 _fnInitComplete( settings ); 7661 _fnProcessingDisplay( settings, false ); 7662 } ); 7663 } 7664 }; 7665 7666 7667 /** 7668 * Get the JSON response from the last Ajax request that DataTables made to the 7669 * server. Note that this returns the JSON from the first table in the current 7670 * context. 7671 * 7672 * @return {object} JSON received from the server. 7673 */ 7674 _api_register( 'ajax.json()', function () { 7675 var ctx = this.context; 7676 7677 if ( ctx.length > 0 ) { 7678 return ctx[0].json; 7679 } 7680 7681 // else return undefined; 7682 } ); 7683 7684 7685 /** 7686 * Get the data submitted in the last Ajax request 7687 */ 7688 _api_register( 'ajax.params()', function () { 7689 var ctx = this.context; 7690 7691 if ( ctx.length > 0 ) { 7692 return ctx[0].oAjaxData; 7693 } 7694 7695 // else return undefined; 7696 } ); 7697 7698 7699 /** 7700 * Reload tables from the Ajax data source. Note that this function will 7701 * automatically re-draw the table when the remote data has been loaded. 7702 * 7703 * @param {boolean} [reset=true] Reset (default) or hold the current paging 7704 * position. A full re-sort and re-filter is performed when this method is 7705 * called, which is why the pagination reset is the default action. 7706 * @returns {DataTables.Api} this 7707 */ 7708 _api_register( 'ajax.reload()', function ( callback, resetPaging ) { 7709 return this.iterator( 'table', function (settings) { 7710 __reload( settings, resetPaging===false, callback ); 7711 } ); 7712 } ); 7713 7714 7715 /** 7716 * Get the current Ajax URL. Note that this returns the URL from the first 7717 * table in the current context. 7718 * 7719 * @return {string} Current Ajax source URL 7720 *//** 7721 * Set the Ajax URL. Note that this will set the URL for all tables in the 7722 * current context. 7723 * 7724 * @param {string} url URL to set. 7725 * @returns {DataTables.Api} this 7726 */ 7727 _api_register( 'ajax.url()', function ( url ) { 7728 var ctx = this.context; 7729 7730 if ( url === undefined ) { 7731 // get 7732 if ( ctx.length === 0 ) { 7733 return undefined; 7734 } 7735 ctx = ctx[0]; 7736
7737 return $.isPlainObject( ctx.ajax ) ? 7738 ctx.ajax.url : 7739 ctx.ajax; 7740 } 7741 7742 // set 7743 return this.iterator( 'table', function ( settings ) { 7744 if ( $.isPlainObject( settings.ajax ) ) { 7745 settings.ajax.url = url; 7746 } 7747 else { 7748 settings.ajax = url; 7749 } 7750 } ); 7751 } ); 7752 7753 7754 /** 7755 * Load data from the newly set Ajax URL. Note that this method is only 7756 * available when `ajax.url()` is used to set a URL. Additionally, this method 7757 * has the same effect as calling `ajax.reload()` but is provided for 7758 * convenience when setting a new URL. Like `ajax.reload()` it will 7759 * automatically redraw the table once the remote data has been loaded. 7760 * 7761 * @returns {DataTables.Api} this 7762 */ 7763 _api_register( 'ajax.url().load()', function ( callback, resetPaging ) { 7764 // Same as a reload, but makes sense to present it for easy access after a 7765 // url change 7766 return this.iterator( 'table', function ( ctx ) { 7767 __reload( ctx, resetPaging===false, callback ); 7768 } ); 7769 } ); 7770 7771 7772 7773 7774 var _selector_run = function ( type, selector, selectFn, settings, opts ) 7775 { 7776 var 7777 out = [], res, 7778 a, i, ien, j, jen, 7779 selectorType = typeof selector; 7780 7781 // Can't just check for isArray here, as an API or jQuery instance might be 7782 // given with their array like look 7783 if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) { 7784 selector = [ selector ]; 7785 } 7786 7787 for ( i=0, ien=selector.length ; i<ien ; i++ ) { 7788 // Only split on simple strings - complex expressions will be jQuery selectors 7789 a = selector[i] && selector[i].split && ! selector[i].match(/[[(:]/) ? 7790 selector[i].split(',') : 7791 [ selector[i] ]; 7792 7793 for ( j=0, jen=a.length ; j<jen ; j++ ) { 7794 res = selectFn( typeof a[j] === 'string' ? (a[j]).trim() : a[j] ); 7795 7796 // Remove empty items 7797 res = res.filter( function (item) { 7798 return item !== null && item !== undefined; 7799 }); 7800 7801 if ( res && res.length ) { 7802 out = out.concat( res ); 7803 } 7804 } 7805 } 7806 7807 // selector extensions 7808 var ext = _ext.selector[ type ]; 7809 if ( ext.length ) { 7810 for ( i=0, ien=ext.length ; i<ien ; i++ ) { 7811 out = ext[i]( settings, opts, out ); 7812 } 7813 } 7814 7815 return _unique( out ); 7816 }; 7817 7818 7819 var _selector_opts = function ( opts ) 7820 { 7821 if ( ! opts ) { 7822 opts = {}; 7823 } 7824 7825 // Backwards compatibility for 1.9- which used the terminology filter rather 7826 // than search 7827 if ( opts.filter && opts.search === undefined ) { 7828 opts.search = opts.filter; 7829 } 7830 7831 return $.extend( { 7832 search: 'none', 7833 order: 'current', 7834 page: 'all' 7835 }, opts ); 7836 }; 7837 7838 7839 // Reduce the API instance to the first item found 7840 var _selector_first = function ( old ) 7841 { 7842 var inst = new _Api(old.context[0]); 7843 7844 // Use a push rather than passing to the constructor, since it will 7845 // merge arrays down automatically, which isn't what is wanted here 7846 if (old.length) { 7847 inst.push( old[0] ); 7848 } 7849 7850 inst.selector = old.selector; 7851 7852 // Limit to a single row / column / cell 7853 if (inst.length && inst[0].length > 1) { 7854 inst[0].splice(1); 7855 } 7856 7857 return inst; 7858 }; 7859 7860 7861 var _selector_row_indexes = function ( settings, opts ) 7862 { 7863 var 7864 i, ien, tmp, a=[], 7865 displayFiltered = settings.aiDisplay, 7866 displayMaster = settings.aiDisplayMaster; 7867 7868 var 7869 search = opts.search, // none, applied, removed 7870 order = opts.order, // applied, current, index (original - compatibility with 1.9) 7871 page = opts.page; // all, current 7872 7873 if ( _fnDataSource( settings ) == 'ssp' ) { 7874 // In server-side processing mode, most options are irrelevant since 7875 // rows not shown don't exist and the index order is the applied order 7876 // Removed is a special case - for consistency just return an empty 7877 // array 7878 return search === 'removed' ? 7879 [] : 7880 _range( 0, displayMaster.length ); 7881 } 7882 7883 if ( page == 'current' ) { 7884 // Current page implies that order=current and filter=applied, since it is 7885 // fairly senseless otherwise, regardless of what order and search actually 7886 // are 7887 for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i<ien ; i++ ) { 7888 a.push( displayFiltered[i] ); 7889 } 7890 } 7891 else if ( order == 'current' || order == 'applied' ) { 7892 if ( search == 'none') {
7893 a = displayMaster.slice(); 7894 } 7895 else if ( search == 'applied' ) { 7896 a = displayFiltered.slice(); 7897 } 7898 else if ( search == 'removed' ) { 7899 // O(n+m) solution by creating a hash map 7900 var displayFilteredMap = {}; 7901 7902 for ( i=0, ien=displayFiltered.length ; i<ien ; i++ ) { 7903 displayFilteredMap[displayFiltered[i]] = null; 7904 } 7905 7906 displayMaster.forEach(function (item) { 7907 if (! Object.prototype.hasOwnProperty.call(displayFilteredMap, item)) { 7908 a.push(item); 7909 } 7910 }); 7911 } 7912 } 7913 else if ( order == 'index' || order == 'original' ) { 7914 for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) { 7915 if (! settings.aoData[i]) { 7916 continue; 7917 } 7918 7919 if ( search == 'none' ) { 7920 a.push( i ); 7921 } 7922 else { // applied | removed 7923 tmp = displayFiltered.indexOf(i); 7924 7925 if ((tmp === -1 && search == 'removed') || 7926 (tmp >= 0 && search == 'applied') ) 7927 { 7928 a.push( i ); 7929 } 7930 } 7931 } 7932 } 7933 else if ( typeof order === 'number' ) { 7934 // Order the rows by the given column 7935 var ordered = _fnSort(settings, order, 'asc'); 7936 7937 if (search === 'none') { 7938 a = ordered; 7939 } 7940 else { // applied | removed 7941 for (i=0; i<ordered.length; i++) { 7942 tmp = displayFiltered.indexOf(ordered[i]); 7943 7944 if ((tmp === -1 && search == 'removed') || 7945 (tmp >= 0 && search == 'applied') ) 7946 { 7947 a.push( ordered[i] ); 7948 } 7949 } 7950 } 7951 } 7952 7953 return a; 7954 }; 7955 7956 7957 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 7958 * Rows 7959 * 7960 * {} - no selector - use all available rows 7961 * {integer} - row aoData index 7962 * {node} - TR node 7963 * {string} - jQuery selector to apply to the TR elements 7964 * {array} - jQuery array of nodes, or simply an array of TR nodes 7965 * 7966 */ 7967 var __row_selector = function ( settings, selector, opts ) 7968 { 7969 var rows; 7970 var run = function ( sel ) { 7971 var selInt = _intVal( sel ); 7972 var aoData = settings.aoData; 7973 7974 // Short cut - selector is a number and no options provided (default is 7975 // all records, so no need to check if the index is in there, since it 7976 // must be - dev error if the index doesn't exist). 7977 if ( selInt !== null && ! opts ) { 7978 return [ selInt ]; 7979 } 7980 7981 if ( ! rows ) { 7982 rows = _selector_row_indexes( settings, opts ); 7983 } 7984 7985 if ( selInt !== null && rows.indexOf(selInt) !== -1 ) { 7986 // Selector - integer 7987 return [ selInt ]; 7988 } 7989 else if ( sel === null || sel === undefined || sel === '' ) { 7990 // Selector - none 7991 return rows; 7992 } 7993 7994 // Selector - function 7995 if ( typeof sel === 'function' ) { 7996 return rows.map( function (idx) { 7997 var row = aoData[ idx ]; 7998 return sel( idx, row._aData, row.nTr ) ? idx : null; 7999 } ); 8000 } 8001 8002 // Selector - node 8003 if ( sel.nodeName ) { 8004 var rowIdx = sel._DT_RowIndex; // Property added by DT for fast lookup 8005 var cellIdx = sel._DT_CellIndex; 8006 8007 if ( rowIdx !== undefined ) { 8008 // Make sure that the row is actually still present in the table 8009 return aoData[ rowIdx ] && aoData[ rowIdx ].nTr === sel ? 8010 [ rowIdx ] : 8011 []; 8012 } 8013 else if ( cellIdx ) { 8014 return aoData[ cellIdx.row ] && aoData[ cellIdx.row ].nTr === sel.parentNode ? 8015 [ cellIdx.row ] : 8016 []; 8017 } 8018 else { 8019 var host = $(sel).closest('*[data-dt-row]'); 8020 return host.length ? 8021 [ host.data('dt-row') ] : 8022 []; 8023 } 8024 } 8025 8026 // ID selector. Want to always be able to select rows by id, regardless 8027 // of if the tr element has been created or not, so can't rely upon 8028 // jQuery here - hence a custom implementation. This does not match 8029 // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything, 8030 // but to select it using a CSS selector engine (like Sizzle or 8031 // querySelect) it would need to need to be escaped for some characters. 8032 // DataTables simplifies this for row selectors since you can select 8033 // only a row. A # indicates an id any anything that follows is the id - 8034 // unescaped. 8035 if ( typeof sel === 'string' && sel.charAt(0) === '#' ) { 8036 // get row index from id 8037 var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ]; 8038 if ( rowObj !== undefined ) { 8039 return [ rowObj.idx ]; 8040 } 8041 8042 // need to fall through to jQuery in case there is DOM id that 8043 // matches 8044 } 8045
8046 // Get nodes in the order from the `rows` array with null values removed 8047 var nodes = _removeEmpty( 8048 _pluck_order( settings.aoData, rows, 'nTr' ) 8049 ); 8050 8051 // Selector - jQuery selector string, array of nodes or jQuery object/ 8052 // As jQuery's .filter() allows jQuery objects to be passed in filter, 8053 // it also allows arrays, so this will cope with all three options 8054 return $(nodes) 8055 .filter( sel ) 8056 .map( function () { 8057 return this._DT_RowIndex; 8058 } ) 8059 .toArray(); 8060 }; 8061 8062 var matched = _selector_run( 'row', selector, run, settings, opts ); 8063 8064 if (opts.order === 'current' || opts.order === 'applied') { 8065 _fnSortDisplay(settings, matched); 8066 } 8067 8068 return matched; 8069 }; 8070 8071 8072 _api_register( 'rows()', function ( selector, opts ) { 8073 // argument shifting 8074 if ( selector === undefined ) { 8075 selector = ''; 8076 } 8077 else if ( $.isPlainObject( selector ) ) { 8078 opts = selector; 8079 selector = ''; 8080 } 8081 8082 opts = _selector_opts( opts ); 8083 8084 var inst = this.iterator( 'table', function ( settings ) { 8085 return __row_selector( settings, selector, opts ); 8086 }, 1 ); 8087 8088 // Want argument shifting here and in __row_selector? 8089 inst.selector.rows = selector; 8090 inst.selector.opts = opts; 8091 8092 return inst; 8093 } ); 8094 8095 _api_register( 'rows().nodes()', function () { 8096 return this.iterator( 'row', function ( settings, row ) { 8097 return settings.aoData[ row ].nTr || undefined; 8098 }, 1 ); 8099 } ); 8100 8101 _api_register( 'rows().data()', function () { 8102 return this.iterator( true, 'rows', function ( settings, rows ) { 8103 return _pluck_order( settings.aoData, rows, '_aData' ); 8104 }, 1 ); 8105 } ); 8106 8107 _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) { 8108 return this.iterator( 'row', function ( settings, row ) { 8109 var r = settings.aoData[ row ]; 8110 return type === 'search' ? r._aFilterData : r._aSortData; 8111 }, 1 ); 8112 } ); 8113 8114 _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) { 8115 return this.iterator( 'row', function ( settings, row ) { 8116 _fnInvalidate( settings, row, src ); 8117 } ); 8118 } ); 8119 8120 _api_registerPlural( 'rows().indexes()', 'row().index()', function () { 8121 return this.iterator( 'row', function ( settings, row ) { 8122 return row; 8123 }, 1 ); 8124 } ); 8125 8126 _api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) { 8127 var a = []; 8128 var context = this.context; 8129 8130 // `iterator` will drop undefined values, but in this case we want them 8131 for ( var i=0, ien=context.length ; i<ien ; i++ ) { 8132 for ( var j=0, jen=this[i].length ; j<jen ; j++ ) { 8133 var id = context[i].rowIdFn( context[i].aoData[ this[i][j] ]._aData ); 8134 a.push( (hash === true ? '#' : '' )+ id ); 8135 } 8136 } 8137 8138 return new _Api( context, a ); 8139 } ); 8140 8141 _api_registerPlural( 'rows().remove()', 'row().remove()', function () { 8142 this.iterator( 'row', function ( settings, row ) { 8143 var data = settings.aoData; 8144 var rowData = data[ row ]; 8145 8146 // Delete from the display arrays 8147 var idx = settings.aiDisplayMaster.indexOf(row); 8148 if (idx !== -1) { 8149 settings.aiDisplayMaster.splice(idx, 1); 8150 } 8151 8152 // For server-side processing tables - subtract the deleted row from the count 8153 if ( settings._iRecordsDisplay > 0 ) { 8154 settings._iRecordsDisplay--; 8155 } 8156 8157 // Check for an 'overflow' they case for displaying the table 8158 _fnLengthOverflow( settings ); 8159 8160 // Remove the row's ID reference if there is one 8161 var id = settings.rowIdFn( rowData._aData ); 8162 if ( id !== undefined ) { 8163 delete settings.aIds[ id ]; 8164 } 8165 8166 data[row] = null; 8167 } ); 8168 8169 return this; 8170 } ); 8171 8172 8173 _api_register( 'rows.add()', function ( rows ) { 8174 var newRows = this.iterator( 'table', function ( settings ) { 8175 var row, i, ien; 8176 var out = []; 8177 8178 for ( i=0, ien=rows.length ; i<ien ; i++ ) { 8179 row = rows[i]; 8180 8181 if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) { 8182 out.push( _fnAddTr( settings, row )[0] ); 8183 } 8184 else { 8185 out.push( _fnAddData( settings, row ) ); 8186 } 8187 } 8188 8189 return out; 8190 }, 1 ); 8191 8192 // Return an Api.rows() extended instance, so rows().nodes() etc can be used 8193 var modRows = this.rows( -1 ); 8194 modRows.pop(); 8195 _fnArrayApply(modRows, newRows); 8196 8197 return modRows; 8198 } ); 8199 8200 8201 8202 8203 8204 /** 8205 * 8206 */ 8207 _api_register( 'row()', function ( selector, opts ) { 8208 return _selector_first( this.rows( selector, opts ) ); 8209 } ); 8210 8211 8212 _api_register( 'row().data()', function ( data ) { 8213 var ctx = this.context; 8214 8215 if ( data === undefined ) { 8216 // Get 8217 return ctx.length && this.length && this[0].length ? 8218 ctx[0].aoData[ this[0] ]._aData : 8219 undefined; 8220 } 8221 8222 // Set 8223 var row = ctx[0].aoData[ this[0] ]; 8224 row._aData = data; 8225 8226 // If the DOM has an id, and the data source is an array 8227 if ( Array.isArray( data ) && row.nTr && row.nTr.id ) { 8228 _fnSetObjectDataFn( ctx[0].rowId )( data, row.nTr.id ); 8229 } 8230 8231 // Automatically invalidate 8232 _fnInvalidate( ctx[0], this[0], 'data' ); 8233 8234 return this; 8235 } ); 8236 8237 8238 _api_register( 'row().node()', function () { 8239 var ctx = this.context; 8240 8241 if (ctx.length && this.length && this[0].length) { 8242 var row = ctx[0].aoData[ this[0] ]; 8243 8244 if (row && row.nTr) { 8245 return row.nTr; 8246 } 8247 } 8248 8249 return null; 8250 } ); 8251 8252 8253 _api_register( 'row.add()', function ( row ) { 8254 // Allow a jQuery object to be passed in - only a single row is added from 8255 // it though - the first element in the set 8256 if ( row instanceof $ && row.length ) { 8257 row = row[0]; 8258 } 8259 8260 var rows = this.iterator( 'table', function ( settings ) { 8261 if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) { 8262 return _fnAddTr( settings, row )[0]; 8263 } 8264 return _fnAddData( settings, row ); 8265 } ); 8266 8267 // Return an Api.rows() extended instance, with the newly added row selected 8268 return this.row( rows[0] ); 8269 } ); 8270 8271 8272 $(document).on('plugin-init.dt', function (e, context) { 8273 var api = new _Api( context ); 8274 8275 api.on( 'stateSaveParams.DT', function ( e, settings, d ) { 8276 // This could be more compact with the API, but it is a lot faster as a simple 8277 // internal loop 8278 var idFn = settings.rowIdFn; 8279 var rows = settings.aiDisplayMaster; 8280 var ids = []; 8281 8282 for (var i=0 ; i<rows.length ; i++) { 8283 var rowIdx = rows[i]; 8284 var data = settings.aoData[rowIdx]; 8285 8286 if (data._detailsShow) { 8287 ids.push( '#' + idFn(data._aData) ); 8288 } 8289 } 8290 8291 d.childRows = ids; 8292 }); 8293 8294 // For future state loads (e.g. with StateRestore) 8295 api.on( 'stateLoaded.DT', function (e, settings, state) { 8296 __details_state_load( api, state ); 8297 }); 8298 8299 // And the initial load state 8300 __details_state_load( api, api.state.loaded() ); 8301 }); 8302 8303 var __details_state_load = function (api, state) 8304 { 8305 if ( state && state.childRows ) { 8306 api 8307 .rows( state.childRows.map(function (id) { 8308 // Escape any `:` characters from the row id. Accounts for 8309 // already escaped characters. 8310 return id.replace(/([^:\\]*(?:\\.[^:\\]*)*):/g, "$1\\:"); 8311 }) ) 8312 .every( function () { 8313 _fnCallbackFire( api.settings()[0], null, 'requestChild', [ this ] ) 8314 }); 8315 } 8316 } 8317 8318 var __details_add = function ( ctx, row, data, klass ) 8319 { 8320 // Convert to array of TR elements 8321 var rows = []; 8322 var addRow = function ( r, k ) { 8323 // Recursion to allow for arrays of jQuery objects 8324 if ( Array.isArray( r ) || r instanceof $ ) { 8325 for ( var i=0, ien=r.length ; i<ien ; i++ ) { 8326 addRow( r[i], k ); 8327 } 8328 return; 8329 } 8330 8331 // If we get a TR element, then just add it directly - up to the dev 8332 // to add the correct number of columns etc 8333 if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) { 8334 r.setAttribute( 'data-dt-row', row.idx ); 8335 rows.push( r ); 8336 } 8337 else { 8338 // Otherwise create a row with a wrapper 8339 var created = $('<tr><td></td></tr>') 8340 .attr( 'data-dt-row', row.idx ) 8341 .addClass( k ); 8342 8343 $('td', created) 8344 .addClass( k ) 8345 .html( r )[0].colSpan = _fnVisbleColumns( ctx ); 8346 8347 rows.push( created[0] ); 8348 } 8349 }; 8350 8351 addRow( data, klass ); 8352 8353 if ( row._details ) { 8354 row._details.detach(); 8355 } 8356 8357 row._details = $(rows); 8358 8359 // If the children were already shown, that state should be retained 8360 if ( row._detailsShow ) { 8361 row._details.insertAfter( row.nTr ); 8362 } 8363 }; 8364 8365 8366 // Make state saving of child row details async to allow them to be batch processed 8367 var __details_state = DataTable.util.throttle( 8368 function (ctx) { 8369 _fnSaveState( ctx[0] ) 8370 }, 8371 500 8372 ); 8373 8374 8375 var __details_remove = function ( api, idx ) 8376 { 8377 var ctx = api.context; 8378 8379 if ( ctx.length ) { 8380 var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ]; 8381 8382 if ( row && row._details ) { 8383 row._details.remove(); 8384 8385 row._detailsShow = undefined; 8386 row._details = undefined; 8387 $( row.nTr ).removeClass( 'dt-hasChild' ); 8388 __details_state( ctx ); 8389 } 8390 } 8391 }; 8392 8393
8394 var __details_display = function ( api, show ) { 8395 var ctx = api.context; 8396 8397 if ( ctx.length && api.length ) { 8398 var row = ctx[0].aoData[ api[0] ]; 8399 8400 if ( row._details ) { 8401 row._detailsShow = show; 8402 8403 if ( show ) { 8404 row._details.insertAfter( row.nTr ); 8405 $( row.nTr ).addClass( 'dt-hasChild' ); 8406 } 8407 else { 8408 row._details.detach(); 8409 $( row.nTr ).removeClass( 'dt-hasChild' ); 8410 } 8411 8412 _fnCallbackFire( ctx[0], null, 'childRow', [ show, api.row( api[0] ) ] ) 8413 8414 __details_events( ctx[0] ); 8415 __details_state( ctx ); 8416 } 8417 } 8418 }; 8419 8420 8421 var __details_events = function ( settings ) 8422 { 8423 var api = new _Api( settings ); 8424 var namespace = '.dt.DT_details'; 8425 var drawEvent = 'draw'+namespace; 8426 var colvisEvent = 'column-sizing'+namespace; 8427 var destroyEvent = 'destroy'+namespace; 8428 var data = settings.aoData; 8429 8430 api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent ); 8431 8432 if ( _pluck( data, '_details' ).length > 0 ) { 8433 // On each draw, insert the required elements into the document 8434 api.on( drawEvent, function ( e, ctx ) { 8435 if ( settings !== ctx ) { 8436 return; 8437 } 8438 8439 api.rows( {page:'current'} ).eq(0).each( function (idx) { 8440 // Internal data grab 8441 var row = data[ idx ]; 8442 8443 if ( row._detailsShow ) { 8444 row._details.insertAfter( row.nTr ); 8445 } 8446 } ); 8447 } ); 8448 8449 // Column visibility change - update the colspan 8450 api.on( colvisEvent, function ( e, ctx ) { 8451 if ( settings !== ctx ) { 8452 return; 8453 } 8454 8455 // Update the colspan for the details rows (note, only if it already has 8456 // a colspan) 8457 var row, visible = _fnVisbleColumns( ctx ); 8458 8459 for ( var i=0, ien=data.length ; i<ien ; i++ ) { 8460 row = data[i]; 8461 8462 if ( row && row._details ) { 8463 row._details.each(function () { 8464 var el = $(this).children('td'); 8465 8466 if (el.length == 1) { 8467 el.attr('colspan', visible); 8468 } 8469 }); 8470 } 8471 } 8472 } ); 8473 8474 // Table destroyed - nuke any child rows 8475 api.on( destroyEvent, function ( e, ctx ) { 8476 if ( settings !== ctx ) { 8477 return; 8478 } 8479 8480 for ( var i=0, ien=data.length ; i<ien ; i++ ) { 8481 if ( data[i] && data[i]._details ) { 8482 __details_remove( api, i ); 8483 } 8484 } 8485 } ); 8486 } 8487 }; 8488 8489 // Strings for the method names to help minification 8490 var _emp = ''; 8491 var _child_obj = _emp+'row().child'; 8492 var _child_mth = _child_obj+'()'; 8493 8494 // data can be: 8495 // tr 8496 // string 8497 // jQuery or array of any of the above 8498 _api_register( _child_mth, function ( data, klass ) { 8499 var ctx = this.context; 8500 8501 if ( data === undefined ) { 8502 // get 8503 return ctx.length && this.length && ctx[0].aoData[ this[0] ] 8504 ? ctx[0].aoData[ this[0] ]._details 8505 : undefined; 8506 } 8507 else if ( data === true ) { 8508 // show 8509 this.child.show(); 8510 } 8511 else if ( data === false ) { 8512 // remove 8513 __details_remove( this ); 8514 } 8515 else if ( ctx.length && this.length ) { 8516 // set 8517 __details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass ); 8518 } 8519 8520 return this; 8521 } ); 8522 8523 8524 _api_register( [ 8525 _child_obj+'.show()', 8526 _child_mth+'.show()' // only when `child()` was called with parameters (without 8527 ], function () { // it returns an object and this method is not executed) 8528 __details_display( this, true ); 8529 return this; 8530 } ); 8531 8532 8533 _api_register( [ 8534 _child_obj+'.hide()', 8535 _child_mth+'.hide()' // only when `child()` was called with parameters (without 8536 ], function () { // it returns an object and this method is not executed) 8537 __details_display( this, false ); 8538 return this; 8539 } ); 8540 8541 8542 _api_register( [ 8543 _child_obj+'.remove()', 8544 _child_mth+'.remove()' // only when `child()` was called with parameters (without 8545 ], function () { // it returns an object and this method is not executed) 8546 __details_remove( this ); 8547 return this; 8548 } ); 8549 8550 8551 _api_register( _child_obj+'.isShown()', function () { 8552 var ctx = this.context; 8553 8554 if ( ctx.length && this.length && ctx[0].aoData[ this[0] ] ) { 8555 // _detailsShown as false or undefined will fall through to return false 8556 return ctx[0].aoData[ this[0] ]._detailsShow || false; 8557 } 8558 return false;
8559 } ); 8560 8561 8562 8563 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 8564 * Columns 8565 * 8566 * {integer} - column index (>=0 count from left, <0 count from right) 8567 * "{integer}:visIdx" - visible column index (i.e. translate to column index) (>=0 count from left, <0 count from right) 8568 * "{integer}:visible" - alias for {integer}:visIdx (>=0 count from left, <0 count from right) 8569 * "{string}:name" - column name 8570 * "{string}" - jQuery selector on column header nodes 8571 * 8572 */ 8573 8574 // can be an array of these items, comma separated list, or an array of comma 8575 // separated lists 8576 8577 var __re_column_selector = /^([^:]+)?:(name|title|visIdx|visible)$/; 8578 8579 8580 // r1 and r2 are redundant - but it means that the parameters match for the 8581 // iterator callback in columns().data() 8582 var __columnData = function ( settings, column, r1, r2, rows, type ) { 8583 var a = []; 8584 for ( var row=0, ien=rows.length ; row<ien ; row++ ) { 8585 a.push( _fnGetCellData( settings, rows[row], column, type ) ); 8586 } 8587 return a; 8588 }; 8589 8590 8591 var __column_header = function ( settings, column, row ) { 8592 var header = settings.aoHeader; 8593 var target = row !== undefined 8594 ? row 8595 : settings.bSortCellsTop // legacy support 8596 ? 0 8597 : header.length - 1; 8598 8599 return header[target][column].cell; 8600 }; 8601 8602 var __column_selector = function ( settings, selector, opts ) 8603 { 8604 var 8605 columns = settings.aoColumns, 8606 names = _pluck( columns, 'sName' ), 8607 titles = _pluck( columns, 'sTitle' ), 8608 cells = DataTable.util.get('[].[].cell')(settings.aoHeader), 8609 nodes = _unique( _flatten([], cells) ); 8610 8611 var run = function ( s ) { 8612 var selInt = _intVal( s ); 8613 8614 // Selector - all 8615 if ( s === '' ) { 8616 return _range( columns.length ); 8617 } 8618 8619 // Selector - index 8620 if ( selInt !== null ) { 8621 return [ selInt >= 0 ? 8622 selInt : // Count from left 8623 columns.length + selInt // Count from right (+ because its a negative value) 8624 ]; 8625 } 8626 8627 // Selector = function 8628 if ( typeof s === 'function' ) { 8629 var rows = _selector_row_indexes( settings, opts ); 8630 8631 return columns.map(function (col, idx) { 8632 return s( 8633 idx, 8634 __columnData( settings, idx, 0, 0, rows ), 8635 __column_header( settings, idx ) 8636 ) ? idx : null; 8637 }); 8638 } 8639 8640 // jQuery or string selector 8641 var match = typeof s === 'string' ? 8642 s.match( __re_column_selector ) : 8643 ''; 8644 8645 if ( match ) { 8646 switch( match[2] ) { 8647 case 'visIdx': 8648 case 'visible': 8649 // Selector is a column index 8650 if (match[1] && match[1].match(/^\d+$/)) { 8651 var idx = parseInt( match[1], 10 ); 8652 8653 // Visible index given, convert to column index 8654 if ( idx < 0 ) { 8655 // Counting from the right 8656 var visColumns = columns.map( function (col,i) { 8657 return col.bVisible ? i : null; 8658 } ); 8659 return [ visColumns[ visColumns.length + idx ] ]; 8660 } 8661 // Counting from the left 8662 return [ _fnVisibleToColumnIndex( settings, idx ) ]; 8663 } 8664 8665 return columns.map( function (col, idx) { 8666 // Not visible, can't match 8667 if (! col.bVisible) { 8668 return null; 8669 } 8670 8671 // Selector 8672 if (match[1]) { 8673 return $(nodes[idx]).filter(match[1]).length > 0 ? idx : null; 8674 } 8675 8676 // `:visible` on its own 8677 return idx; 8678 } ); 8679 8680 case 'name': 8681 // match by name. `names` is column index complete and in order 8682 return names.map( function (name, i) { 8683 return name === match[1] ? i : null; 8684 } ); 8685 8686 case 'title': 8687 // match by column title 8688 return titles.map( function (title, i) { 8689 return title === match[1] ? i : null; 8690 } ); 8691 8692 default: 8693 return []; 8694 } 8695 } 8696 8697 // Cell in the table body 8698 if ( s.nodeName && s._DT_CellIndex ) { 8699 return [ s._DT_CellIndex.column ]; 8700 } 8701 8702 // jQuery selector on the TH elements for the columns 8703 var jqResult = $( nodes ) 8704 .filter( s ) 8705 .map( function () { 8706 return _fnColumnsFromHeader( this ); // `nodes` is column index complete and in order 8707 } ) 8708 .toArray() 8709 .sort(function (a, b) { 8710 return a - b; 8711 }); 8712 8713 if ( jqResult.length || ! s.nodeName ) {
8714 return jqResult; 8715 } 8716 8717 // Otherwise a node which might have a `dt-column` data attribute, or be 8718 // a child or such an element 8719 var host = $(s).closest('*[data-dt-column]'); 8720 return host.length ? 8721 [ host.data('dt-column') ] : 8722 []; 8723 }; 8724 8725 return _selector_run( 'column', selector, run, settings, opts ); 8726 }; 8727 8728 8729 var __setColumnVis = function ( settings, column, vis ) { 8730 var 8731 cols = settings.aoColumns, 8732 col = cols[ column ], 8733 data = settings.aoData, 8734 cells, i, ien, tr; 8735 8736 // Get 8737 if ( vis === undefined ) { 8738 return col.bVisible; 8739 } 8740 8741 // Set 8742 // No change 8743 if ( col.bVisible === vis ) { 8744 return false; 8745 } 8746 8747 if ( vis ) { 8748 // Insert column 8749 // Need to decide if we should use appendChild or insertBefore 8750 var insertBefore = _pluck(cols, 'bVisible').indexOf(true, column+1); 8751 8752 for ( i=0, ien=data.length ; i<ien ; i++ ) { 8753 if (data[i]) { 8754 tr = data[i].nTr; 8755 cells = data[i].anCells; 8756 8757 if ( tr ) { 8758 // insertBefore can act like appendChild if 2nd arg is null 8759 tr.insertBefore( cells[ column ], cells[ insertBefore ] || null ); 8760 } 8761 } 8762 } 8763 } 8764 else { 8765 // Remove column 8766 $( _pluck( settings.aoData, 'anCells', column ) ).detach(); 8767 } 8768 8769 // Common actions 8770 col.bVisible = vis; 8771 8772 _colGroup(settings); 8773 8774 return true; 8775 }; 8776 8777 8778 _api_register( 'columns()', function ( selector, opts ) { 8779 // argument shifting 8780 if ( selector === undefined ) { 8781 selector = ''; 8782 } 8783 else if ( $.isPlainObject( selector ) ) { 8784 opts = selector; 8785 selector = ''; 8786 } 8787 8788 opts = _selector_opts( opts ); 8789 8790 var inst = this.iterator( 'table', function ( settings ) { 8791 return __column_selector( settings, selector, opts ); 8792 }, 1 ); 8793 8794 // Want argument shifting here and in _row_selector? 8795 inst.selector.cols = selector; 8796 inst.selector.opts = opts; 8797 8798 return inst; 8799 } ); 8800 8801 _api_registerPlural( 'columns().header()', 'column().header()', function ( row ) { 8802 return this.iterator( 'column', function (settings, column) { 8803 return __column_header(settings, column, row); 8804 }, 1 ); 8805 } ); 8806 8807 _api_registerPlural( 'columns().footer()', 'column().footer()', function ( row ) { 8808 return this.iterator( 'column', function ( settings, column ) { 8809 var footer = settings.aoFooter; 8810 8811 if (! footer.length) { 8812 return null; 8813 } 8814 8815 return settings.aoFooter[row !== undefined ? row : 0][column].cell; 8816 }, 1 ); 8817 } ); 8818 8819 _api_registerPlural( 'columns().data()', 'column().data()', function () { 8820 return this.iterator( 'column-rows', __columnData, 1 ); 8821 } ); 8822 8823 _api_registerPlural( 'columns().render()', 'column().render()', function ( type ) { 8824 return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { 8825 return __columnData( settings, column, i, j, rows, type ); 8826 }, 1 ); 8827 } ); 8828 8829 _api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () { 8830 return this.iterator( 'column', function ( settings, column ) { 8831 return settings.aoColumns[column].mData; 8832 }, 1 ); 8833 } ); 8834 8835 _api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) { 8836 return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { 8837 return _pluck_order( settings.aoData, rows, 8838 type === 'search' ? '_aFilterData' : '_aSortData', column 8839 ); 8840 }, 1 ); 8841 } ); 8842 8843 _api_registerPlural( 'columns().init()', 'column().init()', function () { 8844 return this.iterator( 'column', function ( settings, column ) { 8845 return settings.aoColumns[column]; 8846 }, 1 ); 8847 } ); 8848 8849 _api_registerPlural( 'columns().nodes()', 'column().nodes()', function () { 8850 return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { 8851 return _pluck_order( settings.aoData, rows, 'anCells', column ) ; 8852 }, 1 ); 8853 } ); 8854 8855 _api_registerPlural( 'columns().titles()', 'column().title()', function (title, row) { 8856 return this.iterator( 'column', function ( settings, column ) { 8857 // Argument shifting 8858 if (typeof title === 'number') { 8859 row = title; 8860 title = undefined; 8861 } 8862 8863 var span = $('span.dt-column-title', this.column(column).header(row)); 8864 8865 if (title !== undefined) { 8866 span.html(title); 8867 return this; 8868 } 8869 8870 return span.html(); 8871 }, 1 ); 8872 } ); 8873 8874 _api_registerPlural( 'columns().types()', 'column().type()', function () { 8875 return this.iterator( 'column', function ( settings, column ) { 8876 var type = settings.aoColumns[column].sType; 8877
8878 // If the type was invalidated, then resolve it. This actually does 8879 // all columns at the moment. Would only happen once if getting all 8880 // column's data types. 8881 if (! type) { 8882 _fnColumnTypes(settings); 8883 } 8884 8885 return type; 8886 }, 1 ); 8887 } ); 8888 8889 _api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) { 8890 var that = this; 8891 var changed = []; 8892 var ret = this.iterator( 'column', function ( settings, column ) { 8893 if ( vis === undefined ) { 8894 return settings.aoColumns[ column ].bVisible; 8895 } // else 8896 8897 if (__setColumnVis( settings, column, vis )) { 8898 changed.push(column); 8899 } 8900 } ); 8901 8902 // Group the column visibility changes 8903 if ( vis !== undefined ) { 8904 this.iterator( 'table', function ( settings ) { 8905 // Redraw the header after changes 8906 _fnDrawHead( settings, settings.aoHeader ); 8907 _fnDrawHead( settings, settings.aoFooter ); 8908 8909 // Update colspan for no records display. Child rows and extensions will use their own 8910 // listeners to do this - only need to update the empty table item here 8911 if ( ! settings.aiDisplay.length ) { 8912 $(settings.nTBody).find('td[colspan]').attr('colspan', _fnVisbleColumns(settings)); 8913 } 8914 8915 _fnSaveState( settings ); 8916 8917 // Second loop once the first is done for events 8918 that.iterator( 'column', function ( settings, column ) { 8919 if (changed.includes(column)) { 8920 _fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] ); 8921 } 8922 } ); 8923 8924 if ( changed.length && (calc === undefined || calc) ) { 8925 that.columns.adjust(); 8926 } 8927 }); 8928 } 8929 8930 return ret; 8931 } ); 8932 8933 _api_registerPlural( 'columns().widths()', 'column().width()', function () { 8934 // Injects a fake row into the table for just a moment so the widths can 8935 // be read, regardless of colspan in the header and rows being present in 8936 // the body 8937 var columns = this.columns(':visible').count(); 8938 var row = $('<tr>').html('<td>' + Array(columns).join('</td><td>') + '</td>'); 8939 8940 $(this.table().body()).append(row); 8941 8942 var widths = row.children().map(function () { 8943 return $(this).outerWidth(); 8944 }); 8945 8946 row.remove(); 8947 8948 return this.iterator( 'column', function ( settings, column ) { 8949 var visIdx = _fnColumnIndexToVisible( settings, column ); 8950 8951 return visIdx !== null ? widths[visIdx] : 0; 8952 }, 1); 8953 } ); 8954 8955 _api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) { 8956 return this.iterator( 'column', function ( settings, column ) { 8957 return type === 'visible' ? 8958 _fnColumnIndexToVisible( settings, column ) : 8959 column; 8960 }, 1 ); 8961 } ); 8962 8963 _api_register( 'columns.adjust()', function () { 8964 return this.iterator( 'table', function ( settings ) { 8965 // Force a column sizing to happen with a manual call - otherwise it can skip 8966 // if the size hasn't changed 8967 settings.containerWidth = -1; 8968 8969 _fnAdjustColumnSizing( settings ); 8970 }, 1 ); 8971 } ); 8972 8973 _api_register( 'column.index()', function ( type, idx ) { 8974 if ( this.context.length !== 0 ) { 8975 var ctx = this.context[0]; 8976 8977 if ( type === 'fromVisible' || type === 'toData' ) { 8978 return _fnVisibleToColumnIndex( ctx, idx ); 8979 } 8980 else if ( type === 'fromData' || type === 'toVisible' ) { 8981 return _fnColumnIndexToVisible( ctx, idx ); 8982 } 8983 } 8984 } ); 8985 8986 _api_register( 'column()', function ( selector, opts ) { 8987 return _selector_first( this.columns( selector, opts ) ); 8988 } ); 8989 8990 var __cell_selector = function ( settings, selector, opts ) 8991 { 8992 var data = settings.aoData; 8993 var rows = _selector_row_indexes( settings, opts ); 8994 var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) ); 8995 var allCells = $(_flatten( [], cells )); 8996 var row; 8997 var columns = settings.aoColumns.length; 8998 var a, i, ien, j, o, host; 8999 9000 var run = function ( s ) { 9001 var fnSelector = typeof s === 'function'; 9002 9003 if ( s === null || s === undefined || fnSelector ) { 9004 // All cells and function selectors 9005 a = []; 9006 9007 for ( i=0, ien=rows.length ; i<ien ; i++ ) { 9008 row = rows[i]; 9009 9010 for ( j=0 ; j<columns ; j++ ) { 9011 o = { 9012 row: row, 9013 column: j 9014 }; 9015 9016 if ( fnSelector ) { 9017 // Selector - function 9018 host = data[ row ]; 9019 9020 if ( s( o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null ) ) { 9021 a.push( o ); 9022 } 9023 } 9024 else { 9025 // Selector - all 9026 a.push( o ); 9027 } 9028 } 9029 } 9030 9031 return a; 9032 } 9033 9034 // Selector - index 9035 if ( $.isPlainObject( s ) ) { 9036 // Valid cell index and its in the array of selectable rows 9037 return s.column !== undefined && s.row !== undefined && rows.indexOf(s.row) !== -1 ? 9038 [s] : 9039 []; 9040 } 9041 9042 // Selector - jQuery filtered cells 9043 var jqResult = allCells 9044 .filter( s ) 9045 .map( function (i, el) { 9046 return { // use a new object, in case someone changes the values 9047 row: el._DT_CellIndex.row, 9048 column: el._DT_CellIndex.column 9049 }; 9050 } ) 9051 .toArray(); 9052 9053 if ( jqResult.length || ! s.nodeName ) {
9054 return jqResult; 9055 } 9056 9057 // Otherwise the selector is a node, and there is one last option - the 9058 // element might be a child of an element which has dt-row and dt-column 9059 // data attributes 9060 host = $(s).closest('*[data-dt-row]'); 9061 return host.length ? 9062 [ { 9063 row: host.data('dt-row'), 9064 column: host.data('dt-column') 9065 } ] : 9066 []; 9067 }; 9068 9069 return _selector_run( 'cell', selector, run, settings, opts ); 9070 }; 9071 9072 9073 9074 9075 _api_register( 'cells()', function ( rowSelector, columnSelector, opts ) { 9076 // Argument shifting 9077 if ( $.isPlainObject( rowSelector ) ) { 9078 // Indexes 9079 if ( rowSelector.row === undefined ) { 9080 // Selector options in first parameter 9081 opts = rowSelector; 9082 rowSelector = null; 9083 } 9084 else { 9085 // Cell index objects in first parameter 9086 opts = columnSelector; 9087 columnSelector = null; 9088 } 9089 } 9090 if ( $.isPlainObject( columnSelector ) ) { 9091 opts = columnSelector; 9092 columnSelector = null; 9093 } 9094 9095 // Cell selector 9096 if ( columnSelector === null || columnSelector === undefined ) { 9097 return this.iterator( 'table', function ( settings ) { 9098 return __cell_selector( settings, rowSelector, _selector_opts( opts ) ); 9099 } ); 9100 } 9101 9102 // The default built in options need to apply to row and columns 9103 var internalOpts = opts ? { 9104 page: opts.page, 9105 order: opts.order, 9106 search: opts.search 9107 } : {}; 9108 9109 // Row + column selector 9110 var columns = this.columns( columnSelector, internalOpts ); 9111 var rows = this.rows( rowSelector, internalOpts ); 9112 var i, ien, j, jen; 9113 9114 var cellsNoOpts = this.iterator( 'table', function ( settings, idx ) { 9115 var a = []; 9116 9117 for ( i=0, ien=rows[idx].length ; i<ien ; i++ ) { 9118 for ( j=0, jen=columns[idx].length ; j<jen ; j++ ) { 9119 a.push( { 9120 row: rows[idx][i], 9121 column: columns[idx][j] 9122 } ); 9123 } 9124 } 9125 9126 return a; 9127 }, 1 ); 9128 9129 // There is currently only one extension which uses a cell selector extension 9130 // It is a _major_ performance drag to run this if it isn't needed, so this is 9131 // an extension specific check at the moment 9132 var cells = opts && opts.selected ? 9133 this.cells( cellsNoOpts, opts ) : 9134 cellsNoOpts; 9135 9136 $.extend( cells.selector, { 9137 cols: columnSelector, 9138 rows: rowSelector, 9139 opts: opts 9140 } ); 9141 9142 return cells; 9143 } ); 9144 9145 9146 _api_registerPlural( 'cells().nodes()', 'cell().node()', function () { 9147 return this.iterator( 'cell', function ( settings, row, column ) { 9148 var data = settings.aoData[ row ]; 9149 9150 return data && data.anCells ? 9151 data.anCells[ column ] : 9152 undefined; 9153 }, 1 ); 9154 } ); 9155 9156 9157 _api_register( 'cells().data()', function () { 9158 return this.iterator( 'cell', function ( settings, row, column ) { 9159 return _fnGetCellData( settings, row, column ); 9160 }, 1 ); 9161 } ); 9162 9163 9164 _api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) { 9165 type = type === 'search' ? '_aFilterData' : '_aSortData'; 9166 9167 return this.iterator( 'cell', function ( settings, row, column ) { 9168 return settings.aoData[ row ][ type ][ column ]; 9169 }, 1 ); 9170 } ); 9171 9172 9173 _api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) { 9174 return this.iterator( 'cell', function ( settings, row, column ) { 9175 return _fnGetCellData( settings, row, column, type ); 9176 }, 1 ); 9177 } ); 9178 9179 9180 _api_registerPlural( 'cells().indexes()', 'cell().index()', function () { 9181 return this.iterator( 'cell', function ( settings, row, column ) { 9182 return { 9183 row: row, 9184 column: column, 9185 columnVisible: _fnColumnIndexToVisible( settings, column ) 9186 }; 9187 }, 1 ); 9188 } ); 9189 9190 9191 _api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) { 9192 return this.iterator( 'cell', function ( settings, row, column ) { 9193 _fnInvalidate( settings, row, src, column ); 9194 } ); 9195 } ); 9196 9197 9198 9199 _api_register( 'cell()', function ( rowSelector, columnSelector, opts ) { 9200 return _selector_first( this.cells( rowSelector, columnSelector, opts ) ); 9201 } ); 9202 9203 9204 _api_register( 'cell().data()', function ( data ) { 9205 var ctx = this.context; 9206 var cell = this[0]; 9207 9208 if ( data === undefined ) { 9209 // Get 9210 return ctx.length && cell.length ? 9211 _fnGetCellData( ctx[0], cell[0].row, cell[0].column ) : 9212 undefined; 9213 } 9214 9215 // Set 9216 _fnSetCellData( ctx[0], cell[0].row, cell[0].column, data ); 9217 _fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column ); 9218 9219 return this; 9220 } ); 9221 9222 9223 9224 /** 9225 * Get current ordering (sorting) that has been applied to the table. 9226 * 9227 * @returns {array} 2D array containing the sorting information for the first 9228 * table in the current context. Each element in the parent array represents 9229 * a column being sorted upon (i.e. multi-sorting with two columns would have 9230 * 2 inner arrays). The inner arrays may have 2 or 3 elements. The first is 9231 * the column index that the sorting condition applies to, the second is the 9232 * direction of the sort (`desc` or `asc`) and, optionally, the third is the 9233 * index of the sorting order from the `column.sorting` initialisation array.
9234 *//** 9235 * Set the ordering for the table. 9236 * 9237 * @param {integer} order Column index to sort upon. 9238 * @param {string} direction Direction of the sort to be applied (`asc` or `desc`) 9239 * @returns {DataTables.Api} this 9240 *//** 9241 * Set the ordering for the table. 9242 * 9243 * @param {array} order 1D array of sorting information to be applied. 9244 * @param {array} [...] Optional additional sorting conditions 9245 * @returns {DataTables.Api} this 9246 *//** 9247 * Set the ordering for the table. 9248 * 9249 * @param {array} order 2D array of sorting information to be applied. 9250 * @returns {DataTables.Api} this 9251 */ 9252 _api_register( 'order()', function ( order, dir ) { 9253 var ctx = this.context; 9254 var args = Array.prototype.slice.call( arguments ); 9255 9256 if ( order === undefined ) { 9257 // get 9258 return ctx.length !== 0 ? 9259 ctx[0].aaSorting : 9260 undefined; 9261 } 9262 9263 // set 9264 if ( typeof order === 'number' ) { 9265 // Simple column / direction passed in 9266 order = [ [ order, dir ] ]; 9267 } 9268 else if ( args.length > 1 ) { 9269 // Arguments passed in (list of 1D arrays) 9270 order = args; 9271 } 9272 // otherwise a 2D array was passed in 9273 9274 return this.iterator( 'table', function ( settings ) { 9275 settings.aaSorting = Array.isArray(order) ? order.slice() : order; 9276 } ); 9277 } ); 9278 9279 9280 /** 9281 * Attach a sort listener to an element for a given column 9282 * 9283 * @param {node|jQuery|string} node Identifier for the element(s) to attach the 9284 * listener to. This can take the form of a single DOM node, a jQuery 9285 * collection of nodes or a jQuery selector which will identify the node(s). 9286 * @param {integer} column the column that a click on this node will sort on 9287 * @param {function} [callback] callback function when sort is run 9288 * @returns {DataTables.Api} this 9289 */ 9290 _api_register( 'order.listener()', function ( node, column, callback ) { 9291 return this.iterator( 'table', function ( settings ) { 9292 _fnSortAttachListener(settings, node, {}, column, callback); 9293 } ); 9294 } ); 9295 9296 9297 _api_register( 'order.fixed()', function ( set ) { 9298 if ( ! set ) { 9299 var ctx = this.context; 9300 var fixed = ctx.length ? 9301 ctx[0].aaSortingFixed : 9302 undefined; 9303 9304 return Array.isArray( fixed ) ? 9305 { pre: fixed } : 9306 fixed; 9307 } 9308 9309 return this.iterator( 'table', function ( settings ) { 9310 settings.aaSortingFixed = $.extend( true, {}, set ); 9311 } ); 9312 } ); 9313 9314 9315 // Order by the selected column(s) 9316 _api_register( [ 9317 'columns().order()', 9318 'column().order()' 9319 ], function ( dir ) { 9320 var that = this; 9321 9322 if ( ! dir ) { 9323 return this.iterator( 'column', function ( settings, idx ) { 9324 var sort = _fnSortFlatten( settings ); 9325 9326 for ( var i=0, ien=sort.length ; i<ien ; i++ ) { 9327 if ( sort[i].col === idx ) { 9328 return sort[i].dir; 9329 } 9330 } 9331 9332 return null; 9333 }, 1 ); 9334 } 9335 else { 9336 return this.iterator( 'table', function ( settings, i ) { 9337 settings.aaSorting = that[i].map( function (col) { 9338 return [ col, dir ]; 9339 } ); 9340 } ); 9341 } 9342 } ); 9343 9344 _api_registerPlural('columns().orderable()', 'column().orderable()', function ( directions ) { 9345 return this.iterator( 'column', function ( settings, idx ) { 9346 var col = settings.aoColumns[idx]; 9347 9348 return directions ? 9349 col.asSorting : 9350 col.bSortable; 9351 }, 1 ); 9352 } ); 9353 9354 9355 _api_register( 'processing()', function ( show ) { 9356 return this.iterator( 'table', function ( ctx ) { 9357 _fnProcessingDisplay( ctx, show ); 9358 } ); 9359 } ); 9360 9361 9362 _api_register( 'search()', function ( input, regex, smart, caseInsen ) { 9363 var ctx = this.context; 9364 9365 if ( input === undefined ) { 9366 // get 9367 return ctx.length !== 0 ? 9368 ctx[0].oPreviousSearch.search : 9369 undefined; 9370 } 9371 9372 // set 9373 return this.iterator( 'table', function ( settings ) { 9374 if ( ! settings.oFeatures.bFilter ) { 9375 return; 9376 } 9377 9378 if (typeof regex === 'object') { 9379 // New style options to pass to the search builder 9380 _fnFilterComplete( settings, $.extend( settings.oPreviousSearch, regex, { 9381 search: input 9382 } ) ); 9383 } 9384 else { 9385 // Compat for the old options 9386 _fnFilterComplete( settings, $.extend( settings.oPreviousSearch, { 9387 search: input, 9388 regex: regex === null ? false : regex,
9389 smart: smart === null ? true : smart, 9390 caseInsensitive: caseInsen === null ? true : caseInsen 9391 } ) ); 9392 } 9393 } ); 9394 } ); 9395 9396 _api_register( 'search.fixed()', function ( name, search ) { 9397 var ret = this.iterator( true, 'table', function ( settings ) { 9398 var fixed = settings.searchFixed; 9399 9400 if (! name) { 9401 return Object.keys(fixed) 9402 } 9403 else if (search === undefined) { 9404 return fixed[name]; 9405 } 9406 else if (search === null) { 9407 delete fixed[name]; 9408 } 9409 else { 9410 fixed[name] = search; 9411 } 9412 9413 return this; 9414 } ); 9415 9416 return name !== undefined && search === undefined 9417 ? ret[0] 9418 : ret; 9419 } ); 9420 9421 _api_registerPlural( 9422 'columns().search()', 9423 'column().search()', 9424 function ( input, regex, smart, caseInsen ) { 9425 return this.iterator( 'column', function ( settings, column ) { 9426 var preSearch = settings.aoPreSearchCols; 9427 9428 if ( input === undefined ) { 9429 // get 9430 return preSearch[ column ].search; 9431 } 9432 9433 // set 9434 if ( ! settings.oFeatures.bFilter ) { 9435 return; 9436 } 9437 9438 if (typeof regex === 'object') { 9439 // New style options to pass to the search builder 9440 $.extend( preSearch[ column ], regex, { 9441 search: input 9442 } ); 9443 } 9444 else { 9445 // Old style (with not all options available) 9446 $.extend( preSearch[ column ], { 9447 search: input, 9448 regex: regex === null ? false : regex, 9449 smart: smart === null ? true : smart, 9450 caseInsensitive: caseInsen === null ? true : caseInsen 9451 } ); 9452 } 9453 9454 _fnFilterComplete( settings, settings.oPreviousSearch ); 9455 } ); 9456 } 9457 ); 9458 9459 _api_register([ 9460 'columns().search.fixed()', 9461 'column().search.fixed()' 9462 ], 9463 function ( name, search ) { 9464 var ret = this.iterator( true, 'column', function ( settings, colIdx ) { 9465 var fixed = settings.aoColumns[colIdx].searchFixed; 9466 9467 if (! name) { 9468 return Object.keys(fixed) 9469 } 9470 else if (search === undefined) { 9471 return fixed[name]; 9472 } 9473 else if (search === null) { 9474 delete fixed[name]; 9475 } 9476 else { 9477 fixed[name] = search; 9478 } 9479 9480 return this; 9481 } ); 9482 9483 return name !== undefined && search === undefined 9484 ? ret[0] 9485 : ret; 9486 } 9487 ); 9488 /* 9489 * State API methods 9490 */ 9491 9492 _api_register( 'state()', function ( set, ignoreTime ) { 9493 // getter 9494 if ( ! set ) { 9495 return this.context.length ? 9496 this.context[0].oSavedState : 9497 null; 9498 } 9499 9500 var setMutate = $.extend( true, {}, set ); 9501 9502 // setter 9503 return this.iterator( 'table', function ( settings ) { 9504 if ( ignoreTime !== false ) { 9505 setMutate.time = +new Date() + 100; 9506 } 9507 9508 _fnImplementState( settings, setMutate, function(){} ); 9509 } ); 9510 } ); 9511 9512 9513 _api_register( 'state.clear()', function () { 9514 return this.iterator( 'table', function ( settings ) { 9515 // Save an empty object 9516 settings.fnStateSaveCallback.call( settings.oInstance, settings, {} ); 9517 } ); 9518 } ); 9519 9520 9521 _api_register( 'state.loaded()', function () { 9522 return this.context.length ? 9523 this.context[0].oLoadedState : 9524 null; 9525 } ); 9526 9527 9528 _api_register( 'state.save()', function () { 9529 return this.iterator( 'table', function ( settings ) { 9530 _fnSaveState( settings ); 9531 } ); 9532 } ); 9533 9534 // Can be assigned in DateTable.use() - note luxon and moment vars are in helpers.js 9535 var __bootstrap; 9536 var __foundation; 9537 9538 /** 9539 * Set the libraries that DataTables uses, or the global objects. 9540 * Note that the arguments can be either way around (legacy support) 9541 * and the second is optional. See docs. 9542 */ 9543 DataTable.use = function (arg1, arg2) { 9544 // Reverse arguments for legacy support 9545 var module = typeof arg1 === 'string' 9546 ? arg2 9547 : arg1; 9548 var type = typeof arg2 === 'string' 9549 ? arg2 9550 : arg1; 9551 9552 // Getter 9553 if (module === undefined && typeof type === 'string') { 9554 switch (type) { 9555 case 'lib': 9556 case 'jq': 9557 return $; 9558 9559 case 'win': 9560 return window; 9561 9562 case 'datetime': 9563 return DataTable.DateTime; 9564 9565 case 'luxon': 9566 return __luxon; 9567 9568 case 'moment': 9569 return __moment; 9570 9571 case 'bootstrap': 9572 // Use local if set, otherwise try window, which could be undefined 9573 return __bootstrap || window.bootstrap; 9574 9575 case 'foundation': 9576 // Ditto 9577 return __foundation || window.Foundation; 9578 9579 default: 9580 return null; 9581 } 9582 } 9583 9584 // Setter 9585 if (type === 'lib' || type === 'jq' || (module && module.fn && module.fn.jquery)) { 9586 $ = module; 9587 } 9588 else if (type === 'win' || (module && module.document)) { 9589 window = module; 9590 document = module.document; 9591 } 9592 else if (type === 'datetime' || (module && module.type === 'DateTime')) { 9593 DataTable.DateTime = module; 9594 } 9595 else if (type === 'luxon' || (module && module.FixedOffsetZone)) { 9596 __luxon = module; 9597 } 9598 else if (type === 'moment' || (module && module.isMoment)) { 9599 __moment = module; 9600 } 9601 else if (type === 'bootstrap' || (module && module.Modal && module.Modal.NAME === 'modal')) 9602 { 9603 // This is currently for BS5 only. BS3/4 attach to jQuery, so no need to use `.use()` 9604 __bootstrap = module; 9605 } 9606 else if (type === 'foundation' || (module && module.Reveal)) { 9607 __foundation = module; 9608 } 9609 } 9610 9611 /** 9612 * CommonJS factory function pass through. This will check if the arguments 9613 * given are a window object or a jQuery object. If so they are set 9614 * accordingly. 9615 * @param {*} root Window 9616 * @param {*} jq jQUery 9617 * @returns {boolean} Indicator 9618 */ 9619 DataTable.factory = function (root, jq) { 9620 var is = false;
9621 9622 // Test if the first parameter is a window object 9623 if (root && root.document) { 9624 window = root; 9625 document = root.document; 9626 } 9627 9628 // Test if the second parameter is a jQuery object 9629 if (jq && jq.fn && jq.fn.jquery) { 9630 $ = jq; 9631 is = true; 9632 } 9633 9634 return is; 9635 } 9636 9637 /** 9638 * Provide a common method for plug-ins to check the version of DataTables being 9639 * used, in order to ensure compatibility. 9640 * 9641 * @param {string} version Version string to check for, in the format "X.Y.Z". 9642 * Note that the formats "X" and "X.Y" are also acceptable. 9643 * @param {string} [version2=current DataTables version] As above, but optional. 9644 * If not given the current DataTables version will be used. 9645 * @returns {boolean} true if this version of DataTables is greater or equal to 9646 * the required version, or false if this version of DataTales is not 9647 * suitable 9648 * @static 9649 * @dtopt API-Static 9650 * 9651 * @example 9652 * alert( $.fn.dataTable.versionCheck( '1.9.0' ) ); 9653 */ 9654 DataTable.versionCheck = function( version, version2 ) 9655 { 9656 var aThis = version2 ? 9657 version2.split('.') : 9658 DataTable.version.split('.'); 9659 var aThat = version.split('.'); 9660 var iThis, iThat; 9661 9662 for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) { 9663 iThis = parseInt( aThis[i], 10 ) || 0; 9664 iThat = parseInt( aThat[i], 10 ) || 0; 9665 9666 // Parts are the same, keep comparing 9667 if (iThis === iThat) { 9668 continue; 9669 } 9670 9671 // Parts are different, return immediately 9672 return iThis > iThat; 9673 } 9674 9675 return true; 9676 }; 9677 9678 9679 /** 9680 * Check if a `<table>` node is a DataTable table already or not. 9681 * 9682 * @param {node|jquery|string} table Table node, jQuery object or jQuery 9683 * selector for the table to test. Note that if more than more than one 9684 * table is passed on, only the first will be checked 9685 * @returns {boolean} true the table given is a DataTable, or false otherwise 9686 * @static 9687 * @dtopt API-Static 9688 * 9689 * @example 9690 * if ( ! $.fn.DataTable.isDataTable( '#example' ) ) { 9691 * $('#example').dataTable(); 9692 * } 9693 */ 9694 DataTable.isDataTable = function ( table ) 9695 { 9696 var t = $(table).get(0); 9697 var is = false; 9698 9699 if ( table instanceof DataTable.Api ) { 9700 return true; 9701 } 9702 9703 $.each( DataTable.settings, function (i, o) { 9704 var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null; 9705 var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null; 9706 9707 if ( o.nTable === t || head === t || foot === t ) { 9708 is = true; 9709 } 9710 } ); 9711 9712 return is; 9713 }; 9714 9715 9716 /** 9717 * Get all DataTable tables that have been initialised - optionally you can 9718 * select to get only currently visible tables. 9719 * 9720 * @param {boolean} [visible=false] Flag to indicate if you want all (default) 9721 * or visible tables only. 9722 * @returns {array} Array of `table` nodes (not DataTable instances) which are 9723 * DataTables 9724 * @static 9725 * @dtopt API-Static 9726 * 9727 * @example 9728 * $.each( $.fn.dataTable.tables(true), function () { 9729 * $(table).DataTable().columns.adjust(); 9730 * } ); 9731 */ 9732 DataTable.tables = function ( visible ) 9733 { 9734 var api = false; 9735 9736 if ( $.isPlainObject( visible ) ) { 9737 api = visible.api; 9738 visible = visible.visible; 9739 } 9740 9741 var a = DataTable.settings 9742 .filter( function (o) { 9743 return !visible || (visible && $(o.nTable).is(':visible')) 9744 ? true 9745 : false; 9746 } ) 9747 .map( function (o) { 9748 return o.nTable; 9749 }); 9750 9751 return api ? 9752 new _Api( a ) : 9753 a; 9754 }; 9755 9756 9757 /** 9758 * Convert from camel case parameters to Hungarian notation. This is made public 9759 * for the extensions to provide the same ability as DataTables core to accept 9760 * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase 9761 * parameters. 9762 * 9763 * @param {object} src The model object which holds all parameters that can be 9764 * mapped. 9765 * @param {object} user The object to convert from camel case to Hungarian. 9766 * @param {boolean} force When set to `true`, properties which already have a 9767 * Hungarian value in the `user` object will be overwritten. Otherwise they 9768 * won't be. 9769 */ 9770 DataTable.camelToHungarian = _fnCamelToHungarian; 9771 9772 9773 9774 /** 9775 * 9776 */ 9777 _api_register( '$()', function ( selector, opts ) { 9778 var 9779 rows = this.rows( opts ).nodes(), // Get all rows 9780 jqRows = $(rows); 9781 9782 return $( [].concat( 9783 jqRows.filter( selector ).toArray(), 9784 jqRows.find( selector ).toArray() 9785 ) ); 9786 } ); 9787 9788 9789 // jQuery functions to operate on the tables 9790 $.each( [ 'on', 'one', 'off' ], function (i, key) { 9791 _api_register( key+'()', function ( /* event, handler */ ) { 9792 var args = Array.prototype.slice.call(arguments); 9793 9794 // Add the `dt` namespace automatically if it isn't already present 9795 args[0] = args[0].split( /\s/ ).map( function ( e ) { 9796 return ! e.match(/\.dt\b/) ? 9797 e+'.dt' : 9798 e; 9799 } ).join( ' ' ); 9800 9801 var inst = $( this.tables().nodes() ); 9802 inst[key].apply( inst, args ); 9803 return this; 9804 } ); 9805 } ); 9806 9807 9808 _api_register( 'clear()', function () { 9809 return this.iterator( 'table', function ( settings ) { 9810 _fnClearTable( settings ); 9811 } ); 9812 } ); 9813 9814 9815 _api_register( 'error()', function (msg) { 9816 return this.iterator( 'table', function ( settings ) { 9817 _fnLog( settings, 0, msg ); 9818 } ); 9819 } ); 9820 9821 9822 _api_register( 'settings()', function () { 9823 return new _Api( this.context, this.context ); 9824 } ); 9825 9826 9827 _api_register( 'init()', function () { 9828 var ctx = this.context; 9829 return ctx.length ? ctx[0].oInit : null; 9830 } ); 9831 9832 9833 _api_register( 'data()', function () { 9834 return this.iterator( 'table', function ( settings ) { 9835 return _pluck( settings.aoData, '_aData' ); 9836 } ).flatten(); 9837 } ); 9838 9839 9840 _api_register( 'trigger()', function ( name, args, bubbles ) { 9841 return this.iterator( 'table', function ( settings ) { 9842 return _fnCallbackFire( settings, null, name, args, bubbles ); 9843 } ).flatten(); 9844 } ); 9845 9846 9847 _api_register( 'ready()', function ( fn ) { 9848 var ctx = this.context; 9849 9850 // Get status of first table 9851 if (! fn) { 9852 return ctx.length 9853 ? (ctx[0]._bInitComplete || false) 9854 : null; 9855 } 9856 9857 // Function to run either once the table becomes ready or 9858 // immediately if it is already ready. 9859 return this.tables().every(function () { 9860 var api = this; 9861 9862 if (this.context[0]._bInitComplete) { 9863 fn.call(api); 9864 } 9865 else { 9866 this.on('init.dt.DT', function () { 9867 fn.call(api); 9868 }); 9869 } 9870 } ); 9871 } ); 9872 9873 9874 _api_register( 'destroy()', function ( remove ) { 9875 remove = remove || false; 9876 9877 return this.iterator( 'table', function ( settings ) { 9878 var classes = settings.oClasses; 9879 var table = settings.nTable; 9880 var tbody = settings.nTBody; 9881 var thead = settings.nTHead; 9882 var tfoot = settings.nTFoot; 9883 var jqTable = $(table); 9884 var jqTbody = $(tbody); 9885 var jqWrapper = $(settings.nTableWrapper); 9886 var rows = settings.aoData.map( function (r) { return r ? r.nTr : null; } ); 9887 var orderClasses = classes.order;
9888 9889 // Flag to note that the table is currently being destroyed - no action 9890 // should be taken 9891 settings.bDestroying = true; 9892 9893 // Fire off the destroy callbacks for plug-ins etc 9894 _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings], true ); 9895 9896 // If not being removed from the document, make all columns visible 9897 if ( ! remove ) { 9898 new _Api( settings ).columns().visible( true ); 9899 } 9900 9901 // Container width change listener 9902 if (settings.resizeObserver) { 9903 settings.resizeObserver.disconnect(); 9904 } 9905 9906 // Blitz all `DT` namespaced events (these are internal events, the 9907 // lowercase, `dt` events are user subscribed and they are responsible 9908 // for removing them 9909 jqWrapper.off('.DT').find(':not(tbody *)').off('.DT'); 9910 $(window).off('.DT-'+settings.sInstance); 9911 9912 // When scrolling we had to break the table up - restore it 9913 if ( table != thead.parentNode ) { 9914 jqTable.children('thead').detach(); 9915 jqTable.append( thead ); 9916 } 9917 9918 if ( tfoot && table != tfoot.parentNode ) { 9919 jqTable.children('tfoot').detach(); 9920 jqTable.append( tfoot ); 9921 } 9922 9923 // Clean up the header 9924 $(thead).find('span.dt-column-order').remove(); 9925 $(thead).find('span.dt-column-title').each(function () { 9926 var title = $(this).html(); 9927 $(this).parent().append(title); 9928 $(this).remove(); 9929 }); 9930 9931 settings.colgroup.remove(); 9932 9933 settings.aaSorting = []; 9934 settings.aaSortingFixed = []; 9935 _fnSortingClasses( settings ); 9936 9937 $(jqTable).find('th, td').removeClass( 9938 $.map(DataTable.ext.type.className, function (v) { 9939 return v; 9940 }).join(' ') 9941 ); 9942 9943 $('th, td', thead) 9944 .removeClass( 9945 orderClasses.none + ' ' + 9946 orderClasses.canAsc + ' ' + 9947 orderClasses.canDesc + ' ' + 9948 orderClasses.isAsc + ' ' + 9949 orderClasses.isDesc 9950 ) 9951 .css('width', '') 9952 .removeAttr('data-dt-column') 9953 .removeAttr('aria-sort'); 9954 9955 // Add the TR elements back into the table in their original order 9956 jqTbody.children().detach(); 9957 jqTbody.append( rows ); 9958 9959 var orig = settings.nTableWrapper.parentNode; 9960 var insertBefore = settings.nTableWrapper.nextSibling; 9961 9962 // Remove the DataTables generated nodes, events and classes 9963 var removedMethod = remove ? 'remove' : 'detach'; 9964 jqTable[ removedMethod ](); 9965 jqWrapper[ removedMethod ](); 9966 9967 // If we need to reattach the table to the document 9968 if ( ! remove && orig ) { 9969 // insertBefore acts like appendChild if !arg[1] 9970 orig.insertBefore( table, insertBefore ); 9971 9972 // Restore the width of the original table - was read from the style property, 9973 // so we can restore directly to that 9974 jqTable 9975 .css( 'width', settings.sDestroyWidth ) 9976 .removeClass( classes.table ); 9977 } 9978 9979 /* Remove the settings object from the settings array */ 9980 var idx = DataTable.settings.indexOf(settings); 9981 if ( idx !== -1 ) { 9982 DataTable.settings.splice( idx, 1 ); 9983 } 9984 } ); 9985 } ); 9986 9987 9988 // Add the `every()` method for rows, columns and cells in a compact form 9989 $.each( [ 'column', 'row', 'cell' ], function ( i, type ) { 9990 _api_register( type+'s().every()', function ( fn ) { 9991 var opts = this.selector.opts; 9992 var api = this; 9993 var inst; 9994 var counter = 0; 9995 9996 return this.iterator( 'every', function ( settings, selectedIdx, tableIdx ) { 9997 inst = api[ type ](selectedIdx, opts); 9998 9999 if (type === 'cell') { 10000 fn.call(inst, inst[0][0].row, inst[0][0].column, tableIdx, counter); 10001 } 10002 else { 10003 fn.call(inst, selectedIdx, tableIdx, counter); 10004 } 10005 10006 counter++; 10007 } ); 10008 } ); 10009 } ); 10010 10011 10012 // i18n method for extensions to be able to use the language object from the 10013 // DataTable 10014 _api_register( 'i18n()', function ( token, def, plural ) { 10015 var ctx = this.context[0]; 10016 var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage ); 10017 10018 if ( resolved === undefined ) { 10019 resolved = def; 10020 } 10021 10022 if ( $.isPlainObject( resolved ) ) { 10023 resolved = plural !== undefined && resolved[ plural ] !== undefined ? 10024 resolved[ plural ] : 10025 resolved._; 10026 } 10027 10028 return typeof resolved === 'string' 10029 ? resolved.replace( '%d', plural ) // nb: plural might be undefined, 10030 : resolved; 10031 } ); 10032 10033 /**
10034 * Version string for plug-ins to check compatibility. Allowed format is 10035 * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used 10036 * only for non-release builds. See https://semver.org/ for more information. 10037 * @member 10038 * @type string 10039 * @default Version number 10040 */ 10041 DataTable.version = "2.2.2"; 10042 10043 /** 10044 * Private data store, containing all of the settings objects that are 10045 * created for the tables on a given page. 10046 * 10047 * Note that the `DataTable.settings` object is aliased to 10048 * `jQuery.fn.dataTableExt` through which it may be accessed and 10049 * manipulated, or `jQuery.fn.dataTable.settings`. 10050 * @member 10051 * @type array 10052 * @default [] 10053 * @private 10054 */ 10055 DataTable.settings = []; 10056 10057 /** 10058 * Object models container, for the various models that DataTables has 10059 * available to it. These models define the objects that are used to hold 10060 * the active state and configuration of the table. 10061 * @namespace 10062 */ 10063 DataTable.models = {}; 10064 10065 10066 10067 /** 10068 * Template object for the way in which DataTables holds information about 10069 * search information for the global filter and individual column filters. 10070 * @namespace 10071 */ 10072 DataTable.models.oSearch = { 10073 /** 10074 * Flag to indicate if the filtering should be case insensitive or not 10075 */ 10076 "caseInsensitive": true, 10077 10078 /** 10079 * Applied search term 10080 */ 10081 "search": "", 10082 10083 /** 10084 * Flag to indicate if the search term should be interpreted as a 10085 * regular expression (true) or not (false) and therefore and special 10086 * regex characters escaped. 10087 */ 10088 "regex": false, 10089 10090 /** 10091 * Flag to indicate if DataTables is to use its smart filtering or not. 10092 */ 10093 "smart": true, 10094 10095 /** 10096 * Flag to indicate if DataTables should only trigger a search when 10097 * the return key is pressed. 10098 */ 10099 "return": false 10100 }; 10101 10102 10103 10104 10105 /** 10106 * Template object for the way in which DataTables holds information about 10107 * each individual row. This is the object format used for the settings 10108 * aoData array. 10109 * @namespace 10110 */ 10111 DataTable.models.oRow = { 10112 /** 10113 * TR element for the row 10114 */ 10115 "nTr": null, 10116 10117 /** 10118 * Array of TD elements for each row. This is null until the row has been 10119 * created. 10120 */ 10121 "anCells": null, 10122 10123 /** 10124 * Data object from the original data source for the row. This is either 10125 * an array if using the traditional form of DataTables, or an object if 10126 * using mData options. The exact type will depend on the passed in 10127 * data from the data source, or will be an array if using DOM a data 10128 * source. 10129 */ 10130 "_aData": [], 10131 10132 /** 10133 * Sorting data cache - this array is ostensibly the same length as the 10134 * number of columns (although each index is generated only as it is 10135 * needed), and holds the data that is used for sorting each column in the 10136 * row. We do this cache generation at the start of the sort in order that 10137 * the formatting of the sort data need be done only once for each cell 10138 * per sort. This array should not be read from or written to by anything 10139 * other than the master sorting methods. 10140 */ 10141 "_aSortData": null, 10142 10143 /** 10144 * Per cell filtering data cache. As per the sort data cache, used to 10145 * increase the performance of the filtering in DataTables 10146 */ 10147 "_aFilterData": null, 10148 10149 /** 10150 * Filtering data cache. This is the same as the cell filtering cache, but 10151 * in this case a string rather than an array. This is easily computed with 10152 * a join on `_aFilterData`, but is provided as a cache so the join isn't 10153 * needed on every search (memory traded for performance) 10154 */ 10155 "_sFilterRow": null, 10156 10157 /** 10158 * Denote if the original data source was from the DOM, or the data source 10159 * object. This is used for invalidating data, so DataTables can 10160 * automatically read data from the original source, unless uninstructed 10161 * otherwise. 10162 */ 10163 "src": null, 10164 10165 /** 10166 * Index in the aoData array. This saves an indexOf lookup when we have the 10167 * object, but want to know the index 10168 */ 10169 "idx": -1, 10170 10171 /** 10172 * Cached display value 10173 */ 10174 displayData: null 10175 }; 10176 10177 10178 /** 10179 * Template object for the column information object in DataTables. This object 10180 * is held in the settings aoColumns array and contains all the information that 10181 * DataTables needs about each individual column. 10182 * 10183 * Note that this object is related to {@link DataTable.defaults.column} 10184 * but this one is the internal data store for DataTables's cache of columns. 10185 * It should NOT be manipulated outside of DataTables. Any configuration should 10186 * be done through the initialisation options. 10187 * @namespace 10188 */ 10189 DataTable.models.oColumn = { 10190 /** 10191 * Column index. 10192 */ 10193 "idx": null, 10194 10195 /** 10196 * A list of the columns that sorting should occur on when this column 10197 * is sorted. That this property is an array allows multi-column s
10197orting 10198 * to be defined for a column (for example first name / last name columns 10199 * would benefit from this). The values are integers pointing to the 10200 * columns to be sorted on (typically it will be a single integer pointing 10201 * at itself, but that doesn't need to be the case). 10202 */ 10203 "aDataSort": null, 10204 10205 /** 10206 * Define the sorting directions that are applied to the column, in sequence 10207 * as the column is repeatedly sorted upon - i.e. the first value is used 10208 * as the sorting direction when the column if first sorted (clicked on). 10209 * Sort it again (click again) and it will move on to the next index. 10210 * Repeat until loop. 10211 */ 10212 "asSorting": null, 10213 10214 /** 10215 * Flag to indicate if the column is searchable, and thus should be included 10216 * in the filtering or not. 10217 */ 10218 "bSearchable": null, 10219 10220 /** 10221 * Flag to indicate if the column is sortable or not. 10222 */ 10223 "bSortable": null, 10224 10225 /** 10226 * Flag to indicate if the column is currently visible in the table or not 10227 */ 10228 "bVisible": null, 10229 10230 /** 10231 * Store for manual type assignment using the `column.type` option. This 10232 * is held in store so we can manipulate the column's `sType` property. 10233 */ 10234 "_sManualType": null, 10235 10236 /** 10237 * Flag to indicate if HTML5 data attributes should be used as the data 10238 * source for filtering or sorting. True is either are. 10239 */ 10240 "_bAttrSrc": false, 10241 10242 /** 10243 * Developer definable function that is called whenever a cell is created (Ajax source, 10244 * etc) or processed for input (DOM source). This can be used as a compliment to mRender 10245 * allowing you to modify the DOM element (add background colour for example) when the 10246 * element is available. 10247 */ 10248 "fnCreatedCell": null, 10249 10250 /** 10251 * Function to get data from a cell in a column. You should <b>never</b> 10252 * access data directly through _aData internally in DataTables - always use 10253 * the method attached to this property. It allows mData to function as 10254 * required. This function is automatically assigned by the column 10255 * initialisation method 10256 */ 10257 "fnGetData": null, 10258 10259 /** 10260 * Function to set data for a cell in the column. You should <b>never</b> 10261 * set the data directly to _aData internally in DataTables - always use 10262 * this method. It allows mData to function as required. This function 10263 * is automatically assigned by the column initialisation method 10264 */ 10265 "fnSetData": null, 10266 10267 /** 10268 * Property to read the value for the cells in the column from the data 10269 * source array / object. If null, then the default content is used, if a 10270 * function is given then the return from the function is used. 10271 */ 10272 "mData": null, 10273 10274 /** 10275 * Partner property to mData which is used (only when defined) to get 10276 * the data - i.e. it is basically the same as mData, but without the 10277 * 'set' option, and also the data fed to it is the result from mData. 10278 * This is the rendering method to match the data method of mData. 10279 */ 10280 "mRender": null, 10281 10282 /** 10283 * The class to apply to all TD elements in the table's TBODY for the column 10284 */ 10285 "sClass": null, 10286 10287 /** 10288 * When DataTables calculates the column widths to assign to each column, 10289 * it finds the longest string in each column and then constructs a 10290 * temporary table and reads the widths from that. The problem with this 10291 * is that "mmm" is much wider then "iiii", but the latter is a longer 10292 * string - thus the calculation can go wrong (doing it properly and putting 10293 * it into an DOM object and measuring that is horribly(!) slow). Thus as 10294 * a "work around" we provide this option. It will append its value to the 10295 * text that is found to be the longest string for the column - i.e. padding. 10296 */ 10297 "sContentPadding": null, 10298 10299 /** 10300 * Allows a default value to be given for a column's data, and will be used 10301 * whenever a null data source is encountered (this can be because mData 10302 * is set to null, or because the data source itself is null). 10303 */ 10304 "sDefaultContent": null, 10305 10306 /** 10307 * Name for the column, allowing reference to the column by name as well as 10308 * by index (needs a lookup to work by name). 10309 */ 10310 "sName": null, 10311 10312 /** 10313 * Custom sorting data type - defines which of the available plug-ins in 10314 * afnSortData the custom sorting will use - if any is defined. 10315 */ 10316 "sSortDataType": 'std', 10317 10318 /** 10319 * Class to be applied to the header element when sorting on this column 10320 */ 10321 "sSortingClass": null, 10322 10323 /** 10324 * Title of the column - what is seen in the TH element (nTh). 10325 */ 10326 "sTitle": null, 10327 10328 /** 10329 * Column sorting and filtering type 10330 */ 10331 "sType": null, 10332 10333 /** 10334 * Width of the column 10335 */ 10336 "sWidth": null, 10337 10338 /** 10339 * Width of the column when it was first "encountered" 10340 */ 10341 "sWidthOrig": null, 10342 10343 /** Cached string which is the longest in the column */ 10344 maxLenString: null, 10345 10346 /** 10347 * Store for named searches 10348 */ 10349 searchFixed: null 10350 }; 10351 10352 10353 /* 10354 * Developer note: The properties of the object below are given in Hungarian 10355 * notation, that was used as the interface for DataTables prior to v1.10, however 10356 * from v1.10 onwards the primary interface is camel case. In order to avoid 10357 * breaking backwards compatibility utterly with this change, the Hungarian
10358 * version is still, internally the primary interface, but is is not documented 10359 * - hence the @name tags in each doc comment. This allows a Javascript function 10360 * to create a map from Hungarian notation to camel case (going the other direction 10361 * would require each property to be listed, which would add around 3K to the size 10362 * of DataTables, while this method is about a 0.5K hit). 10363 * 10364 * Ultimately this does pave the way for Hungarian notation to be dropped 10365 * completely, but that is a massive amount of work and will break current 10366 * installs (therefore is on-hold until v2). 10367 */ 10368 10369 /** 10370 * Initialisation options that can be given to DataTables at initialisation 10371 * time. 10372 * @namespace 10373 */ 10374 DataTable.defaults = { 10375 /** 10376 * An array of data to use for the table, passed in at initialisation which 10377 * will be used in preference to any data which is already in the DOM. This is 10378 * particularly useful for constructing tables purely in Javascript, for 10379 * example with a custom Ajax call. 10380 */ 10381 "aaData": null, 10382 10383 10384 /** 10385 * If ordering is enabled, then DataTables will perform a first pass sort on 10386 * initialisation. You can define which column(s) the sort is performed 10387 * upon, and the sorting direction, with this variable. The `sorting` array 10388 * should contain an array for each column to be sorted initially containing 10389 * the column's index and a direction string ('asc' or 'desc'). 10390 */ 10391 "aaSorting": [[0,'asc']], 10392 10393 10394 /** 10395 * This parameter is basically identical to the `sorting` parameter, but 10396 * cannot be overridden by user interaction with the table. What this means 10397 * is that you could have a column (visible or hidden) which the sorting 10398 * will always be forced on first - any sorting after that (from the user) 10399 * will then be performed as required. This can be useful for grouping rows 10400 * together. 10401 */ 10402 "aaSortingFixed": [], 10403 10404 10405 /** 10406 * DataTables can be instructed to load data to display in the table from a 10407 * Ajax source. This option defines how that Ajax call is made and where to. 10408 * 10409 * The `ajax` property has three different modes of operation, depending on 10410 * how it is defined. These are: 10411 * 10412 * * `string` - Set the URL from where the data should be loaded from. 10413 * * `object` - Define properties for `jQuery.ajax`. 10414 * * `function` - Custom data get function 10415 * 10416 * `string` 10417 * -------- 10418 * 10419 * As a string, the `ajax` property simply defines the URL from which 10420 * DataTables will load data. 10421 * 10422 * `object` 10423 * -------- 10424 * 10425 * As an object, the parameters in the object are passed to 10426 * [jQuery.ajax](https://api.jquery.com/jQuery.ajax/) allowing fine control 10427 * of the Ajax request. DataTables has a number of default parameters which 10428 * you can override using this option. Please refer to the jQuery 10429 * documentation for a full description of the options available, although 10430 * the following parameters provide additional options in DataTables or 10431 * require special consideration: 10432 * 10433 * * `data` - As with jQuery, `data` can be provided as an object, but it 10434 * can also be used as a function to manipulate the data DataTables sends 10435 * to the server. The function takes a single parameter, an object of 10436 * parameters with the values that DataTables has readied for sending. An 10437 * object may be returned which will be merged into the DataTables 10438 * defaults, or you can add the items to the object that was passed in and 10439 * not return anything from the function. This supersedes `fnServerParams` 10440 * from DataTables 1.9-. 10441 * 10442 * * `dataSrc` - By default DataTables will look for the property `data` (or 10443 * `aaData` for compatibility with DataTables 1.9-) when obtaining data 10444 * from an Ajax source or for server-side processing - this parameter 10445 * allows that property to be changed. You can use Javascript dotted 10446 * object notation to get a data source for multiple levels of nesting, or 10447 * it my be used as a function. As a function it takes a single parameter, 10448 * the JSON returned from the server, which can be manipulated as 10449 * required, with the returned value being that used by DataTables as the 10450 * data source for the table. 10451 * 10452 * * `success` - Should not be overridden it is used internally in 10453 * DataTables. To manipulate / transform the data returned by the server 10454 * use `ajax.dataSrc`, or use `ajax` as a function (see below). 10455 * 10456 * `function` 10457 * ---------- 10458 * 10459 * As a function, making the Ajax call is left up to yourself allowing 10460 * complete control of the Ajax request. Indeed, if desired, a method other 10461 * than Ajax could be used to obtain the required data, such as Web storage 10462 * or an AIR database. 10463 * 10464 * The function is given four parameters and no return is required. The 10465 * parameters are: 10466 * 10467 * 1. _object_ - Data to send to the server 10468 * 2. _function_ - Callback function that must be executed when the required 10469 * data has been obtained. That data should be passed into the callback 10470 * as the only parameter 10471 * 3. _object_ - DataTables settings object for the table 10472 */ 10473 "ajax": null, 10474 10475 10476 /** 10477 * This parameter allows you to readily specify the entries in the length drop 10478 * down menu that DataTables shows when pagination is enabled. It can be 10479 * either a 1D array of options which will be used for both the displayed 10480 * option and the value, or a 2D array which will use the array in the first 10481 * position as the value, and the array in the second position as the 10482 * displayed options (useful for language strings such as 'All'). 10483 * 10484 * Note that the `pageLength` property will be automatically set to the 10485 * first value given in this array, unless `pageLength` is also provided. 10486 */ 10487 "aLengthMenu": [ 10, 25, 50, 100 ], 10488 10489 10490 /** 10491 * The `columns` option in the initialisation parameter allows you to define 10492 * details about the way individual columns behave. For a full list of 10493 * column options that can be set, please see 10494 * {@link DataTable.defaults.column}. Note that if you use `columns` to 10495 * define your columns, you must have an entry in the array for every single 10496 * column that you have in your table (these can be null if you don't which 10497 * to specify any options). 10498 */ 10499 "aoColumns": null, 10500 10501 /**
10502 * Very similar to `columns`, `columnDefs` allows you to target a specific 10503 * column, multiple columns, or all columns, using the `targets` property of 10504 * each object in the array. This allows great flexibility when creating 10505 * tables, as the `columnDefs` arrays can be of any length, targeting the 10506 * columns you specifically want. `columnDefs` may use any of the column 10507 * options available: {@link DataTable.defaults.column}, but it _must_ 10508 * have `targets` defined in each object in the array. Values in the `targets` 10509 * array may be: 10510 * <ul> 10511 * <li>a string - class name will be matched on the TH for the column</li> 10512 * <li>0 or a positive integer - column index counting from the left</li> 10513 * <li>a negative integer - column index counting from the right</li> 10514 * <li>the string "_all" - all columns (i.e. assign a default)</li> 10515 * </ul> 10516 */ 10517 "aoColumnDefs": null, 10518 10519 10520 /** 10521 * Basically the same as `search`, this parameter defines the individual column 10522 * filtering state at initialisation time. The array must be of the same size 10523 * as the number of columns, and each element be an object with the parameters 10524 * `search` and `escapeRegex` (the latter is optional). 'null' is also 10525 * accepted and the default will be used. 10526 */ 10527 "aoSearchCols": [], 10528 10529 10530 /** 10531 * Enable or disable automatic column width calculation. This can be disabled 10532 * as an optimisation (it takes some time to calculate the widths) if the 10533 * tables widths are passed in using `columns`. 10534 */ 10535 "bAutoWidth": true, 10536 10537 10538 /** 10539 * Deferred rendering can provide DataTables with a huge speed boost when you 10540 * are using an Ajax or JS data source for the table. This option, when set to 10541 * true, will cause DataTables to defer the creation of the table elements for 10542 * each row until they are needed for a draw - saving a significant amount of 10543 * time. 10544 */ 10545 "bDeferRender": true, 10546 10547 10548 /** 10549 * Replace a DataTable which matches the given selector and replace it with 10550 * one which has the properties of the new initialisation object passed. If no 10551 * table matches the selector, then the new DataTable will be constructed as 10552 * per normal. 10553 */ 10554 "bDestroy": false, 10555 10556 10557 /** 10558 * Enable or disable filtering of data. Filtering in DataTables is "smart" in 10559 * that it allows the end user to input multiple words (space separated) and 10560 * will match a row containing those words, even if not in the order that was 10561 * specified (this allow matching across multiple columns). Note that if you 10562 * wish to use filtering in DataTables this must remain 'true' - to remove the 10563 * default filtering input box and retain filtering abilities, please use 10564 * {@link DataTable.defaults.dom}. 10565 */ 10566 "bFilter": true, 10567 10568 /** 10569 * Used only for compatiblity with DT1 10570 * @deprecated 10571 */ 10572 "bInfo": true, 10573 10574 /** 10575 * Used only for compatiblity with DT1 10576 * @deprecated 10577 */ 10578 "bLengthChange": true, 10579 10580 /** 10581 * Enable or disable pagination. 10582 */ 10583 "bPaginate": true, 10584 10585 10586 /** 10587 * Enable or disable the display of a 'processing' indicator when the table is 10588 * being processed (e.g. a sort). This is particularly useful for tables with 10589 * large amounts of data where it can take a noticeable amount of time to sort 10590 * the entries. 10591 */ 10592 "bProcessing": false, 10593 10594 10595 /** 10596 * Retrieve the DataTables object for the given selector. Note that if the 10597 * table has already been initialised, this parameter will cause DataTables 10598 * to simply return the object that has already been set up - it will not take 10599 * account of any changes you might have made to the initialisation object 10600 * passed to DataTables (setting this parameter to true is an acknowledgement 10601 * that you understand this). `destroy` can be used to reinitialise a table if 10602 * you need. 10603 */ 10604 "bRetrieve": false, 10605 10606 10607 /** 10608 * When vertical (y) scrolling is enabled, DataTables will force the height of 10609 * the table's viewport to the given height at all times (useful for layout). 10610 * However, this can look odd when filtering data down to a small data set, 10611 * and the footer is left "floating" further down. This parameter (when 10612 * enabled) will cause DataTables to collapse the table's viewport down when 10613 * the result set will fit within the given Y height. 10614 */
10615 "bScrollCollapse": false, 10616 10617 10618 /** 10619 * Configure DataTables to use server-side processing. Note that the 10620 * `ajax` parameter must also be given in order to give DataTables a 10621 * source to obtain the required data for each draw. 10622 */ 10623 "bServerSide": false, 10624 10625 10626 /** 10627 * Enable or disable sorting of columns. Sorting of individual columns can be 10628 * disabled by the `sortable` option for each column. 10629 */ 10630 "bSort": true, 10631 10632 10633 /** 10634 * Enable or display DataTables' ability to sort multiple columns at the 10635 * same time (activated by shift-click by the user). 10636 */ 10637 "bSortMulti": true, 10638 10639 10640 /** 10641 * Allows control over whether DataTables should use the top (true) unique 10642 * cell that is found for a single column, or the bottom (false - default). 10643 * This is useful when using complex headers. 10644 */ 10645 "bSortCellsTop": null, 10646 10647 10648 /** 10649 * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and 10650 * `sorting\_3` to the columns which are currently being sorted on. This is 10651 * presented as a feature switch as it can increase processing time (while 10652 * classes are removed and added) so for large data sets you might want to 10653 * turn this off. 10654 */ 10655 "bSortClasses": true, 10656 10657 10658 /** 10659 * Enable or disable state saving. When enabled HTML5 `localStorage` will be 10660 * used to save table display information such as pagination information, 10661 * display length, filtering and sorting. As such when the end user reloads 10662 * the page the display display will match what thy had previously set up. 10663 */ 10664 "bStateSave": false, 10665 10666 10667 /** 10668 * This function is called when a TR element is created (and all TD child 10669 * elements have been inserted), or registered if using a DOM source, allowing 10670 * manipulation of the TR element (adding classes etc). 10671 */ 10672 "fnCreatedRow": null, 10673 10674 10675 /** 10676 * This function is called on every 'draw' event, and allows you to 10677 * dynamically modify any aspect you want about the created DOM. 10678 */ 10679 "fnDrawCallback": null, 10680 10681 10682 /** 10683 * Identical to fnHeaderCallback() but for the table footer this function 10684 * allows you to modify the table footer on every 'draw' event. 10685 */ 10686 "fnFooterCallback": null, 10687 10688 10689 /** 10690 * When rendering large numbers in the information element for the table 10691 * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers 10692 * to have a comma separator for the 'thousands' units (e.g. 1 million is 10693 * rendered as "1,000,000") to help readability for the end user. This 10694 * function will override the default method DataTables uses. 10695 */ 10696 "fnFormatNumber": function ( toFormat ) { 10697 return toFormat.toString().replace( 10698 /\B(?=(\d{3})+(?!\d))/g, 10699 this.oLanguage.sThousands 10700 ); 10701 }, 10702 10703 10704 /** 10705 * This function is called on every 'draw' event, and allows you to 10706 * dynamically modify the header row. This can be used to calculate and 10707 * display useful information about the table. 10708 */ 10709 "fnHeaderCallback": null, 10710 10711 10712 /** 10713 * The information element can be used to convey information about the current 10714 * state of the table. Although the internationalisation options presented by 10715 * DataTables are quite capable of dealing with most customisations, there may 10716 * be times where you wish to customise the string further. This callback 10717 * allows you to do exactly that. 10718 */ 10719 "fnInfoCallback": null, 10720 10721 10722 /** 10723 * Called when the table has been initialised. Normally DataTables will 10724 * initialise sequentially and there will be no need for this function, 10725 * however, this does not hold true when using external language information 10726 * since that is obtained using an async XHR call. 10727 */ 10728 "fnInitComplete": null, 10729 10730 10731 /** 10732 * Called at the very start of each table draw and can be used to cancel the 10733 * draw by returning false, any other return (including undefined) results in 10734 * the full draw occurring). 10735 */ 10736 "fnPreDrawCallback": null, 10737 10738 10739 /** 10740 * This function allows you to 'post process' each row after it have been 10741 * generated for each table draw, but before it is rendered on screen. This 10742 * function might be used for setting the row class name etc. 10743 */ 10744 "fnRowCallback": null, 10745 10746 10747 /** 10748 * Load the table state. With this function you can define from where, and how, the 10749 * state of a table is loaded. By default DataTables will load from `localStorage` 10750 * but you might wish to use a server-side database or cookies. 10751 */ 10752 "fnStateLoadCallback": function ( settings ) { 10753 try { 10754 return JSON.parse(
10755 (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem( 10756 'DataTables_'+settings.sInstance+'_'+location.pathname 10757 ) 10758 ); 10759 } catch (e) { 10760 return {}; 10761 } 10762 }, 10763 10764 10765 /** 10766 * Callback which allows modification of the saved state prior to loading that state. 10767 * This callback is called when the table is loading state from the stored data, but 10768 * prior to the settings object being modified by the saved state. Note that for 10769 * plug-in authors, you should use the `stateLoadParams` event to load parameters for 10770 * a plug-in. 10771 */ 10772 "fnStateLoadParams": null, 10773 10774 10775 /** 10776 * Callback that is called when the state has been loaded from the state saving method 10777 * and the DataTables settings object has been modified as a result of the loaded state. 10778 */ 10779 "fnStateLoaded": null, 10780 10781 10782 /** 10783 * Save the table state. This function allows you to define where and how the state 10784 * information for the table is stored By default DataTables will use `localStorage` 10785 * but you might wish to use a server-side database or cookies. 10786 */ 10787 "fnStateSaveCallback": function ( settings, data ) { 10788 try { 10789 (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem( 10790 'DataTables_'+settings.sInstance+'_'+location.pathname, 10791 JSON.stringify( data ) 10792 ); 10793 } catch (e) { 10794 // noop 10795 } 10796 }, 10797 10798 10799 /** 10800 * Callback which allows modification of the state to be saved. Called when the table 10801 * has changed state a new state save is required. This method allows modification of 10802 * the state saving object prior to actually doing the save, including addition or 10803 * other state properties or modification. Note that for plug-in authors, you should 10804 * use the `stateSaveParams` event to save parameters for a plug-in. 10805 */ 10806 "fnStateSaveParams": null, 10807 10808 10809 /** 10810 * Duration for which the saved state information is considered valid. After this period 10811 * has elapsed the state will be returned to the default. 10812 * Value is given in seconds. 10813 */ 10814 "iStateDuration": 7200, 10815 10816 10817 /** 10818 * Number of rows to display on a single page when using pagination. If 10819 * feature enabled (`lengthChange`) then the end user will be able to override 10820 * this to a custom setting using a pop-up menu. 10821 */ 10822 "iDisplayLength": 10, 10823 10824 10825 /** 10826 * Define the starting point for data display when using DataTables with 10827 * pagination. Note that this parameter is the number of records, rather than 10828 * the page number, so if you have 10 records per page and want to start on 10829 * the third page, it should be "20". 10830 */ 10831 "iDisplayStart": 0, 10832 10833 10834 /** 10835 * By default DataTables allows keyboard navigation of the table (sorting, paging, 10836 * and filtering) by adding a `tabindex` attribute to the required elements. This 10837 * allows you to tab through the controls and press the enter key to activate them. 10838 * The tabindex is default 0, meaning that the tab follows the flow of the document. 10839 * You can overrule this using this parameter if you wish. Use a value of -1 to 10840 * disable built-in keyboard navigation. 10841 */ 10842 "iTabIndex": 0, 10843 10844 10845 /** 10846 * Classes that DataTables assigns to the various components and features 10847 * that it adds to the HTML table. This allows classes to be configured 10848 * during initialisation in addition to through the static 10849 * {@link DataTable.ext.oStdClasses} object). 10850 */ 10851 "oClasses": {}, 10852 10853 10854 /** 10855 * All strings that DataTables uses in the user interface that it creates 10856 * are defined in this object, allowing you to modified them individually or 10857 * completely replace them all as required. 10858 */ 10859 "oLanguage": { 10860 /** 10861 * Strings that are used for WAI-ARIA labels and controls only (these are not 10862 * actually visible on the page, but will be read by screenreaders, and thus 10863 * must be internationalised as well). 10864 */ 10865 "oAria": { 10866 /** 10867 * ARIA label that is added to the table headers when the column may be sorted 10868 */ 10869 "orderable": ": Activate to sort", 10870 10871 /** 10872 * ARIA label that is added to the table headers when the column is currently being sorted 10873 */ 10874 "orderableReverse": ": Activate to invert sorting", 10875 10876 /** 10877 * ARIA label that is added to the table headers when the column is currently being 10878 * sorted and next step is to remove sorting 10879 */ 10880 "orderableRemove": ": Activate to remove sorting", 10881 10882 paginate: { 10883 first: 'First', 10884 last: 'Last', 10885 next: 'Next', 10886 previous: 'Previous', 10887 number: '' 10888 } 10889 }, 10890 10891 /** 10892 * Pagination string used by DataTables for the built-in pagination 10893 * control types. 10894 */ 10895 "oPaginate": { 10896 /** 10897 * Label and character for first page button («) 10898 */ 10899 "sFirst": "\u00AB", 10900 10901 /** 10902 * Last page button (») 10903 */ 10904 "sLast": "\u00BB", 10905 10906 /** 10907 * Next page button (âº) 10908 */ 10909 "sNext": "\u203A", 10910 10911 /** 10912 * Previous page button (â¹) 10913 */ 10914 "sPrevious": "\u2039", 10915 }, 10916 10917 /** 10918 * Plural object for the data type the table is showing 10919 */ 10920 entries: { 10921 _: "entries", 10922 1: "entry" 10923 }, 10924 10925 /** 10926 * This string is shown in preference to `zeroRecords` when the table is 10927 * empty of data (regardless of filtering). Note that this is an optional 10928 * parameter - if it is not given, the value of `zeroRecords` will be used 10929 * instead (either the default or given value). 10930 */ 10931 "sEmptyTable": "No data available in table", 10932 10933 10934 /** 10935 * This string gives information to the end user about the information 10936 * that is current on display on the page. The following tokens can be 10937 * used in the string and will be dynamically replaced as the table 10938 * display updates. This tokens can be placed anywhere in the string, or 10939 * removed as needed by the language requires: 10940 * 10941 * * `\_START\_` - Display index of the first record on the current page 10942 * * `\_END\_` - Display index of the last record on the current page 10943 * * `\_TOTAL\_` - Number of records in the table after filtering 10944 * * `\_MAX\_` - Number of records in the table without filtering 10945 * * `\_PAGE\_` - Current page number 10946 * * `\_PAGES\_` - Total number of pages of data in the table 10947 */ 10948 "sInfo": "Showing _START_ to _END_ of _TOTAL_ _ENTRIES-TOTAL_", 10949 10950 10951 /**
10952 * Display information string for when the table is empty. Typically the 10953 * format of this string should match `info`. 10954 */ 10955 "sInfoEmpty": "Showing 0 to 0 of 0 _ENTRIES-TOTAL_", 10956 10957 10958 /** 10959 * When a user filters the information in a table, this string is appended 10960 * to the information (`info`) to give an idea of how strong the filtering 10961 * is. The variable _MAX_ is dynamically updated. 10962 */ 10963 "sInfoFiltered": "(filtered from _MAX_ total _ENTRIES-MAX_)", 10964 10965 10966 /** 10967 * If can be useful to append extra information to the info string at times, 10968 * and this variable does exactly that. This information will be appended to 10969 * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are 10970 * being used) at all times. 10971 */ 10972 "sInfoPostFix": "", 10973 10974 10975 /** 10976 * This decimal place operator is a little different from the other 10977 * language options since DataTables doesn't output floating point 10978 * numbers, so it won't ever use this for display of a number. Rather, 10979 * what this parameter does is modify the sort methods of the table so 10980 * that numbers which are in a format which has a character other than 10981 * a period (`.`) as a decimal place will be sorted numerically. 10982 * 10983 * Note that numbers with different decimal places cannot be shown in 10984 * the same table and still be sortable, the table must be consistent. 10985 * However, multiple different tables on the page can use different 10986 * decimal place characters. 10987 */ 10988 "sDecimal": "", 10989 10990 10991 /** 10992 * DataTables has a build in number formatter (`formatNumber`) which is 10993 * used to format large numbers that are used in the table information. 10994 * By default a comma is used, but this can be trivially changed to any 10995 * character you wish with this parameter. 10996 */ 10997 "sThousands": ",", 10998 10999 11000 /** 11001 * Detail the action that will be taken when the drop down menu for the 11002 * pagination length option is changed. The '_MENU_' variable is replaced 11003 * with a default select list of 10, 25, 50 and 100, and can be replaced 11004 * with a custom select box if required. 11005 */ 11006 "sLengthMenu": "_MENU_ _ENTRIES_ per page", 11007 11008 11009 /** 11010 * When using Ajax sourced data and during the first draw when DataTables is 11011 * gathering the data, this message is shown in an empty row in the table to 11012 * indicate to the end user the the data is being loaded. Note that this 11013 * parameter is not used when loading data by server-side processing, just 11014 * Ajax sourced data with client-side processing. 11015 */ 11016 "sLoadingRecords": "Loading...", 11017 11018 11019 /** 11020 * Text which is displayed when the table is processing a user action 11021 * (usually a sort command or similar). 11022 */ 11023 "sProcessing": "", 11024 11025 11026 /** 11027 * Details the actions that will be taken when the user types into the 11028 * filtering input text box. The variable "_INPUT_", if used in the string, 11029 * is replaced with the HTML text box for the filtering input allowing 11030 * control over where it appears in the string. If "_INPUT_" is not given 11031 * then the input box is appended to the string automatically. 11032 */ 11033 "sSearch": "Search:", 11034 11035 11036 /** 11037 * Assign a `placeholder` attribute to the search `input` element 11038 * @type string 11039 * @default 11040 * 11041 * @dtopt Language 11042 * @name DataTable.defaults.language.searchPlaceholder 11043 */ 11044 "sSearchPlaceholder": "", 11045 11046 11047 /** 11048 * All of the language information can be stored in a file on the 11049 * server-side, which DataTables will look up if this parameter is passed. 11050 * It must store the URL of the language file, which is in a JSON format, 11051 * and the object has the same properties as the oLanguage object in the 11052 * initialiser object (i.e. the above parameters). Please refer to one of 11053 * the example language files to see how this works in action. 11054 */ 11055 "sUrl": "", 11056 11057 11058 /** 11059 * Text shown inside the table records when the is no information to be 11060 * displayed after filtering. `emptyTable` is shown when there is simply no 11061 * information in the table at all (regardless of filtering). 11062 */
11063 "sZeroRecords": "No matching records found" 11064 }, 11065 11066 11067 /** The initial data order is reversed when `desc` ordering */ 11068 orderDescReverse: true, 11069 11070 11071 /** 11072 * This parameter allows you to have define the global filtering state at 11073 * initialisation time. As an object the `search` parameter must be 11074 * defined, but all other parameters are optional. When `regex` is true, 11075 * the search string will be treated as a regular expression, when false 11076 * (default) it will be treated as a straight string. When `smart` 11077 * DataTables will use it's smart filtering methods (to word match at 11078 * any point in the data), when false this will not be done. 11079 */ 11080 "oSearch": $.extend( {}, DataTable.models.oSearch ), 11081 11082 11083 /** 11084 * Table and control layout. This replaces the legacy `dom` option. 11085 */ 11086 layout: { 11087 topStart: 'pageLength', 11088 topEnd: 'search', 11089 bottomStart: 'info', 11090 bottomEnd: 'paging' 11091 }, 11092 11093 11094 /** 11095 * Legacy DOM layout option 11096 */ 11097 "sDom": null, 11098 11099 11100 /** 11101 * Search delay option. This will throttle full table searches that use the 11102 * DataTables provided search input element (it does not effect calls to 11103 * `dt-api search()`, providing a delay before the search is made. 11104 */ 11105 "searchDelay": null, 11106 11107 11108 /** 11109 * DataTables features six different built-in options for the buttons to 11110 * display for pagination control: 11111 * 11112 * * `numbers` - Page number buttons only 11113 * * `simple` - 'Previous' and 'Next' buttons only 11114 * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers 11115 * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons 11116 * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers 11117 * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers 11118 */ 11119 "sPaginationType": "", 11120 11121 11122 /** 11123 * Enable horizontal scrolling. When a table is too wide to fit into a 11124 * certain layout, or you have a large number of columns in the table, you 11125 * can enable x-scrolling to show the table in a viewport, which can be 11126 * scrolled. This property can be `true` which will allow the table to 11127 * scroll horizontally when needed, or any CSS unit, or a number (in which 11128 * case it will be treated as a pixel measurement). Setting as simply `true` 11129 * is recommended. 11130 */ 11131 "sScrollX": "", 11132 11133 11134 /** 11135 * This property can be used to force a DataTable to use more width than it 11136 * might otherwise do when x-scrolling is enabled. For example if you have a 11137 * table which requires to be well spaced, this parameter is useful for 11138 * "over-sizing" the table, and thus forcing scrolling. This property can by 11139 * any CSS unit, or a number (in which case it will be treated as a pixel 11140 * measurement). 11141 */ 11142 "sScrollXInner": "", 11143 11144 11145 /** 11146 * Enable vertical scrolling. Vertical scrolling will constrain the DataTable 11147 * to the given height, and enable scrolling for any data which overflows the 11148 * current viewport. This can be used as an alternative to paging to display 11149 * a lot of data in a small area (although paging and scrolling can both be 11150 * enabled at the same time). This property can be any CSS unit, or a number 11151 * (in which case it will be treated as a pixel measurement). 11152 */ 11153 "sScrollY": "", 11154 11155 11156 /** 11157 * __Deprecated__ The functionality provided by this parameter has now been 11158 * superseded by that provided through `ajax`, which should be used instead. 11159 * 11160 * Set the HTTP method that is used to make the Ajax call for server-side 11161 * processing or Ajax sourced data. 11162 */ 11163 "sServerMethod": "GET", 11164 11165 11166 /** 11167 * DataTables makes use of renderers when displaying HTML elements for 11168 * a table. These renderers can be added or modified by plug-ins to 11169 * generate suitable mark-up for a site. For example the Bootstrap 11170 * integration plug-in for DataTables uses a paging button renderer to 11171 * display pagination buttons in the mark-up required by Bootstrap. 11172 * 11173 * For further information about the renderers available see 11174 * DataTable.ext.renderer 11175 */ 11176 "renderer": null, 11177 11178 11179 /** 11180 * Set the data property name that DataTables should use to get a row's id 11181 * to set as the `id` property in the node. 11182 */ 11183 "rowId": "DT_RowId", 11184 11185 11186 /** 11187 * Caption value 11188 */ 11189 "caption": null, 11190 11191 11192 /** 11193 * For server-side processing - use the data from the DOM for the first draw 11194 */ 11195 iDeferLoading: null 11196 }; 11197 11198 _fnHungarianMap( DataTable.defaults ); 11199 11200 11201 11202 /* 11203 * Developer note - See note in model.defaults.js about the use of Hungarian 11204 * notation and camel case. 11205 */ 11206 11207 /** 11208 * Column options that can be given to DataTables at initialisation time. 11209 * @namespace 11210 */ 11211 DataTable.defaults.column = { 11212 /** 11213 * Define which column(s) an order will occur on for this column. This 11214 * allows a column's ordering to take multiple columns into account when
11215 * doing a sort or use the data from a different column. For example first 11216 * name / last name columns make sense to do a multi-column sort over the 11217 * two columns. 11218 */ 11219 "aDataSort": null, 11220 "iDataSort": -1, 11221 11222 ariaTitle: '', 11223 11224 11225 /** 11226 * You can control the default ordering direction, and even alter the 11227 * behaviour of the sort handler (i.e. only allow ascending ordering etc) 11228 * using this parameter. 11229 */ 11230 "asSorting": [ 'asc', 'desc', '' ], 11231 11232 11233 /** 11234 * Enable or disable filtering on the data in this column. 11235 */ 11236 "bSearchable": true, 11237 11238 11239 /** 11240 * Enable or disable ordering on this column. 11241 */ 11242 "bSortable": true, 11243 11244 11245 /** 11246 * Enable or disable the display of this column. 11247 */ 11248 "bVisible": true, 11249 11250 11251 /** 11252 * Developer definable function that is called whenever a cell is created (Ajax source, 11253 * etc) or processed for input (DOM source). This can be used as a compliment to mRender 11254 * allowing you to modify the DOM element (add background colour for example) when the 11255 * element is available. 11256 */ 11257 "fnCreatedCell": null, 11258 11259 11260 /** 11261 * This property can be used to read data from any data source property, 11262 * including deeply nested objects / properties. `data` can be given in a 11263 * number of different ways which effect its behaviour: 11264 * 11265 * * `integer` - treated as an array index for the data source. This is the 11266 * default that DataTables uses (incrementally increased for each column). 11267 * * `string` - read an object property from the data source. There are 11268 * three 'special' options that can be used in the string to alter how 11269 * DataTables reads the data from the source object: 11270 * * `.` - Dotted Javascript notation. Just as you use a `.` in 11271 * Javascript to read from nested objects, so to can the options 11272 * specified in `data`. For example: `browser.version` or 11273 * `browser.name`. If your object parameter name contains a period, use 11274 * `\\` to escape it - i.e. `first\\.name`. 11275 * * `[]` - Array notation. DataTables can automatically combine data 11276 * from and array source, joining the data with the characters provided 11277 * between the two brackets. For example: `name[, ]` would provide a 11278 * comma-space separated list from the source array. If no characters 11279 * are provided between the brackets, the original array source is 11280 * returned. 11281 * * `()` - Function notation. Adding `()` to the end of a parameter will 11282 * execute a function of the name given. For example: `browser()` for a 11283 * simple function on the data source, `browser.version()` for a 11284 * function in a nested property or even `browser().version` to get an 11285 * object property if the function called returns an object. Note that 11286 * function notation is recommended for use in `render` rather than 11287 * `data` as it is much simpler to use as a renderer. 11288 * * `null` - use the original data source for the row rather than plucking 11289 * data directly from it. This action has effects on two other 11290 * initialisation options: 11291 * * `defaultContent` - When null is given as the `data` option and 11292 * `defaultContent` is specified for the column, the value defined by 11293 * `defaultContent` will be used for the cell. 11294 * * `render` - When null is used for the `data` option and the `render` 11295 * option is specified for the column, the whole data source for the 11296 * row is used for the renderer. 11297 * * `function` - the function given will be executed whenever DataTables 11298 * needs to set or get the data for a cell in the column. The function 11299 * takes three parameters: 11300 * * Parameters: 11301 * * `{array|object}` The data source for the row 11302 * * `{string}` The type call data requested - this will be 'set' when 11303 * setting data or 'filter', 'display', 'type', 'sort' or undefined 11304 * when gathering data. Note that when `undefined` is given for the 11305 * type DataTables expects to get the raw data for the object back< 11306 * * `{*}` Data to set when the second parameter is 'set'. 11307 * * Return: 11308 * * The return value from the function is not required when '
11308set' is 11309 * the type of call, but otherwise the return is what will be used 11310 * for the data requested. 11311 * 11312 * Note that `data` is a getter and setter option. If you just require 11313 * formatting of data for output, you will likely want to use `render` which 11314 * is simply a getter and thus simpler to use. 11315 * 11316 * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The 11317 * name change reflects the flexibility of this property and is consistent 11318 * with the naming of mRender. If 'mDataProp' is given, then it will still 11319 * be used by DataTables, as it automatically maps the old name to the new 11320 * if required. 11321 */ 11322 "mData": null, 11323 11324 11325 /** 11326 * This property is the rendering partner to `data` and it is suggested that 11327 * when you want to manipulate data for display (including filtering, 11328 * sorting etc) without altering the underlying data for the table, use this 11329 * property. `render` can be considered to be the the read only companion to 11330 * `data` which is read / write (then as such more complex). Like `data` 11331 * this option can be given in a number of different ways to effect its 11332 * behaviour: 11333 * 11334 * * `integer` - treated as an array index for the data source. This is the 11335 * default that DataTables uses (incrementally increased for each column). 11336 * * `string` - read an object property from the data source. There are 11337 * three 'special' options that can be used in the string to alter how 11338 * DataTables reads the data from the source object: 11339 * * `.` - Dotted Javascript notation. Just as you use a `.` in 11340 * Javascript to read from nested objects, so to can the options 11341 * specified in `data`. For example: `browser.version` or 11342 * `browser.name`. If your object parameter name contains a period, use 11343 * `\\` to escape it - i.e. `first\\.name`. 11344 * * `[]` - Array notation. DataTables can automatically combine data 11345 * from and array source, joining the data with the characters provided 11346 * between the two brackets. For example: `name[, ]` would provide a 11347 * comma-space separated list from the source array. If no characters 11348 * are provided between the brackets, the original array source is 11349 * returned. 11350 * * `()` - Function notation. Adding `()` to the end of a parameter will 11351 * execute a function of the name given. For example: `browser()` for a 11352 * simple function on the data source, `browser.version()` for a 11353 * function in a nested property or even `browser().version` to get an 11354 * object property if the function called returns an object. 11355 * * `object` - use different data for the different data types requested by 11356 * DataTables ('filter', 'display', 'type' or 'sort'). The property names 11357 * of the object is the data type the property refers to and the value can 11358 * defined using an integer, string or function using the same rules as 11359 * `render` normally does. Note that an `_` option _must_ be specified. 11360 * This is the default value to use if you haven't specified a value for 11361 * the data type requested by DataTables. 11362 * * `function` - the function given will be executed whenever DataTables 11363 * needs to set or get the data for a cell in the column. The function 11364 * takes three parameters: 11365 * * Parameters: 11366 * * {array|object} The data source for the row (based on `data`) 11367 * * {string} The type call data requested - this will be 'filter', 11368 * 'display', 'type' or 'sort'. 11369 * * {array|object} The full data source for the row (not based on 11370 * `data`) 11371 * * Return: 11372 * * The return value from the function is what will be used for the 11373 * data requested. 11374 */ 11375 "mRender": null, 11376 11377 11378 /** 11379 * Change the cell type created for the column - either TD cells or TH cells. This 11380 * can be useful as TH cells have semantic meaning in the table body, allowing them 11381 * to act as a header for a row (you may wish to add scope='row' to the TH elements). 11382 */ 11383 "sCellType": "td", 11384 11385 11386 /** 11387 * Class to give to each cell in this column. 11388 */ 11389 "sClass": "", 11390 11391 /** 11392 * When DataTables calculates the column widths to assign to each column, 11393 * it finds the longest string in each column and then constructs a 11394 * temporary table and reads the widths from that. The problem with this 11395 * is that "mmm" is much wider then "iiii", but the latter is a longer 11396 * string - thus the calculation can go wrong (doing it properly and putting 11397 * it into an DOM object and measuring that is horribly(!) slow). Thus as 11398 * a "work around" we provide this option. It will append its value to the 11399 * text that is found to be the longest string for the column - i.e. padding. 11400 * Generally you shouldn't need this! 11401 */ 11402 "sContentPadding": "", 11403 11404 11405 /** 11406 * Allows a default value to be given for a column's data, and will be used 11407 * whenever a null data source is encountered (this can be because `data` 11408 * is set to null, or because the data source itself is null). 11409 */ 11410 "sDefaultContent": null, 11411 11412 11413 /** 11414 * This parameter is only used in DataTables' server-side processing. It can 11415 * be exceptionally useful to know what columns are being displayed on the 11416 * client side, and to map these to database fields. When defined, the names 11417 * also allow DataTables to reorder information from the server if it comes 11418 * back in an unexpected order (i.e. if you switch your columns around on the 11419 * client-side, your server-side code does not also need updating). 11420 */ 11421 "sName": "", 11422 11423 11424 /** 11425 * Defines a data source type for the ordering which can be used to read 11426 * real-time information from the table (updating the internally cached 11427 * version) prior to ordering. This allows ordering to occur on user 11428 * editable elements such as form inputs. 11429 */ 11430 "sSortDataType": "std", 11431 11432 11433 /** 11434 * The title of this column. 11435 */ 11436 "sTitle": null, 11437 11438 11439 /** 11440 * The type allows you to specify how the data for this column will be
11441 * ordered. Four types (string, numeric, date and html (which will strip 11442 * HTML tags before ordering)) are currently available. Note that only date 11443 * formats understood by Javascript's Date() object will be accepted as type 11444 * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string', 11445 * 'numeric', 'date' or 'html' (by default). Further types can be adding 11446 * through plug-ins. 11447 */ 11448 "sType": null, 11449 11450 11451 /** 11452 * Defining the width of the column, this parameter may take any CSS value 11453 * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not 11454 * been given a specific width through this interface ensuring that the table 11455 * remains readable. 11456 */ 11457 "sWidth": null 11458 }; 11459 11460 _fnHungarianMap( DataTable.defaults.column ); 11461 11462 11463 11464 /** 11465 * DataTables settings object - this holds all the information needed for a 11466 * given table, including configuration, data and current application of the 11467 * table options. DataTables does not have a single instance for each DataTable 11468 * with the settings attached to that instance, but rather instances of the 11469 * DataTable "class" are created on-the-fly as needed (typically by a 11470 * $().dataTable() call) and the settings object is then applied to that 11471 * instance. 11472 * 11473 * Note that this object is related to {@link DataTable.defaults} but this 11474 * one is the internal data store for DataTables's cache of columns. It should 11475 * NOT be manipulated outside of DataTables. Any configuration should be done 11476 * through the initialisation options. 11477 */ 11478 DataTable.models.oSettings = { 11479 /** 11480 * Primary features of DataTables and their enablement state. 11481 */ 11482 "oFeatures": { 11483 11484 /** 11485 * Flag to say if DataTables should automatically try to calculate the 11486 * optimum table and columns widths (true) or not (false). 11487 * Note that this parameter will be set by the initialisation routine. To 11488 * set a default use {@link DataTable.defaults}. 11489 */ 11490 "bAutoWidth": null, 11491 11492 /** 11493 * Delay the creation of TR and TD elements until they are actually 11494 * needed by a driven page draw. This can give a significant speed 11495 * increase for Ajax source and Javascript source data, but makes no 11496 * difference at all for DOM and server-side processing tables. 11497 * Note that this parameter will be set by the initialisation routine. To 11498 * set a default use {@link DataTable.defaults}. 11499 */ 11500 "bDeferRender": null, 11501 11502 /** 11503 * Enable filtering on the table or not. Note that if this is disabled 11504 * then there is no filtering at all on the table, including fnFilter. 11505 * To just remove the filtering input use sDom and remove the 'f' option. 11506 * Note that this parameter will be set by the initialisation routine. To 11507 * set a default use {@link DataTable.defaults}. 11508 */ 11509 "bFilter": null, 11510 11511 /** 11512 * Used only for compatiblity with DT1 11513 * @deprecated 11514 */ 11515 "bInfo": true, 11516 11517 /** 11518 * Used only for compatiblity with DT1 11519 * @deprecated 11520 */ 11521 "bLengthChange": true, 11522 11523 /** 11524 * Pagination enabled or not. Note that if this is disabled then length 11525 * changing must also be disabled. 11526 * Note that this parameter will be set by the initialisation routine. To 11527 * set a default use {@link DataTable.defaults}. 11528 */ 11529 "bPaginate": null, 11530 11531 /** 11532 * Processing indicator enable flag whenever DataTables is enacting a 11533 * user request - typically an Ajax request for server-side processing. 11534 * Note that this parameter will be set by the initialisation routine. To 11535 * set a default use {@link DataTable.defaults}. 11536 */ 11537 "bProcessing": null, 11538 11539 /** 11540 * Server-side processing enabled flag - when enabled DataTables will 11541 * get all data from the server for every draw - there is no filtering, 11542 * sorting or paging done on the client-side. 11543 * Note that this parameter will be set by the initialisation routine. To 11544 * set a default use {@link DataTable.defaults}. 11545 */ 11546 "bServerSide": null, 11547 11548 /** 11549 * Sorting enablement flag. 11550 * Note that this parameter will be set by the initialisation routine. To 11551 * set a default use {@link DataTable.defaults}. 11552 */ 11553 "bSort": null, 11554 11555 /**
11556 * Multi-column sorting 11557 * Note that this parameter will be set by the initialisation routine. To 11558 * set a default use {@link DataTable.defaults}. 11559 */ 11560 "bSortMulti": null, 11561 11562 /** 11563 * Apply a class to the columns which are being sorted to provide a 11564 * visual highlight or not. This can slow things down when enabled since 11565 * there is a lot of DOM interaction. 11566 * Note that this parameter will be set by the initialisation routine. To 11567 * set a default use {@link DataTable.defaults}. 11568 */ 11569 "bSortClasses": null, 11570 11571 /** 11572 * State saving enablement flag. 11573 * Note that this parameter will be set by the initialisation routine. To 11574 * set a default use {@link DataTable.defaults}. 11575 */ 11576 "bStateSave": null 11577 }, 11578 11579 11580 /** 11581 * Scrolling settings for a table. 11582 */ 11583 "oScroll": { 11584 /** 11585 * When the table is shorter in height than sScrollY, collapse the 11586 * table container down to the height of the table (when true). 11587 * Note that this parameter will be set by the initialisation routine. To 11588 * set a default use {@link DataTable.defaults}. 11589 */ 11590 "bCollapse": null, 11591 11592 /** 11593 * Width of the scrollbar for the web-browser's platform. Calculated 11594 * during table initialisation. 11595 */ 11596 "iBarWidth": 0, 11597 11598 /** 11599 * Viewport width for horizontal scrolling. Horizontal scrolling is 11600 * disabled if an empty string. 11601 * Note that this parameter will be set by the initialisation routine. To 11602 * set a default use {@link DataTable.defaults}. 11603 */ 11604 "sX": null, 11605 11606 /** 11607 * Width to expand the table to when using x-scrolling. Typically you 11608 * should not need to use this. 11609 * Note that this parameter will be set by the initialisation routine. To 11610 * set a default use {@link DataTable.defaults}. 11611 * @deprecated 11612 */ 11613 "sXInner": null, 11614 11615 /** 11616 * Viewport height for vertical scrolling. Vertical scrolling is disabled 11617 * if an empty string. 11618 * Note that this parameter will be set by the initialisation routine. To 11619 * set a default use {@link DataTable.defaults}. 11620 */ 11621 "sY": null 11622 }, 11623 11624 /** 11625 * Language information for the table. 11626 */ 11627 "oLanguage": { 11628 /** 11629 * Information callback function. See 11630 * {@link DataTable.defaults.fnInfoCallback} 11631 */ 11632 "fnInfoCallback": null 11633 }, 11634 11635 /** 11636 * Browser support parameters 11637 */ 11638 "oBrowser": { 11639 /** 11640 * Determine if the vertical scrollbar is on the right or left of the 11641 * scrolling container - needed for rtl language layout, although not 11642 * all browsers move the scrollbar (Safari). 11643 */ 11644 "bScrollbarLeft": false, 11645 11646 /** 11647 * Browser scrollbar width 11648 */ 11649 "barWidth": 0 11650 }, 11651 11652 11653 "ajax": null, 11654 11655 11656 /** 11657 * Array referencing the nodes which are used for the features. The 11658 * parameters of this object match what is allowed by sDom - i.e. 11659 * <ul> 11660 * <li>'l' - Length changing</li> 11661 * <li>'f' - Filtering input</li> 11662 * <li>'t' - The table!</li> 11663 * <li>'i' - Information</li> 11664 * <li>'p' - Pagination</li> 11665 * <li>'r' - pRocessing</li> 11666 * </ul> 11667 */ 11668 "aanFeatures": [], 11669 11670 /** 11671 * Store data information - see {@link DataTable.models.oRow} for detailed 11672 * information. 11673 */ 11674 "aoData": [], 11675 11676 /** 11677 * Array of indexes which are in the current display (after filtering etc) 11678 */ 11679 "aiDisplay": [], 11680 11681 /** 11682 * Array of indexes for display - no filtering 11683 */ 11684 "aiDisplayMaster": [], 11685 11686 /** 11687 * Map of row ids to data indexes 11688 */ 11689 "aIds": {}, 11690 11691 /** 11692 * Store information about each column that is in use 11693 */ 11694 "aoColumns": [], 11695 11696 /** 11697 * Store information about the table's header 11698 */ 11699 "aoHeader": [], 11700 11701 /** 11702 * Store information about the table's footer 11703 */ 11704 "aoFooter": [], 11705 11706 /** 11707 * Store the applied global search information in case we want to force a 11708 * research or compare the old search to a new one. 11709 * Note that this parameter will be set by the initialisation routine. To 11710 * set a default use {@link DataTable.defaults}. 11711 */ 11712 "oPreviousSearch": {}, 11713 11714 /** 11715 * Store for named searches 11716 */ 11717 searchFixed: {}, 11718 11719 /** 11720 * Store the applied search for each column - see 11721 * {@link DataTable.models.oSearch} for the format that is used for the 11722 * filtering information for each column. 11723 */ 11724 "aoPreSearchCols": [], 11725 11726 /** 11727 * Sorting that is applied to the table. Note that the inner arrays are 11728 * used in the following manner: 11729 * <ul> 11730 * <li>Index 0 - column number</li> 11731 * <li>Index 1 - current sorting direction</li> 11732 * </ul> 11733 * Note that this parameter will be set by the initialisation routine. To 11734 * set a default use {@link DataTable.defaults}. 11735 */ 11736 "aaSorting": null, 11737 11738 /** 11739 * Sorting that is always applied to the table (i.e. prefixed in front of 11740 * aaSorting). 11741 * Note that this parameter will be set by the initialisation routine. To 11742 * set a default use {@link DataTable.defaults}. 11743 */ 11744 "aaSortingFixed": [], 11745 11746 /** 11747 * If restoring a table - we should restore its width 11748 */ 11749 "sDestroyWidth": 0, 11750 11751 /** 11752 * Callback functions array for every time a row is inserted (i.e. on a draw). 11753 */ 11754 "aoRowCallback": [], 11755 11756 /** 11757 * Callback functions for the header on each draw. 11758 */ 11759 "aoHeaderCallback": [], 11760 11761 /** 11762 * Callback function for the footer on each draw. 11763 */ 11764 "aoFooterCallback": [], 11765 11766 /** 11767 * Array of callback functions for draw callback functions 11768 */ 11769 "aoDrawCallback": [], 11770 11771 /** 11772 * Array of callback functions for row created function 11773 */ 11774 "aoRowCreatedCallback": [], 11775 11776 /** 11777 * Callback functions for just before the table is redrawn. A return of 11778 * false will be used to cancel the draw. 11779 */ 11780 "aoPreDrawCallback": [], 11781 11782 /** 11783 * Callback functions for when the table has been initialised. 11784 */ 11785 "aoInitComplete": [], 11786 11787 11788 /** 11789 * Callbacks for modifying the settings to be stored for state saving, prior to 11790 * saving state. 11791 */ 11792 "aoStateSaveParams": [], 11793 11794 /** 11795 * Callbacks for modifying the settings that have been stored for state saving 11796 * prior to using the stored values to restore the state. 11797 */ 11798 "aoStateLoadParams": [], 11799 11800 /** 11801 * Callbacks for operating on the settings object once the saved state has been 11802 * loaded 11803 */ 11804 "aoStateLoaded": [], 11805 11806 /** 11807 * Cache the table ID for quick access 11808 */ 11809 "sTableId": "", 11810 11811 /** 11812 * The TABLE node for the main table 11813 */ 11814 "nTable": null, 11815 11816 /** 11817 * Permanent ref to the thead element 11818 */ 11819 "nTHead": null, 11820 11821 /** 11822 * Permanent ref to the tfoot element - if it exists 11823 */ 11824 "nTFoot": null, 11825 11826 /** 11827 * Permanent ref to the tbody element 11828 */ 11829 "nTBody": null, 11830 11831 /** 11832 * Cache the wrapper node (contains all DataTables controlled elements) 11833 */ 11834 "nTableWrapper": null, 11835 11836 /** 11837 * Indicate if all required information has been read in 11838 */ 11839 "bInitialised": false, 11840 11841 /** 11842 * Information about open rows. Each object in the array has the parameters 11843 * 'nTr' and 'nParent' 11844 */ 11845 "aoOpenRows": [], 11846 11847 /**
11848 * Dictate the positioning of DataTables' control elements - see 11849 * {@link DataTable.model.oInit.sDom}. 11850 * Note that this parameter will be set by the initialisation routine. To 11851 * set a default use {@link DataTable.defaults}. 11852 */ 11853 "sDom": null, 11854 11855 /** 11856 * Search delay (in mS) 11857 */ 11858 "searchDelay": null, 11859 11860 /** 11861 * Which type of pagination should be used. 11862 * Note that this parameter will be set by the initialisation routine. To 11863 * set a default use {@link DataTable.defaults}. 11864 */ 11865 "sPaginationType": "two_button", 11866 11867 /** 11868 * Number of paging controls on the page. Only used for backwards compatibility 11869 */ 11870 pagingControls: 0, 11871 11872 /** 11873 * The state duration (for `stateSave`) in seconds. 11874 * Note that this parameter will be set by the initialisation routine. To 11875 * set a default use {@link DataTable.defaults}. 11876 */ 11877 "iStateDuration": 0, 11878 11879 /** 11880 * Array of callback functions for state saving. Each array element is an 11881 * object with the following parameters: 11882 * <ul> 11883 * <li>function:fn - function to call. Takes two parameters, oSettings 11884 * and the JSON string to save that has been thus far created. Returns 11885 * a JSON string to be inserted into a json object 11886 * (i.e. '"param": [ 0, 1, 2]')</li> 11887 * <li>string:sName - name of callback</li> 11888 * </ul> 11889 */ 11890 "aoStateSave": [], 11891 11892 /** 11893 * Array of callback functions for state loading. Each array element is an 11894 * object with the following parameters: 11895 * <ul> 11896 * <li>function:fn - function to call. Takes two parameters, oSettings 11897 * and the object stored. May return false to cancel state loading</li> 11898 * <li>string:sName - name of callback</li> 11899 * </ul> 11900 */ 11901 "aoStateLoad": [], 11902 11903 /** 11904 * State that was saved. Useful for back reference 11905 */ 11906 "oSavedState": null, 11907 11908 /** 11909 * State that was loaded. Useful for back reference 11910 */ 11911 "oLoadedState": null, 11912 11913 /** 11914 * Note if draw should be blocked while getting data 11915 */ 11916 "bAjaxDataGet": true, 11917 11918 /** 11919 * The last jQuery XHR object that was used for server-side data gathering. 11920 * This can be used for working with the XHR information in one of the 11921 * callbacks 11922 */ 11923 "jqXHR": null, 11924 11925 /** 11926 * JSON returned from the server in the last Ajax request 11927 */ 11928 "json": undefined, 11929 11930 /** 11931 * Data submitted as part of the last Ajax request 11932 */ 11933 "oAjaxData": undefined, 11934 11935 /** 11936 * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if 11937 * required). 11938 * Note that this parameter will be set by the initialisation routine. To 11939 * set a default use {@link DataTable.defaults}. 11940 */ 11941 "sServerMethod": null, 11942 11943 /** 11944 * Format numbers for display. 11945 * Note that this parameter will be set by the initialisation routine. To 11946 * set a default use {@link DataTable.defaults}. 11947 */ 11948 "fnFormatNumber": null, 11949 11950 /** 11951 * List of options that can be used for the user selectable length menu. 11952 * Note that this parameter will be set by the initialisation routine. To 11953 * set a default use {@link DataTable.defaults}. 11954 */ 11955 "aLengthMenu": null, 11956 11957 /** 11958 * Counter for the draws that the table does. Also used as a tracker for 11959 * server-side processing 11960 */ 11961 "iDraw": 0, 11962 11963 /** 11964 * Indicate if a redraw is being done - useful for Ajax 11965 */ 11966 "bDrawing": false, 11967 11968 /** 11969 * Draw index (iDraw) of the last error when parsing the returned data 11970 */ 11971 "iDrawError": -1, 11972 11973 /** 11974 * Paging display length 11975 */ 11976 "_iDisplayLength": 10, 11977 11978 /** 11979 * Paging start point - aiDisplay index 11980 */ 11981 "_iDisplayStart": 0, 11982 11983 /** 11984 * Server-side processing - number of records in the result set 11985 * (i.e. before filtering), Use fnRecordsTotal rather than 11986 * this property to get the value of the number of records, regardless of 11987 * the server-side processing setting. 11988 */ 11989 "_iRecordsTotal": 0, 11990 11991 /** 11992 * Server-side processing - number of records in the current display set 11993 * (i.e. after filtering). Use fnRecordsDisplay rather than 11994 * this property to get the value of the number of records, regardless of 11995 * the server-side processing setting. 11996 */ 11997 "_iRecordsDisplay": 0, 11998 11999 /** 12000 * The classes to use for the table 12001 */
12002 "oClasses": {}, 12003 12004 /** 12005 * Flag attached to the settings object so you can check in the draw 12006 * callback if filtering has been done in the draw. Deprecated in favour of 12007 * events. 12008 * @deprecated 12009 */ 12010 "bFiltered": false, 12011 12012 /** 12013 * Flag attached to the settings object so you can check in the draw 12014 * callback if sorting has been done in the draw. Deprecated in favour of 12015 * events. 12016 * @deprecated 12017 */ 12018 "bSorted": false, 12019 12020 /** 12021 * Indicate that if multiple rows are in the header and there is more than 12022 * one unique cell per column, if the top one (true) or bottom one (false) 12023 * should be used for sorting / title by DataTables. 12024 * Note that this parameter will be set by the initialisation routine. To 12025 * set a default use {@link DataTable.defaults}. 12026 */ 12027 "bSortCellsTop": null, 12028 12029 /** 12030 * Initialisation object that is used for the table 12031 */ 12032 "oInit": null, 12033 12034 /** 12035 * Destroy callback functions - for plug-ins to attach themselves to the 12036 * destroy so they can clean up markup and events. 12037 */ 12038 "aoDestroyCallback": [], 12039 12040 12041 /** 12042 * Get the number of records in the current record set, before filtering 12043 */ 12044 "fnRecordsTotal": function () 12045 { 12046 return _fnDataSource( this ) == 'ssp' ? 12047 this._iRecordsTotal * 1 : 12048 this.aiDisplayMaster.length; 12049 }, 12050 12051 /** 12052 * Get the number of records in the current record set, after filtering 12053 */ 12054 "fnRecordsDisplay": function () 12055 { 12056 return _fnDataSource( this ) == 'ssp' ? 12057 this._iRecordsDisplay * 1 : 12058 this.aiDisplay.length; 12059 }, 12060 12061 /** 12062 * Get the display end point - aiDisplay index 12063 */ 12064 "fnDisplayEnd": function () 12065 { 12066 var 12067 len = this._iDisplayLength, 12068 start = this._iDisplayStart, 12069 calc = start + len, 12070 records = this.aiDisplay.length, 12071 features = this.oFeatures, 12072 paginate = features.bPaginate; 12073 12074 if ( features.bServerSide ) { 12075 return paginate === false || len === -1 ? 12076 start + records : 12077 Math.min( start+len, this._iRecordsDisplay ); 12078 } 12079 else { 12080 return ! paginate || calc>records || len===-1 ? 12081 records : 12082 calc; 12083 } 12084 }, 12085 12086 /** 12087 * The DataTables object for this table 12088 */ 12089 "oInstance": null, 12090 12091 /** 12092 * Unique identifier for each instance of the DataTables object. If there 12093 * is an ID on the table node, then it takes that value, otherwise an 12094 * incrementing internal counter is used. 12095 */ 12096 "sInstance": null, 12097 12098 /** 12099 * tabindex attribute value that is added to DataTables control elements, allowing 12100 * keyboard navigation of the table and its controls. 12101 */ 12102 "iTabIndex": 0, 12103 12104 /** 12105 * DIV container for the footer scrolling table if scrolling 12106 */ 12107 "nScrollHead": null, 12108 12109 /** 12110 * DIV container for the footer scrolling table if scrolling 12111 */ 12112 "nScrollFoot": null, 12113 12114 /** 12115 * Last applied sort 12116 */ 12117 "aLastSort": [], 12118 12119 /** 12120 * Stored plug-in instances 12121 */ 12122 "oPlugins": {}, 12123 12124 /** 12125 * Function used to get a row's id from the row's data 12126 */ 12127 "rowIdFn": null, 12128 12129 /** 12130 * Data location where to store a row's id 12131 */ 12132 "rowId": null, 12133 12134 caption: '', 12135 12136 captionNode: null, 12137 12138 colgroup: null, 12139 12140 /** Delay loading of data */ 12141 deferLoading: null, 12142 12143 /** Allow auto type detection */ 12144 typeDetect: true, 12145 12146 /** ResizeObserver for the container div */ 12147 resizeObserver: null, 12148 12149 /** Keep a record of the last size of the container, so we can skip duplicates */ 12150 containerWidth: -1 12151 }; 12152 12153 /** 12154 * Extension object for DataTables that is used to provide all extension 12155 * options. 12156 * 12157 * Note that the `DataTable.ext` object is available through 12158 * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is 12159 * also aliased to `jQuery.fn.dataTableExt` for historic reasons. 12160 * @namespace 12161 * @extends DataTable.models.ext 12162 */ 12163 12164 12165 var extPagination = DataTable.ext.pager; 12166 12167 // Paging buttons configuration 12168 $.extend( extPagination, { 12169 simple: function () { 12170 return [ 'previous', 'next' ]; 12171 }, 12172 12173 full: function () { 12174 return [ 'first', 'previous', 'next', 'last' ]; 12175 }, 12176 12177 numbers: function () { 12178 return [ 'numbers' ]; 12179 }, 12180 12181 simple_numbers: function () { 12182 return [ 'previous', 'numbers', 'next' ]; 12183 }, 12184 12185 full_numbers: function () { 12186 return [ 'first', 'previous', 'numbers', 'next', 'last' ]; 12187 }, 12188 12189 first_last: function () { 12190 return ['first', 'last']; 12191 }, 12192 12193 first_last_numbers: function () { 12194 return ['first', 'numbers', 'last']; 12195 }, 12196 12197 // For testing and plug-ins to use 12198 _numbers: _pagingNumbers, 12199 12200 // Number of number buttons - legacy, use `numbers` option for paging feature 12201 numbers_length: 7 12202 } ); 12203 12204 12205 $.extend( true, DataTable.ext.renderer, { 12206 pagingButton: { 12207 _: function (settings, buttonType, content, active, disabled) { 12208 var classes = settings.oClasses.paging; 12209 var btnClasses = [classes.button]; 12210 var btn; 12211 12212 if (active) { 12213 btnClasses.push(classes.active); 12214 } 12215 12216 if (disabled) { 12217 btnClasses.push(classes.disabled) 12218 } 12219 12220 if (buttonType === 'ellipsis') { 12221 btn = $('<span class="ellipsis"></span>').html(content)[0]; 12222 } 12223 else { 12224 btn = $('<button>', { 12225 class: btnClasses.join(' '), 12226 role: 'link', 12227 type: 'button' 12228 }).html(content); 12229 } 12230 12231 return { 12232 display: btn, 12233 clicker: btn 12234 } 12235 } 12236 }, 12237 12238 pagingContainer: { 12239 _: function (settings, buttons) { 12240 // No wrapping element - just append directly to the host 12241 return buttons; 12242 } 12243 } 12244 } ); 12245 12246 // Common function to remove new lines, strip HTML and diacritic control 12247 var _filterString = function (stripHtml, normalize) { 12248 return function (str) { 12249 if (_empty(str) || typeof str !== 'string') { 12250 return str; 12251 } 12252 12253 str = str.replace( _re_new_lines, " " ); 12254 12255 if (stripHtml) { 12256 str = _stripHtml(str); 12257 } 12258 12259 if (normalize) { 12260 str = _normalize(str, false); 12261 } 12262 12263 return str; 12264 }; 12265 } 12266 12267 /* 12268 * Public helper functions. These aren't used internally by DataTables, or 12269 * called by any of the options passed into DataTables, but they can be used 12270 * externally by developers working with DataTables. They are helper functions 12271 * to make working with DataTables a little bit easier. 12272 */ 12273 12274 /** 12275 * Common logic for moment, luxon or a date action. 12276 * 12277 * Happens after __mldObj, so don't need to call `resolveWindowsLibs` again 12278 */ 12279 function __mld( dtLib, momentFn, luxonFn, dateFn, arg1 ) { 12280 if (__moment) { 12281 return dtLib[momentFn]( arg1 ); 12282 } 12283 else if (__luxon) { 12284 return dtLib[luxonFn]( arg1 ); 12285 } 12286 12287 return dateFn ? dtLib[dateFn]( arg1 ) : dtLib; 12288 } 12289 12290 12291 var __mlWarning = false;
12292 var __luxon; // Can be assigned in DateTable.use() 12293 var __moment; // Can be assigned in DateTable.use() 12294 12295 /** 12296 * 12297 */ 12298 function resolveWindowLibs() { 12299 if (window.luxon && ! __luxon) { 12300 __luxon = window.luxon; 12301 } 12302 12303 if (window.moment && ! __moment) { 12304 __moment = window.moment; 12305 } 12306 } 12307 12308 function __mldObj (d, format, locale) { 12309 var dt; 12310 12311 resolveWindowLibs(); 12312 12313 if (__moment) { 12314 dt = __moment.utc( d, format, locale, true ); 12315 12316 if (! dt.isValid()) { 12317 return null; 12318 } 12319 } 12320 else if (__luxon) { 12321 dt = format && typeof d === 'string' 12322 ? __luxon.DateTime.fromFormat( d, format ) 12323 : __luxon.DateTime.fromISO( d ); 12324 12325 if (! dt.isValid) { 12326 return null; 12327 } 12328 12329 dt = dt.setLocale(locale); 12330 } 12331 else if (! format) { 12332 // No format given, must be ISO 12333 dt = new Date(d); 12334 } 12335 else { 12336 if (! __mlWarning) { 12337 alert('DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17'); 12338 } 12339 12340 __mlWarning = true; 12341 } 12342 12343 return dt; 12344 } 12345 12346 // Wrapper for date, datetime and time which all operate the same way with the exception of 12347 // the output string for auto locale support 12348 function __mlHelper (localeString) { 12349 return function ( from, to, locale, def ) { 12350 // Luxon and Moment support 12351 // Argument shifting 12352 if ( arguments.length === 0 ) { 12353 locale = 'en'; 12354 to = null; // means toLocaleString 12355 from = null; // means iso8601 12356 } 12357 else if ( arguments.length === 1 ) { 12358 locale = 'en'; 12359 to = from; 12360 from = null; 12361 } 12362 else if ( arguments.length === 2 ) { 12363 locale = to; 12364 to = from; 12365 from = null; 12366 } 12367 12368 var typeName = 'datetime' + (to ? '-' + to : ''); 12369 12370 // Add type detection and sorting specific to this date format - we need to be able to identify 12371 // date type columns as such, rather than as numbers in extensions. Hence the need for this. 12372 if (! DataTable.ext.type.order[typeName + '-pre']) { 12373 DataTable.type(typeName, { 12374 detect: function (d) { 12375 // The renderer will give the value to type detect as the type! 12376 return d === typeName ? typeName : false; 12377 }, 12378 order: { 12379 pre: function (d) { 12380 // The renderer gives us Moment, Luxon or Date obects for the sorting, all of which have a 12381 // `valueOf` which gives milliseconds epoch 12382 return d.valueOf(); 12383 } 12384 }, 12385 className: 'dt-right' 12386 }); 12387 } 12388 12389 return function ( d, type ) { 12390 // Allow for a default value 12391 if (d === null || d === undefined) { 12392 if (def === '--now') { 12393 // We treat everything as UTC further down, so no changes are 12394 // made, as such need to get the local date / time as if it were 12395 // UTC 12396 var local = new Date(); 12397 d = new Date( Date.UTC( 12398 local.getFullYear(), local.getMonth(), local.getDate(), 12399 local.getHours(), local.getMinutes(), local.getSeconds() 12400 ) ); 12401 } 12402 else { 12403 d = ''; 12404 } 12405 } 12406 12407 if (type === 'type') { 12408 // Typing uses the type name for fast matching 12409 return typeName; 12410 } 12411 12412 if (d === '') { 12413 return type !== 'sort' 12414 ? '' 12415 : __mldObj('0000-01-01 00:00:00', null, locale); 12416 } 12417 12418 // Shortcut. If `from` and `to` are the same, we are using the renderer to 12419 // format for ordering, not display - its already in the display format. 12420 if ( to !== null && from === to && type !== 'sort' && type !== 'type' && ! (d instanceof Date) ) { 12421 return d; 12422 } 12423 12424 var dt = __mldObj(d, from, locale); 12425 12426 if (dt === null) { 12427 return d; 12428 } 12429 12430 if (type === 'sort') { 12431 return dt; 12432 } 12433 12434 var formatted = to === null 12435 ? __mld(dt, 'toDate', 'toJSDate', '')[localeString]() 12436 : __mld(dt, 'format', 'toFormat', 'toISOString', to); 12437 12438 // XSS protection 12439 return type === 'display' ? 12440 _escapeHtml( formatted ) : 12441 formatted; 12442 }; 12443 } 12444 } 12445 12446 // Based on locale, determine standard number formatting 12447 // Fallback for legacy browsers is US English 12448 var __thousands = ','; 12449 var __decimal = '.'; 12450 12451 if (window.Intl !== undefined) { 12452 try { 12453 var num = new Intl.NumberFormat().formatToParts(100000.1); 12454 12455 for (var i=0 ; i<num.length ; i++) { 12456 if (num[i].type === 'group') { 12457 __thousands = num[i].value; 12458 } 12459 else if (num[i].type === 'decimal') { 12460 __decimal = num[i].value; 12461 } 12462 } 12463 } 12464 catch (e) { 12465 // noop 12466 } 12467 } 12468 12469 // Formatted date time detection - use by declaring the formats you are going to use 12470 DataTable.datetime = function ( format, locale ) { 12471 var typeName = 'datetime-' + format; 12472 12473 if (! locale) { 12474 locale = 'en'; 12475 } 12476 12477 if (! DataTable.ext.type.order[typeName]) { 12478 DataTable.type(typeName, { 12479 detect: function (d) { 12480 var dt = __mldObj(d, format, locale); 12481 return d === '' || dt ? typeName : false;
12482 }, 12483 order: { 12484 pre: function (d) { 12485 return __mldObj(d, format, locale) || 0; 12486 } 12487 }, 12488 className: 'dt-right' 12489 }); 12490 } 12491 } 12492 12493 /** 12494 * Helpers for `columns.render`. 12495 * 12496 * The options defined here can be used with the `columns.render` initialisation 12497 * option to provide a display renderer. The following functions are defined: 12498 * 12499 * * `moment` - Uses the MomentJS library to convert from a given format into another. 12500 * This renderer has three overloads: 12501 * * 1 parameter: 12502 * * `string` - Format to convert to (assumes input is ISO8601 and locale is `en`) 12503 * * 2 parameters: 12504 * * `string` - Format to convert from 12505 * * `string` - Format to convert to. Assumes `en` locale 12506 * * 3 parameters: 12507 * * `string` - Format to convert from 12508 * * `string` - Format to convert to 12509 * * `string` - Locale 12510 * * `number` - Will format numeric data (defined by `columns.data`) for 12511 * display, retaining the original unformatted data for sorting and filtering. 12512 * It takes 5 parameters: 12513 * * `string` - Thousands grouping separator 12514 * * `string` - Decimal point indicator 12515 * * `integer` - Number of decimal points to show 12516 * * `string` (optional) - Prefix. 12517 * * `string` (optional) - Postfix (/suffix). 12518 * * `text` - Escape HTML to help prevent XSS attacks. It has no optional 12519 * parameters. 12520 * 12521 * @example 12522 * // Column definition using the number renderer 12523 * { 12524 * data: "salary", 12525 * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' ) 12526 * } 12527 * 12528 * @namespace 12529 */ 12530 DataTable.render = { 12531 date: __mlHelper('toLocaleDateString'), 12532 datetime: __mlHelper('toLocaleString'), 12533 time: __mlHelper('toLocaleTimeString'), 12534 number: function ( thousands, decimal, precision, prefix, postfix ) { 12535 // Auto locale detection 12536 if (thousands === null || thousands === undefined) { 12537 thousands = __thousands; 12538 } 12539 12540 if (decimal === null || decimal === undefined) { 12541 decimal = __decimal; 12542 } 12543 12544 return { 12545 display: function ( d ) { 12546 if ( typeof d !== 'number' && typeof d !== 'string' ) { 12547 return d; 12548 } 12549 12550 if (d === '' || d === null) { 12551 return d; 12552 } 12553 12554 var negative = d < 0 ? '-' : ''; 12555 var flo = parseFloat( d ); 12556 var abs = Math.abs(flo); 12557 12558 // Scientific notation for large and small numbers 12559 if (abs >= 100000000000 || (abs < 0.0001 && abs !== 0) ) { 12560 var exp = flo.toExponential(precision).split(/e\+?/); 12561 return exp[0] + ' x 10<sup>' + exp[1] + '</sup>'; 12562 } 12563 12564 // If NaN then there isn't much formatting that we can do - just 12565 // return immediately, escaping any HTML (this was supposed to 12566 // be a number after all) 12567 if ( isNaN( flo ) ) { 12568 return _escapeHtml( d ); 12569 } 12570 12571 flo = flo.toFixed( precision ); 12572 d = Math.abs( flo ); 12573 12574 var intPart = parseInt( d, 10 ); 12575 var floatPart = precision ? 12576 decimal+(d - intPart).toFixed( precision ).substring( 2 ): 12577 ''; 12578 12579 // If zero, then can't have a negative prefix 12580 if (intPart === 0 && parseFloat(floatPart) === 0) { 12581 negative = ''; 12582 } 12583 12584 return negative + (prefix||'') + 12585 intPart.toString().replace( 12586 /\B(?=(\d{3})+(?!\d))/g, thousands 12587 ) + 12588 floatPart + 12589 (postfix||''); 12590 } 12591 }; 12592 }, 12593 12594 text: function () { 12595 return { 12596 display: _escapeHtml, 12597 filter: _escapeHtml 12598 }; 12599 } 12600 }; 12601 12602 12603 var _extTypes = DataTable.ext.type; 12604 12605 // Get / set type 12606 DataTable.type = function (name, prop, val) { 12607 if (! prop) { 12608 return { 12609 className: _extTypes.className[name], 12610 detect: _extTypes.detect.find(function (fn) { 12611 return fn._name === name; 12612 }), 12613 order: { 12614 pre: _extTypes.order[name + '-pre'], 12615 asc: _extTypes.order[name + '-asc'], 12616 desc: _extTypes.order[name + '-desc'] 12617 }, 12618 render: _extTypes.render[name], 12619 search: _extTypes.search[name] 12620 }; 12621 } 12622 12623 var setProp = function(prop, propVal) { 12624 _extTypes[prop][name] = propVal; 12625 }; 12626 var setDetect = function (detect) { 12627 // `detect` can be a function or an object - we set a name 12628 // property for either - that is used for the detection 12629 Object.defineProperty(detect, "_name", {value: name}); 12630 12631 var idx = _extTypes.detect.findIndex(function (item) { 12632 return item._name === name; 12633 }); 12634 12635 if (idx === -1) { 12636 _extTypes.detect.unshift(detect); 12637 } 12638 else { 12639 _extTypes.detect.splice(idx, 1, detect); 12640 } 12641 }; 12642 var setOrder = function (obj) { 12643 _extTypes.order[name + '-pre'] = obj.pre; // can be undefined 12644 _extTypes.order[name + '-asc'] = obj.asc; // can be undefined 12645 _extTypes.order[name + '-desc'] = obj.desc; // can be undefined 12646 }; 12647 12648 // prop is optional 12649 if (val === undefined) { 12650 val = prop; 12651 prop = null; 12652 } 12653 12654 if (prop === 'className') { 12655 setProp('className', val); 12656 } 12657 else if (prop === 'detect') { 12658 setDetect(val); 12659 } 12660 else if (prop === 'order') { 12661 setOrder(val); 12662 } 12663 else if (prop === 'render') { 12664 setProp('render', val); 12665 } 12666 else if (prop === 'search') { 12667 setProp('search', val); 12668 } 12669 else if (! prop) { 12670 if (val.className) { 12671 setProp('className', val.className); 12672 } 12673 12674 if (val.detect !== undefined) { 12675 setDetect(val.detect); 12676 } 12677 12678 if (val.order) { 12679 setOrder(val.order); 12680 } 12681 12682 if (val.render !== undefined) { 12683 setProp('render', val.render); 12684 } 12685 12686 if (val.search !== undefined) { 12687 setProp('search', val.search); 12688 } 12689 } 12690 } 12691 12692 // Get a list of types 12693 DataTable.types = function () { 12694 return _extTypes.detect.map(function (fn) { 12695 return fn._name; 12696 }); 12697 }; 12698 12699 var __diacriticSort = function (a, b) { 12700 a = a !== null && a !== undefined ? a.toString().toLowerCase() : ''; 12701 b = b !== null && b !== undefined ? b.toString().toLowerCase() : ''; 12702 12703 // Checked for `navigator.languages` support in `oneOf` so this code can't execute in old 12704 // Safari and thus can disable this check 12705 // eslint-disable-next-line compat/compat 12706 return a.localeCompare(b, navigator.languages[0] || navigator.language, { 12707 numeric: true, 12708 ignorePunctuation: true, 12709 }); 12710 } 12711 12712 var __diacriticHtmlSort = function (a, b) { 12713 a = _stripHtml(a); 12714 b = _stripHtml(b); 12715 12716 return __diacriticSort(a, b); 12717 } 12718 12719 // 12720 // Built in data types 12721 // 12722 12723 DataTable.type('string', { 12724 detect: function () { 12725 return 'string'; 12726 }, 12727 order: { 12728 pre: function ( a ) { 12729 // This is a little complex, but faster than always calling toString, 12730 // http://jsperf.com/tostring-v-check 12731 return _empty(a) && typeof a !== 'boolean' ? 12732 '' : 12733 typeof a === 'string' ? 12734 a.toLowerCase() : 12735 ! a.toString ? 12736 '' : 12737 a.toString(); 12738 } 12739 }, 12740 search: _filterString(false, true) 12741 }); 12742 12743 DataTable.type('string-utf8', { 12744 detect: { 12745 allOf: function ( d ) { 12746 return true; 12747 }, 12748 oneOf: function ( d ) { 12749 // At least one data point must contain a non-ASCII character 12750 // This line will also check if navigator.languages is supported or not. If not (Safari 10.0-) 12751 // this data type won't be supported. 12752 // eslint-disable-next-line compat/compat 12753 return ! _empty( d ) && navigator.languages && typeof d === 'string' && d.match(/[^\x00-\x7F]/); 12754 } 12755 }, 12756 order: { 12757 asc: __diacriticSort, 12758 desc: function (a, b) { 12759 return __diacriticSort(a, b) * -1; 12760 } 12761 }, 12762 search: _filterString(false, true) 12763 }); 12764 12765 12766 DataTable.type('html', { 12767 detect: { 12768 allOf: function ( d ) { 12769 return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1); 12770 }, 12771 oneOf: function ( d ) { 12772 // At least one data point must contain a `<` 12773 return ! _empty( d ) && typeof d === 'string' && d.indexOf('<') !== -1; 12774 } 12775 }, 12776 order: { 12777 pre: function ( a ) { 12778 return _empty(a) ? 12779 '' : 12780 a.replace ?
12781 _stripHtml(a).trim().toLowerCase() : 12782 a+''; 12783 } 12784 }, 12785 search: _filterString(true, true) 12786 }); 12787 12788 12789 DataTable.type('html-utf8', { 12790 detect: { 12791 allOf: function ( d ) { 12792 return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1); 12793 }, 12794 oneOf: function ( d ) { 12795 // At least one data point must contain a `<` and a non-ASCII character 12796 // eslint-disable-next-line compat/compat 12797 return navigator.languages && 12798 ! _empty( d ) && 12799 typeof d === 'string' && 12800 d.indexOf('<') !== -1 && 12801 typeof d === 'string' && d.match(/[^\x00-\x7F]/); 12802 } 12803 }, 12804 order: { 12805 asc: __diacriticHtmlSort, 12806 desc: function (a, b) { 12807 return __diacriticHtmlSort(a, b) * -1; 12808 } 12809 }, 12810 search: _filterString(true, true) 12811 }); 12812 12813 12814 DataTable.type('date', { 12815 className: 'dt-type-date', 12816 detect: { 12817 allOf: function ( d ) { 12818 // V8 tries _very_ hard to make a string passed into `Date.parse()` 12819 // valid, so we need to use a regex to restrict date formats. Use a 12820 // plug-in for anything other than ISO8601 style strings 12821 if ( d && !(d instanceof Date) && ! _re_date.test(d) ) { 12822 return null; 12823 } 12824 var parsed = Date.parse(d); 12825 return (parsed !== null && !isNaN(parsed)) || _empty(d); 12826 }, 12827 oneOf: function ( d ) { 12828 // At least one entry must be a date or a string with a date 12829 return (d instanceof Date) || (typeof d === 'string' && _re_date.test(d)); 12830 } 12831 }, 12832 order: { 12833 pre: function ( d ) { 12834 var ts = Date.parse( d ); 12835 return isNaN(ts) ? -Infinity : ts; 12836 } 12837 } 12838 }); 12839 12840 12841 DataTable.type('html-num-fmt', { 12842 className: 'dt-type-numeric', 12843 detect: { 12844 allOf: function ( d, settings ) { 12845 var decimal = settings.oLanguage.sDecimal; 12846 return _htmlNumeric( d, decimal, true, false ); 12847 }, 12848 oneOf: function (d, settings) { 12849 // At least one data point must contain a numeric value 12850 var decimal = settings.oLanguage.sDecimal; 12851 return _htmlNumeric( d, decimal, true, false ); 12852 } 12853 }, 12854 order: { 12855 pre: function ( d, s ) { 12856 var dp = s.oLanguage.sDecimal; 12857 return __numericReplace( d, dp, _re_html, _re_formatted_numeric ); 12858 } 12859 }, 12860 search: _filterString(true, true) 12861 }); 12862 12863 12864 DataTable.type('html-num', { 12865 className: 'dt-type-numeric', 12866 detect: { 12867 allOf: function ( d, settings ) { 12868 var decimal = settings.oLanguage.sDecimal; 12869 return _htmlNumeric( d, decimal, false, true ); 12870 }, 12871 oneOf: function (d, settings) { 12872 // At least one data point must contain a numeric value 12873 var decimal = settings.oLanguage.sDecimal; 12874 return _htmlNumeric( d, decimal, false, false ); 12875 } 12876 }, 12877 order: { 12878 pre: function ( d, s ) { 12879 var dp = s.oLanguage.sDecimal; 12880 return __numericReplace( d, dp, _re_html ); 12881 } 12882 }, 12883 search: _filterString(true, true) 12884 }); 12885 12886 12887 DataTable.type('num-fmt', { 12888 className: 'dt-type-numeric', 12889 detect: { 12890 allOf: function ( d, settings ) { 12891 var decimal = settings.oLanguage.sDecimal; 12892 return _isNumber( d, decimal, true, true ); 12893 }, 12894 oneOf: function (d, settings) { 12895 // At least one data point must contain a numeric value 12896 var decimal = settings.oLanguage.sDecimal; 12897 return _isNumber( d, decimal, true, false ); 12898 } 12899 }, 12900 order: { 12901 pre: function ( d, s ) { 12902 var dp = s.oLanguage.sDecimal; 12903 return __numericReplace( d, dp, _re_formatted_numeric ); 12904 } 12905 } 12906 }); 12907 12908 12909 DataTable.type('num', { 12910 className: 'dt-type-numeric', 12911 detect: { 12912 allOf: function ( d, settings ) { 12913 var decimal = settings.oLanguage.sDecimal; 12914 return _isNumber( d, decimal, false, true ); 12915 }, 12916 oneOf: function (d, settings) { 12917 // At least one data point must contain a numeric value 12918 var decimal = settings.oLanguage.sDecimal; 12919 return _isNumber( d, decimal, false, false ); 12920 } 12921 }, 12922 order: { 12923 pre: function (d, s) { 12924 var dp = s.oLanguage.sDecimal; 12925 return __numericReplace( d, dp ); 12926 } 12927 } 12928 }); 12929 12930 12931 12932 12933 var __numericReplace = function ( d, decimalPlace, re1, re2 ) { 12934 if ( d !== 0 && (!d || d === '-') ) { 12935 return -Infinity; 12936 } 12937 12938 var type = typeof d; 12939 12940 if (type === 'number' || type === 'bigint') { 12941 return d; 12942 } 12943 12944 // If a decimal place other than `.` is used, it needs to be given to the 12945 // function so we can detect it and replace with a `.` which is the only 12946 // decimal place Javascript recognises - it is not locale aware. 12947 if ( decimalPlace ) { 12948 d = _numToDecimal( d, decimalPlace ); 12949 } 12950 12951 if ( d.replace ) { 12952 if ( re1 ) { 12953 d = d.replace( re1, '' ); 12954 } 12955 12956 if ( re2 ) { 12957 d = d.replace( re2, '' ); 12958 } 12959 } 12960 12961 return d * 1; 12962 }; 12963 12964 12965 $.extend( true, DataTable.ext.renderer, { 12966 footer: { 12967 _: function ( settings, cell, classes ) {
12968 cell.addClass(classes.tfoot.cell); 12969 } 12970 }, 12971 12972 header: { 12973 _: function ( settings, cell, classes ) { 12974 cell.addClass(classes.thead.cell); 12975 12976 if (! settings.oFeatures.bSort) { 12977 cell.addClass(classes.order.none); 12978 } 12979 12980 var legacyTop = settings.bSortCellsTop; 12981 var headerRows = cell.closest('thead').find('tr'); 12982 var rowIdx = cell.parent().index(); 12983 12984 // Conditions to not apply the ordering icons 12985 if ( 12986 // Cells and rows which have the attribute to disable the icons 12987 cell.attr('data-dt-order') === 'disable' || 12988 cell.parent().attr('data-dt-order') === 'disable' || 12989 12990 // Legacy support for `orderCellsTop`. If it is set, then cells 12991 // which are not in the top or bottom row of the header (depending 12992 // on the value) do not get the sorting classes applied to them 12993 (legacyTop === true && rowIdx !== 0) || 12994 (legacyTop === false && rowIdx !== headerRows.length - 1) 12995 ) { 12996 return; 12997 } 12998 12999 // No additional mark-up required 13000 // Attach a sort listener to update on sort - note that using the 13001 // `DT` namespace will allow the event to be removed automatically 13002 // on destroy, while the `dt` namespaced event is the one we are 13003 // listening for 13004 $(settings.nTable).on( 'order.dt.DT column-visibility.dt.DT', function ( e, ctx ) { 13005 if ( settings !== ctx ) { // need to check this this is the host 13006 return; // table, not a nested one 13007 } 13008 13009 var sorting = ctx.sortDetails; 13010
13011 if (! sorting) { 13012 return; 13013 } 13014 13015 var i; 13016 var orderClasses = classes.order; 13017 var columns = ctx.api.columns( cell ); 13018 var col = settings.aoColumns[columns.flatten()[0]]; 13019 var orderable = columns.orderable().includes(true); 13020 var ariaType = ''; 13021 var indexes = columns.indexes(); 13022 var sortDirs = columns.orderable(true).flatten(); 13023 var orderedColumns = _pluck(sorting, 'col'); 13024 var tabIndex = settings.iTabIndex; 13025 13026 cell 13027 .removeClass( 13028 orderClasses.isAsc +' '+ 13029 orderClasses.isDesc 13030 ) 13031 .toggleClass( orderClasses.none, ! orderable ) 13032 .toggleClass( orderClasses.canAsc, orderable && sortDirs.includes('asc') ) 13033 .toggleClass( orderClasses.canDesc, orderable && sortDirs.includes('desc') ); 13034 13035 // Determine if all of the columns that this cell covers are included in the 13036 // current ordering 13037 var isOrdering = true; 13038 13039 for (i=0; i<indexes.length; i++) { 13040 if (! orderedColumns.includes(indexes[i])) { 13041 isOrdering = false; 13042 } 13043 } 13044 13045 if ( isOrdering ) { 13046 // Get the ordering direction for the columns under this cell 13047 // Note that it is possible for a cell to be asc and desc sorting 13048 // (column spanning cells) 13049 var orderDirs = columns.order(); 13050 13051 cell.addClass( 13052 orderDirs.includes('asc') ? orderClasses.isAsc : '' + 13053 orderDirs.includes('desc') ? orderClasses.isDesc : '' 13054 ); 13055 } 13056 13057 // Find the first visible column that has ordering applied to it - it get's 13058 // the aria information, as the ARIA spec says that only one column should 13059 // be marked with aria-sort 13060 var firstVis = -1; // column index 13061 13062 for (i=0; i<orderedColumns.length; i++) { 13063 if (settings.aoColumns[orderedColumns[i]].bVisible) { 13064 firstVis = orderedColumns[i]; 13065 break; 13066 } 13067 } 13068 13069 if (indexes[0] == firstVis) { 13070 var firstSort = sorting[0]; 13071 var sortOrder = col.asSorting; 13072 13073 cell.attr('aria-sort', firstSort.dir === 'asc' ? 'ascending' : 'descending'); 13074 13075 // Determine if the next click will remove sorting or change the sort 13076 ariaType = ! sortOrder[firstSort.index + 1] ? 'Remove' : 'Reverse'; 13077 } 13078 else { 13079 cell.removeAttr('aria-sort'); 13080 } 13081 13082 // Make the headers tab-able for keyboard navigation 13083 if (orderable) { 13084 var orderSpan = cell.find('.dt-column-order'); 13085 13086 orderSpan 13087 .attr('role', 'button') 13088 .attr('aria-label', orderable 13089 ? col.ariaTitle + ctx.api.i18n('oAria.orderable' + ariaType) 13090 : col.ariaTitle 13091 ); 13092 13093 if (tabIndex !== -1) { 13094 orderSpan.attr('tabindex', tabIndex); 13095 } 13096 } 13097 } ); 13098 } 13099 }, 13100 13101 layout: { 13102 _: function ( settings, container, items ) { 13103 var classes = settings.oClasses.layout; 13104 var row = $('<div/>') 13105 .attr('id', items.id || null) 13106 .addClass(items.className || classes.row) 13107 .appendTo( container ); 13108 13109 DataTable.ext.renderer.layout._forLayoutRow(items, function (key, val) { 13110 if (key === 'id' || key === 'className') { 13111 return; 13112 } 13113 13114 var klass = ''; 13115 13116 if (val.table) {
13117 row.addClass(classes.tableRow); 13118 klass += classes.tableCell + ' '; 13119 } 13120 13121 if (key === 'start') { 13122 klass += classes.start; 13123 } 13124 else if (key === 'end') { 13125 klass += classes.end; 13126 } 13127 else { 13128 klass += classes.full; 13129 } 13130 13131 $('<div/>') 13132 .attr({ 13133 id: val.id || null, 13134 "class": val.className 13135 ? val.className 13136 : classes.cell + ' ' + klass 13137 }) 13138 .append( val.contents ) 13139 .appendTo( row ); 13140 }); 13141 }, 13142 13143 // Shared for use by the styling frameworks 13144 _forLayoutRow: function (items, fn) { 13145 // As we are inserting dom elements, we need start / end in a 13146 // specific order, this function is used for sorting the layout 13147 // keys. 13148 var layoutEnum = function (x) { 13149 switch (x) { 13150 case '': return 0; 13151 case 'start': return 1; 13152 case 'end': return 2; 13153 default: return 3; 13154 } 13155 }; 13156 13157 Object 13158 .keys(items) 13159 .sort(function (a, b) { 13160 return layoutEnum(a) - layoutEnum(b); 13161 }) 13162 .forEach(function (key) { 13163 fn(key, items[key]); 13164 }); 13165 } 13166 } 13167 } ); 13168 13169 13170 DataTable.feature = {}; 13171 13172 // Third parameter is internal only! 13173 DataTable.feature.register = function ( name, cb, legacy ) { 13174 DataTable.ext.features[ name ] = cb; 13175 13176 if (legacy) { 13177 _ext.feature.push({ 13178 cFeature: legacy, 13179 fnInit: cb 13180 }); 13181 } 13182 }; 13183 13184 function _divProp(el, prop, val) { 13185 if (val) { 13186 el[prop] = val; 13187 } 13188 } 13189 13190 DataTable.feature.register( 'div', function ( settings, opts ) { 13191 var n = $('<div>')[0]; 13192 13193 if (opts) { 13194 _divProp(n, 'className', opts.className); 13195 _divProp(n, 'id', opts.id); 13196 _divProp(n, 'innerHTML', opts.html); 13197 _divProp(n, 'textContent', opts.text); 13198 } 13199 13200 return n; 13201 } ); 13202 13203 DataTable.feature.register( 'info', function ( settings, opts ) { 13204 // For compatibility with the legacy `info` top level option 13205 if (! settings.oFeatures.bInfo) { 13206 return null; 13207 } 13208 13209 var 13210 lang = settings.oLanguage, 13211 tid = settings.sTableId, 13212 n = $('<div/>', { 13213 'class': settings.oClasses.info.container, 13214 } ); 13215 13216 opts = $.extend({ 13217 callback: lang.fnInfoCallback, 13218 empty: lang.sInfoEmpty, 13219 postfix: lang.sInfoPostFix, 13220 search: lang.sInfoFiltered, 13221 text: lang.sInfo, 13222 }, opts); 13223 13224 13225 // Update display on each draw 13226 settings.aoDrawCallback.push(function (s) { 13227 _fnUpdateInfo(s, opts, n); 13228 }); 13229 13230 // For the first info display in the table, we add a callback and aria information. 13231 if (! settings._infoEl) { 13232 n.attr({ 13233 'aria-live': 'polite', 13234 id: tid+'_info', 13235 role: 'status' 13236 }); 13237 13238 // Table is described by our info div 13239 $(settings.nTable).attr( 'aria-describedby', tid+'_info' ); 13240 13241 settings._infoEl = n; 13242 } 13243 13244 return n; 13245 }, 'i' ); 13246 13247 /** 13248 * Update the information elements in the display 13249 * @param {object} settings dataTables settings object 13250 * @memberof DataTable#oApi 13251 */ 13252 function _fnUpdateInfo ( settings, opts, node ) 13253 { 13254 var 13255 start = settings._iDisplayStart+1, 13256 end = settings.fnDisplayEnd(), 13257 max = settings.fnRecordsTotal(), 13258 total = settings.fnRecordsDisplay(), 13259 out = total 13260 ? opts.text 13261 : opts.empty; 13262 13263 if ( total !== max ) { 13264 // Record set after filtering 13265 out += ' ' + opts.search; 13266 } 13267 13268 // Convert the macros 13269 out += opts.postfix; 13270 out = _fnMacros( settings, out ); 13271 13272 if ( opts.callback ) { 13273 out = opts.callback.call( settings.oInstance, 13274 settings, start, end, max, total, out 13275 ); 13276 } 13277 13278 node.html( out ); 13279 13280 _fnCallbackFire(settings, null, 'info', [settings, node[0], out]); 13281 } 13282 13283 var __searchCounter = 0; 13284 13285 // opts 13286 // - text 13287 // - placeholder 13288 DataTable.feature.register( 'search', function ( settings, opts ) { 13289 // Don't show the input if filtering isn't available on the table 13290 if (! settings.oFeatures.bFilter) { 13291 return null; 13292 } 13293 13294 var classes = settings.oClasses.search; 13295 var tableId = settings.sTableId; 13296 var language = settings.oLanguage; 13297 var previousSearch = settings.oPreviousSearch; 13298 var input = '<input type="search" class="'+classes.input+'"/>'; 13299 13300 opts = $.extend({ 13301 placeholder: language.sSearchPlaceholder, 13302 processing: false, 13303 text: language.sSearch 13304 }, opts); 13305
13306 // The _INPUT_ is optional - is appended if not present 13307 if (opts.text.indexOf('_INPUT_') === -1) { 13308 opts.text += '_INPUT_'; 13309 } 13310 13311 opts.text = _fnMacros(settings, opts.text); 13312 13313 // We can put the <input> outside of the label if it is at the start or end 13314 // which helps improve accessability (not all screen readers like implicit 13315 // for elements). 13316 var end = opts.text.match(/_INPUT_$/); 13317 var start = opts.text.match(/^_INPUT_/); 13318 var removed = opts.text.replace(/_INPUT_/, ''); 13319 var str = '<label>' + opts.text + '</label>'; 13320 13321 if (start) { 13322 str = '_INPUT_<label>' + removed + '</label>'; 13323 } 13324 else if (end) { 13325 str = '<label>' + removed + '</label>_INPUT_'; 13326 } 13327 13328 var filter = $('<div>') 13329 .addClass(classes.container) 13330 .append(str.replace(/_INPUT_/, input)); 13331 13332 // add for and id to label and input 13333 filter.find('label').attr('for', 'dt-search-' + __searchCounter); 13334 filter.find('input').attr('id', 'dt-search-' + __searchCounter); 13335 __searchCounter++; 13336 13337 var searchFn = function(event) { 13338 var val = this.value; 13339 13340 if(previousSearch.return && event.key !== "Enter") { 13341 return; 13342 } 13343 13344 /* Now do the filter */ 13345 if ( val != previousSearch.search ) { 13346 _fnProcessingRun(settings, opts.processing, function () { 13347 previousSearch.search = val; 13348 13349 _fnFilterComplete( settings, previousSearch ); 13350 13351 // Need to redraw, without resorting 13352 settings._iDisplayStart = 0; 13353 _fnDraw( settings ); 13354 }); 13355 } 13356 }; 13357 13358 var searchDelay = settings.searchDelay !== null ? 13359 settings.searchDelay : 13360 0; 13361 13362 var jqFilter = $('input', filter) 13363 .val( previousSearch.search ) 13364 .attr( 'placeholder', opts.placeholder ) 13365 .on( 13366 'keyup.DT search.DT input.DT paste.DT cut.DT', 13367 searchDelay ? 13368 DataTable.util.debounce( searchFn, searchDelay ) : 13369 searchFn 13370 ) 13371 .on( 'mouseup.DT', function(e) { 13372 // Edge fix! Edge 17 does not trigger anything other than mouse events when clicking 13373 // on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn` 13374 // checks the value to see if it has changed. In other browsers it won't have. 13375 setTimeout( function () { 13376 searchFn.call(jqFilter[0], e); 13377 }, 10); 13378 } ) 13379 .on( 'keypress.DT', function(e) { 13380 /* Prevent form submission */ 13381 if ( e.keyCode == 13 ) { 13382 return false; 13383 } 13384 } ) 13385 .attr('aria-controls', tableId); 13386 13387 // Update the input elements whenever the table is filtered 13388 $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) { 13389 if ( settings === s && jqFilter[0] !== document.activeElement ) { 13390 jqFilter.val( typeof previousSearch.search !== 'function' 13391 ? previousSearch.search 13392 : '' 13393 ); 13394 } 13395 } ); 13396 13397 return filter; 13398 }, 'f' ); 13399 13400 // opts 13401 // - type - button configuration 13402 // - buttons - number of buttons to show - must be odd 13403 DataTable.feature.register( 'paging', function ( settings, opts ) { 13404 // Don't show the paging input if the table doesn't have paging enabled 13405 if (! settings.oFeatures.bPaginate) { 13406 return null; 13407 } 13408 13409 opts = $.extend({ 13410 buttons: DataTable.ext.pager.numbers_length, 13411 type: settings.sPaginationType, 13412 boundaryNumbers: true, 13413 firstLast: true, 13414 previousNext: true, 13415 numbers: true 13416 }, opts); 13417 13418 var host = $('<div/>') 13419 .addClass(settings.oClasses.paging.container + (opts.type ? ' paging_' + opts.type : '')) 13420 .append( 13421 $('<nav>') 13422 .attr('aria-label', 'pagination') 13423 .addClass(settings.oClasses.paging.nav) 13424 ); 13425 var draw = function () { 13426 _pagingDraw(settings, host.children(), opts); 13427 }; 13428 13429 settings.aoDrawCallback.push(draw); 13430 13431 // Responsive redraw of paging control 13432 $(settings.nTable).on('column-sizing.dt.DT', draw); 13433 13434 return host; 13435 }, 'p' ); 13436 13437 /** 13438 * Dynamically create the button type array based on the configuration options. 13439 * This will only happen if the paging type is not defined. 13440 */ 13441 function _pagingDynamic(opts) { 13442 var out = []; 13443 13444 if (opts.numbers) { 13445 out.push('numbers'); 13446 } 13447 13448 if (opts.previousNext) { 13449 out.unshift('previous'); 13450 out.push('next'); 13451 } 13452 13453 if (opts.firstLast) { 13454 out.unshift('first'); 13455 out.push('last'); 13456 } 13457 13458 return out; 13459 } 13460 13461 function _pagingDraw(settings, host, opts) { 13462 if (! settings._bInitComplete) { 13463 return; 13464 } 13465 13466 var 13467 plugin = opts.type 13468 ? DataTable.ext.pager[ opts.type ] 13469 : _pagingDynamic, 13470 aria = settings.oLanguage.oAria.paginate || {}, 13471 start = settings._iDisplayStart, 13472 len = settings._iDisplayLength, 13473 visRecords = settings.fnRecordsDisplay(), 13474 all = len === -1, 13475 page = all ? 0 : Math.ceil( start / len ), 13476 pages = all ? 1 : Math.ceil( visRecords / len ), 13477 buttons = [], 13478 buttonEls = [], 13479 buttonsNested = plugin(opts) 13480 .map(function (val) { 13481 return val === 'numbers' 13482 ? _pagingNumbers(page, pages, opts.buttons, opts.boundaryNumbers) 13483 : val; 13484 }); 13485 13486 // .flat() would be better, but not supported in old Safari 13487 buttons = buttons.concat.apply(buttons, buttonsNested); 13488 13489 for (var i=0 ; i<buttons.length ; i++) { 13490 var button = buttons[i]; 13491 13492 var btnInfo = _pagingButtonInfo(settings, button, page, pages); 13493 var btn = _fnRenderer( settings, 'pagingButton' )( 13494 settings, 13495 button, 13496 btnInfo.display, 13497 btnInfo.active, 13498 btnInfo.disabled 13499 ); 13500 13501 var ariaLabel = typeof button === 'string' 13502 ? aria[ button ] 13503 : aria.number 13504 ? aria.number + (button+1) 13505 : null; 13506 13507 // Common attributes 13508 $(btn.clicker).attr({ 13509 'aria-controls': settings.sTableId, 13510 'aria-disabled': btnInfo.disabled ? 'true' : null, 13511 'aria-current': btnInfo.active ? 'page' : null, 13512 'aria-label': ariaLabel, 13513 'data-dt-idx': button, 13514 'tabIndex': btnInfo.disabled 13515 ? -1 13516 : settings.iTabIndex && btn.clicker[0].nodeName.toLowerCase() !== 'span' 13517 ? settings.iTabIndex 13518 : null, // `0` doesn't need a tabIndex since it is the default 13519 }); 13520 13521 if (typeof button !== 'number') { 13522 $(btn.clicker).addClass(button); 13523 } 13524 13525 _fnBindAction( 13526 btn.clicker, {action: button}, function(e) { 13527 e.preventDefault(); 13528 13529 _fnPageChange( settings, e.data.action, true ); 13530 } 13531 ); 13532 13533 buttonEls.push(btn.display); 13534 } 13535 13536 var wrapped = _fnRenderer(settings, 'pagingContainer')( 13537 settings, buttonEls 13538 ); 13539 13540 var activeEl = host.find(document.activeElement).data('dt-idx'); 13541 13542 host.empty().append(wrapped); 13543 13544 if ( activeEl !== undefined ) { 13545 host.find( '[data-dt-idx='+activeEl+']' ).trigger('focus'); 13546 } 13547 13548 // Responsive - check if the buttons are over two lines based on the 13549 // height of the buttons and the container. 13550 if (buttonEls.length) { 13551 var outerHeight = $(buttonEls[0]).outerHeight(); 13552 13553 if ( 13554 opts.buttons > 1 && // prevent infinite 13555 outerHeight > 0 && // will be 0 if hidden 13556 $(host).height() >= (outerHeight * 2) - 10 13557 ) { 13558 _pagingDraw(settings, host, $.extend({}, opts, { buttons: opts.buttons - 2 })); 13559 } 13560 } 13561 } 13562 13563 /** 13564 * Get properties for a button based on the current paging state of the table 13565 * 13566 * @param {*} settings DT settings object 13567 * @param {*} button The button type in question 13568 * @param {*} page Table's current page 13569 * @param {*} pages Number of pages 13570 * @returns Info object 13571 */ 13572 function _pagingButtonInfo(settings, button, page, pages) { 13573 var lang = settings.oLanguage.oPaginate; 13574 var o = { 13575 display: '', 13576 active: false, 13577 disabled: false 13578 }; 13579 13580 switch ( button ) { 13581 case 'ellipsis': 13582 o.display = '…'; 13583 break; 13584 13585 case 'first': 13586 o.display = lang.sFirst; 13587 13588 if (page === 0) { 13589 o.disabled = true; 13590 } 13591 break; 13592 13593 case 'previous': 13594 o.display = lang.sPrevious; 13595 13596 if ( page === 0 ) { 13597 o.disabled = true; 13598 } 13599 break; 13600 13601 case 'next': 13602 o.display = lang.sNext; 13603 13604 if ( pages === 0 || page === pages-1 ) { 13605 o.disabled = true; 13606 } 13607 break; 13608 13609 case 'last': 13610 o.display = lang.sLast; 13611 13612 if ( pages === 0 || page === pages-1 ) { 13613 o.disabled = true; 13614 } 13615 break; 13616 13617 default: 13618 if ( typeof button === 'number' ) { 13619 o.display = settings.fnFormatNumber( button + 1 ); 13620 13621 if (page === button) { 13622 o.active = true; 13623 } 13624 } 13625 break; 13626 } 13627 13628 return o; 13629 } 13630 13631 /** 13632 * Compute what number buttons to show in the paging control 13633 * 13634 * @param {*} page Current page 13635 * @param {*} pages Total number of pages 13636 * @param {*} buttons Target number of number buttons 13637 * @param {boolean} addFirstLast Indicate if page 1 and end should be included 13638 * @returns Buttons to show 13639 */ 13640 function _pagingNumbers ( page, pages, buttons, addFirstLast ) { 13641 var 13642 numbers = [], 13643 half = Math.floor(buttons / 2), 13644 before = addFirstLast ? 2 : 1, 13645 after = addFirstLast ? 1 : 0; 13646 13647 if ( pages <= buttons ) { 13648 numbers = _range(0, pages); 13649 } 13650 else if (buttons === 1) { 13651 // Single button - current page only 13652 numbers = [page]; 13653 } 13654 else if (buttons === 3) { 13655 // Special logic for just three buttons 13656 if (page <= 1) { 13657 numbers = [0, 1, 'ellipsis']; 13658 } 13659 else if (page >= pages - 2) { 13660 numbers = _range(pages-2, pages); 13661 numbers.unshift('ellipsis'); 13662 } 13663 else { 13664 numbers = ['ellipsis', page, 'ellipsis']; 13665 } 13666 } 13667 else if ( page <= half ) { 13668 numbers = _range(0, buttons-before); 13669 numbers.push('ellipsis'); 13670 13671 if (addFirstLast) { 13672 numbers.push(pages-1); 13673 } 13674 } 13675 else if ( page >= pages - 1 - half ) { 13676 numbers = _range(pages-(buttons-before), pages); 13677 numbers.unshift('ellipsis'); 13678 13679 if (addFirstLast) { 13680 numbers.unshift(0); 13681 } 13682 } 13683 else { 13684 numbers = _range(page-half+before, page+half-after); 13685 numbers.push('ellipsis'); 13686 numbers.unshift('ellipsis'); 13687 13688 if (addFirstLast) { 13689 numbers.push(pages-1); 13690 numbers.unshift(0); 13691 } 13692 } 13693 13694 return numbers; 13695 } 13696 13697 var __lengthCounter = 0; 13698 13699 // opts 13700 // - menu 13701 // - text 13702 DataTable.feature.register( 'pageLength', function ( settings, opts ) { 13703 var features = settings.oFeatures; 13704 13705 // For compatibility with the legacy `pageLength` top level option 13706 if (! features.bPaginate || ! features.bLengthChange) { 13707 return null; 13708 } 13709 13710 opts = $.extend({ 13711 menu: settings.aLengthMenu, 13712 text: settings.oLanguage.sLengthMenu 13713 }, opts); 13714 13715 var 13716 classes = settings.oClasses.length, 13717 tableId = settings.sTableId, 13718 menu = opts.menu, 13719 lengths = [], 13720 language = [], 13721 i; 13722 13723 // Options can be given in a number of ways 13724 if (Array.isArray( menu[0] )) { 13725 // Old 1.x style - 2D array 13726 lengths = menu[0]; 13727 language = menu[1]; 13728 } 13729 else { 13730 for ( i=0 ; i<menu.length ; i++ ) { 13731 // An object with different label and value 13732 if ($.isPlainObject(menu[i])) { 13733 lengths.push(menu[i].value); 13734 language.push(menu[i].label); 13735 } 13736 else { 13737 // Or just a number to display and use 13738 lengths.push(menu[i]); 13739 language.push(menu[i]); 13740 } 13741 } 13742 } 13743 13744 // We can put the <select> outside of the label if it is at the start or 13745 // end which helps improve accessability (not all screen readers like 13746 // implicit for elements). 13747 var end = opts.text.match(/_MENU_$/); 13748 var start = opts.text.match(/^_MENU_/); 13749 var removed = opts.text.replace(/_MENU_/, ''); 13750 var str = '<label>' + opts.text + '</label>'; 13751 13752 if (start) { 13753 str = '_MENU_<label>' + removed + '</label>'; 13754 } 13755 else if (end) { 13756 str = '<label>' + removed + '</label>_MENU_'; 13757 } 13758 13759 // Wrapper element - use a span as a holder for where the select will go 13760 var tmpId = 'tmp-' + (+new Date())
13761 var div = $('<div/>') 13762 .addClass( classes.container ) 13763 .append( 13764 str.replace( '_MENU_', '<span id="'+tmpId+'"></span>' ) 13765 ); 13766 13767 // Save text node content for macro updating 13768 var textNodes = []; 13769 Array.prototype.slice.call(div.find('label')[0].childNodes).forEach(function (el) { 13770 if (el.nodeType === Node.TEXT_NODE) { 13771 textNodes.push({ 13772 el: el, 13773 text: el.textContent 13774 }); 13775 } 13776 }); 13777 13778 // Update the label text in case it has an entries value 13779 var updateEntries = function (len) { 13780 textNodes.forEach(function (node) { 13781 node.el.textContent = _fnMacros(settings, node.text, len); 13782 }); 13783 } 13784 13785 // Next, the select itself, along with the options 13786 var select = $('<select/>', { 13787 'aria-controls': tableId, 13788 'class': classes.select 13789 } ); 13790 13791 for ( i=0 ; i<lengths.length ; i++ ) { 13792 select[0][ i ] = new Option( 13793 typeof language[i] === 'number' ? 13794 settings.fnFormatNumber( language[i] ) : 13795 language[i], 13796 lengths[i] 13797 ); 13798 } 13799 13800 // add for and id to label and input 13801 div.find('label').attr('for', 'dt-length-' + __lengthCounter); 13802 select.attr('id', 'dt-length-' + __lengthCounter); 13803 __lengthCounter++; 13804 13805 // Swap in the select list 13806 div.find('#' + tmpId).replaceWith(select); 13807 13808 // Can't use `select` variable as user might provide their own and the 13809 // reference is broken by the use of outerHTML 13810 $('select', div) 13811 .val( settings._iDisplayLength ) 13812 .on( 'change.DT', function() { 13813 _fnLengthChange( settings, $(this).val() ); 13814 _fnDraw( settings ); 13815 } ); 13816 13817 // Update node value whenever anything changes the table's length 13818 $(settings.nTable).on( 'length.dt.DT', function (e, s, len) { 13819 if ( settings === s ) { 13820 $('select', div).val( len ); 13821 13822 // Resolve plurals in the text for the new length 13823 updateEntries(len); 13824 } 13825 } ); 13826 13827 updateEntries(settings._iDisplayLength); 13828 13829 return div; 13830 }, 'l' ); 13831 13832 // jQuery access 13833 $.fn.dataTable = DataTable; 13834 13835 // Provide access to the host jQuery object (circular reference) 13836 DataTable.$ = $; 13837 13838 // Legacy aliases 13839 $.fn.dataTableSettings = DataTable.settings; 13840 $.fn.dataTableExt = DataTable.ext; 13841 13842 // With a capital `D` we return a DataTables API instance rather than a 13843 // jQuery object 13844 $.fn.DataTable = function ( opts ) { 13845 return $(this).dataTable( opts ).api(); 13846 }; 13847 13848 // All properties that are available to $.fn.dataTable should also be 13849 // available on $.fn.DataTable 13850 $.each( DataTable, function ( prop, val ) { 13851 $.fn.DataTable[ prop ] = val; 13852 } ); 13853 13854 return DataTable; 13855})); 13856 13857 13858/*! DataTables styling integration 13859 * © SpryMedia Ltd - datatables.net/license 13860 */ 13861 13862(function( factory ){ 13863 if ( typeof define === 'function' && define.amd ) { 13864 // AMD 13865 define( ['jquery', 'datatables.net'], function ( $ ) { 13866 return factory( $, window, document ); 13867 } ); 13868 } 13869 else if ( typeof exports === 'object' ) { 13870 // CommonJS 13871 var jq = require('jquery'); 13872 var cjsRequires = function (root, $) { 13873 if ( ! $.fn.dataTable ) { 13874 require('datatables.net')(root, $); 13875 } 13876 }; 13877 13878 if (typeof window === 'undefined') { 13879 module.exports = function (root, $) { 13880 if ( ! root ) { 13881 // CommonJS environments without a window global must pass a 13882 // root. This will give an error otherwise 13883 root = window; 13884 } 13885 13886 if ( ! $ ) { 13887 $ = jq( root ); 13888 } 13889 13890 cjsRequires( root, $ ); 13891 return factory( $, root, root.document ); 13892 }; 13893 } 13894 else { 13895 cjsRequires( window, jq ); 13896 module.exports = factory( jq, window, window.document ); 13897 } 13898 } 13899 else { 13900 // Browser 13901 factory( jQuery, window, document ); 13902 } 13903}(function( $, window, document ) { 13904'use strict'; 13905var DataTable = $.fn.dataTable; 13906 13907 13908 13909 13910 13911return DataTable; 13912})); 13913 13914 13915 13916/*! 13917 * jScrollPane - v2.0.19 - 2013-11-16 13918 * http://jscrollpane.kelvinluck.com/ 13919 * 13920 * Copyright (c) 2013 Kelvin Luck 13921 * Dual licensed under the MIT or GPL licenses. 13922 */ 13923! function(a, b, c) { 13924 a.fn.jScrollPane = function(d) { 13925 function e(d, e) { 13926 function f(b) { 13927 var e, h, j, l, m, n, q = !1, 13928 r = !1; 13929 if (P = b, Q === c) m = d.scrollTop(), n = d.scrollLeft(), d.css({ 13930 overflow: "hidden", 13931 padding: 0 13932 }), R = d.innerWidth() + tb, S = d.innerHeight(), d.width(R), Q = a('<div class="jspPane" />').css("padding", sb).append(d.children()), T = a('<div class="jspContainer" />').css({ 13933 width: R + "px", 13934 height: S + "px" 13935 }).append(Q).appendTo(d); 13936 else { 13937 if (d.css("width", ""), q = P.stickToBottom && C(), r = P.stickToRight && D(), l = d.innerWidth() + tb != R || d.outerHeight() != S, l && (R = d.innerWidth() + tb, S = d.innerHeight(), T.css({ 13938 width: R + "px", 13939 height: S + "px" 13940 })), !l && ub == U && Q.outerHeight() == V) return d.width(R), void 0; 13941 ub = U, Q.css("width", ""), d.width(R), T.find(">.jspVerticalBar,>.jspHorizontalBar").remove().end() 13942 } 13943 Q.css("overflow", "auto"), U = b.contentWidth ? b.contentWidth : Q[0].scrollWidth, V = Q[0].scrollHeight, Q.css("overflow", ""), W = U / R, X = V / S, Y = X > 1, Z = W > 1, Z || Y ? (d.addClass("jspScrollable"), e = P.maintainPosition && (ab || db), e && (h = A(), j = B()), g(), i(), k(), e && (y(r ? U - R : h, !1), x(q ? V - S : j, !1)), H(), E(), N(), P.enableKeyboardNavigation && J(), P.clickOnTrack && o(), L(), P.hijackInternalLinks && M()) : (d.removeClass("jspScrollable"), Q.css({ 13944 top: 0, 13945 left: 0, 13946 width: T.width() - tb 13947 }), F(), I(), K(), p()), P.autoReinitialise && !rb ? rb = setInterval(function() { 13948 f(P) 13949 }, P.autoReinitialiseDelay) : !P.autoReinitialise && rb && clearInterval(rb), m && d.scrollTop(0) && x(m, !1), n && d.scrollLeft(0) && y(n, !1), d.trigger("jsp-initialised", [Z || Y]) 13950 } 13951 13952 function g() { 13953 Y && (T.append(a('<div class="jspVerticalBar" />').append(a('<div class="jspCap jspCapTop" />'), a('<div class="jspTrack" />').append(a('<div class="jspDrag" />').append(a('<div class="jspDragTop" />'), a('<div class="jspDragBottom" />'))), a('<div class="jspCap jspCapBottom" />'))), eb = T.find(">.jspVerticalBar"), fb = eb.find(">.jspTrack"), $ = fb.find(">
vendor: 5,041 bytes, lines 13953-14029
13953.jspDrag"), P.showArrows && (jb = a('<a class="jspArrow jspArrowUp" />').bind("mousedown.jsp", m(0, -1)).bind("click.jsp", G), kb = a('<a class="jspArrow jspArrowDown" />').bind("mousedown.jsp", m(0, 1)).bind("click.jsp", G), P.arrowScrollOnHover && (jb.bind("mouseover.jsp", m(0, -1, jb)), kb.bind("mouseover.jsp", m(0, 1, kb))), l(fb, P.verticalArrowPositions, jb, kb)), hb = S, T.find(">.jspVerticalBar>.jspCap:visible,>.jspVerticalBar>.jspArrow").each(function() { 13954 hb -= a(this).outerHeight() 13955 }), $.hover(function() { 13956 $.addClass("jspHover") 13957 }, function() { 13958 $.removeClass("jspHover") 13959 }).bind("mousedown.jsp", function(b) { 13960 a("html").bind("dragstart.jsp selectstart.jsp", G), $.addClass("jspActive"); 13961 var c = b.pageY - $.position().top; 13962 return a("html").bind("mousemove.jsp", function(a) { 13963 r(a.pageY - c, !1) 13964 }).bind("mouseup.jsp mouseleave.jsp", q), !1 13965 }), h()) 13966 } 13967 13968 function h() { 13969 fb.height(hb + "px"), ab = 0, gb = P.verticalGutter + fb.outerWidth(), Q.width(R - gb - tb); 13970 try { 13971 0 === eb.position().left && Q.css("margin-left", gb + "px") 13972 } catch (a) {} 13973 } 13974 13975 function i() { 13976 Z && (T.append(a('<div class="jspHorizontalBar" />').append(a('<div class="jspCap jspCapLeft" />'), a('<div class="jspTrack" />').append(a('<div class="jspDrag" />').append(a('<div class="jspDragLeft" />'), a('<div class="jspDragRight" />'))), a('<div class="jspCap jspCapRight" />'))), lb = T.find(">.jspHorizontalBar"), mb = lb.find(">.jspTrack"), bb = mb.find(">.jspDrag"), P.showArrows && (pb = a('<a class="jspArrow jspArrowLeft" />').bind("mousedown.jsp", m(-1, 0)).bind("click.jsp", G), qb = a('<a class="jspArrow jspArrowRight" />').bind("mousedown.jsp", m(1, 0)).bind("click.jsp", G), P.arrowScrollOnHover && (pb.bind("mouseover.jsp", m(-1, 0, pb)), qb.bind("mouseover.jsp", m(1, 0, qb))), l(mb, P.horizontalArrowPositions, pb, qb)), bb.hover(function() { 13977 bb.addClass("jspHover") 13978 }, function() { 13979 bb.removeClass("jspHover") 13980 }).bind("mousedown.jsp", function(b) { 13981 a("html").bind("dragstart.jsp selectstart.jsp", G), bb.addClass("jspActive"); 13982 var c = b.pageX - bb.position().left; 13983 return a("html").bind("mousemove.jsp", function(a) { 13984 t(a.pageX - c, !1) 13985 }).bind("mouseup.jsp mouseleave.jsp", q), !1 13986 }), nb = T.innerWidth(), j()) 13987 } 13988 13989 function j() { 13990 T.find(">.jspHorizontalBar>.jspCap:visible,>.jspHorizontalBar>.jspArrow").each(function() { 13991 nb -= a(this).outerWidth() 13992 }), mb.width(nb + "px"), db = 0 13993 } 13994 13995 function k() { 13996 if (Z && Y) { 13997 var b = mb.outerHeight(), 13998 c = fb.outerWidth(); 13999 hb -= b, a(lb).find(">.jspCap:visible,>.jspArrow").each(function() { 14000 nb += a(this).outerWidth() 14001 }), nb -= c, S -= c, R -= b, mb.parent().append(a('<div class="jspCorner" />').css("width", b + "px")), h(), j() 14002 } 14003 Z && Q.width(T.outerWidth() - tb + "px"), V = Q.outerHeight(), X = V / S, Z && (ob = Math.ceil(1 / W * nb), ob > P.horizontalDragMaxWidth ? ob = P.horizontalDragMaxWidth : ob < P.horizontalDragMinWidth && (ob = P.horizontalDragMinWidth), bb.width(ob + "px"), cb = nb - ob, u(db)), Y && (ib = Math.ceil(1 / X * hb), ib > P.verticalDragMaxHeight ? ib = P.verticalDragMaxHeight : ib < P.verticalDragMinHeight && (ib = P.verticalDragMinHeight), $.height(ib + "px"), _ = hb - ib, s(ab)) 14004 } 14005 14006 function l(a, b, c, d) { 14007 var e, f = "before", 14008 g = "after"; 14009 "os" == b && (b = /Mac/.test(navigator.platform) ? "after" : "split"), b == f ? g = b : b == g && (f = b, e = c, c = d, d = e), a[f](c)[g](d) 14010 } 14011 14012 function m(a, b, c) { 14013 return function() { 14014 return n(a, b, this, c), this.blur(), !1 14015 } 14016 } 14017 14018 function n(b, c, d, e) { 14019 d = a(d).addClass("jspActive"); 14020 var f, g, h = !0, 14021 i = function() { 14022 0 !== b && vb.scrollByX(b * P.arrowButtonSpeed), 0 !== c && vb.scrollByY(c * P.arrowButtonSpeed), g = setTimeout(i, h ? P.initialDelay : P.arrowRepeatFreq), h = !1 14023 }; 14024 i(), f = e ? "mouseout.jsp" : "mouseup.jsp", e = e || a("html"), e.bind(f, function() { 14025 d.removeClass("jspActive"), g && clearTimeout(g), g = null, e.unbind(f) 14026 }) 14027 } 14028 14029 function o() {
14030 p(), Y && fb.bind("mousedown.jsp", function(b) { 14031 if (b.originalTarget === c || b.originalTarget == b.currentTarget) { 14032 var d, e = a(this), 14033 f = e.offset(), 14034 g = b.pageY - f.top - ab, 14035 h = !0, 14036 i = function() { 14037 var a = e.offset(), 14038 c = b.pageY - a.top - ib / 2, 14039 f = S * P.scrollPagePercent, 14040 k = _ * f / (V - S); 14041 if (0 > g) ab - k > c ? vb.scrollByY(-f) : r(c); 14042 else { 14043 if (!(g > 0)) return j(), void 0; 14044 c > ab + k ? vb.scrollByY(f) : r(c) 14045 } 14046 d = setTimeout(i, h ? P.initialDelay : P.trackClickRepeatFreq), h = !1 14047 }, 14048 j = function() { 14049 d && clearTimeout(d), d = null, a(document).unbind("mouseup.jsp", j) 14050 }; 14051 return i(), a(document).bind("mouseup.jsp", j), !1 14052 } 14053 }), Z && mb.bind("mousedown.jsp", function(b) { 14054 if (b.originalTarget === c || b.originalTarget == b.currentTarget) { 14055 var d, e = a(this), 14056 f = e.offset(), 14057 g = b.pageX - f.left - db, 14058 h = !0, 14059 i = function() { 14060 var a = e.offset(), 14061 c = b.pageX - a.left - ob / 2, 14062 f = R * P.scrollPagePercent, 14063 k = cb * f / (U - R); 14064 if (0 > g) db - k > c ? vb.scrollByX(-f) : t(c); 14065 else { 14066 if (!(g > 0)) return j(), void 0; 14067 c > db + k ? vb.scrollByX(f) : t(c) 14068 } 14069 d = setTimeout(i, h ? P.initialDelay : P.trackClickRepeatFreq), h = !1 14070 }, 14071 j = function() { 14072 d && clearTimeout(d), d = null, a(document).unbind("mouseup.jsp", j) 14073 }; 14074 return i(), a(document).bind("mouseup.jsp", j), !1 14075 } 14076 }) 14077 } 14078 14079 function p() { 14080 mb && mb.unbind("mousedown.jsp"), fb && fb.unbind("mousedown.jsp") 14081 } 14082 14083 function q() { 14084 a("html").unbind("dragstart.jsp selectstart.jsp mousemove.jsp mouseup.jsp mouseleave.jsp"), $ && $.removeClass("jspActive"), bb && bb.removeClass("jspActive") 14085 } 14086 14087 function r(a, b) { 14088 Y && (0 > a ? a = 0 : a > _ && (a = _), b === c && (b = P.animateScroll), b ? vb.animate($, "top", a, s) : ($.css("top", a), s(a))) 14089 } 14090 14091 function s(a) { 14092 a === c && (a = $.position().top), T.scrollTop(0), ab = a; 14093 var b = 0 === ab, 14094 e = ab == _, 14095 f = a / _, 14096 g = -f * (V - S); 14097 (wb != b || yb != e) && (wb = b, yb = e, d.trigger("jsp-arrow-change", [wb, yb, xb, zb])), v(b, e), Q.css("top", g), d.trigger("jsp-scroll-y", [-g, b, e]).trigger("scroll") 14098 } 14099 14100 function t(a, b) { 14101 Z && (0 > a ? a = 0 : a > cb && (a = cb), b === c && (b = P.animateScroll), b ? vb.animate(bb, "left", a, u) : (bb.css("left", a), u(a))) 14102 } 14103 14104 function u(a) { 14105 a === c && (a = bb.position().left), T.scrollTop(0), db = a; 14106 var b = 0 === db, 14107 e = db == cb, 14108 f = a / cb, 14109 g = -f * (U - R); 14110 (xb != b || zb != e) && (xb = b, zb = e, d.trigger("jsp-arrow-change", [wb, yb, xb, zb])), w(b, e), Q.css("left", g), d.trigger("jsp-scroll-x", [-g, b, e]).trigger("scroll") 14111 } 14112 14113 function v(a, b) { 14114 P.showArrows && (jb[a ? "addClass" : "removeClass"]("jspDisabled"), kb[b ? "addClass" : "removeClass"]("jspDisabled")) 14115 } 14116 14117 function w(a, b) { 14118 P.showArrows && (pb[a ? "addClass" : "removeClass"]("jspDisabled"), qb[b ? "addClass" : "removeClass"]("jspDisabled")) 14119 } 14120 14121 function x(a, b) { 14122 var c = a / (V - S); 14123 r(c * _, b) 14124 } 14125 14126 function y(a, b) { 14127 var c = a / (U - R); 14128 t(c * cb, b) 14129 } 14130 14131 function z(b, c, d) { 14132 var e, f, g, h, i, j, k, l, m, n = 0, 14133 o = 0; 14134 try { 14135 e = a(b) 14136 } catch (p) { 14137 return 14138 } 14139 for (f = e.outerHeight(), g = e.outerWidth(), T.scrollTop(0), T.scrollLeft(0); !e.is(".jspPane");) 14140 if (n += e.position().top, o += e.position().left, e = e.offsetParent(), /^body|html$/i.test(e[0].nodeName)) return; 14141 h = B(), j = h + S, h > n || c ? l = n - P.horizontalGutter : n + f > j && (l = n - S + f + P.horizontalGutter), isNaN(l) || x(l, d), i = A(), k = i + R, i > o || c ? m = o - P.horizontalGutter : o + g > k && (m = o - R + g + P.horizontalGutter), isNaN(m) || y(m, d) 14142 } 14143 14144 function A() { 14145 return -Q.position().left 14146 } 14147 14148 function B() { 14149 return -Q.position().top 14150 } 14151 14152 function C() { 14153 var a = V - S; 14154 return a > 20 && a - B() < 10 14155 } 14156 14157 function D() { 14158 var a = U - R; 14159 return a > 20 && a - A() < 10 14160 } 14161 14162 function E() { 14163 T.unbind(Bb).bind(Bb, function(a, b, c, d) { 14164 var e = db, 14165 f = ab, 14166 g = a.deltaFactor || P.mouseWheelSpeed; 14167 return vb.scrollBy(c * g, -d * g, !1), e == db && f == ab 14168 }) 14169 } 14170 14171 function F() { 14172 T.unbind(Bb) 14173 } 14174 14175 function G() { 14176 return !1 14177 } 14178 14179 function H() { 14180 Q.find(":input,a").unbind("focus.jsp").bind("focus.jsp", function(a) { 14181 z(a.target, !1) 14182 }) 14183 } 14184 14185 function I() { 14186 Q.find(":input,a").unbind("focus.jsp") 14187 } 14188 14189 function J() { 14190 function b() { 14191 var a = db, 14192 b = ab; 14193 switch (c) { 14194 case 40: 14195 vb.scrollByY(P.keyboardSpeed, !1); 14196 break; 14197 case 38: 14198 vb.scrollByY(-P.keyboardSpeed, !1); 14199 break; 14200 case 34: 14201 case 32: 14202 vb.scrollByY(S * P.scrollPagePercent, !1); 14203 break; 14204 case 33: 14205 vb.scrollByY(-S * P.scrollPagePercent, !1); 14206 break; 14207 case 39: 14208 vb.scrollByX(P.keyboardSpeed, !1); 14209 break; 14210 case 37: 14211 vb.scrollByX(-P.keyboardSpeed, !1) 14212 } 14213 return e = a != db || b != ab 14214 } 14215 var c, e, f = []; 14216 Z && f.push(lb[0]), Y && f.push(eb[0]), Q.focus(function() { 14217 d.focus() 14218 }
14218), d.attr("tabindex", 0).unbind("keydown.jsp keypress.jsp").bind("keydown.jsp", function(d) { 14219 if (d.target === this || f.length && a(d.target).closest(f).length) { 14220 var g = db, 14221 h = ab; 14222 switch (d.keyCode) { 14223 case 40: 14224 case 38: 14225 case 34: 14226 case 32: 14227 case 33: 14228 case 39: 14229 case 37: 14230 c = d.keyCode, b(); 14231 break; 14232 case 35: 14233 x(V - S), c = null; 14234 break; 14235 case 36: 14236 x(0), c = null 14237 } 14238 return e = d.keyCode == c && g != db || h != ab, !e 14239 } 14240 }).bind("keypress.jsp", function(a) { 14241 return a.keyCode == c && b(), !e 14242 }), P.hideFocus ? (d.css("outline", "none"), "hideFocus" in T[0] && d.attr("hideFocus", !0)) : (d.css("outline", ""), "hideFocus" in T[0] && d.attr("hideFocus", !1)) 14243 } 14244 14245 function K() { 14246 d.attr("tabindex", "-1").removeAttr("tabindex").unbind("keydown.jsp keypress.jsp") 14247 } 14248 14249 function L() { 14250 if (location.hash && location.hash.length > 1) { 14251 var b, c, d = escape(location.hash.substr(1)); 14252 try { 14253 b = a("#" + d + ', a[name="' + d + '"]') 14254 } catch (e) { 14255 return 14256 } 14257 b.length && Q.find(d) && (0 === T.scrollTop() ? c = setInterval(function() { 14258 T.scrollTop() > 0 && (z(b, !0), a(document).scrollTop(T.position().top), clearInterval(c)) 14259 }, 50) : (z(b, !0), a(document).scrollTop(T.position().top))) 14260 } 14261 } 14262 14263 function M() { 14264 a(document.body).data("jspHijack") || (a(document.body).data("jspHijack", !0), a(document.body).delegate("a[href*=#]", "click", function(c) { 14265 var d, e, f, g, h, i, j = this.href.substr(0, this.href.indexOf("#")), 14266 k = location.href; 14267 if (-1 !== location.href.indexOf("#") && (k = location.href.substr(0, location.href.indexOf("#"))), j === k) { 14268 d = escape(this.href.substr(this.href.indexOf("#") + 1)); 14269 try { 14270 e = a("#" + d + ', a[name="' + d + '"]') 14271 } catch (l) { 14272 return 14273 } 14274 e.length && (f = e.closest(".jspScrollable"), g = f.data("jsp"), g.scrollToElement(e, !0), f[0].scrollIntoView && (h = a(b).scrollTop(), i = e.offset().top, (h > i || i > h + a(b).height()) && f[0].scrollIntoView()), c.preventDefault()) 14275 } 14276 })) 14277 } 14278 14279 function N() { 14280 var a, b, c, d, e, f = !1; 14281 T.unbind("touchstart.jsp touchmove.jsp touchend.jsp click.jsp-touchclick").bind("touchstart.jsp", function(g) { 14282 var h = g.originalEvent.touches[0]; 14283 a = A(), b = B(), c = h.pageX, d = h.pageY, e = !1, f = !0 14284 }).bind("touchmove.jsp", function(g) { 14285 if (f) { 14286 var h = g.originalEvent.touches[0], 14287 i = db, 14288 j = ab; 14289 return vb.scrollTo(a + c - h.pageX, b + d - h.pageY), e = e || Math.abs(c - h.pageX) > 5 || Math.abs(d - h.pageY) > 5, i == db && j == ab 14290 } 14291 }).bind("touchend.jsp", function() { 14292 f = !1 14293 }).bind("click.jsp-touchclick", function() { 14294 return e ? (e = !1, !1) : void 0 14295 }) 14296 } 14297 14298 function O() { 14299 var a = B(), 14300 b = A(); 14301 d.removeClass("jspScrollable").unbind(".jsp"), d.replaceWith(Ab.append(Q.children())), Ab.scrollTop(a), Ab.scrollLeft(b), rb && clearInterval(rb) 14302 } 14303 var P, Q, R, S, T, U, V, W, X, Y, Z, $, _, ab, bb, cb, db, eb, fb, gb, hb, ib, jb, kb, lb, mb, nb, ob, pb, qb, rb, sb, tb, ub, vb = this, 14304 wb = !0, 14305 xb = !0, 14306 yb = !1, 14307 zb = !1, 14308 Ab = d.clone(!1, !1).empty(), 14309 Bb = a.fn.mwheelIntent ? "mwheelIntent.jsp" : "mousewheel.jsp"; 14310 "border-box" === d.css("box-sizing") ? (sb = 0, tb = 0) : (sb = d.css("paddingTop") + " " + d.
14310css("paddingRight") + " " + d.css("paddingBottom") + " " + d.css("paddingLeft"), tb = (parseInt(d.css("paddingLeft"), 10) || 0) + (parseInt(d.css("paddingRight"), 10) || 0)), a.extend(vb, { 14311 reinitialise: function(b) { 14312 b = a.extend({}, P, b), f(b) 14313 }, 14314 scrollToElement: function(a, b, c) { 14315 z(a, b, c) 14316 }, 14317 scrollTo: function(a, b, c) { 14318 y(a, c), x(b, c) 14319 }, 14320 scrollToX: function(a, b) { 14321 y(a, b) 14322 }, 14323 scrollToY: function(a, b) { 14324 x(a, b) 14325 }, 14326 scrollToPercentX: function(a, b) { 14327 y(a * (U - R), b) 14328 }, 14329 scrollToPercentY: function(a, b) { 14330 x(a * (V - S), b) 14331 }, 14332 scrollBy: function(a, b, c) { 14333 vb.scrollByX(a, c), vb.scrollByY(b, c) 14334 }, 14335 scrollByX: function(a, b) { 14336 var c = A() + Math[0 > a ? "floor" : "ceil"](a), 14337 d = c / (U - R); 14338 t(d * cb, b) 14339 }, 14340 scrollByY: function(a, b) { 14341 var c = B() + Math[0 > a ? "floor" : "ceil"](a), 14342 d = c / (V - S); 14343 r(d * _, b) 14344 }, 14345 positionDragX: function(a, b) { 14346 t(a, b) 14347 }, 14348 positionDragY: function(a, b) { 14349 r(a, b) 14350 }, 14351 animate: function(a, b, c, d) { 14352 var e = {}; 14353 e[b] = c, a.animate(e, { 14354 duration: P.animateDuration, 14355 easing: P.animateEase, 14356 queue: !1, 14357 step: d 14358 }) 14359 }, 14360 getContentPositionX: function() { 14361 return A() 14362 }, 14363 getContentPositionY: function() { 14364 return B() 14365 }, 14366 getContentWidth: function() { 14367 return U 14368 }, 14369 getContentHeight: function() { 14370 return V 14371 }, 14372 getPercentScrolledX: function() { 14373 return A() / (U - R) 14374 }, 14375 getPercentScrolledY: function() { 14376 return B() / (V - S) 14377 }, 14378 getIsScrollableH: function() { 14379 return Z 14380 }, 14381 getIsScrollableV: function() { 14382 return Y 14383 }, 14384 getContentPane: function() { 14385 return Q 14386 }, 14387 scrollToBottom: function(a) { 14388 r(_, a) 14389 }, 14390 hijackInternalLinks: a.noop, 14391 destroy: function() { 14392 O() 14393 } 14394 }), f(e) 14395 } 14396 return d = a.extend({}, a.fn.jScrollPane.defaults, d), a.each(["arrowButtonSpeed", "trackClickSpeed", "keyboardSpeed"], function() { 14397 d[this] = d[this] || d.speed 14398 }), this.each(function() { 14399 var b = a(this), 14400 c = b.data("jsp"); 14401 c ? c.reinitialise(d) : (a("script", b).filter('[type="text/javascript"],:not([type])').remove(), c = new e(b, d), b.data("jsp", c)) 14402 }) 14403 }, a.fn.jScrollPane.defaults = { 14404 showArrows: !1, 14405 maintainPosition: !0, 14406 stickToBottom: !1, 14407 stickToRight: !1, 14408 clickOnTrack: !0, 14409 autoReinitialise: !1, 14410 autoReinitialiseDelay: 500, 14411 verticalDragMinHeight: 0, 14412 verticalDragMaxHeight: 99999, 14413 horizontalDragMinWidth: 0, 14414 horizontalDragMaxWidth: 99999, 14415 contentWidth: c, 14416 animateScroll: !1, 14417 animateDuration: 300, 14418 animateEase: "linear", 14419 hijackInternalLinks: !1, 14420 verticalGutter: 4, 14421 horizontalGutter: 4, 14422 mouseWheelSpeed: 3, 14423 arrowButtonSpeed: 0, 14424 arrowRepeatFreq: 50, 14425 arrowScrollOnHover: !1, 14426 trackClickSpeed: 0, 14427 trackClickRepeatFreq: 70, 14428 verticalArrowPositions: "split", 14429 horizontalArrowPositions: "split", 14430 enableKeyboardNavigation: !0, 14431 hideFocus: !1, 14432 keyboardSpeed: 0, 14433 initialDelay: 300, 14434 speed: 30, 14435 scrollPagePercent: .8 14436 } 14437}(jQuery, this); 14438/* 14439 OverlappingMarkerSpiderfier 14440https://github.com/jawj/OverlappingMarkerSpiderfier 14441Copyright (c) 2011 - 2017 George MacKerron 14442Released under the MIT licence: http://opensource.org/licenses/mit-license 14443Note: The Google Maps API v3 must be included *before* this code 14444*/ 14445(function(){var m,t,w,y,u,z={}.hasOwnProperty,A=[].slice;this.OverlappingMarkerSpiderfier=function(){function r(a,d){var b,f,e;this.map=a;null==d&&(d={});null==this.constructor.N&&(this.constructor.N=!0,h=google.maps,l=h.event,p=h.MapTypeId,c.keepSpiderfied=!1,c.ignoreMapClick=!1,c.markersWontHide=!1,c.markersWontMove=!1,c.basicFormatEvents=!1,c.nearbyDistance=20,c.circleSpiralSwitchover=9,c.circleFootSeparation=23,c.circleStartAngle=x
14445/12,c.spiralFootSeparation=26,c.spiralLengthStart=11,c.spiralLengthFactor= 144464,c.spiderfiedZIndex=h.Marker.MAX_ZINDEX+2E4,c.highlightedLegZIndex=h.Marker.MAX_ZINDEX+1E4,c.usualLegZIndex=h.Marker.MAX_ZINDEX+1,c.legWeight=1.5,c.legColors={usual:{},highlighted:{}},e=c.legColors.usual,f=c.legColors.highlighted,e[p.HYBRID]=e[p.SATELLITE]="#fff",f[p.HYBRID]=f[p.SATELLITE]="#f00",e[p.TERRAIN]=e[p.ROADMAP]="#444",f[p.TERRAIN]=f[p.ROADMAP]="#f00",this.constructor.j=function(a){return this.setMap(a)},this.constructor.j.prototype=new h.OverlayView,this.constructor.j.prototype.draw=function(){}); 14447for(b in d)z.call(d,b)&&(f=d[b],this[b]=f);this.g=new this.constructor.j(this.map);this.C();this.c={};this.B=this.l=null;this.addListener("click",function(a,b){return l.trigger(a,"spider_click",b)});this.addListener("format",function(a,b){return l.trigger(a,"spider_format",b)});this.ignoreMapClick||l.addListener(this.map,"click",function(a){return function(){return a.unspiderfy()}}(this));l.addListener(this.map,"maptypeid_changed",function(a){return function(){return a.unspiderfy()}}(this));l.addListener(this.map, 14448"zoom_changed",function(a){return function(){a.unspiderfy();if(!a.basicFormatEvents)return a.h()}}(this))}var l,h,m,v,p,c,t,x,u;c=r.prototype;t=[r,c];m=0;for(v=t.length;m<v;m++)u=t[m],u.VERSION="1.0.3";x=2*Math.PI;h=l=p=null;r.markerStatus={SPIDERFIED:"SPIDERFIED",SPIDERFIABLE:"SPIDERFIABLE",UNSPIDERFIABLE:"UNSPIDERFIABLE",UNSPIDERFIED:"UNSPIDERFIED"};c.C=function(){this.a=[];this.s=[]};c.addMarker=function(a,d){a.setMap(this.map);return this.trackMarker(a,d)};c.trackMarker=function(a,d){var b;if(null!= 14449a._oms)return this;a._oms=!0;b=[l.addListener(a,"click",function(b){return function(d){return b.V(a,d)}}(this))];this.markersWontHide||b.push(l.addListener(a,"visible_changed",function(b){return function(){return b.D(a,!1)}}(this)));this.markersWontMove||b.push(l.addListener(a,"position_changed",function(b){return function(){return b.D(a,!0)}}(this)));null!=d&&b.push(l.addListener(a,"spider_click",d));this.s.push(b);this.a.push(a);this.basicFormatEvents?this.trigger("format",a,this.constructor.markerStatus.UNSPIDERFIED): 14450(this.trigger("format",a,this.constructor.markerStatus.UNSPIDERFIABLE),this.h());return this};c.D=function(a,d){if(!this.J&&!this.K)return null==a._omsData||!d&&a.getVisible()||this.unspiderfy(d?a:null),this.h()};c.getMarkers=function(){return this.a.slice(0)};c.removeMarker=function(a){this.forgetMarker(a);return a.setMap(null)};c.forgetMarker=function(a){var d,b,f,e,g;null!=a._omsData&&this.unspiderfy();d=this.A(this.a,a);if(0>d)return this;g=this.s.splice(d,1)[0];b=0;for(f=g.length;b<f;b++)e=g[b], 14451l.removeListener(e);delete a._oms;this.a.splice(d,1);this.h();return this};c.removeAllMarkers=c.clearMarkers=function(){var a,d,b,f;f=this.getMarkers();this.forgetAllMarkers();a=0;for(d=f.length;a<d;a++)b=f[a],b.setMap(null);return this};c.forgetAllMarkers=function(){var a,d,b,f,e,g,c,q;this.unspiderfy();q=this.a;a=d=0;for(b=q.length;d<b;a=++d){g=q[a];e=this.s[a];c=0;for(a=e.length;c<a;c++)f=e[c],l.removeListener(f);delete g._oms}this.C();return this};c.addListener=function(a,d){var b;(null!=(b=this.c)[a]? 14452b[a]:b[a]=[]).push(d);return this};c.removeListener=function(a,d){var b;b=this.A(this.c[a],d);0>b||this.c[a].splice(b,1);return this};c.clearListeners=function(a){this.c[a]=[];return this};c.trigger=function(){var a,d,b,f,e,g;d=arguments[0];a=2<=arguments.length?A.call(arguments,1):[];d=null!=(b=this.c[d])?b:[];g=[];f=0;for(e=d.length;f<e;f++)b=d[f],g.push(b.apply(null,a));return g};c.L=function(a,d){var b,f,e,g,c;g=this.circleFootSeparation*(2+a)/x;f=x/a;c=[];for(b=e=0;0<=a?e<a:e>a;b=0<=a?++e:--e)b= 14453this.circleStartAngle+b*f,c.push(new h.Point(d.x+g*Math.cos(b),d.y+g*Math.sin(b)));return c};c.M=function(a,d){var b,f,e,c,k;c=this.spiralLengthStart;b=0;k=[];for(f=e=0;0<=a?e<a:e>a;f=0<=a?++e:--e)b+=this.spiralFootSeparation/c+5E-4*f,f=new h.Point(d.x+c*Math.cos(b),d.y+c*Math.sin(b)),c+=x*this.spiralLengthFactor/b,k.push(f);return k};c.V=function(a,d){var b,f,e,c,k,q,n,l,h;(q=null!=a._omsData)&&this.keepSpiderfied||this.unspiderfy();if(q||this.map.getStreetView().getVisible()||"GoogleEarthAPI"=== 14454this.map.getMapTypeId())return this.trigger("click",a,d);q=[];n=[];b=this.nearbyDistance;l=b*b;k=this.f(a.position);h=this.a;b=0;for(f=h.length;b<f;b++)e=h[b],null!=e.map&&e.getVisible()&&(c=this.f(e.position),this.i(c,k)<l?q.push({R:e,G:c}):n.push(e));return 1===q.length?this.trigger("click",a,d):this.W(q,n)};c.markersNearMarker=function(a,d){var b,f,e,c,k,q,n,l,h,m;null==d&&(d=!1);if(null==this.g.getProjection())throw"Must wait for 'idle' event on map before calling markersNearMarker";b=this.nearbyDistance; 14455n=b*b;k=this.f(a.position);q=[];l=this.a;b=0;for(f=l.length;b<f&&!(e=l[b],e!==a&&null!=e.map&&e.getVisible()&&(c=this.f(null!=(h=null!=(m=e._omsData)?m.v:void 0)?h:e.position),this.i(c,k)<n&&(q.push(e),d)));b++);return q};c.F=function(){var a,d,b,f,e,c,k,l,n,h,m;if(null==this.g.getProjection())throw"Must wait for 'idle' event on map before calling markersNearAnyOtherMar
14455ker";n=this.nearbyDistance;n*=n;var p;e=this.a;p=[];h=0;for(d=e.length;h<d;h++)f=e[h],p.push({H:this.f(null!=(a=null!=(b=f._omsData)? 14456b.v:void 0)?a:f.position),b:!1});h=this.a;a=b=0;for(f=h.length;b<f;a=++b)if(d=h[a],null!=d.getMap()&&d.getVisible()&&(c=p[a],!c.b))for(m=this.a,d=l=0,e=m.length;l<e;d=++l)if(k=m[d],d!==a&&null!=k.getMap()&&k.getVisible()&&(k=p[d],(!(d<a)||k.b)&&this.i(c.H,k.H)<n)){c.b=k.b=!0;break}return p};c.markersNearAnyOtherMarker=function(){var a,d,b,c,e,g,k;e=this.F();g=this.a;k=[];a=d=0;for(b=g.length;d<b;a=++d)c=g[a],e[a].b&&k.push(c);return k};c.setImmediate=function(a){return window.setTimeout(a,0)};c.h= 14457function(){if(!this.basicFormatEvents&&null==this.l)return this.l=this.setImmediate(function(a){return function(){a.l=null;return null!=a.g.getProjection()?a.w():null!=a.B?void 0:a.B=l.addListenerOnce(a.map,"idle",function(){return a.w()})}}(this))};c.w=function(){var a,d,b,c,e,g,k;if(this.basicFormatEvents){e=[];d=0;for(b=markers.length;d<b;d++)c=markers[d],a=null!=c._omsData?"SPIDERFIED":"UNSPIDERFIED",e.push(this.trigger("format",c,this.constructor.markerStatus[a]));return e}e=this.F();g=this.a; 14458k=[];a=b=0;for(d=g.length;b<d;a=++b)c=g[a],a=null!=c._omsData?"SPIDERFIED":e[a].b?"SPIDERFIABLE":"UNSPIDERFIABLE",k.push(this.trigger("format",c,this.constructor.markerStatus[a]));return k};c.P=function(a){return{m:function(d){return function(){return a._omsData.o.setOptions({strokeColor:d.legColors.highlighted[d.map.mapTypeId],zIndex:d.highlightedLegZIndex})}}(this),u:function(d){return function(){return a._omsData.o.setOptions({strokeColor:d.legColors.usual[d.map.mapTypeId],zIndex:d.usualLegZIndex})}}(this)}}; 14459c.W=function(a,d){var b,c,e,g,k,q,n,m,p,r;this.J=!0;r=a.length;b=this.T(function(){var b,d,c;c=[];b=0;for(d=a.length;b<d;b++)m=a[b],c.push(m.G);return c}());g=r>=this.circleSpiralSwitchover?this.M(r,b).reverse():this.L(r,b);b=function(){var b,d,f;f=[];
14459b=0;for(d=g.length;b<d;b++)e=g[b],c=this.U(e),p=this.S(a,function(a){return function(b){return a.i(b.G,e)}}(this)),n=p.R,q=new h.Polyline({map:this.map,path:[n.position,c],strokeColor:this.legColors.usual[this.map.mapTypeId],strokeWeight:this.legWeight, 14460zIndex:this.usualLegZIndex}),n._omsData={v:n.getPosition(),X:n.getZIndex(),o:q},this.legColors.highlighted[this.map.mapTypeId]!==this.legColors.usual[this.map.mapTypeId]&&(k=this.P(n),n._omsData.O={m:l.addListener(n,"mouseover",k.m),u:l.addListener(n,"mouseout",k.u)}),this.trigger("format",n,this.constructor.markerStatus.SPIDERFIED),n.setPosition(c),n.setZIndex(Math.round(this.spiderfiedZIndex+e.y)),f.push(n);return f}.call(this);delete this.J;this.I=!0;return this.trigger("spiderfy",b,d)};c.unspiderfy= 14461function(a){var d,b,c,e,g,k,h;null==a&&(a=null);if(null==this.I)return this;this.K=!0;h=[];g=[];k=this.a;d=0;for(b=k.length;d<b;d++)e=k[d],null!=e._omsData?(e._omsData.o.setMap(null),e!==a&&e.setPosition(e._omsData.v),e.setZIndex(e._omsData.X),c=e._omsData.O,null!=c&&(l.removeListener(c.m),l.removeListener(c.u)),delete e._omsData,e!==a&&(c=this.basicFormatEvents?"UNSPIDERFIED":"SPIDERFIABLE",this.trigger("format",e,this.constructor.markerStatus[c])),h.push(e)):g.push(e);delete this.K;delete this.I; 14462this.trigger("unspiderfy",h,g);return this};c.i=function(a,d){var b,c;b=a.x-d.x;c=a.y-d.y;return b*b+c*c};c.T=function(a){var c,b,f,e,g;c=e=g=0;for(b=a.length;c<b;c++)f=a[c],e+=f.x,g+=f.y;a=a.length;return new h.Point(e/a,g/a)};c.f=function(a){return this.g.getProjection().fromLatLngToDivPixel(a)};c.U=function(a){return this.g.getProjection().fromDivPixelToLatLng(a)};c.S=function(a,c){var b,d,e,g,k,h;e=k=0;for(h=a.length;k<h;e=++k)if(g=a[e],g=c(g),"undefined"===typeof b||null===b||g<d)d=g,b=e;return a.splice(b, 144631)[0]};c.A=function(a,c){var b,d,e,g;if(null!=a.indexOf)return a.indexOf(c);b=d=0;for(e=a.length;d<e;b=++d)if(g=a[b],g===c)return b;return-1};return r}();t=/(\?.*(&|&)|\?)spiderfier_callback=(\w+)/;m=document.currentScript;null==m&&(m=function(){var m,l,h,w,v;h=document.getElementsByTagName("script");v=[];m=0;for(l=h.length;m<l;m++)u=h[m],null!=(w=u.getAttribute("src"))&&w.match(t)&&v.push(u);return v}()[0]);if(null!=m&&(m=null!=(w=m.getAttribute("src"))?null!=(y=w.match(t))?y[3]:void 0:void 0)&& 14464"function"===typeof window[m])window[m]();"function"===typeof window.spiderfier_callback&&window.spiderfier_callback()}).call(this); 14465/* Thu 11 May 2017 09:00:27 BST */ 14466/* jQuery.print, version 1.0.3 14467 * (c) Sathvik Ponangi, Doers' Guild 14468 * Licence: CC-By (http://creativecommons.org/licenses/by/3.0/) 14469 *--------------------------------------------------------------------------*/ 14470 14471(function($) { 14472 "use strict"; 14473 // A nice closure for our definitions 14474 14475 function getjQueryObject(string) { 14476 // Make string a vaild jQuery thing 14477 var jqObj = $(""); 14478 try { 14479 jqObj = $(string).clone(); 14480 } catch (e) { 14481 jqObj = $("<span />").html(string); 14482 } 14483 return jqObj; 14484 } 14485 14486 function isNode(o) { 14487 /* http://stackoverflow.com/a/384380/937891 */ 14488 return !!(typeof Node === "object" ? o instanceof Node : o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string"); 14489 } 14490 14491 14492 $.print = $.fn.print = function() { 14493 // Print a given set of elements 14494 14495 var options, $this, self = this; 14496 14497 // console.log("Printing", this, arguments); 14498 14499 if (self instanceof $) { 14500 // Get the node if it is a jQuery object 14501 self = self.get(0); 14502 } 14503 14504 if (isNode(self)) { 14505 // If `this` is a HTML element, i.e. for 14506 // $(selector).print() 14507 $this = $(self); 14508 if (arguments.length > 0) { 14509 options = arguments[0]; 14510 } 14511 } else { 14512 if (arguments.length > 0) { 14513 // $.print(selector,options) 14514 $this = $(arguments[0]); 14515 if (isNode($this[0])) { 14516 if (arguments.length > 1) { 14517 options = arguments[1]; 14518 } 14519 } else { 14520 // $.print(options) 14521 options = arguments[0]; 14522 $this = $("html"); 14523 } 14524 } else { 14525 // $.print() 14526 $this = $("html"); 14527 } 14528 } 14529 14530 // Default options 14531 var defaults = { 14532 globalStyles: true, 14533 mediaPrint: false, 14534 stylesheet: null, 14535 noPrintSelector: ".no-print", 14536 iframe: true, 14537 append: null, 14538 prepend: null 14539 }; 14540 // Merge with user-options 14541 options = $.extend({}, defaults, (options || {})); 14542 14543 var $styles = $(""); 14544 if (options.globalStyles) { 14545 // Apply the stlyes from the current sheet to the printed page 14546 $styles = $("style, link, meta, title"); 14547 } else if (options.mediaPrint) { 14548 // Apply the media-print stylesheet 14549 $styles = $("link[media=print]"); 14550 } 14551 if (options.stylesheet) { 14552 // Add a custom stylesheet if given 14553 $styles = $.merge($styles, $('<link rel="stylesheet" href="' + options.stylesheet + '">')); 14554 } 14555 14556 // Create a copy of the element to print 14557 var copy = $this.clone(); 14558 // Wrap it in a span to get the HTML markup string 14559 copy = $("<span/>").append(copy); 14560 // Remove unwanted elements 14561 copy.find(options.noPrintSelector).remove(); 14562 // Add in the styles 14563 copy.append($styles.clone()); 14564 // Appedned content 14565 copy.append(getjQueryObject(options.append)); 14566 // Prepended content 14567 copy.prepend(getjQueryObject(options.prepend)); 14568 // Get the HTML markup string 14569 var content = copy.html(); 14570 // Destroy the copy 14571 copy.remove(); 14572 14573 var w, wdoc; 14574 if (options.iframe) { 14575 // Use an iframe for printing 14576 try { 14577 var $iframe = $(options.iframe + "");
14578 var iframeCount = $iframe.length; 14579 if (iframeCount === 0) { 14580 // Create a new iFrame if none is given 14581 $iframe = $('<iframe height="0" width="0" border="0" wmode="Opaque"/>').prependTo('body').css({ 14582 "position": "absolute", 14583 "top": -999, 14584 "left": -999 14585 }); 14586 } 14587 w = $iframe.get(0); 14588 w = w.contentWindow || w.contentDocument || w; 14589 wdoc = w.document || w.contentDocument || w; 14590 wdoc.open(); 14591 wdoc.write(content); 14592 wdoc.close(); 14593 setTimeout(function() { 14594 // Fix for IE : Allow it to render the iframe 14595 w.focus(); 14596 w.print(); 14597 setTimeout(function() { 14598 // Fix for IE 14599 if (iframeCount === 0) { 14600 // Destroy the iframe if created here 14601 $iframe.remove(); 14602 } 14603 }, 100); 14604 }, 250); 14605 } catch (e) { 14606 // Use the pop-up method if iframe fails for some reason 14607 console.error("Failed to print from iframe", e.stack, e.message); 14608 w = window.open(); 14609 w.document.write(content); 14610 w.document.close(); 14611 w.focus(); 14612 w.print(); 14613 w.close(); 14614 } 14615 } else { 14616 // Use a new window for printing 14617 w = window.open(); 14618 w.document.write(content); 14619 w.document.close(); 14620 w.focus(); 14621 w.print(); 14622 w.close(); 14623 } 14624 return this; 14625 }; 14626 14627})(jQuery); 14628/** 14629 * @name MarkerClustererPlus for Google Maps V3 14630 * @version 2.1.2 [May 28, 2014] 14631 * @author Gary Little 14632 * @fileoverview 14633 * The library creates and manages per-zoom-level clusters for large amounts of markers. 14634 * <p> 14635 * This is an enhanced V3 implementation of the 14636 * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/" 14637 * >V2 MarkerClusterer</a> by Xiaoxi Wu. It is based on the 14638 * <a href="http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclusterer/" 14639 * >V3 MarkerClusterer</a> port by Luke Mahe. MarkerClustererPlus was created by Gary Little. 14640 * <p> 14641 * v2.0 release: MarkerClustererPlus v2.0 is backward compatible with MarkerClusterer v1.0. It 14642 * adds support for the <code>ignoreHidden</code>, <code>title</code>, <code>batchSizeIE</code>, 14643 * and <code>calculator</code> properties as well as support for four more events. It also allows 14644 * greater control over the styling of the text that appears on the cluster marker. The 14645 * documentation has been significantly improved and the overall code has been simplified and 14646 * polished. Very large numbers of markers can now be managed without causing Javascript timeout 14647 * errors on Internet Explorer. Note that the name of the <code>clusterclick</code> event has been 14648 * deprecated. The new name is <code>click</code>, so please change your application code now. 14649 */ 14650 14651/** 14652 * Licensed under the Apache License, Version 2.0 (the "License"); 14653 * you may not use this file except in compliance with the License. 14654 * You may obtain a copy of the License at 14655 * 14656 * http://www.apache.org/licenses/LICENSE-2.0 14657 * 14658 * Unless required by applicable law or agreed to in writing, software 14659 * distributed under the License is distributed on an "AS IS" BASIS, 14660 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14661 * See the License for the specific language governing permissions and 14662 * limitations under the License. 14663 */ 14664 14665 14666/** 14667 * @name ClusterIconStyle 14668 * @class This class represents the object for values in the <code>styles</code> array passed 14669 * to the {@link MarkerClusterer} constructor. The element in this array that is used to 14670 * style the cluster icon is determined by calling the <code>calculator</code> function. 14671 * 14672 * @property {string} url The URL of the cluster icon image file. Required. 14673 * @property {number} height The display height (in pixels) of the cluster icon. Required. 14674 * @property {number} width The display width (in pixels) of the cluster icon. Required. 14675 * @property {Array} [anchorText] The position (in pixels) from the center of the cluster icon to 14676 * where the text label is to be centered and drawn. The format is <code>[yoffset, xoffset]</code> 14677 * where <code>yoffset</code> increases as you go down from center and <code>xoffset</code> 14678 * increases to the right of center. The default is <code>[0, 0]</code>. 14679 * @property {Array} [anchorIcon] The anchor position (in pixels) of the cluster icon. This is the 14680 * spot on the cluster icon that is to be aligned with the cluster position. The format is 14681 * <code>[yoffset, xoffset]</code> where <code>yoffset</code> increases as you go down and 14682 * <code>xoffset</code> increases to the right of the top-left corner of the icon. The default 14683 * anchor position is the center of the cluster icon. 14684 * @property {string} [textColor="black"] The color of the label text shown on the 14685 * cluster icon. 14686 * @property {number} [textSize=11] The size (in pixels) of the label text shown on the 14687 * cluster icon. 14688 * @property {string} [textDecoration="none"] The value of the CSS <code>text-decoration</code> 14689 * property for the label text shown on the cluster icon. 14690 * @property {string} [fontWeight="bold"] The value of the CSS <code>font-weight</code> 14691 * property for the label text shown on the cluster icon. 14692 * @property {string} [fontStyle="normal"] The value of the CSS <code>font-style</code> 14693 * property for the label text shown on the cluster icon. 14694 * @property {string} [fontFamily="Arial,sans-serif"] The value of the CSS <code>font-family</code> 14695 * property for the label text shown on the cluster icon. 14696 * @property {string} [backgroundPosition="0 0"] The position of the cluster icon image 14697 * within the image defined by <code>url</code>. The format is <code>"xpos ypos"</code> 14698 * (the same format as for the CSS <code>background-position</code> property). You must set 14699 * this property appropriately when the image defined by <code>url</code> represents a sprite 14700 * containing multiple images. Note that the position <i>must</i> be specified in px units. 14701 */ 14702/** 14703 * @name ClusterIconInfo
14704 * @class This class is an object containing general information about a cluster icon. This is 14705 * the object that a <code>calculator</code> function returns. 14706 * 14707 * @property {string} text The text of the label to be shown on the cluster icon. 14708 * @property {number} index The index plus 1 of the element in the <code>styles</code> 14709 * array to be used to style the cluster icon. 14710 * @property {string} title The tooltip to display when the mouse moves over the cluster icon. 14711 * If this value is <code>undefined</code> or <code>""</code>, <code>title</code> is set to the 14712 * value of the <code>title</code> property passed to the MarkerClusterer. 14713 */ 14714/** 14715 * A cluster icon. 14716 * 14717 * @constructor 14718 * @extends google.maps.OverlayView 14719 * @param {Cluster} cluster The cluster with which the icon is to be associated. 14720 * @param {Array} [styles] An array of {@link ClusterIconStyle} defining the cluster icons 14721 * to use for various cluster sizes. 14722 * @private 14723 */ 14724function ClusterIcon(cluster, styles) { 14725 cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); 14726 14727 this.cluster_ = cluster; 14728 this.className_ = cluster.getMarkerClusterer().getClusterClass(); 14729 this.styles_ = styles; 14730 this.center_ = null; 14731 this.div_ = null; 14732 this.sums_ = null; 14733 this.visible_ = false; 14734 14735 this.setMap(cluster.getMap()); // Note: this causes onAdd to be called 14736} 14737 14738 14739/** 14740 * Adds the icon to the DOM. 14741 */ 14742ClusterIcon.prototype.onAdd = function() { 14743 var cClusterIcon = this; 14744 var cMouseDownInCluster; 14745 var cDraggingMapByCluster; 14746 14747 this.div_ = document.createElement("div"); 14748 this.div_.className = this.className_; 14749 if (this.visible_) { 14750 this.show(); 14751 } 14752 14753 this.getPanes().overlayMouseTarget.appendChild(this.div_); 14754 14755 // Fix for Issue 157 14756 this.boundsChangedListener_ = google.maps.event.addListener(this.getMap(), "bounds_changed", function() { 14757 cDraggingMapByCluster = cMouseDownInCluster; 14758 }); 14759 14760 google.maps.event.addDomListener(this.div_, "mousedown", function() { 14761 cMouseDownInCluster = true; 14762 cDraggingMapByCluster = false; 14763 }); 14764 14765 google.maps.event.addDomListener(this.div_, "click", function(e) { 14766 cMouseDownInCluster = false; 14767 if (!cDraggingMapByCluster) { 14768 var theBounds; 14769 var mz; 14770 var mc = cClusterIcon.cluster_.getMarkerClusterer(); 14771 /** 14772 * This event is fired when a cluster marker is clicked. 14773 * @name MarkerClusterer#click 14774 * @param {Cluster} c The cluster that was clicked. 14775 * @event 14776 */ 14777 google.maps.event.trigger(mc, "click", cClusterIcon.cluster_); 14778 google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name 14779 14780 // The default click handler follows. Disable it by setting 14781 // the zoomOnClick property to false. 14782 if (mc.getZoomOnClick()) { 14783 // Zoom into the cluster. 14784 mz = mc.getMaxZoom(); 14785 theBounds = cClusterIcon.cluster_.getBounds(); 14786 mc.getMap().fitBounds(theBounds); 14787 // There is a fix for Issue 170 here: 14788 setTimeout(function() { 14789 mc.getMap().fitBounds(theBounds); 14790 // Don't zoom beyond the max zoom level 14791 if (mz !== null && (mc.getMap().getZoom() > mz)) { 14792 mc.getMap().setZoom(mz + 1); 14793 } 14794 }, 100); 14795 } 14796 14797 // Prevent event propagation to the map: 14798 e.cancelBubble = true; 14799 if (e.stopPropagation) { 14800 e.stopPropagation(); 14801 } 14802 } 14803 }); 14804 14805 google.maps.event.addDomListener(this.div_, "mouseover", function() { 14806 var mc = cClusterIcon.cluster_.getMarkerClusterer(); 14807 /** 14808 * This event is fired when the mouse moves over a cluster marker. 14809 * @name MarkerClusterer#mouseover 14810 * @param {Cluster} c The cluster that the mouse moved over. 14811 * @event 14812 */ 14813 google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_); 14814 }); 14815 14816 google.maps.event.addDomListener(this.div_, "mouseout", function() { 14817 var mc = cClusterIcon.cluster_.getMarkerClusterer(); 14818 /**
14819 * This event is fired when the mouse moves out of a cluster marker. 14820 * @name MarkerClusterer#mouseout 14821 * @param {Cluster} c The cluster that the mouse moved out of. 14822 * @event 14823 */ 14824 google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_); 14825 }); 14826}; 14827 14828 14829/** 14830 * Removes the icon from the DOM. 14831 */ 14832ClusterIcon.prototype.onRemove = function() { 14833 if (this.div_ && this.div_.parentNode) { 14834 this.hide(); 14835 google.maps.event.removeListener(this.boundsChangedListener_); 14836 google.maps.event.clearInstanceListeners(this.div_); 14837 this.div_.parentNode.removeChild(this.div_); 14838 this.div_ = null; 14839 } 14840}; 14841 14842 14843/** 14844 * Draws the icon. 14845 */ 14846ClusterIcon.prototype.draw = function() { 14847 if (this.visible_) { 14848 var pos = this.getPosFromLatLng_(this.center_); 14849 this.div_.style.top = pos.y + "px"; 14850 this.div_.style.left = pos.x + "px"; 14851 } 14852}; 14853 14854 14855/** 14856 * Hides the icon. 14857 */ 14858ClusterIcon.prototype.hide = function() { 14859 if (this.div_) { 14860 this.div_.style.display = "none"; 14861 } 14862 this.visible_ = false; 14863}; 14864 14865 14866/** 14867 * Positions and shows the icon. 14868 */ 14869ClusterIcon.prototype.show = function() { 14870 if (this.div_) { 14871 var img = ""; 14872 // NOTE: values must be specified in px units 14873 var bp = this.backgroundPosition_.split(" "); 14874 var spriteH = parseInt(bp[0].replace(/^\s+|\s+$/g, ""), 10); 14875 var spriteV = parseInt(bp[1].replace(/^\s+|\s+$/g, ""), 10); 14876 var pos = this.getPosFromLatLng_(this.center_); 14877 this.div_.style.cssText = this.createCss(pos); 14878 img = "<img src='" + this.url_ + "' style='position: absolute; top: " + spriteV + "px; left: " + spriteH + "px; "; 14879 if (!this.cluster_.getMarkerClusterer().enableRetinaIcons_) { 14880 img += "clip: rect(" + (-1 * spriteV) + "px, " + ((-1 * spriteH) + this.width_) + "px, " + 14881 ((-1 * spriteV) + this.height_) + "px, " + (-1 * spriteH) + "px);"; 14882 } 14883 img += "'>"; 14884 this.div_.innerHTML = img + "<div style='" + 14885 "position: absolute;" + 14886 "top: " + this.anchorText_[0] + "px;" + 14887 "left: " + this.anchorText_[1] + "px;" + 14888 "color: " + this.textColor_ + ";" + 14889 "font-size: " + this.textSize_ + "px;" + 14890 "font-family: " + this.fontFamily_ + ";" + 14891 "font-weight: " + this.fontWeight_ + ";" + 14892 "font-style: " + this.fontStyle_ + ";" + 14893 "text-decoration: " + this.textDecoration_ + ";" + 14894 "text-align: center;" + 14895 "width: " + this.width_ + "px;" + 14896 "line-height:" + this.height_ + "px;" + 14897 "'>" + this.sums_.text + "</div>"; 14898 if (typeof this.sums_.title === "undefined" || this.sums_.title === "") { 14899 this.div_.title = this.cluster_.getMarkerClusterer().getTitle(); 14900 } else { 14901 this.div_.title = this.sums_.title; 14902 } 14903 this.div_.style.display = ""; 14904 } 14905 this.visible_ = true; 14906}; 14907 14908 14909/** 14910 * Sets the icon styles to the appropriate element in the styles array. 14911 * 14912 * @param {ClusterIconInfo} sums The icon label text and styles index. 14913 */ 14914ClusterIcon.prototype.useStyle = function(sums) { 14915 this.sums_ = sums; 14916 var index = Math.max(0, sums.index - 1); 14917 index = Math.min(this.styles_.length - 1, index); 14918 var style = this.styles_[index]; 14919 this.url_ = style.url; 14920 this.height_ = style.height; 14921 this.width_ = style.width; 14922 this.anchorText_ = style.anchorText || [0, 0]; 14923 this.anchorIcon_ = style.anchorIcon || [parseInt(this.height_ / 2, 10), parseInt(this.width_ / 2, 10)]; 14924 this.textColor_ = style.textColor || "black"; 14925 this.textSize_ = style.textSize || 11; 14926 this.textDecoration_ = style.textDecoration || "none"; 14927 this.fontWeight_ = style.fontWeight || "bold"; 14928 this.fontStyle_ = style.fontStyle || "normal"; 14929 this.fontFamily_ = style.fontFamily || "Arial,sans-serif"; 14930 this.backgroundPosition_ = style.backgroundPosition || "0 0"; 14931}; 14932 14933 14934/** 14935 * Sets the position at which to center the icon. 14936 * 14937 * @param {google.maps.LatLng} center The latlng to set as the center. 14938 */ 14939ClusterIcon.prototype.setCenter = function(center) { 14940 this.center_ = center; 14941}; 14942 14943 14944/** 14945 * Creates the cssText style parameter based on the position of the icon. 14946 * 14947 * @param {google.maps.Point} pos The position of the icon. 14948 * @return {string} The CSS style text. 14949 */ 14950ClusterIcon.prototype.createCss = function(pos) { 14951 var style = []; 14952 style.push("cursor: pointer;"); 14953 style.push("position: absolute; top: " + pos.y + "px; left: " + pos.x + "px;"); 14954 style.push("width: " + this.width_ + "px; height: " + this.height_ + "px;"); 14955 return style.join(""); 14956}; 14957 14958 14959/** 14960 * Returns the position at which to place the DIV depending on the latlng. 14961 * 14962 * @param {google.maps.LatLng} latlng The position in latlng. 14963 * @return {google.maps.Point} The position in pixels. 14964 */ 14965ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) { 14966 var pos = this.getProjection().fromLatLngToDivPixel(latlng); 14967 pos.x -= this.anchorIcon_[1]; 14968 pos.y -= this.anchorIcon_[0]; 14969 pos.x = parseInt(pos.x, 10); 14970 pos.y = parseInt(pos.y, 10); 14971 return pos; 14972}; 14973 14974 14975/** 14976 * Creates a single cluster that manages a group of proximate markers. 14977 * Used internally, do not call this constructor directly. 14978 * @constructor 14979 * @param {MarkerClusterer} mc The <code>MarkerClusterer</code> object with which this 14980 * cluster is associated. 14981 */ 14982function Cluster(mc) { 14983 this.markerClusterer_ = mc; 14984 this.map_ = mc.getMap(); 14985 this.gridSize_ = mc.getGridSize(); 14986 this.minClusterSize_ = mc.getMinimumClusterSize(); 14987 this.averageCenter_ = mc.getAverageCenter(); 14988 this.markers_ = []; 14989 this.center_ = null; 14990 this.bounds_ = null; 14991 this.clusterIcon_ = new ClusterIcon(this, mc.getStyles()); 14992} 14993 14994 14995/** 14996 * Returns the number of markers managed by the cluster. You can call this from 14997 * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler 14998 * for the <code>MarkerClusterer</code> object. 14999 * 15000 * @return {number} The number of markers in the cluster. 15001 */ 15002Cluster.prototype.getSize = function() { 15003 return this.markers_.length; 15004}; 15005 15006 15007/** 15008 * Returns the array of markers managed by the cluster. You can call this from 15009 * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler 15010 * for the <code>MarkerClusterer</code> object. 15011 * 15012 * @return {Array} The array of markers in the cluster. 15013 */ 15014Cluster.prototype.getMarkers = function() { 15015 return this.markers_; 15016}; 15017 15018 15019/** 15020 * Returns the center of the cluster. You can call this from 15021 * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler 15022 * for the <code>MarkerClusterer</code> object. 15023 * 15024 * @return {google.maps.LatLng} The center of the cluster. 15025 */ 15026Cluster.prototype.getCenter = function() { 15027 return this.center_; 15028}; 15029 15030 15031/** 15032 * Returns the map with which the cluster is associated. 15033 * 15034 * @return {google.maps.Map} The map. 15035 * @ignore 15036 */ 15037Cluster.prototype.getMap = function() { 15038 return this.map_; 15039}; 15040 15041 15042/** 15043 * Returns the <code>MarkerClusterer</code> object with which the cluster is associated. 15044 * 15045 * @return {MarkerClusterer} The associated marker clusterer. 15046 * @ignore 15047 */ 15048Cluster.prototype.getMarkerClusterer = function() { 15049 return this.markerClusterer_; 15050}; 15051 15052 15053/** 15054 * Returns the bounds of the cluster. 15055 * 15056 * @return {google.maps.LatLngBounds} the cluster bounds. 15057 * @ignore 15058 */ 15059Cluster.prototype.getBounds = function() { 15060 var i; 15061 var bounds = new google.maps.LatLngBounds(this.center_, this.center_); 15062 var markers = this.getMarkers(); 15063 for (i = 0; i < markers.length; i++) { 15064 bounds.extend(markers[i].getPosition()); 15065 } 15066 return bounds; 15067}; 15068 15069 15070/** 15071 * Removes the cluster from the map. 15072 * 15073 * @ignore 15074 */ 15075Cluster.prototype.remove = function() { 15076 this.clusterIcon_.setMap(null); 15077 this.markers_ = []; 15078 delete this.markers_; 15079}; 15080 15081 15082/** 15083 * Adds a marker to the cluster. 15084 * 15085 * @param {google.maps.Marker} marker The marker to be added. 15086 * @return {boolean} True if the marker was added. 15087 * @ignore 15088 */ 15089Cluster.prototype.addMarker = function(marker) { 15090 var i; 15091 var mCount; 15092 var mz; 15093 15094 if (this.isMarkerAlreadyAdded_(marker)) { 15095 return false;
15096 } 15097 15098 if (!this.center_) { 15099 this.center_ = marker.getPosition(); 15100 this.calculateBounds_(); 15101 } else { 15102 if (this.averageCenter_) { 15103 var l = this.markers_.length + 1; 15104 var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l; 15105 var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l; 15106 this.center_ = new google.maps.LatLng(lat, lng); 15107 this.calculateBounds_(); 15108 } 15109 } 15110 15111 marker.isAdded = true; 15112 this.markers_.push(marker); 15113 15114 mCount = this.markers_.length; 15115 mz = this.markerClusterer_.getMaxZoom(); 15116 if (mz !== null && this.map_.getZoom() > mz) { 15117 // Zoomed in past max zoom, so show the marker. 15118 if (marker.getMap() !== this.map_) { 15119 marker.setMap(this.map_); 15120 } 15121 } else if (mCount < this.minClusterSize_) { 15122 // Min cluster size not reached so show the marker. 15123 if (marker.getMap() !== this.map_) { 15124 marker.setMap(this.map_); 15125 } 15126 } else if (mCount === this.minClusterSize_) { 15127 // Hide the markers that were showing. 15128 for (i = 0; i < mCount; i++) { 15129 this.markers_[i].setMap(null); 15130 } 15131 } else { 15132 marker.setMap(null); 15133 } 15134 15135 this.updateIcon_(); 15136 return true; 15137}; 15138 15139 15140/** 15141 * Determines if a marker lies within the cluster's bounds. 15142 * 15143 * @param {google.maps.Marker} marker The marker to check. 15144 * @return {boolean} True if the marker lies in the bounds. 15145 * @ignore 15146 */ 15147Cluster.prototype.isMarkerInClusterBounds = function(marker) { 15148 return this.bounds_.contains(marker.getPosition()); 15149}; 15150 15151 15152/**
15153 * Calculates the extended bounds of the cluster with the grid. 15154 */ 15155Cluster.prototype.calculateBounds_ = function() { 15156 var bounds = new google.maps.LatLngBounds(this.center_, this.center_); 15157 this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); 15158}; 15159 15160 15161/** 15162 * Updates the cluster icon. 15163 */ 15164Cluster.prototype.updateIcon_ = function() { 15165 var mCount = this.markers_.length; 15166 var mz = this.markerClusterer_.getMaxZoom(); 15167 15168 if (mz !== null && this.map_.getZoom() > mz) { 15169 this.clusterIcon_.hide(); 15170 return; 15171 } 15172 15173 if (mCount < this.minClusterSize_) { 15174 // Min cluster size not yet reached. 15175 this.clusterIcon_.hide(); 15176 return; 15177 } 15178 15179 var numStyles = this.markerClusterer_.getStyles().length; 15180 var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); 15181 this.clusterIcon_.setCenter(this.center_); 15182 this.clusterIcon_.useStyle(sums); 15183 this.clusterIcon_.show(); 15184}; 15185 15186 15187/** 15188 * Determines if a marker has already been added to the cluster. 15189 * 15190 * @param {google.maps.Marker} marker The marker to check. 15191 * @return {boolean} True if the marker has already been added. 15192 */ 15193Cluster.prototype.isMarkerAlreadyAdded_ = function(marker) { 15194 var i; 15195 if (this.markers_.indexOf) { 15196 return this.markers_.indexOf(marker) !== -1; 15197 } else { 15198 for (i = 0; i < this.markers_.length; i++) { 15199 if (marker === this.markers_[i]) { 15200 return true; 15201 } 15202 } 15203 } 15204 return false; 15205}; 15206 15207 15208/** 15209 * @name MarkerClustererOptions 15210 * @class This class represents the optional parameter passed to 15211 * the {@link MarkerClusterer} constructor. 15212 * @property {number} [gridSize=60] The grid size of a cluster in pixels. The grid is a square. 15213 * @property {number} [maxZoom=null] The maximum zoom level at which clustering is enabled or 15214 * <code>null</code> if clustering is to be enabled at all zoom levels. 15215 * @property {boolean} [zoomOnClick=true] Whether to zoom the map when a cluster marker is 15216 * clicked. You may want to set this to <code>false</code> if you have installed a handler 15217 * for the <code>click</code> event and it deals with zooming on its own. 15218 * @property {boolean} [averageCenter=false] Whether the position of a cluster marker should be 15219 * the average position of all markers in the cluster. If set to <code>false</code>, the 15220 * cluster marker is positioned at the location of the first marker added to the cluster. 15221 * @property {number} [minimumClusterSize=2] The minimum number of markers needed in a cluster 15222 * before the markers are hidden and a cluster marker appears. 15223 * @property {boolean} [ignoreHidden=false] Whether to ignore hidden markers in clusters. You 15224 * may want to set this to <code>true</code> to ensure that hidden markers are not included 15225 * in the marker count that appears on a cluster marker (this count is the value of the 15226 * <code>text</code> property of the result returned by the default <code>calculator</code>). 15227 * If set to <code>true</code> and you change the visibility of a marker being clustered, be 15228 * sure to also call <code>MarkerClusterer.repaint()</code>. 15229 * @property {string} [title=""] The tooltip to display when the mouse moves over a cluster 15230 * marker. (Alternatively, you can use a custom <code>calculator</code> function to specify a 15231 * different tooltip for each cluster marker.) 15232 * @property {function} [calculator=MarkerClusterer.CALCULATOR] The function used to determine 15233 * the text to be displayed on a cluster marker and the index indicating which style to use 15234 * for the cluster marker. The input parameters for the function are (1) the array of markers 15235 * represented by a cluster marker and (2) the number of cluster icon styles. It returns a 15236 * {@link ClusterIconInfo} object. The default <code>calculator</code> returns a 15237 * <code>text</code> property which is the number of markers in the cluster and an 15238 * <code>index</code> property which is one higher than the lowest integer such that 15239 * <code>10^i</code> exceeds the number of markers in the cluster, or the size of the styles 15240 * array, whichever is less. The <code>styles</code> array element used has an index of 15241 * <code>index</code> minus 1. For example, the default <code>calculator</code> returns a 15242 * <code>text</code> value of <code>"125"</code> and an <code>index</code> of <code>3</code> 15243 * for a cluster icon representing 125 markers so the element used in the <code>styles</code> 15244 * array is <code>2</code>. A <code>calculator</code> may also return a <code>title</code> 15245 * property that contains the text of the tooltip to be used for the cluster marker. If 15246 * <code>title</code> is not defined, the tooltip is set to the value of the <code>title</code> 15247 * property for the MarkerClusterer. 15248 * @property {string} [clusterClass="cluster"] The name of the CSS class defining general styles 15249 * for the cluster markers. Use this class to define CSS styles that are not set up by the code 15250 * that processes the <code>styles</code> array.
15251 * @property {Array} [styles] An array of {@link ClusterIconStyle} elements defining the styles 15252 * of the cluster markers to be used. The element to be used to style a given cluster marker 15253 * is determined by the function defined by the <code>calculator</code> property. 15254 * The default is an array of {@link ClusterIconStyle} elements whose properties are derived 15255 * from the values for <code>imagePath</code>, <code>imageExtension</code>, and 15256 * <code>imageSizes</code>. 15257 * @property {boolean} [enableRetinaIcons=false] Whether to allow the use of cluster icons that 15258 * have sizes that are some multiple (typically double) of their actual display size. Icons such 15259 * as these look better when viewed on high-resolution monitors such as Apple's Retina displays. 15260 * Note: if this property is <code>true</code>, sprites cannot be used as cluster icons. 15261 * @property {number} [batchSize=MarkerClusterer.BATCH_SIZE] Set this property to the 15262 * number of markers to be processed in a single batch when using a browser other than 15263 * Internet Explorer (for Internet Explorer, use the batchSizeIE property instead). 15264 * @property {number} [batchSizeIE=MarkerClusterer.BATCH_SIZE_IE] When Internet Explorer is 15265 * being used, markers are processed in several batches with a small delay inserted between 15266 * each batch in an attempt to avoid Javascript timeout errors. Set this property to the 15267 * number of markers to be processed in a single batch; select as high a number as you can 15268 * without causing a timeout error in the browser. This number might need to be as low as 100 15269 * if 15,000 markers are being managed, for example. 15270 * @property {string} [imagePath=MarkerClusterer.IMAGE_PATH] 15271 * The full URL of the root name of the group of image files to use for cluster icons. 15272 * The complete file name is of the form <code>imagePath</code>n.<code>imageExtension</code> 15273 * where n is the image file number (1, 2, etc.). 15274 * @property {string} [imageExtension=MarkerClusterer.IMAGE_EXTENSION] 15275 * The extension name for the cluster icon image files (e.g., <code>"png"</code> or 15276 * <code>"jpg"</code>). 15277 * @property {Array} [imageSizes=MarkerClusterer.IMAGE_SIZES] 15278 * An array of numbers containing the widths of the group of 15279 * <code>imagePath</code>n.<code>imageExtension</code> image files. 15280 * (The images are assumed to be square.) 15281 */ 15282/** 15283 * Creates a MarkerClusterer object with the options specified in {@link MarkerClustererOptions}. 15284 * @constructor 15285 * @extends google.maps.OverlayView 15286 * @param {google.maps.Map} map The Google map to attach to. 15287 * @param {Array.<google.maps.Marker>} [opt_markers] The markers to be added to the cluster. 15288 * @param {MarkerClustererOptions} [opt_options] The optional parameters. 15289 */ 15290function MarkerClusterer(map, opt_markers, opt_options) { 15291 // MarkerClusterer implements google.maps.OverlayView interface. We use the 15292 // extend function to extend MarkerClusterer with google.maps.OverlayView 15293 // because it might not always be available when the code is defined so we 15294 // look for it at the last possible moment. If it doesn't exist now then 15295 // there is no point going ahead :) 15296 this.extend(MarkerClusterer, google.maps.OverlayView); 15297 15298 opt_markers = opt_markers || []; 15299 opt_options = opt_options || {}; 15300 15301 this.markers_ = []; 15302 this.clusters_ = []; 15303 this.listeners_ = []; 15304 this.activeMap_ = null; 15305 this.ready_ = false; 15306 15307 this.gridSize_ = opt_options.gridSize || 60; 15308 this.minClusterSize_ = opt_options.minimumClusterSize || 2; 15309 this.maxZoom_ = opt_options.maxZoom || null; 15310 this.styles_ = opt_options.styles || []; 15311 this.title_ = opt_options.title || ""; 15312 this.zoomOnClick_ = true; 15313 if (opt_options.zoomOnClick !== undefined) { 15314 this.zoomOnClick_ = opt_options.zoomOnClick; 15315 } 15316 this.averageCenter_ = false; 15317 if (opt_options.averageCenter !== undefined) { 15318 this.averageCenter_ = opt_options.averageCenter; 15319 } 15320 this.ignoreHidden_ = false; 15321 if (opt_options.ignoreHidden !== undefined) { 15322 this.ignoreHidden_ = opt_options.ignoreHidden; 15323 } 15324 this.enableRetinaIcons_ = false;
15325 if (opt_options.enableRetinaIcons !== undefined) { 15326 this.enableRetinaIcons_ = opt_options.enableRetinaIcons; 15327 } 15328 this.imagePath_ = opt_options.imagePath || MarkerClusterer.IMAGE_PATH; 15329 this.imageExtension_ = opt_options.imageExtension || MarkerClusterer.IMAGE_EXTENSION; 15330 this.imageSizes_ = opt_options.imageSizes || MarkerClusterer.IMAGE_SIZES; 15331 this.calculator_ = opt_options.calculator || MarkerClusterer.CALCULATOR; 15332 this.batchSize_ = opt_options.batchSize || MarkerClusterer.BATCH_SIZE; 15333 this.batchSizeIE_ = opt_options.batchSizeIE || MarkerClusterer.BATCH_SIZE_IE; 15334 this.clusterClass_ = opt_options.clusterClass || "cluster"; 15335 15336 if (navigator.userAgent.toLowerCase().indexOf("msie") !== -1) { 15337 // Try to avoid IE timeout when processing a huge number of markers: 15338 this.batchSize_ = this.batchSizeIE_; 15339 } 15340 15341 this.setupStyles_(); 15342 15343 this.addMarkers(opt_markers, true); 15344 this.setMap(map); // Note: this causes onAdd to be called 15345} 15346 15347 15348/** 15349 * Implementation of the onAdd interface method. 15350 * @ignore 15351 */ 15352MarkerClusterer.prototype.onAdd = function() { 15353 var cMarkerClusterer = this; 15354 15355 this.activeMap_ = this.getMap(); 15356 this.ready_ = true; 15357 15358 this.repaint(); 15359 15360 // Add the map event listeners 15361 this.listeners_ = [ 15362 google.maps.event.addListener(this.getMap(), "zoom_changed", function() { 15363 cMarkerClusterer.resetViewport_(false); 15364 // Workaround for this Google bug: when map is at level 0 and "-" of 15365 // zoom slider is clicked, a "zoom_changed" event is fired even though 15366 // the map doesn't zoom out any further. In this situation, no "idle" 15367 // event is triggered so the cluster markers that have been removed 15368 // do not get redrawn. Same goes for a zoom in at maxZoom. 15369 if (this.getZoom() === (this.get("minZoom") || 0) || this.getZoom() === this.get("maxZoom")) { 15370 google.maps.event.trigger(this, "idle"); 15371 } 15372 }), 15373 google.maps.event.addListener(this.getMap(), "idle", function() { 15374 cMarkerClusterer.redraw_(); 15375 }) 15376 ]; 15377}; 15378 15379 15380/** 15381 * Implementation of the onRemove interface method. 15382 * Removes map event listeners and all cluster icons from the DOM. 15383 * All managed markers are also put back on the map. 15384 * @ignore 15385 */ 15386MarkerClusterer.prototype.onRemove = function() { 15387 var i; 15388 15389 // Put all the managed markers back on the map: 15390 for (i = 0; i < this.markers_.length; i++) { 15391 if (this.markers_[i].getMap() !== this.activeMap_) { 15392 this.markers_[i].setMap(this.activeMap_); 15393 } 15394 } 15395 15396 // Remove all clusters: 15397 for (i = 0; i < this.clusters_.length; i++) { 15398 this.clusters_[i].remove(); 15399 } 15400 this.clusters_ = []; 15401 15402 // Remove map event listeners: 15403 for (i = 0; i < this.listeners_.length; i++) { 15404 google.maps.event.removeListener(this.listeners_[i]); 15405 } 15406 this.listeners_ = []; 15407 15408 this.activeMap_ = null; 15409 this.ready_ = false; 15410}; 15411 15412 15413/** 15414 * Implementation of the draw interface method. 15415 * @ignore 15416 */ 15417MarkerClusterer.prototype.draw = function() {}; 15418 15419 15420/** 15421 * Sets up the styles object. 15422 */ 15423MarkerClusterer.prototype.setupStyles_ = function() { 15424 var i, size; 15425 if (this.styles_.length > 0) { 15426 return; 15427 } 15428 15429 for (i = 0; i < this.imageSizes_.length; i++) { 15430 size = this.imageSizes_[i]; 15431 this.styles_.push({ 15432 url: this.imagePath_ + (i + 1) + "." + this.imageExtension_, 15433 height: size, 15434 width: size 15435 }); 15436 } 15437}; 15438 15439 15440/** 15441 * Fits the map to the bounds of the markers managed by the clusterer. 15442 */ 15443MarkerClusterer.prototype.fitMapToMarkers = function() { 15444 var i; 15445 var markers = this.getMarkers(); 15446 var bounds = new google.maps.LatLngBounds(); 15447 for (i = 0; i < markers.length; i++) { 15448 bounds.extend(markers[i].getPosition()); 15449 } 15450 15451 this.getMap().fitBounds(bounds); 15452}; 15453 15454 15455/** 15456 * Returns the value of the <code>gridSize</code> property. 15457 * 15458 * @return {number} The grid size. 15459 */ 15460MarkerClusterer.prototype.getGridSize = function() { 15461 return this.gridSize_; 15462}; 15463 15464 15465/** 15466 * Sets the value of the <code>gridSize</code> property. 15467 * 15468 * @param {number} gridSize The grid size. 15469 */ 15470MarkerClusterer.prototype.setGridSize = function(gridSize) { 15471 this.gridSize_ = gridSize; 15472}; 15473 15474 15475/** 15476 * Returns the value of the <code>minimumClusterSize</code> property. 15477 * 15478 * @return {number} The minimum cluster size. 15479 */ 15480MarkerClusterer.prototype.getMinimumClusterSize = function() { 15481 return this.minClusterSize_; 15482}; 15483 15484/** 15485 * Sets the value of the <code>minimumClusterSize</code> property. 15486 * 15487 * @param {number} minimumClusterSize The minimum cluster size. 15488 */ 15489MarkerClusterer.prototype.setMinimumClusterSize = function(minimumClusterSize) { 15490 this.minClusterSize_ = minimumClusterSize; 15491}; 15492 15493 15494/** 15495 * Returns the value of the <code>maxZoom</code> property. 15496 * 15497 * @return {number} The maximum zoom level. 15498 */ 15499MarkerClusterer.prototype.getMaxZoom = function() { 15500 return this.maxZoom_; 15501}; 15502 15503 15504/** 15505 * Sets the value of the <code>maxZoom</code> property. 15506 * 15507 * @param {number} maxZoom The maximum zoom level. 15508 */ 15509MarkerClusterer.prototype.setMaxZoom = function(maxZoom) { 15510 this.maxZoom_ = maxZoom; 15511}; 15512 15513 15514/** 15515 * Returns the value of the <code>styles</code> property. 15516 * 15517 * @return {Array} The array of styles defining the cluster markers to be used. 15518 */ 15519MarkerClusterer.prototype.getStyles = function() { 15520 return this.styles_; 15521}; 15522 15523 15524/** 15525 * Sets the value of the <code>styles</code> property. 15526 * 15527 * @param {Array.<ClusterIconStyle>} styles The array of styles to use. 15528 */ 15529MarkerClusterer.prototype.setStyles = function(styles) { 15530 this.styles_ = styles; 15531}; 15532 15533 15534/** 15535 * Returns the value of the <code>title</code> property. 15536 * 15537 * @return {string} The content of the title text. 15538 */ 15539MarkerClusterer.prototype.getTitle = function() { 15540 return this.title_; 15541}; 15542 15543 15544/** 15545 * Sets the value of the <code>title</code> property. 15546 * 15547 * @param {string} title The value of the title property. 15548 */ 15549MarkerClusterer.prototype.setTitle = function(title) { 15550 this.title_ = title; 15551}; 15552 15553 15554/** 15555 * Returns the value of the <code>zoomOnClick</code> property. 15556 * 15557 * @return {boolean} True if zoomOnClick property is set. 15558 */ 15559MarkerClusterer.prototype.getZoomOnClick = function() { 15560 return this.zoomOnClick_; 15561}; 15562 15563 15564/** 15565 * Sets the value of the <code>zoomOnClick</code> property. 15566 * 15567 * @param {boolean} zoomOnClick The value of the zoomOnClick property. 15568 */ 15569MarkerClusterer.prototype.setZoomOnClick = function(zoomOnClick) { 15570 this.zoomOnClick_ = zoomOnClick; 15571}; 15572 15573 15574/** 15575 * Returns the value of the <code>averageCenter</code> property. 15576 * 15577 * @return {boolean} True if averageCenter property is set. 15578 */ 15579MarkerClusterer.prototype.getAverageCenter = function() { 15580 return this.averageCenter_; 15581}; 15582 15583 15584/** 15585 * Sets the value of the <code>averageCenter</code> property. 15586 * 15587 * @param {boolean} averageCenter The value of the averageCenter property. 15588 */ 15589MarkerClusterer.prototype.setAverageCenter = function(averageCenter) { 15590 this.averageCenter_ = averageCenter; 15591}; 15592 15593 15594/** 15595 * Returns the value of the <code>ignoreHidden</code> property. 15596 * 15597 * @return {boolean} True if ignoreHidden property is set. 15598 */ 15599MarkerClusterer.prototype.getIgnoreHidden = function() { 15600 return this.ignoreHidden_; 15601}; 15602 15603 15604/** 15605 * Sets the value of the <code>ignoreHidden</code> property. 15606 * 15607 * @param {boolean} ignoreHidden The value of the ignoreHidden property. 15608 */ 15609MarkerClusterer.prototype.setIgnoreHidden = function(ignoreHidden) { 15610 this.ignoreHidden_ = ignoreHidden; 15611}; 15612 15613 15614/** 15615 * Returns the value of the <code>enableRetinaIcons</code> property. 15616 * 15617 * @return {boolean} True if enableRetinaIcons property is set. 15618 */ 15619MarkerClusterer.prototype.getEnableRetinaIcons = function() { 15620 return this.enableRetinaIcons_; 15621}; 15622 15623 15624/** 15625 * Sets the value of the <code>enableRetinaIcons</code> property. 15626 * 15627 * @param {boolean} enableRetinaIcons The value of the enableRetinaIcons property. 15628 */ 15629MarkerClusterer.prototype.setEnableRetinaIcons = function(enableRetinaIcons) { 15630 this.enableRetinaIcons_ = enableRetinaIcons; 15631}; 15632 15633 15634/** 15635 * Returns the value of the <code>imageExtension</code> property. 15636 * 15637 * @return {string} The value of the imageExtension property. 15638 */ 15639MarkerClusterer.prototype.getImageExtension = function() { 15640 return this.imageExtension_; 15641}; 15642 15643 15644/** 15645 * Sets the value of the <code>imageExtension</code> property. 15646 * 15647 * @param {string} imageExtension The value of the imageExtension property. 15648 */ 15649MarkerClusterer.prototype.setImageExtension = function(imageExtension) { 15650 this.imageExtension_ = imageExtension; 15651}; 15652 15653 15654/** 15655 * Returns the value of the <code>imagePath</code> property. 15656 * 15657 * @return {string} The value of the imagePath property. 15658 */ 15659MarkerClusterer.prototype.getImagePath = function() { 15660 return this.imagePath_; 15661}; 15662 15663 15664/** 15665 * Sets the value of the <code>imagePath</code> property. 15666 * 15667 * @param {string} imagePath The value of the imagePath property. 15668 */ 15669MarkerClusterer.prototype.setImagePath = function(imagePath) { 15670 this.imagePath_ = imagePath; 15671}; 15672 15673 15674/** 15675 * Returns the value of the <code>imageSizes</code> property. 15676 * 15677 * @return {Array} The value of the imageSizes property. 15678 */ 15679MarkerClusterer.prototype.getImageSizes = function() { 15680 return this.imageSizes_; 15681}; 15682 15683 15684/** 15685 * Sets the value of the <code>imageSizes</code> property. 15686 * 15687 * @param {Array} imageSizes The value of the imageSizes property. 15688 */ 15689MarkerClusterer.prototype.setImageSizes = function(imageSizes) { 15690 this.imageSizes_ = imageSizes; 15691}; 15692 15693 15694/** 15695 * Returns the value of the <code>calculator</code> property. 15696 * 15697 * @return {function} the value of the calculator property. 15698 */ 15699MarkerClusterer.prototype.getCalculator = function() { 15700 return this.calculator_; 15701}; 15702 15703 15704/** 15705 * Sets the value of the <code>calculator</code> property. 15706 * 15707 * @param {function(Array.<google.maps.Marker>, number)} calculator The value 15708 * of the calculator property. 15709 */ 15710MarkerClusterer.prototype.setCalculator = function(calculator) { 15711 this.calculator_ = calculator; 15712}; 15713 15714 15715/** 15716 * Returns the value of the <code>batchSizeIE</code> property. 15717 * 15718 * @return {number} the value of the batchSizeIE property. 15719 */ 15720MarkerClusterer.prototype.getBatchSizeIE = function() { 15721 return this.batchSizeIE_; 15722}; 15723 15724 15725/** 15726 * Sets the value of the <code>batchSizeIE</code> property. 15727 * 15728 * @param {number} batchSizeIE The value of the batchSizeIE property. 15729 */ 15730MarkerClusterer.prototype.setBatchSizeIE = function(batchSizeIE) { 15731 this.batchSizeIE_ = batchSizeIE; 15732}; 15733 15734 15735/** 15736 * Returns the value of the <code>clusterClass</code> property. 15737 * 15738 * @return {string} the value of the clusterClass property. 15739 */ 15740MarkerClusterer.prototype.getClusterClass = function() { 15741 return this.clusterClass_; 15742}; 15743 15744 15745/** 15746 * Sets the value of the <code>clusterClass</code> property. 15747 * 15748 * @param {string} clusterClass The value of the clusterClass property. 15749 */ 15750MarkerClusterer.prototype.setClusterClass = function(clusterClass) { 15751 this.clusterClass_ = clusterClass; 15752}; 15753 15754 15755/** 15756 * Returns the array of markers managed by the clusterer. 15757 * 15758 * @return {Array} The array of markers managed by the clusterer. 15759 */ 15760MarkerClusterer.prototype.getMarkers = function() { 15761 return this.markers_; 15762}; 15763 15764 15765/** 15766 * Returns the number of markers managed by the clusterer. 15767 * 15768 * @return {number} The number of markers. 15769 */ 15770MarkerClusterer.prototype.getTotalMarkers = function() { 15771 return this.markers_.length; 15772}; 15773 15774 15775/**
15776 * Returns the current array of clusters formed by the clusterer. 15777 * 15778 * @return {Array} The array of clusters formed by the clusterer. 15779 */ 15780MarkerClusterer.prototype.getClusters = function() { 15781 return this.clusters_; 15782}; 15783 15784 15785/** 15786 * Returns the number of clusters formed by the clusterer. 15787 * 15788 * @return {number} The number of clusters formed by the clusterer. 15789 */ 15790MarkerClusterer.prototype.getTotalClusters = function() { 15791 return this.clusters_.length; 15792}; 15793 15794 15795/** 15796 * Adds a marker to the clusterer. The clusters are redrawn unless 15797 * <code>opt_nodraw</code> is set to <code>true</code>. 15798 * 15799 * @param {google.maps.Marker} marker The marker to add. 15800 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing. 15801 */ 15802MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) { 15803 this.pushMarkerTo_(marker); 15804 if (!opt_nodraw) { 15805 this.redraw_(); 15806 } 15807}; 15808 15809 15810/** 15811 * Adds an array of markers to the clusterer. The clusters are redrawn unless 15812 * <code>opt_nodraw</code> is set to <code>true</code>. 15813 * 15814 * @param {Array.<google.maps.Marker>} markers The markers to add. 15815 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing. 15816 */ 15817MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) { 15818 var key; 15819 for (key in markers) { 15820 if (markers.hasOwnProperty(key)) { 15821 this.pushMarkerTo_(markers[key]); 15822 } 15823 } 15824 if (!opt_nodraw) { 15825 this.redraw_(); 15826 } 15827}; 15828 15829 15830/** 15831 * Pushes a marker to the clusterer. 15832 * 15833 * @param {google.maps.Marker} marker The marker to add. 15834 */ 15835MarkerClusterer.prototype.pushMarkerTo_ = function(marker) { 15836 // If the marker is draggable add a listener so we can update the clusters on the dragend: 15837 if (marker.getDraggable()) { 15838 var cMarkerClusterer = this; 15839 google.maps.event.addListener(marker, "dragend", function() { 15840 if (cMarkerClusterer.ready_) { 15841 this.isAdded = false; 15842 cMarkerClusterer.repaint(); 15843 } 15844 }); 15845 } 15846 marker.isAdded = false; 15847 this.markers_.push(marker); 15848}; 15849 15850 15851/** 15852 * Removes a marker from the cluster. The clusters are redrawn unless 15853 * <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if the 15854 * marker was removed from the clusterer. 15855 * 15856 * @param {google.maps.Marker} marker The marker to remove. 15857 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing. 15858 * @return {boolean} True if the marker was removed from the clusterer. 15859 */ 15860MarkerClusterer.prototype.removeMarker = function(marker, opt_nodraw) { 15861 var removed = this.removeMarker_(marker); 15862 15863 if (!opt_nodraw && removed) { 15864 this.repaint(); 15865 } 15866 15867 return removed; 15868}; 15869 15870 15871/** 15872 * Removes an array of markers from the cluster. The clusters are redrawn unless 15873 * <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if markers 15874 * were removed from the clusterer. 15875 * 15876 * @param {Array.<google.maps.Marker>} markers The markers to remove. 15877 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing. 15878 * @return {boolean} True if markers were removed from the clusterer. 15879 */ 15880MarkerClusterer.prototype.removeMarkers = function(markers, opt_nodraw) { 15881 var i, r; 15882 var removed = false; 15883 15884 for (i = 0; i < markers.length; i++) { 15885 r = this.removeMarker_(markers[i]); 15886 removed = removed || r; 15887 } 15888 15889 if (!opt_nodraw && removed) { 15890 this.repaint(); 15891 } 15892 15893 return removed; 15894}; 15895 15896 15897/** 15898 * Removes a marker and returns true if removed, false if not. 15899 * 15900 * @param {google.maps.Marker} marker The marker to remove 15901 * @return {boolean} Whether the marker was removed or not 15902 */ 15903MarkerClusterer.prototype.removeMarker_ = function(marker) { 15904 var i; 15905 var index = -1; 15906 if (this.markers_.indexOf) { 15907 index = this.markers_.indexOf(marker); 15908 } else { 15909 for (i = 0; i < this.markers_.length; i++) { 15910 if (marker === this.markers_[i]) { 15911 index = i; 15912 break; 15913 } 15914 } 15915 } 15916 15917 if (index === -1) { 15918 // Marker is not in our list of markers, so do nothing: 15919 return false;
15920 } 15921 15922 marker.setMap(null); 15923 this.markers_.splice(index, 1); // Remove the marker from the list of managed markers 15924 return true; 15925}; 15926 15927 15928/** 15929 * Removes all clusters and markers from the map and also removes all markers 15930 * managed by the clusterer. 15931 */ 15932MarkerClusterer.prototype.clearMarkers = function() { 15933 this.resetViewport_(true); 15934 this.markers_ = []; 15935}; 15936 15937 15938/** 15939 * Recalculates and redraws all the marker clusters from scratch. 15940 * Call this after changing any properties. 15941 */ 15942MarkerClusterer.prototype.repaint = function() { 15943 var oldClusters = this.clusters_.slice(); 15944 this.clusters_ = []; 15945 this.resetViewport_(false); 15946 this.redraw_(); 15947 15948 // Remove the old clusters. 15949 // Do it in a timeout to prevent blinking effect. 15950 setTimeout(function() { 15951 var i; 15952 for (i = 0; i < oldClusters.length; i++) { 15953 oldClusters[i].remove(); 15954 } 15955 }, 0); 15956}; 15957 15958 15959/** 15960 * Returns the current bounds extended by the grid size. 15961 * 15962 * @param {google.maps.LatLngBounds} bounds The bounds to extend. 15963 * @return {google.maps.LatLngBounds}
15963 The extended bounds. 15964 * @ignore 15965 */ 15966MarkerClusterer.prototype.getExtendedBounds = function(bounds) { 15967 var projection = this.getProjection(); 15968 15969 // Turn the bounds into latlng. 15970 var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), 15971 bounds.getNorthEast().lng()); 15972 var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), 15973 bounds.getSouthWest().lng()); 15974 15975 // Convert the points to pixels and the extend out by the grid size. 15976 var trPix = projection.fromLatLngToDivPixel(tr); 15977 trPix.x += this.gridSize_; 15978 trPix.y -= this.gridSize_; 15979 15980 var blPix = projection.fromLatLngToDivPixel(bl); 15981 blPix.x -= this.gridSize_; 15982 blPix.y += this.gridSize_; 15983 15984 // Convert the pixel points back to LatLng 15985 var ne = projection.fromDivPixelToLatLng(trPix); 15986 var sw = projection.fromDivPixelToLatLng(blPix); 15987 15988 // Extend the bounds to contain the new bounds. 15989 bounds.extend(ne); 15990 bounds.extend(sw); 15991 15992 return bounds; 15993}; 15994 15995 15996/** 15997 * Redraws all the clusters. 15998 */ 15999MarkerClusterer.prototype.redraw_ = function() { 16000 this.createClusters_(0); 16001}; 16002 16003 16004/** 16005 * Removes all clusters from the map. The markers are also removed from the map 16006 * if <code>opt_hide</code> is set to <code>true</code>. 16007 * 16008 * @param {boolean} [opt_hide] Set to <code>true</code> to also remove the markers 16009 * from the map. 16010 */ 16011MarkerClusterer.prototype.resetViewport_ = function(opt_hide) { 16012 var i, marker; 16013 // Remove all the clusters 16014 for (i = 0; i < this.clusters_.length; i++) { 16015 this.clusters_[i].remove(); 16016 } 16017 this.clusters_ = []; 16018 16019 // Reset the markers to not be added and to be removed from the map. 16020 for (i = 0; i < this.markers_.length; i++) { 16021 marker = this.markers_[i]; 16022 marker.isAdded = false; 16023 if (opt_hide) { 16024 marker.setMap(null); 16025 } 16026 } 16027}; 16028 16029 16030/** 16031 * Calculates the distance between two latlng locations in km. 16032 * 16033 * @param {google.maps.LatLng} p1 The first lat lng point. 16034 * @param {google.maps.LatLng} p2 The second lat lng point. 16035 * @return {number} The distance between the two points in km. 16036 * @see http://www.movable-type.co.uk/scripts/latlong.html 16037 */ 16038MarkerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) { 16039 var R = 6371; // Radius of the Earth in km 16040 var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; 16041 var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; 16042 var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + 16043 Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * 16044 Math.sin(dLon / 2) * Math.sin(dLon / 2); 16045 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 16046 var d = R * c; 16047 return d; 16048}; 16049 16050 16051/** 16052 * Determines if a marker is contained in a bounds. 16053 * 16054 * @param {google.maps.Marker} marker The marker to check. 16055 * @param {google.maps.LatLngBounds} bounds The bounds to check against. 16056 * @return {boolean} True if the marker is in the bounds. 16057 */ 16058MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) { 16059 return bounds.contains(marker.getPosition()); 16060}; 16061 16062 16063/** 16064 * Adds a marker to a cluster, or creates a new cluster. 16065 * 16066 * @param {google.maps.Marker} marker The marker to add. 16067 */ 16068MarkerClusterer.prototype.addToClosestCluster_ = function(marker) { 16069 var i, d, cluster, center; 16070 var distance = 40000; // Some large number 16071 var clusterToAddTo = null; 16072 for (i = 0; i < this.clusters_.length; i++) { 16073 cluster = this.clusters_[i]; 16074 center = cluster.getCenter(); 16075 if (center) { 16076 d = this.distanceBetweenPoints_(center, marker.getPosition()); 16077 if (d < distance) { 16078 distance = d; 16079 clusterToAddTo = cluster; 16080 } 16081 } 16082 } 16083 16084 if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) { 16085 clusterToAddTo.addMarker(marker); 16086 } else { 16087 cluster = new Cluster(this); 16088 cluster.addMarker(marker); 16089 this.clusters_.push(cluster); 16090 } 16091}; 16092 16093 16094/** 16095 * Creates the clusters. This is done in batches to avoid timeout errors 16096 * in some browsers when there is a huge number of markers. 16097 * 16098 * @param {number} iFirst The index of the first marker in the batch of 16099 * markers to be added to clusters. 16100 */
16101MarkerClusterer.prototype.createClusters_ = function(iFirst) { 16102 var i, marker; 16103 var mapBounds; 16104 var cMarkerClusterer = this; 16105 if (!this.ready_) { 16106 return; 16107 } 16108 16109 // Cancel previous batch processing if we're working on the first batch: 16110 if (iFirst === 0) { 16111 /** 16112 * This event is fired when the <code>MarkerClusterer</code> begins 16113 * clustering markers. 16114 * @name MarkerClusterer#clusteringbegin 16115 * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered. 16116 * @event 16117 */ 16118 google.maps.event.trigger(this, "clusteringbegin", this); 16119 16120 if (typeof this.timerRefStatic !== "undefined") { 16121 clearTimeout(this.timerRefStatic); 16122 delete this.timerRefStatic; 16123 } 16124 } 16125 16126 // Get our current map view bounds. 16127 // Create a new bounds object so we don't affect the map. 16128 // 16129 // See Comments 9 & 11 on Issue 3651 relating to this workaround for a Google Maps bug: 16130 if (this.getMap().getZoom() > 3) { 16131 mapBounds = new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest
16131(), 16132 this.getMap().getBounds().getNorthEast()); 16133 } else { 16134 mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472, -178.48388434375), new google.maps.LatLng(-85.08136444384544, 178.00048865625)); 16135 } 16136 var bounds = this.getExtendedBounds(mapBounds); 16137 16138 var iLast = Math.min(iFirst + this.batchSize_, this.markers_.length); 16139 16140 for (i = iFirst; i < iLast; i++) { 16141 marker = this.markers_[i]; 16142 if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { 16143 if (!this.ignoreHidden_ || (this.ignoreHidden_ && marker.getVisible())) { 16144 this.addToClosestCluster_(marker); 16145 } 16146 } 16147 } 16148 16149 if (iLast < this.markers_.length) { 16150 this.timerRefStatic = setTimeout(function() { 16151 cMarkerClusterer.createClusters_(iLast); 16152 }, 0); 16153 } else { 16154 delete this.timerRefStatic; 16155 16156 /** 16157 * This event is fired when the <code>MarkerClusterer</code> stops 16158 * clustering markers. 16159 * @name MarkerClusterer#clusteringend 16160 * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered. 16161 * @event 16162 */ 16163 google.maps.event.trigger(this, "clusteringend", this); 16164 } 16165}; 16166 16167 16168/** 16169 * Extends an object's prototype by another's. 16170 * 16171 * @param {Object} obj1 The object to be extended. 16172 * @param {Object} obj2 The object to extend with. 16173 * @return {Object} The new extended object. 16174 * @ignore 16175 */ 16176MarkerClusterer.prototype.extend = function(obj1, obj2) { 16177 return (function(object) { 16178 var property; 16179 for (property in object.prototype) { 16180 this.prototype[property] = object.prototype[property]; 16181 } 16182 return this; 16183 }).apply(obj1, [obj2]); 16184}; 16185 16186 16187/** 16188 * The default function for determining the label text and style 16189 * for a cluster icon. 16190 * 16191 * @param {Array.<google.maps.Marker>} markers The array of markers represented by the cluster. 16192 * @param {number} numStyles The number of marker styles available. 16193 * @return {ClusterIconInfo} The information resource for the cluster. 16194 * @constant 16195 * @ignore 16196 */ 16197MarkerClusterer.CALCULATOR = function(markers, numStyles) { 16198 var index = 0; 16199 var title = ""; 16200 var count = markers.length.toString(); 16201 16202 var dv = count; 16203 while (dv !== 0) { 16204 dv = parseInt(dv / 10, 10); 16205 index++; 16206 } 16207 16208 index = Math.min(index, numStyles); 16209 return { 16210 text: count, 16211 index: index, 16212 title: title 16213 }; 16214}; 16215 16216 16217/** 16218 * The number of markers to process in one batch. 16219 * 16220 * @type {number} 16221 * @constant 16222 */ 16223MarkerClusterer.BATCH_SIZE = 2000; 16224 16225 16226/** 16227 * The number of markers to process in one batch (IE only). 16228 * 16229 * @type {number} 16230 * @constant 16231 */ 16232MarkerClusterer.BATCH_SIZE_IE = 500; 16233 16234 16235/** 16236 * The default root name for the marker cluster images. 16237 * 16238 * @type {string} 16239 * @constant 16240 */ 16241MarkerClusterer.IMAGE_PATH = "http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/images/m"; 16242 16243 16244/** 16245 * The default extension name for the marker cluster images. 16246 * 16247 * @type {string} 16248 * @constant 16249 */ 16250MarkerClusterer.IMAGE_EXTENSION = "png"; 16251 16252 16253/** 16254 * The default array of sizes for the marker cluster images. 16255 * 16256 * @type {Array.<number>} 16257 * @constant 16258 */ 16259MarkerClusterer.IMAGE_SIZES = [53, 56, 66, 78, 90]; 16260/** 16261 * Accordion, jQuery Plugin 16262 * 16263 * This plugin provides an accordion with cookie support. 16264 * 16265 * Copyright (c) 2011 John Snyder (snyderplace.com) 16266 * @license http://www.snyderplace.com/accordion/license.txt New BSD 16267 * @version 1.1 16268 */ 16269(function($) { 16270 $.fn.accordion = function(options) { 16271 16272 //firewalling 16273 if (!this || this.length < 1) { 16274 return this; 16275 } 16276 16277 initialize(this, options); 16278 16279 }; 16280 16281 //create the initial accordion 16282 function initialize(obj, options) { 16283 16284 //build main options before element iteration 16285 var opts = $.extend({}, $.fn.accordion.defaults, options); 16286 16287 //store any opened default values to set cookie later 16288 var opened = ''; 16289 16290 //iterate each matched object, bind, and open/close 16291 obj.each(function() { 16292 var $this = $(this); 16293 saveOpts($this, opts); 16294 16295 //bind it to the event 16296 if (opts.bind == 'mouseenter') { 16297 $this.bind('mouseenter', function(e) { 16298 e.preventDefault(); 16299 toggle($this, opts); 16300 }); 16301 } 16302 16303 //bind it to the event 16304 if (opts.bind == 'mouseover') { 16305 $this.bind('mouseover', function(e) { 16306 e.preventDefault(); 16307 toggle($this, opts); 16308 }); 16309 } 16310 16311 //bind it to the event 16312 if (opts.bind == 'click') { 16313 $this.bind('click', function(e) { 16314 e.preventDefault(); 16315 toggle($this, opts); 16316 }); 16317 } 16318 16319 //bind it to the event 16320 if (opts.bind == 'dblclick') { 16321 $this.bind('dblclick', function(e) { 16322 e.preventDefault(); 16323 toggle($this, opts); 16324 }); 16325 } 16326 16327 //initialize the panels 16328 //get the id for this element 16329 var id = $this.attr('id'); 16330 16331 //if not using cookies, open defaults 16332 if (!useCookies(opts)) { 16333 //close it if not defaulted to open 16334 if (id != opts.defaultOpen) { 16335 $this.addClass(opts.cssClose); 16336 opts.loadClose($this, opts); 16337 } else { //its a default open, open it 16338 $this.addClass(opts.cssOpen); 16339 opts.loadOpen($this, opts); 16340 opened = id; 16341 } 16342 } else { //can use cookies, use them now 16343 //has a cookie been set, this overrides default open 16344 if (issetCookie(opts)) { 16345 if (inCookie(id, opts) === false) { 16346 $this.addClass(opts.cssClose); 16347 opts.loadClose($this, opts); 16348 } else { 16349 $this.addClass(opts.cssOpen); 16350 opts.loadOpen($this, opts); 16351 opened = id; 16352 } 16353 } else { //a cookie hasn't been set open defaults 16354 if (id != opts.defaultOpen) { 16355 $this.addClass(opts.cssClose); 16356 opts.loadClose($this, opts); 16357 } else { //its a default open, open it 16358 $this.addClass(opts.cssOpen); 16359 opts.loadOpen($this, opts); 16360 opened = id; 16361 } 16362 } 16363 } 16364 }); 16365 16366 //now that the loop is done, set the cookie 16367 if (opened.length > 0 && useCookies(opts)) {
16368 setCookie(opened, opts); 16369 } else { //there are none open, set cookie 16370 setCookie('', opts); 16371 } 16372 16373 return obj; 16374 }; 16375 16376 //load opts from object 16377 function loadOpts($this) { 16378 return $this.data('accordion-opts'); 16379 } 16380 16381 //save opts into object 16382 function saveOpts($this, opts) { 16383 return $this.data('accordion-opts', opts); 16384 } 16385 16386 //hides a accordion panel 16387 function close(opts) { 16388 opened = $(document).find('.' + opts.cssOpen); 16389 $.each(opened, function() { 16390 //give the proper class to the linked element 16391 $(this).addClass(opts.cssClose).removeClass(opts.cssOpen); 16392 opts.animateClose($(this), opts); 16393 }); 16394 } 16395 16396 //opens a accordion panel 16397 function open($this, opts) { 16398 close(opts); 16399 //give the proper class to the linked element 16400 $this.removeClass(opts.cssClose).addClass(opts.cssOpen); 16401 16402 //open the element 16403 opts.animateOpen($this, opts); 16404 16405 //do cookies if plugin available 16406 if (useCookies(opts)) { 16407 // split the cookieOpen string by "," 16408 id = $this.attr('id'); 16409 setCookie(id, opts); 16410 } 16411 } 16412 16413 //toggle a accordion on an event 16414 function toggle($this, opts) { 16415 // close the only open item 16416 if ($this.hasClass(opts.cssOpen)) { 16417 close(opts); 16418 //do cookies if plugin available 16419 if (useCookies(opts)) { 16420 // split the cookieOpen string by "," 16421 setCookie('', opts); 16422 } 16423 return false; 16424 } 16425 close(opts); 16426 //open a closed element 16427 open($this, opts); 16428 return false; 16429 } 16430 16431 //use cookies? 16432 function useCookies(opts) { 16433 //return false if cookie plugin not present or if a cookie name is not provided 16434 if (!$.cookie || opts.cookieName == '') { 16435 return false; 16436 } 16437 16438 //we can use cookies 16439 return true; 16440 } 16441 16442 //set a cookie 16443 function setCookie(value, opts) { 16444 //can use the cookie plugin 16445 if (!useCookies(opts)) { //no, quit here 16446 return false; 16447 } 16448 16449 //cookie plugin is available, lets set the cookie 16450 $.cookie(opts.cookieName, value, opts.cookieOptions); 16451 } 16452 16453 //check if a accordion is in the cookie 16454 function inCookie(value, opts) { 16455 //can use the cookie plugin 16456 if (!useCookies(opts)) { 16457 return false; 16458 } 16459 16460 //if its not there we don't need to remove from it 16461 if (!issetCookie(opts)) { //quit here, don't have a cookie 16462 return false; 16463 } 16464 16465 //unescape it 16466 cookie = unescape($.cookie(opts.cookieName)); 16467 16468 //is this value in the cookie arrray 16469 if (cookie != value) { //no, quit here 16470 return false; 16471 } 16472 16473 return true; 16474 } 16475 16476 //check if a cookie is set 16477 function issetCookie(opts) { 16478 //can we use the cookie plugin 16479 if (!useCookies(opts)) { //no, quit here 16480 return false; 16481 } 16482 16483 //is the cookie set 16484 if ($.cookie(opts.cookieName) == null) { //no, quit here 16485 return false; 16486 } 16487 16488 return true; 16489 } 16490 16491 // settings 16492 $.fn.accordion.defaults = { 16493 cssClose: 'accordion-close', //class you want to assign to a closed accordion header 16494 cssOpen: 'accordion-open', //class you want to assign an opened accordion header 16495 cookieName: 'accordion', //name of the cookie you want to set for this accordion 16496 cookieOptions: { //cookie options, see cookie plugin for details 16497 path: '/', 16498 expires: 7, 16499 domain: '', 16500 secure: '' 16501 }, 16502 defaultOpen: '', //id that you want opened by default 16503 speed: 'slow', //speed of the slide effect 16504 bind: 'click', //event to bind to, supports click, dblclick, mouseover and mouseenter 16505 animateOpen: function(elem, opts) { //replace the standard slideDown with custom function 16506 elem.next().stop(true, true).slideDown(opts.speed); 16507 }, 16508 animateClose: function(elem, opts) { //replace the standard slideUp with custom function
vendor: 10,614 bytes, lines 16509-16547
16509 elem.next().stop(true, true).slideUp(opts.speed); 16510 }, 16511 loadOpen: function(elem, opts) { //replace the default open state with custom function 16512 elem.next().show(); 16513 }, 16514 loadClose: function(elem, opts) { //replace the default close state with custom function 16515 elem.next().hide(); 16516 } 16517 }; 16518})(jQuery); 16519/* 16520 * Copyright 2016 Small Batch, Inc. 16521 * 16522 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 16523 * use this file except in compliance with the License. You may obtain a copy of 16524 * the License at 16525 * 16526 * http://www.apache.org/licenses/LICENSE-2.0 16527 * 16528 * Unless required by applicable law or agreed to in writing, software 16529 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 16530 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16531 * License for the specific language governing permissions and limitations under 16532 * the License. 16533 */ 16534/* Web Font Loader v1.6.26 - (c) Adobe Systems, Google. License: Apache 2.0 */(function(){function aa(a,b,c){return a.call.apply(a.bind,arguments)}function ba(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}}function p(a,b,c){p=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?aa:ba;return p.apply(null,arguments)}var q=Date.now||function(){return+new Date};function ca(a,b){this.a=a;this.m=b||a;this.c=this.m.document}var da=!!window.FontFace;function t(a,b,c,d){b=a.c.createElement(b);if(c)for(var e in c)c.hasOwnProperty(e)&&("style"==e?b.style.cssText=c[e]:b.setAttribute(e,c[e]));d&&b.appendChild(a.c.createTextNode(d));return b}function u(a,b,c){a=a.c.getElementsByTagName(b)[0];a||(a=document.documentElement);a.insertBefore(c,a.lastChild)}function v(a){a.parentNode&&a.parentNode.removeChild(a)} 16535function w(a,b,c){b=b||[];c=c||[];for(var d=a.className.split(/\s+/),e=0;e<b.length;e+=1){for(var f=!1,g=0;g<d.length;g+=1)if(b[e]===d[g]){f=!0;break}f||d.push(b[e])}b=[];for(e=0;e<d.length;e+=1){f=!1;for(g=0;g<c.length;g+=1)if(d[e]===c[g]){f=!0;break}f||b.push(d[e])}a.className=b.join(" ").replace(/\s+/g," ").replace(/^\s+|\s+$/,"")}function y(a,b){for(var c=a.className.split(/\s+/),d=0,e=c.length;d<e;d++)if(c[d]==b)return!0;return!1} 16536function z(a){if("string"===typeof a.f)return a.f;var b=a.m.location.protocol;"about:"==b&&(b=a.a.location.protocol);return"https:"==b?"https:":"http:"}function ea(a){return a.m.location.hostname||a.a.location.hostname} 16537function A(a,b,c){function d(){k&&e&&f&&(k(g),k=null)}b=t(a,"link",{rel:"stylesheet",href:b,media:"all"});var e=!1,f=!0,g=null,k=c||null;da?(b.onload=function(){e=!0;d()},b.onerror=function(){e=!0;g=Error("Stylesheet failed to load");d()}):setTimeout(function(){e=!0;d()},0);u(a,"head",b)} 16538function B(a,b,c,d){var e=a.c.getElementsByTagName("head")[0];if(e){var f=t(a,"script",{src:b}),g=!1;f.onload=f.onreadystatechange=function(){g||this.readyState&&"loaded"!=this.readyState&&"complete"!=this.readyState||(g=!0,c&&c(null),f.onload=f.onreadystatechange=null,"HEAD"==f.parentNode.tagName&&e.removeChild(f))};e.appendChild(f);setTimeout(function(){g||(g=!0,c&&c(Error("Script load timeout")))},d||5E3);return f}return null};function C(){this.a=0;this.c=null}function D(a){a.a++;return function(){a.a--;E(a)}}function F(a,b){a.c=b;E(a)}function E(a){0==a.a&&a.c&&(a.c(),a.c=null)};function G(a){this.a=a||"-"}G.prototype.c=function(a){for(var b=[],c=0;c<arguments.length;c++)b.push(arguments[c].replace(/[\W_]+/g,"").toLowerCase());return b.join(this.a)};function H(a,b){this.c=a;this.f=4;this.a="n";var c=(b||"n4").match(/^([nio])([1-9])$/i);c&&(this.a=c[1],this.f=parseInt(c[2],10))}function fa(a){return I(a)+" "+(a.f+"00")+" 300px "+J(a.c)}function J(a){var b=[];a=a.split(/,\s*/);for(var c=0;c<a.length;c++){var d=a[c].replace(/['"]/g,"");-1!=d.indexOf(" ")||/^\d/.test(d)?b.push("'"+d+"'"):b.push(d)}return b.join(",")}function K(a){return a.a+a.f}function I(a){var b="normal";"o"===a.a?b="oblique":"i"===a.a&&(b="italic");return b} 16539function ga(a){var b=4,c="n",d=null;a&&((d=a.match(/(normal|oblique|italic)/i))&&d[1]&&(c=d[1].substr(0,1).toLowerCase()),(d=a.match(/([1-9]00|normal|bold)/i))&&d[1]&&(/bold/i.test(d[1])?b=7:/[1-9]00/.test(d[1])&&(b=parseInt(d[1].substr(0,1),10))));return c+b};function ha(a,b){this.c=a;this.f=a.m.document.documentElement;this.h=b;this.a=new G("-");this.j=!1!==b.events;this.g=!1!==b.classes}function ia(a){a.g&&w(a.f,[a.a.c("wf","loading")]);L(a,"loading")}function M(a){if(a.g){var b=y(a.f,a.a.c("wf","active")),c=[],d=[a.a.c("wf","loading")];b||c.push(a.a.c("wf","inactive"));w(a.f,c,d)}L(a,"inactive")}function L(a,b,c){if(a.j&&a.h[b])if(c)a.h[b](c.c,K(c));else a.h[b]()};function ja(){this.c={}}function ka(a,b,c){var d=[],e;for(e in b)if(b.hasOwnProperty(e)){var f=a.c[e];f&&d.push(f(b[e],c))}return d};function N(a,b){this.c=a;this.f=b;this.a=t(this.c,"span",{"aria-hidden":"true"},this.f)}function O(a){u(a.c,"body",a.a)}function P(a){return"display:block;position:absolute;top:-9999px;left:-9999px;font-size:300px;width:auto;height:auto;line-height:normal;margin:0;padding:0;font-variant:normal;white-space:nowrap;font-family:"+J(a.c)+";"+("font-style:"+I(a)+";font-weight:"+(a.f+"00")+";")};function Q(a,b,c,d,e,f){this.g=a;this.j=b;this.a=d;this.c=c;this.f=e||3E3;this.h=f||void 0}Q.prototype.start=function(){var a=this.c.m.document,b=this,c=q(),d=new Promise(function(d,e){function k(){q()-c>=b.f?e():a.fonts.load(fa(b.a),b.h).then(function(a){1<=a.length?d():setTimeout(k,25)},function(){e()})}k()}),e=new Promise(function(a,d){setTimeout(d,b.f)});Promise.race([e,d]).then(function(){b.g(b.a)},function(){b.j(b.a)})};function R(a,b,c,d,e,f,g){this.v=a;this.B=b;this.c=c;this.a=d;this.s=g||"BESbswy";this.f={};this.w=e||3E3;this.u=f||null;this.o=this.j=this.h=this.g=null;this.g=new N(this.c,this.s);this.h=new N(this.c,this.s);this.j=new N(this.c,this.s);this.o=new N(this.c,this.s);a=new H(this.a.c+",serif",K(this.a));a=P(a);this.g.a.style.cssText=a;a=new H(this.a.c+",sans-serif",K(this.a));a=P(a);this.h.a.style.cssText=a;a=new H("serif",K(this.a));a=P(a);this.j.a.style.cssText=a;a=new H("sans-serif",K(this.a));a= 16540P(a);this.o.a.style.cssText=a;O(this.g);O(this.h);O(this.j);O(this.o)}var S={D:"serif",C:"sans-serif"},T=null;function U(){if(null===T){var a=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent);T=!!a&&(536>parseInt(a[1],10)||536===parseInt(a[1],10)&&11>=parseInt(a[2],10))}return T}R.prototype.start=function(){this.f.serif=this.j.a.offsetWidth;this.f["sans-serif"]=this.o.a.offsetWidth;this.A=q();la(this)}; 16541function ma(a,b,c){for(var d in S)if(S.hasOwnProperty(d)&&b===a.f[S[d]]&&c===a.f[S[d]])return!0;return!1}function la(a){var b=a.g.a.offsetWidth,c=a.h.a.offsetWidth,d;(d=b===a.f.serif&&c===a.f["sans-serif"])||(d=U()&&ma(a,b,c));d?q()-a.A>=a.w?U()&&ma(a,b,c)&&(null===a.u||a.u.hasOwnProperty(a.a.c))?V(a,a.v):V(a,a.B):na(a):V(a,a.v)}function na(a){setTimeout(p(function(){la(this)},a),50)}function V(a,b){setTimeout(p(function(){v(this.g.a);v(this.h.a);v(this.j.a);v(this.o.a);b(this.a)},a),0)};function W(a,b,c){this.c=a;this.a=b;this.f=0;this.o=this.j=!1;this.s=c}var X=null;W.prototype.g=function(a){var b=this.a;b.g&&w(b.f,[b.a.c("wf",a.c,K(a).toString(),"active")],[b.a.c("wf",a.c,K(a).toString(),"loading"),b.a.c("wf",a.c,K(a).toString(),"inactive")]);L(b,"fontactive",a);this.o=!0;oa(this)}; 16542W.prototype.h=function(a){var b=this.a;if(b.g){var c=y(b.f,b.a.c("wf",a.c,K(a).toString(),"active")),d=[],e=[b.a.c("wf",a.c,K(a).toString(),"loading")];c||d.push(b.a.c("wf",a.c,K(a).toString(),"inactive"));w(b.f,d,e)}L(b,"fontinactive",a);oa(this)};function oa(a){0==--a.f&&a.j&&(a.o?(a=a.a,a.g&&w(a.f,[a.a.c("wf","active")],[a.a.c("wf","loading"),a.a.c("wf","inactive")]),L(a,"active")):M(a.a))};function pa(a){this.j=a;this.a=new ja;this.h=0;this.f=this.g=!0}pa.prototype.load=function(a){this.c=new ca(this.j,a.context||this.j);this.g=!1!==a.events;this.f=!1!==a.classes;qa(this,new ha(this.c,a),a)}; 16543function ra(a,b,c,d,e){var f=0==--a.h;(a.f||a.g)&&setTimeout(function(){var a=e||null,k=d||null||{};if(0===c.length&&f)M(b.a);else{b.f+=c.length;f&&(b.j=f);var h,m=[];for(h=0;h<c.length;h++){var l=c[h],n=k[l.c],r=b.a,x=l;r.g&&w(r.f,[r.a.c("wf",x.c,K(x).toString(),"loading")]);L(r,"fontloading",x);r=null;null===X&&(X=window.FontFace?(x=/Gecko.*Firefox\/(\d+)/.exec(window.navigator.userAgent))?42<parseInt(x[1],10):!0:!1);X?r=new Q(p(b.g,b),p(b.h,b),b.c,l,b.s,n):r=new R(p(b.g,b),p(b.h,b),b.c,l,b.s,a, 16544n);m.push(r)}for(h=0;h<m.length;h++)m[h].start()}},0)}function qa(a,b,c){var d=[],e=c.timeout;ia(b);var d=ka(a.a,c,a.c),f=new W(a.c,b,e);a.h=d.length;b=0;for(c=d.length;b<c;b++)d[b].load(function(b,d,c){ra(a,f,b,d,c)})};function sa(a,b){this.c=a;this.a=b}function ta(a,b,c){var d=z(a.c);a=(a.a.api||"fast.fonts.net/jsapi").replace(/^.*http(s?):(\/\/)?/,"");return d+"//"+a+"/"+b+".js"+(c?"?v="+c:"")} 16545sa.prototype.load=function(a){function b(){if(f["__mti_fntLst"+d]){var c=f["__mti_fntLst"+d](),e=[],h;if(c)for(var m=0;m<c.length;m++){var l=c[m].fontfamily;void 0!=c[m].fontStyle&&void 0!=c[m].fontWeight?(h=c[m].fontStyle+c[m].fontWeight,e.push(new H(l,h))):e.push(new H(l))}a(e)}else setTimeout(function(){b()},50)}var c=this,d=c.a.projectId,e=c.a.version;if(d){var f=c.c.m;B(this.c,ta(c,d,e),function(e){e?a([]):(f["__MonotypeConfiguration__"+d]=function(){return c.a},b())}).id="__MonotypeAPIScript__"+ 16546d}else a([])};function ua(a,b){this.c=a;this.a=b}ua.prototype.load=function(a){var b,c,d=this.a.urls||[],e=this.a.families||[],f=this.a.testStrings||{},g=new C;b=0;for(c=d.length;b<c;b++)A(this.c,d[b],D(g));var k=[];b=0;for(c=e.length;b<c;b++)if(d=e[b].split(":"),d[1])for(var h=d[1].split(","),m=0;m<h.length;m+=1)k.push(new H(d[0],h[m]));else k.push(new H(d[0]));F(g,function(){a(k,f)})};function va(a,b,c){a?this.c=a:this.c=b+wa;this.a=[];this.f=[];this.g=c||""}var wa="//fonts.googleapis.com/css";function xa(a,b){for(var c=b.length,d=0;d<c;d++){var e=b[d].split(":");3==e.length&&a.f.push(e.pop());var f="";2==e.length&&""!=e[1]&&(f=":");a.a.push(e.join(f))}} 16547function ya(a){if(0==a.a.length)throw Error("No fonts to load!");if(-1!=a.c.indexOf("kit="))return a.c;for(var b=a.a.length,c=[],d=0;d<b;d++)c.push(a.a[d].replace(/ /g,"+"));b=a.c+"?family="+c.join("%7C");0<a.f.length&&(b+="&subset="+a.f.join(","));0<a.g.length&&(b+="&text="+encodeURIComponent(a.g));return b};function za(a){this.f=a;this.a=[];this.c={}}
16548var Aa={latin:"BESbswy","latin-ext":"\u00e7\u00f6\u00fc\u011f\u015f",cyrillic:"\u0439\u044f\u0416",greek:"\u03b1\u03b2\u03a3",khmer:"\u1780\u1781\u1782",Hanuman:"\u1780\u1781\u1782"},Ba={thin:"1",extralight:"2","extra-light":"2",ultralight:"2","ultra-light":"2",light:"3",regular:"4",book:"4",medium:"5","semi-bold":"6",semibold:"6","demi-bold":"6",demibold:"6",bold:"7","extra-bold":"8",extrabold:"8","ultra-bold":"8",ultrabold:"8",black:"9",heavy:"9",l:"3",r:"4",b:"7"},Ca={i:"i",italic:"i",n:"n",normal:"n"}, 16549Da=/^(thin|(?:(?:extra|ultra)-?)?light|regular|book|medium|(?:(?:semi|demi|extra|ultra)-?)?bold|black|heavy|l|r|b|[1-9]00)?(n|i|normal|italic)?$/; 16550function Ea(a){for(var b=a.f.length,c=0;c<b;c++){var d=a.f[c].split(":"),e=d[0].replace(/\+/g," "),f=["n4"];if(2<=d.length){var g;var k=d[1];g=[];if(k)for(var k=k.split(","),h=k.length,m=0;m<h;m++){var l;l=k[m];if(l.match(/^[\w-]+$/)){var n=Da.exec(l.toLowerCase());if(null==n)l="";else{l=n[2];l=null==l||""==l?"n":Ca[l];n=n[1];if(null==n||""==n)n="4";else var r=Ba[n],n=r?r:isNaN(n)?"4":n.substr(0,1);l=[l,n].join("")}}else l="";l&&g.push(l)}0<g.length&&(f=g);3==d.length&&(d=d[2],g=[],d=d?d.split(","): 16551g,0<d.length&&(d=Aa[d[0]])&&(a.c[e]=d))}a.c[e]||(d=Aa[e])&&(a.c[e]=d);for(d=0;d<f.length;d+=1)a.a.push(new H(e,f[d]))}};function Fa(a,b){this.c=a;this.a=b}var Ga={Arimo:!0,Cousine:!0,Tinos:!0};Fa.prototype.load=function(a){var b=new C,c=this.c,d=new va(this.a.api,z(c),this.a.text),e=this.a.families;xa(d,e);var f=new za(e);Ea(f);A(c,ya(d),D(b));F(b,function(){a(f.a,f.c,Ga)})};function Ha(a,b){this.c=a;this.a=b}Ha.prototype.load=function(a){var b=this.a.id,c=this.c.m;b?B(this.c,(this.a.api||"https://use.typekit.net")+"/"+b+".js",function(b){if(b)a([]);else if(c.Typekit&&c.Typekit.config&&c.Typekit.config.fn){b=c.Typekit.config.fn;for(var e=[],f=0;f<b.length;f+=2)for(var g=b[f],k=b[f+1],h=0;h<k.length;h++)e.push(new H(g,k[h]));try{c.Typekit.load({events:!1,classes:!1,async:!0})}catch(m){}a(e)}},2E3):a([])};function Ia(a,b){this.c=a;this.f=b;this.a=[]}Ia.prototype.load=function(a){var b=this.f.id,c=this.c.m,d=this;b?(c.__webfontfontdeckmodule__||(c.__webfontfontdeckmodule__={}),c.__webfontfontdeckmodule__[b]=function(b,c){for(var g=0,k=c.fonts.length;g<k;++g){var h=c.fonts[g];d.a.push(new H(h.name,ga("font-weight:"+h.weight+";font-style:"+h.style)))}a(d.a)},B(this.c,z(this.c)+(this.f.api||"//f.fontdeck.com/s/css/js/")+ea(this.c)+"/"+b+".js",function(b){b&&a([])})):a([])};var Y=new pa(window);Y.a.c.custom=function(a,b){return new ua(b,a)};Y.a.c.fontdeck=function(a,b){return new Ia(b,a)};Y.a.c.monotype=function(a,b){return new sa(b,a)};Y.a.c.typekit=function(a,b){return new Ha(b,a)};Y.a.c.google=function(a,b){return new Fa(b,a)};var Z={load:p(Y.load,Y)};"function"===typeof define&&define.amd?define(function(){return Z}):"undefined"!==typeof module&&module.exports?module.exports=Z:(window.WebFont=Z,window.WebFontConfig&&Y.load(window.WebFontConfig));}()); 16552 16553(function($) { 16554 "use strict"; 16555 16556 $.fn.pagination = function(maxentries, opts) { 16557 opts = jQuery.extend({ 16558 items_per_page: 10, 16559 num_display_entries: 10, 16560 current_page: 0, 16561 num_edge_entries: 0, 16562 link_to: "#", 16563 prev_text: "Prev", 16564 next_text: "Next", 16565 ellipse_text: "...", 16566 prev_show_always: false, 16567 next_show_always: false, 16568 data_source: '', 16569 listing_container: '', 16570 16571 callback: function() { 16572 return false; 16573 } 16574 }, opts || {}); 16575 16576 return this.each(function() { 16577 /** 16578 * Calculate the maximum number of pages 16579 */ 16580 function numPages() { 16581 return Math.ceil(maxentries / opts.items_per_page); 16582 } 16583 16584 /** 16585 * Calculate start and end point of pagination links depending on 16586 * current_page and num_display_entries. 16587 * @return {Array} 16588 */ 16589 function getInterval() { 16590 var ne_half = Math.ceil(opts.num_display_entries / 2); 16591 var np = numPages(); 16592 var upper_limit = np - opts.num_display_entries; 16593 var start = current_page > ne_half ? Math.max(Math.min(current_page - ne_half, upper_limit), 0) : 0; 16594 var end = current_page > ne_half ? Math.min(current_page + ne_half, np) : Math.min(opts.num_display_entries, np); 16595 return [start, end]; 16596 } 16597 16598 /** 16599 * This is the event handling function for the pagination links. 16600 * @param {int} page_id The new page number 16601 */ 16602 function pageSelected(page_id, evt) { 16603 current_page = page_id; 16604 drawLinks(); 16605 var continuePropagation = opts.callback(page_id, panel); 16606 if (!continuePropagation) { 16607 if (evt.stopPropagation) { 16608 evt.stopPropagation(); 16609 } else { 16610 evt.cancelBubble = true; 16611 } 16612 } 16613 return continuePropagation; 16614 } 16615 16616 /** 16617 * This function inserts the pagination links into the container element 16618 */ 16619 function drawLinks() { 16620 panel.empty(); 16621 var interval = getInterval(); 16622 var np = numPages(); 16623 // This helper function returns a handler function that calls pageSelected with the right page_id 16624 var getClickHandler = function(page_id) { 16625 return function(evt) { 16626 return pageSelected(page_id, evt); 16627 } 16628 } 16629 // Helper function for generating a single link (or a span tag if it's the current page) 16630 var appendItem = function(page_id, appendopts) { 16631 page_id = page_id < 0 ? 0 : (page_id < np ? page_id : np - 1); // Normalize page id to sane value 16632 appendopts = jQuery.extend({ 16633 text: page_id + 1, 16634 classes: "" 16635 }, appendopts || {}); 16636 if (page_id == current_page) {
16637 var lnk = jQuery("<span class='current'>" + (appendopts.text) + "</span>"); 16638 } else { 16639 var lnk = jQuery("<a>" + (appendopts.text) + "</a>") 16640 .bind("click", getClickHandler(page_id)) 16641 .attr('href', opts.link_to.replace(/__id__/, page_id)); 16642 16643 16644 } 16645 if (appendopts.classes) { 16646 lnk.addClass(appendopts.classes); 16647 } 16648 panel.append(lnk); 16649 } 16650 // Generate "Previous"-Link 16651 if (opts.prev_text && (current_page > 0 || opts.prev_show_always)) { 16652 appendItem(current_page - 1, { 16653 text: opts.prev_text, 16654 classes: "prev" 16655 }); 16656 } 16657 // Generate starting points 16658 16659 if (np > 1) { 16660 16661 if (interval[0] > 0 && opts.num_edge_entries > 0) { 16662 var end = Math.min(opts.num_edge_entries, interval[0]); 16663 for (var i = 0; i < end; i++) { 16664 appendItem(i); 16665 } 16666 if (opts.num_edge_entries < interval[0] && opts.ellipse_text) { 16667 jQuery("<span>" + opts.ellipse_text + "</span>").appendTo(panel); 16668 } 16669 } 16670 // Generate interval links 16671 for (var i = interval[0]; i < interval[1]; i++) { 16672 appendItem(i); 16673 } 16674 // Generate ending points 16675 if (interval[1] < np && opts.num_edge_entries > 0) { 16676 if (np - opts.num_edge_entries > interval[1] && opts.ellipse_text) { 16677 jQuery("<span>" + opts.ellipse_text + "</span>").appendTo(panel); 16678 } 16679 var begin = Math.max(np - opts.num_edge_entries, interval[1]); 16680 for (var i = begin; i < np; i++) { 16681 appendItem(i); 16682 } 16683 16684 } 16685 16686 } 16687 16688 16689 // Generate "Next"-Link 16690 if (opts.next_text && (current_page < np - 1 || opts.next_show_always)) { 16691 appendItem(current_page + 1, { 16692 text: opts.next_text, 16693 classes: "next" 16694 }); 16695 } 16696 } 16697 16698 // Extract current_page from options 16699 var current_page = opts.current_page; 16700 // Create a sane value for maxentries and items_per_page 16701 maxentries = (!maxentries || maxentries < 0) ? 1 : maxentries; 16702 opts.items_per_page = (!opts.items_per_page || opts.items_per_page < 0) ? 1 : opts.items_per_page; 16703 // Store DOM element for easy access from all inner functions 16704 var panel = jQuery(this); 16705 // Attach control functions to the DOM element 16706 this.selectPage = function(page_id,evt) { 16707 pageSelected(page_id,evt); 16708 } 16709 this.prevPage = function() { 16710 if (current_page > 0) { 16711 pageSelected(current_page - 1); 16712 return true; 16713 } else { 16714 return false; 16715 } 16716 } 16717 this.nextPage = function() { 16718 if (current_page < numPages() - 1) { 16719 pageSelected(current_page + 1); 16720 return true; 16721 } else { 16722 return false; 16723 } 16724 } 16725 // When all initialisation is done, draw the links 16726 drawLinks(); 16727 // call callback function 16728 opts.callback(current_page, this); 16729 }); 16730 } 16731 16732 var masonry; 16733 $(document).ready(function($) { 16734 $('.gm-style-iw').parent().parent().addClass('wpgmp_infowindow_css'); 16735 $("div.scroll-pane").jScrollPane(); 16736 16737 //Social share popups 16738 $(".wpgmp-social-share").on("click", function(e) { 16739 e.preventDefault(); 16740 var url = $(this).attr("href"); 16741 var check_url = url.split('&url='); 16742 if (check_url[1] == '') { 16743 url += window.location.href; 16744 } 16745 var width = 500, 16746 height = 300; 16747 var left = (screen.width / 2) - (width / 2), 16748 top = (screen.height / 2) - (height / 2); 16749 window.open( 16750 url, 16751 "", 16752 "menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width="
16752+ width + ",height=" + height + ",top=" + top + ",left=" + left 16753 ); 16754 }); 16755 var masonry_ref = ''; 16756 $(document).on('click','.categories_filter span a', function(e) { 16757 var ref = $(this).closest(".wpgmp_map_container"); 16758 var elem = $(ref).find('div.wpgmp_categories'); 16759 16760 if ($(this).hasClass('wpgmp_grid')) { 16761 $(this).addClass('active'); 16762 $(ref).find('span a.wpgmp_list').removeClass('active'); 16763 $(ref).find('span a.wpgmp_print').removeClass('active'); 16764 elem.fadeOut(500, function() { 16765 elem.removeClass('wpgmp_listing_list').addClass('wpgmp_listing_grid'); 16766 elem.show(); 16767 try { 16768 16769 16770 var container = $(ref).find('.wpgmp_listing_grid'); 16771 if (container) { 16772 16773 var msnry = $(container).data('masonry'); 16774 if (msnry) { 16775 msnry.destroy(); 16776 } 16777 16778 var $grid = $(container).imagesLoaded(function() { 16779 // init Masonry after all images have loaded 16780 if( ! $.isFunction($.fn.agfgm_grid_view) ) { 16781 16782 $grid.masonry({ 16783 itemSelector: '.wpgmp_listing_grid .wpgmp_locations', 16784 columnWidth: '.wpgmp_listing_grid .wpgmp_locations', 16785 }); 16786 } 16787 16788 }); 16789 16790 } 16791 16792 } catch (err) { 16793 console.log(err); 16794 } 16795 16796 }); 16797 } else if ($(this).hasClass('wpgmp_list')) { 16798 if ($(ref).find('.wpgmp_listing_grid').hasClass('masonry')) { 16799 var msnry = $(ref).find('.wpgmp_listing_grid').data('masonry'); 16800 msnry.destroy(); 16801 } 16802 16803 $(this).addClass('active'); 16804 $(ref).find('span a.wpgmp_grid').removeClass('active'); 16805 $(ref).find('span a.wpgmp_print').removeClass('active'); 16806 elem.fadeOut(500, function() { 16807 elem.removeClass('wpgmp_listing_grid').addClass('wpgmp_listing_list'); 16808 $(ref).find('.wpgmp_locations').equalHeightGrid(); 16809 elem.fadeIn(500); 16810 }); 16811 } else if ($(this).hasClass('wpgmp_print')) { 16812 $(this).addClass('active'); 16813 $(ref).find('span a.wpgmp_grid').removeClass('active'); 16814 $(ref).find('span a.wpgmp_list').removeClass('active'); 16815 $(ref).find('span a.wpgmp_print').removeClass('active'); 16816 } 16817 }); 16818 16819 }); 16820 16821 $.fn.equalHeight = function() { 16822 var heights = []; 16823 $.each(this, function(i, element) { 16824 $element = $(element); 16825 var element_height; 16826 // Should we include the elements padding in it's height? 16827 var includePadding = ($element.css('box-sizing') == 'border-box') || ($element.css('-moz-box-sizing') == 'border-box'); 16828 if (includePadding) { 16829 element_height = $element.innerHeight(); 16830 } else { 16831 element_height = $element.height(); 16832 } 16833 heights.push(element_height); 16834 }); 16835 this.css('height', Math.max.apply(window, heights) + 'px'); 16836 return this; 16837 }; 16838 16839 $.fn.equalHeightGrid = function(columns) { 16840 var $tiles = this; 16841 $tiles.css('height', 'auto'); 16842 for (var i = 0; i < $tiles.length; i++) { 16843 if (i % columns === 0) { 16844 var row = $($tiles[i]); 16845 for (var n = 1; n < columns; n++) { 16846 row = row.add($tiles[i + n]); 16847 } 16848 row.equalHeight(); 16849 } 16850 } 16851 return this; 16852 }; 16853 16854 $.fn.detectGridColumns = function() { 16855 var offset = 0, 16856 cols = 0; 16857 this.each(function(i, elem) { 16858 var elem_offset = $(elem).offset().top; 16859 if (offset === 0 || elem_offset == offset) { 16860 cols++; 16861 offset = elem_offset; 16862 } else { 16863 return false;
16864 } 16865 }); 16866 return cols; 16867 }; 16868 16869 $.fn.responsiveEqualHeightGrid = function() { 16870 var _this = this; 16871 16872 function syncHeights() { 16873 var cols = _this.detectGridColumns(); 16874 _this.equalHeightGrid(cols); 16875 } 16876 $(window).bind('resize load', syncHeights); 16877 syncHeights(); 16878 return this; 16879 }; 16880 16881 var re = /([^&=]+)=?([^&]*)/g; 16882 var decodeRE = /\+/g; // Regex for replacing addition symbol with a space 16883 var decode = function(str) { 16884 return decodeURIComponent(str.replace(decodeRE, " ")); 16885 }; 16886 $.parseParams = function(query) { 16887 var params = {}, 16888 e; 16889 while (e = re.exec(query)) { 16890 var k = decode(e[1]), 16891 v = decode(e[2]); 16892 if (k.substring(k.length - 2) === '[]') { 16893 k = k.substring(0, k.length - 2); 16894 (params[k] || (params[k] = [])).push(v); 16895 } else params[k] = v; 16896 } 16897 return params; 16898 }; 16899 16900 16901})(jQuery); 16902 16903function wpgmp_set_consent_cookies() { 16904 wpgmp_set_cookie('wpgmp_show_map', "yes", wpgmp_flocal.days_to_remember); 16905 window.location.reload(); 16906} 16907 16908function wpgmp_set_noconsent_cookies() { 16909 wpgmp_set_cookie('wpgmp_show_map', "no", wpgmp_flocal.days_to_remember); 16910 window.location.reload(); 16911} 16912 16913function wpgmp_get_cookie(cname) { 16914 var name = cname + "="; 16915 var ca = document.cookie.split(';'); 16916 for (var i = 0; i < ca.length; i++) { 16917 var c = ca[i]; 16918 while (c.charAt(0) == ' ') { 16919 c = c.substring(1); 16920 } 16921 if (c.indexOf(name) == 0) { 16922 return c.substring(name.length, c.length); 16923 } 16924 } 16925 return null; 16926} 16927 16928function wpgmp_set_cookie(cname, cvalue, exdays) { 16929 var d = new Date(); 16930 d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); 16931 var expires = "expires=" + d.toUTCString(); 16932 document.cookie = cname + "=" + cvalue + "; " + expires + "; path=/"; 16933}
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.